Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client-react-hooks/src/create-client.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
72 changes: 56 additions & 16 deletions client-vms/tests/eddsa.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -11,32 +12,72 @@ 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;

beforeAll(async () => {
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")
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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()
Expand All @@ -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;
});
});
34 changes: 9 additions & 25 deletions client-vms/tests/wasm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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),
},
Expand All @@ -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);

Expand Down
33 changes: 24 additions & 9 deletions client-wasm/src/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())?;
Expand Down Expand Up @@ -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")?);
Expand All @@ -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(
Expand All @@ -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(())
}
}
2 changes: 1 addition & 1 deletion examples-nextjs/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import {
NillionProvider,
ClientBuilder,
NillionProvider,
getKeplr,
} from "@nillion/client-react-hooks";
import type { VmClient } from "@nillion/client-vms";
Expand Down