diff --git a/client-react-hooks/src/create-client.ts b/client-react-hooks/src/create-client.ts index 7dede7b..3bef5cc 100644 --- a/client-react-hooks/src/create-client.ts +++ b/client-react-hooks/src/create-client.ts @@ -1,6 +1,6 @@ import type { OfflineSigner } from "@cosmjs/proto-signing"; import type { Keplr } from "@keplr-wallet/types"; -import { VmClientBuilder, type VmClient } from "@nillion/client-vms"; +import { type VmClient, VmClientBuilder } from "@nillion/client-vms"; import { createSignerFromKey } from "@nillion/client-vms"; import type { PaymentMode } from "@nillion/client-vms"; diff --git a/client-vms/tests/eddsa.test.ts b/client-vms/tests/eddsa.test.ts index 7ddb265..b698fca 100644 --- a/client-vms/tests/eddsa.test.ts +++ b/client-vms/tests/eddsa.test.ts @@ -1,4 +1,5 @@ import { type EddsaSignature, NadaValue } from "@nillion/client-wasm"; +import { mod } from "@noble/curves/abstract/modular"; import { ed25519 } from "@noble/curves/ed25519"; import { sha256 } from "@noble/hashes/sha2"; import { beforeAll, describe, expect, it } from "vitest"; @@ -11,24 +12,64 @@ import { import { UpdatePermissionsBuilder, type VmClient, VmClientBuilder } from "#/vm"; import { Env, PrivateKeyPerSuite } from "./helpers"; -// This is ignored because NadaValue::new_eddsa_private_key fails for random private keys. This should be fixed -// in nilvm side -describe.skip("Eddsa Signature", () => { +type EddsaKeyPair = { + privateKey: Uint8Array; + publicKey: Uint8Array; +}; + +/** + * Generates a random EdDSA (Ed25519) key pair, consisting of a private key (scalar) and a public key. + * The private key is generated using a random 32-byte seed, which is then processed and reduced modulo the order of the curve (L) to ensure it is within the valid scalar range. + * The public key is derived by multiplying the base point of the curve by the valid private scalar. + * + * This function ensures that the generated private key is valid according to the Ed25519 curve, and that the corresponding public key can be used for cryptographic operations. + * + * @returns {EddsaKeyPair} An object containing the `publicKey` (the public key as a compressed byte array) and the `privateKey` (the private key as a Uint8Array). + */ +function generateRandomEddsaKeyPair(): EddsaKeyPair { + const seed = ed25519.utils.randomPrivateKey(); + let num = BigInt(`0x${Buffer.from(seed).toString("hex")}`); + + // Apply modulus with L (the order of the Ed25519 curve) to ensure the scalar is valid + num = mod(num, ed25519.CURVE.n); + + const privateKeyBuffer = Buffer.alloc(32); + // Split the BigInt into four 64-bit chunks and store them in the private key buffer + privateKeyBuffer.writeBigUInt64LE(num & BigInt("0xFFFFFFFFFFFFFFFF"), 0); + privateKeyBuffer.writeBigUInt64LE( + (num >> BigInt(64)) & BigInt("0xFFFFFFFFFFFFFFFF"), + 8, + ); + privateKeyBuffer.writeBigUInt64LE( + (num >> BigInt(128)) & BigInt("0xFFFFFFFFFFFFFFFF"), + 16, + ); + privateKeyBuffer.writeBigUInt64LE( + (num >> BigInt(192)) & BigInt("0xFFFFFFFFFFFFFFFF"), + 24, + ); + + return { + publicKey: ed25519.ExtendedPoint.BASE.multiply(num).toRawBytes(true), + privateKey: new Uint8Array(privateKeyBuffer), + }; +} + +describe("Eddsa Signature", () => { // Program id const teddsaProgramId = "builtin/teddsa_sign"; // Input store name const teddsaKeyName = "teddsa_private_key"; - const teddsaDigestName = "teddsa_digest_message"; + const teddsaMessageName = "teddsa_message"; const teddsaSignatureName = "teddsa_signature"; // Party names const teddsaKeyParty = "teddsa_key_party"; - const teddsaDigestParty = "teddsa_digest_message_party"; + const teddsaMessageParty = "teddsa_message_party"; const teddsaOutputParty = "teddsa_output_party"; - const privateKey: Uint8Array = ed25519.utils.randomPrivateKey(); - const publicKey: Uint8Array = ed25519.getPublicKey(privateKey); + const { privateKey, publicKey } = generateRandomEddsaKeyPair(); - let digestMessage: Uint8Array; + let message: Uint8Array; let client: VmClient; @@ -36,7 +77,7 @@ describe.skip("Eddsa Signature", () => { const signer = await createSignerFromKey( PrivateKeyPerSuite.EddsaSignatures, ); - digestMessage = sha256("A deep message with a deep number: 42"); + message = sha256("A deep message with a deep number: 42"); client = await new VmClientBuilder() .seed("tests") @@ -86,7 +127,7 @@ describe.skip("Eddsa Signature", () => { digestMessageStoreId = await client .storeValues() .ttl(1) - .value(teddsaDigestName, NadaValue.new_eddsa_message(digestMessage)) + .value(teddsaMessageName, NadaValue.new_eddsa_message(message)) .permissions(permissions) .build() .invoke(); @@ -100,10 +141,10 @@ describe.skip("Eddsa Signature", () => { .build() .invoke(); - const values = data[teddsaDigestName]!; + const values = data[teddsaMessageName]!; expect(values).toBeDefined(); - expect(values.type).toBe("EddsaDigestMessage"); - expect(values.value).toEqual(digestMessage); + expect(values.type).toBe("EddsaMessage"); + expect(values.value).toEqual(message); }); let computeResultId: Uuid; @@ -112,7 +153,7 @@ describe.skip("Eddsa Signature", () => { .invokeCompute() .program(teddsaProgramId) .inputParty(teddsaKeyParty, client.id) - .inputParty(teddsaDigestParty, client.id) + .inputParty(teddsaMessageParty, client.id) .outputParty(teddsaOutputParty, [client.id]) .valueIds(privateKeyStoreId, digestMessageStoreId) .build() @@ -135,7 +176,6 @@ describe.skip("Eddsa Signature", () => { it("eddsa verify", async () => { const signature = computeResult[teddsaSignatureName] ?.value as EddsaSignature; - expect(ed25519.verify(signature.signature(), digestMessage, publicKey)) - .true; + expect(ed25519.verify(signature.signature(), message, publicKey)).true; }); }); diff --git a/client-vms/tests/wasm.test.ts b/client-vms/tests/wasm.test.ts index 306ad25..6c4fbb8 100644 --- a/client-vms/tests/wasm.test.ts +++ b/client-vms/tests/wasm.test.ts @@ -25,11 +25,6 @@ const byteArray = Uint8Array.from([ 136, 145, 98, 150, 152, 122, 50, 91, 141, 227, 182, 233, 8, 245, 72, 38, ]); -const privateKey = Uint8Array.from([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, -]); - const pubKey = Uint8Array.from([ 186, 236, 247, 198, 7, 225, 204, 147, 116, 47, 207, 45, 149, 49, 212, 168, 136, 145, 98, 150, 152, 122, 50, 91, 141, 227, 182, 233, 8, 245, 72, 38, 56, @@ -39,6 +34,11 @@ const storeId = Uint8Array.from([ 186, 236, 247, 198, 7, 225, 204, 147, 116, 47, 207, 45, 149, 49, 212, 168, ]); +const eddsaPrivateKey = Uint8Array.from([ + 67, 125, 56, 30, 209, 152, 89, 230, 27, 85, 136, 128, 43, 116, 85, 113, 124, + 43, 197, 3, 29, 148, 6, 50, 169, 92, 97, 171, 152, 26, 90, 3, +]); + const eddsaSignatureR = Uint8Array.from([ 228, 118, 63, 53, 138, 161, 20, 164, 93, 86, 233, 11, 211, 204, 186, 63, 255, 174, 220, 173, 222, 58, 64, 79, 108, 173, 130, 1, 134, 44, 244, 104, @@ -59,98 +59,82 @@ const digestMessage = "A deep message with a deep number: 42"; const data = [ { type: "PublicInteger", - name: "a", value: -42, nadaValue: NadaValue.new_public_integer("-42"), }, { type: "PublicUnsignedInteger", - name: "b", value: 42, nadaValue: NadaValue.new_public_unsigned_integer("42"), }, { type: "PublicBoolean", - name: "c", value: true, nadaValue: NadaValue.new_public_boolean(true), }, { type: "SecretInteger", - name: "d", value: -100, nadaValue: NadaValue.new_secret_integer("-100"), }, { type: "SecretUnsignedInteger", - name: "e", value: 100, nadaValue: NadaValue.new_secret_unsigned_integer("100"), }, { type: "SecretBoolean", - name: "f", value: true, nadaValue: NadaValue.new_secret_boolean(true), }, { type: "SecretBlob", - name: "g", value: Uint8Array.from([1, 2, 3]), nadaValue: NadaValue.new_secret_blob(Uint8Array.from([1, 2, 3])), }, { type: "EcdsaPrivateKey", - name: "h", value: ecdsaPrivateKey, nadaValue: NadaValue.new_ecdsa_private_key(ecdsaPrivateKey), }, { type: "EcdsaDigestMessage", - name: "i", value: sha256(digestMessage), nadaValue: NadaValue.new_ecdsa_digest_message(sha256(digestMessage)), }, { type: "EcdsaSignature", - name: "j", value: byteArray, nadaValue: NadaValue.new_ecdsa_signature(byteArray, byteArray), }, { type: "EcdsaPublicKey", - name: "k", value: pubKey, nadaValue: NadaValue.new_ecdsa_public_key(pubKey), }, { type: "StoreId", - name: "l", value: storeId, nadaValue: NadaValue.new_store_id(storeId), }, { type: "EddsaPrivateKey", - name: "m", - value: privateKey, - nadaValue: NadaValue.new_eddsa_private_key(privateKey), + value: eddsaPrivateKey, + nadaValue: NadaValue.new_eddsa_private_key(eddsaPrivateKey), }, { type: "EddsaMessage", - name: "n", value: sha256(digestMessage), nadaValue: NadaValue.new_eddsa_message(sha256(digestMessage)), }, { type: "EddsaSignature", - name: "o", r: eddsaSignatureR, z: eddsaSignatureZ, nadaValue: NadaValue.new_eddsa_signature(eddsaSignatureR, eddsaSignatureZ), }, { type: "EddsaPublicKey", - name: "p", value: eddsaPublicKey, nadaValue: NadaValue.new_eddsa_public_key(eddsaPublicKey), }, @@ -163,13 +147,13 @@ describe("Wasm compatability", () => { data.forEach((test, index) => { describe(test.type, () => { it("can insert into NadaValues", () => { - values.insert(test.name, test.nadaValue); + values.insert(test.type, test.nadaValue); expect(values).toHaveLength(index + 1); }); it("can retrieve from NadaValues", () => { const record = values.to_record() as unknown as NadaValuesRecord; - const actual = record[test.name]; + const actual = record[test.type]; expect(actual).toBeDefined(); expect(actual?.type).toEqual(test.type); diff --git a/client-wasm/src/values.rs b/client-wasm/src/values.rs index ce19374..9ee5eb6 100644 --- a/client-wasm/src/values.rs +++ b/client-wasm/src/values.rs @@ -1106,10 +1106,17 @@ mod test { #[wasm_bindgen_test] fn mask_unmask() -> Result<(), JsValue> { let mut values = NadaValues::new()?; - values.insert("a".into(), &NadaValue::new_secret_integer("42")?); - values.insert("b".into(), &NadaValue::new_secret_blob(vec![1, 2, 3])); - values.insert("c".into(), &NadaValue::new_secret_unsigned_integer("1337")?); - values.insert("d".into(), &NadaValue::new_secret_boolean(true)?); + values.insert("secret_integer".into(), &NadaValue::new_secret_integer("42")?); + values.insert("secret_blob".into(), &NadaValue::new_secret_blob(vec![1, 2, 3])); + values.insert("secret_unsigned_integer".into(), &NadaValue::new_secret_unsigned_integer("1337")?); + values.insert("secret_boolean".into(), &NadaValue::new_secret_boolean(true)?); + values.insert( + "eddsa_private_key".into(), + &NadaValue::new_eddsa_private_key(vec![ + 67, 125, 56, 30, 209, 152, 89, 230, 27, 85, 136, 128, 43, 116, 85, 113, 124, 43, 197, 3, 29, 148, 6, + 50, 169, 92, 97, 171, 152, 26, 90, 3, + ])?, + ); let masker = make_masker(); let masked_values = masker.mask(values.clone())?; @@ -1138,6 +1145,11 @@ mod test { #[wasm_bindgen_test] fn encrypted_nada_values_from_js_object() -> Result<(), JsValue> { + let eddsa_private_key = vec![ + 67, 125, 56, 30, 209, 152, 89, 230, 27, 85, 136, 128, 43, 116, 85, 113, 124, 43, 197, 3, 29, 148, 6, 50, + 169, 92, 97, 171, 152, 26, 90, 3, + ]; + let mut values = NadaValues::new()?; values.insert("integer".into(), &NadaValue::new_public_integer("42")?); values.insert("unsigned_integer".into(), &NadaValue::new_public_unsigned_integer("42")?); @@ -1150,7 +1162,7 @@ mod test { values.insert("ecdsa_message".into(), &NadaValue::new_ecdsa_digest_message(vec![1; 32])?); values.insert("ecdsa_public_key".into(), &NadaValue::new_ecdsa_public_key(vec![1; 33])?); values.insert("ecdsa_signature".into(), &NadaValue::new_ecdsa_signature(vec![1; 32], vec![1; 32])?); - values.insert("eddsa_private_key".into(), &NadaValue::new_eddsa_private_key(vec![1; 32])?); + values.insert("eddsa_private_key".into(), &NadaValue::new_eddsa_private_key(eddsa_private_key)?); values.insert("eddsa_message".into(), &NadaValue::new_eddsa_message(vec![1; 32])?); values.insert("eddsa_public_key".into(), &NadaValue::new_eddsa_public_key(vec![1; 32])?); values.insert( @@ -1169,12 +1181,15 @@ mod test { values.insert("store_id".into(), &NadaValue::new_store_id(vec![1; 16])?); let masker = make_masker(); - let values = masker.mask(values.clone())?.into_iter().next().unwrap().shares; - let js_object = values.to_js_object()?; - let from_values = EncryptedNadaValues::from_js_object(&js_object, masker.modulo())?; + let party_shares = masker.mask(values.clone())?; - assert_eq!(values, from_values); + let masked_values = party_shares.iter().next().unwrap().shares.clone(); + let js_object = masked_values.to_js_object()?; + let from_values = EncryptedNadaValues::from_js_object(&js_object, masker.modulo())?; + assert_eq!(masked_values, from_values); + let unmasked_values = masker.unmask(party_shares)?; + assert_eq!(values, unmasked_values); Ok(()) } } diff --git a/examples-nextjs/app/page.tsx b/examples-nextjs/app/page.tsx index 3a39d6a..79eedba 100644 --- a/examples-nextjs/app/page.tsx +++ b/examples-nextjs/app/page.tsx @@ -1,8 +1,8 @@ "use client"; import { - NillionProvider, ClientBuilder, + NillionProvider, getKeplr, } from "@nillion/client-react-hooks"; import type { VmClient } from "@nillion/client-vms"; diff --git a/nilvm b/nilvm index d53b729..3f3a942 160000 --- a/nilvm +++ b/nilvm @@ -1 +1 @@ -Subproject commit d53b7295b7cdb3376af1186aa96915768a01223c +Subproject commit 3f3a9420ed7ec29f1d086a72260e0ebe6e6aafa6