From 04d60856b065064f30cfa5e274a119b048f59f0b Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 17 Feb 2024 19:57:50 +0100 Subject: [PATCH 01/22] add custom network id --- src/mina-signer/mina-signer.ts | 21 ++++++++++++------- src/mina-signer/src/sign-zkapp-command.ts | 1 - .../src/test-vectors/legacySignatures.ts | 2 +- src/mina-signer/src/types.ts | 8 ++++++- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/mina-signer/mina-signer.ts b/src/mina-signer/mina-signer.ts index fe3739404f..ed40baeb09 100644 --- a/src/mina-signer/mina-signer.ts +++ b/src/mina-signer/mina-signer.ts @@ -39,16 +39,14 @@ export { Client as default }; const defaultValidUntil = '4294967295'; class Client { - private network: NetworkId; // TODO: Rename to "networkId" for consistency with remaining codebase. + private network: NetworkId; constructor(options: { network: NetworkId }) { if (!options?.network) { throw Error('Invalid Specified Network'); } const specifiedNetwork = options.network.toLowerCase(); - if (specifiedNetwork !== 'mainnet' && specifiedNetwork !== 'testnet') { - throw Error('Invalid Specified Network'); - } + this.network = specifiedNetwork; } @@ -122,9 +120,13 @@ class Client { * @param privateKey The private key used for signing * @returns The signed field elements */ - signFields(fields: bigint[], privateKey: Json.PrivateKey): Signed { + signFields( + fields: bigint[], + privateKey: Json.PrivateKey, + network?: NetworkId + ): Signed { let privateKey_ = PrivateKey.fromBase58(privateKey); - let signature = sign({ fields }, privateKey_, 'testnet'); + let signature = sign({ fields }, privateKey_, network ?? 'testnet'); return { signature: Signature.toBase58(signature), publicKey: PublicKey.toBase58(PrivateKey.toPublicKey(privateKey_)), @@ -139,12 +141,15 @@ class Client { * @returns True if the `signedFields` contains a valid signature matching * the fields and publicKey. */ - verifyFields({ data, signature, publicKey }: Signed) { + verifyFields( + { data, signature, publicKey }: Signed, + network?: NetworkId + ) { return verify( Signature.fromBase58(signature), { fields: data }, PublicKey.fromBase58(publicKey), - 'testnet' + network ?? 'testnet' ); } diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index e5a6985f08..05cc303768 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -165,7 +165,6 @@ const zkAppBodyPrefix = (network: string) => { return prefixes.zkappBodyMainnet; case 'testnet': return prefixes.zkappBodyTestnet; - default: return 'ZkappBody' + network; } diff --git a/src/mina-signer/src/test-vectors/legacySignatures.ts b/src/mina-signer/src/test-vectors/legacySignatures.ts index d31f538f58..7090587475 100644 --- a/src/mina-signer/src/test-vectors/legacySignatures.ts +++ b/src/mina-signer/src/test-vectors/legacySignatures.ts @@ -90,7 +90,7 @@ let strings = [ * - the 3 stake delegations, * - the 3 strings. */ -let signatures = { +let signatures: { [k: string]: { field: string; scalar: string }[] } = { testnet: [ { field: diff --git a/src/mina-signer/src/types.ts b/src/mina-signer/src/types.ts index d0e2c6991a..28ed239753 100644 --- a/src/mina-signer/src/types.ts +++ b/src/mina-signer/src/types.ts @@ -9,7 +9,13 @@ export type Field = number | bigint | string; export type PublicKey = string; export type PrivateKey = string; export type Signature = SignatureJson; -export type NetworkId = 'mainnet' | 'testnet'; +export type NetworkId = 'mainnet' | 'testnet' | string; + +export const NetworkID = { + Mainnet: 'mainnet', + Testnet: 'testnet', + Other: (other: string) => other, +}; export type Keypair = { readonly privateKey: PrivateKey; From 66f0699f3f7dd8e8152d9b9abc3537bcd53a9ac4 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 00:42:10 +0100 Subject: [PATCH 02/22] temp --- src/lib/account-update.ts | 35 ++++-- src/mina-signer/src/random-transaction.ts | 2 +- src/mina-signer/src/sign-zkapp-command.ts | 15 ++- .../src/sign-zkapp-command.unit-test.ts | 102 +++++++++--------- src/mina-signer/src/signature.ts | 41 +++++-- src/mina-signer/src/signature.unit-test.ts | 4 +- src/mina-signer/src/types.ts | 2 +- src/snarky.d.ts | 3 +- 8 files changed, 134 insertions(+), 70 deletions(-) diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index f8673c7ad4..b06e1bf2fe 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -65,6 +65,7 @@ import { } from './mina/smart-contract-context.js'; import { assert } from './util/assert.js'; import { RandomId } from './provable-types/auxiliary.js'; +import { max } from 'src/bindings/crypto/bigint-helpers.js'; // external API export { @@ -104,6 +105,32 @@ const TransactionVersion = { current: () => UInt32.from(protocolVersions.txnVersion), }; +// TODO FIX ME PLS + +const createCustomBodyPrefix = (prefix: string) => { + const maxLength = 20; + const paddingChar = '*'; + let length = prefix.length; + + if (length <= maxLength) { + let diff = maxLength - length; + return prefix + paddingChar.repeat(diff); + } else { + return prefix.substring(0, maxLength); + } +}; + +const zkAppBodyPrefix = (network: string) => { + switch (network) { + case 'mainnet': + return prefixes.zkappBodyMainnet; + case 'testnet': + return prefixes.zkappBodyTestnet; + default: + return createCustomBodyPrefix(network + 'ZkappBody'); + } +}; + type ZkappProverData = { transaction: ZkappCommand; accountUpdate: AccountUpdate; @@ -1018,9 +1045,7 @@ class AccountUpdate implements Types.AccountUpdate { if (Provable.inCheckedComputation()) { let input = Types.AccountUpdate.toInput(this); return hashWithPrefix( - activeInstance.getNetworkId() === 'mainnet' - ? prefixes.zkappBodyMainnet - : prefixes.zkappBodyTestnet, + zkAppBodyPrefix(activeInstance.getNetworkId()), packToFields(input) ); } else { @@ -1399,9 +1424,7 @@ class AccountUpdate implements Types.AccountUpdate { function hashAccountUpdate(update: AccountUpdate) { return genericHash( AccountUpdate, - activeInstance.getNetworkId() === 'mainnet' - ? prefixes.zkappBodyMainnet - : prefixes.zkappBodyTestnet, + zkAppBodyPrefix(activeInstance.getNetworkId()), update ); } diff --git a/src/mina-signer/src/random-transaction.ts b/src/mina-signer/src/random-transaction.ts index 6ac2ab78ea..4709b7e433 100644 --- a/src/mina-signer/src/random-transaction.ts +++ b/src/mina-signer/src/random-transaction.ts @@ -141,6 +141,6 @@ const RandomTransaction = { zkappCommand, zkappCommandAndFeePayerKey, zkappCommandJson, - networkId: Random.oneOf('testnet', 'mainnet'), + networkId: Random.oneOf('testnet', 'mainnet', 'other'), accountUpdateWithCallDepth: accountUpdate, }; diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 05cc303768..79f57ee932 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -159,6 +159,19 @@ function accountUpdatesToCallForest( return forest; } +const createCustomBodyPrefix = (prefix: string) => { + const maxLength = 20; + const paddingChar = '*'; + let length = prefix.length; + + if (length <= maxLength) { + let diff = maxLength - length; + return prefix + paddingChar.repeat(diff); + } else { + return prefix.substring(0, maxLength); + } +}; + const zkAppBodyPrefix = (network: string) => { switch (network) { case 'mainnet': @@ -166,7 +179,7 @@ const zkAppBodyPrefix = (network: string) => { case 'testnet': return prefixes.zkappBodyTestnet; default: - return 'ZkappBody' + network; + return createCustomBodyPrefix(network + 'ZkappBody'); } }; diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index b11d9a02c9..1345bc116a 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -82,42 +82,38 @@ expect(stringify(dummyInput.packed)).toEqual( stringify(dummyInputSnarky.packed) ); -test(Random.accountUpdate, (accountUpdate) => { - const testnetMinaInstance = Network({ - networkId: 'testnet', - mina: 'http://localhost:8080/graphql', - }); - const mainnetMinaInstance = Network({ - networkId: 'mainnet', - mina: 'http://localhost:8080/graphql', - }); - - fixVerificationKey(accountUpdate); - - // example account update - let accountUpdateJson: Json.AccountUpdate = - AccountUpdate.toJSON(accountUpdate); - - // account update hash - let accountUpdateSnarky = AccountUpdateSnarky.fromJSON(accountUpdateJson); - let inputSnarky = TypesSnarky.AccountUpdate.toInput(accountUpdateSnarky); - let input = AccountUpdate.toInput(accountUpdate); - expect(toJSON(input.fields)).toEqual(toJSON(inputSnarky.fields)); - expect(toJSON(input.packed)).toEqual(toJSON(inputSnarky.packed)); - - let packed = packToFields(input); - let packedSnarky = packToFieldsSnarky(inputSnarky); - expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); - - let hashTestnet = accountUpdateHash(accountUpdate, 'testnet'); - let hashMainnet = accountUpdateHash(accountUpdate, 'mainnet'); - setActiveInstance(testnetMinaInstance); - let hashSnarkyTestnet = accountUpdateSnarky.hash(); - setActiveInstance(mainnetMinaInstance); - let hashSnarkyMainnet = accountUpdateSnarky.hash(); - expect(hashTestnet).toEqual(hashSnarkyTestnet.toBigInt()); - expect(hashMainnet).toEqual(hashSnarkyMainnet.toBigInt()); -}); +test( + Random.accountUpdate, + RandomTransaction.networkId, + (accountUpdate, networkId) => { + const minaInstance = Network({ + networkId, + mina: 'http://localhost:8080/graphql', + }); + + fixVerificationKey(accountUpdate); + + // example account update + let accountUpdateJson: Json.AccountUpdate = + AccountUpdate.toJSON(accountUpdate); + + // account update hash + let accountUpdateSnarky = AccountUpdateSnarky.fromJSON(accountUpdateJson); + let inputSnarky = TypesSnarky.AccountUpdate.toInput(accountUpdateSnarky); + let input = AccountUpdate.toInput(accountUpdate); + expect(toJSON(input.fields)).toEqual(toJSON(inputSnarky.fields)); + expect(toJSON(input.packed)).toEqual(toJSON(inputSnarky.packed)); + + let packed = packToFields(input); + let packedSnarky = packToFieldsSnarky(inputSnarky); + expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); + + setActiveInstance(minaInstance); + let hashSnarky = accountUpdateSnarky.hash(); + let hash = accountUpdateHash(accountUpdate, networkId); + expect(hash).toEqual(hashSnarky.toBigInt()); + } +); // private key to/from base58 test(Random.json.privateKey, (feePayerKeyBase58) => { @@ -140,19 +136,25 @@ test(memoGenerator, (memoString) => { }); // zkapp transaction - basic properties & commitment -test(RandomTransaction.zkappCommand, (zkappCommand, assert) => { - zkappCommand.accountUpdates.forEach(fixVerificationKey); - - assert(isCallDepthValid(zkappCommand)); - let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); - let ocamlCommitments = Test.hashFromJson.transactionCommitments( - JSON.stringify(zkappCommandJson), - 'testnet' - ); - let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); - let commitment = callForestHash(callForest, 'testnet'); - expect(commitment).toEqual(FieldConst.toBigint(ocamlCommitments.commitment)); -}); +test( + RandomTransaction.zkappCommand, + RandomTransaction.networkId, + (zkappCommand, networkId, assert) => { + zkappCommand.accountUpdates.forEach(fixVerificationKey); + + assert(isCallDepthValid(zkappCommand)); + let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); + let ocamlCommitments = Test.hashFromJson.transactionCommitments( + JSON.stringify(zkappCommandJson), + networkId + ); + let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); + let commitment = callForestHash(callForest, networkId); + expect(commitment).toEqual( + FieldConst.toBigint(ocamlCommitments.commitment) + ); + } +); // invalid zkapp transactions test.negative( @@ -242,7 +244,7 @@ test( let sigOCaml = Test.signature.signFieldElement( ocamlCommitments.fullCommitment, Ml.fromPrivateKey(feePayerKeySnarky), - networkId === 'mainnet' ? true : false + networkId ); expect(Signature.toBase58(sigFieldElements)).toEqual(sigOCaml); diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 14a2096ec0..58eeb59cce 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -150,10 +150,10 @@ function deriveNonce( ): Scalar { let { x, y } = publicKey; let d = Field(privateKey); - let id = networkId === 'mainnet' ? networkIdMainnet : networkIdTestnet; + let id = getNetworkId(networkId); let input = HashInput.append(message, { fields: [x, y, d], - packed: [[id, 8]], + packed: [[id, 40]], }); let packedInput = packToFields(input); let inputBits = packedInput.map(Field.toBits).flat(); @@ -189,11 +189,12 @@ function hashMessage( ): Scalar { let { x, y } = publicKey; let input = HashInput.append(message, { fields: [x, y, r] }); - let prefix = - networkId === 'mainnet' - ? prefixes.signatureMainnet - : prefixes.signatureTestnet; - return hashWithPrefix(prefix, packToFields(input)); + + let chain = ''; + if (networkId === 'mainnet') chain = prefixes.signatureMainnet; + else if (networkId === 'testnet') chain = prefixes.signatureTestnet; + else chain = 'otherSignature******'; + return hashWithPrefix(chain, packToFields(input)); } /** @@ -280,7 +281,7 @@ function deriveNonceLegacy( ): Scalar { let { x, y } = publicKey; let scalarBits = Scalar.toBits(privateKey); - let id = networkId === 'mainnet' ? networkIdMainnet : networkIdTestnet; + let id = getNetworkId(networkId); let idBits = bytesToBits([Number(id)]); let input = HashInputLegacy.append(message, { fields: [x, y], @@ -317,3 +318,27 @@ function hashMessageLegacy( : prefixes.signatureTestnet; return HashLegacy.hashWithPrefix(prefix, packToFieldsLegacy(input)); } + +const toBytePadded = (b: number) => ('000000000' + b.toString(2)).substr(-8); + +function networkIdOfString(n: string) { + let l = n.length; + let acc = ''; + for (let i = l - 1; i >= 0; i--) { + let b = n.charCodeAt(i); + let padded = toBytePadded(b); + acc = acc.concat(padded); + } + return BigInt('0b' + [...acc].reverse().join('')); +} + +function getNetworkId(networkId: string) { + switch (networkId) { + case 'mainnet': + return networkIdMainnet; + case 'testnet': + return networkIdTestnet; + default: + return networkIdOfString(networkId); + } +} diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index c6ae459e04..e3161cbc2b 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -39,8 +39,8 @@ function checkConsistentSingle( // consistent with OCaml let msgMl = FieldConst.fromBigint(msg); let keyMl = Ml.fromPrivateKey(keySnarky); - let actualTest = Test.signature.signFieldElement(msgMl, keyMl, false); - let actualMain = Test.signature.signFieldElement(msgMl, keyMl, true); + let actualTest = Test.signature.signFieldElement(msgMl, keyMl, 'testnet'); + let actualMain = Test.signature.signFieldElement(msgMl, keyMl, 'mainnet'); expect(Signature.toBase58(sigTest)).toEqual(actualTest); expect(Signature.toBase58(sigMain)).toEqual(actualMain); } diff --git a/src/mina-signer/src/types.ts b/src/mina-signer/src/types.ts index 28ed239753..6591bcaf91 100644 --- a/src/mina-signer/src/types.ts +++ b/src/mina-signer/src/types.ts @@ -9,7 +9,7 @@ export type Field = number | bigint | string; export type PublicKey = string; export type PrivateKey = string; export type Signature = SignatureJson; -export type NetworkId = 'mainnet' | 'testnet' | string; +export type NetworkId = string; export const NetworkID = { Mainnet: 'mainnet', diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 51a43dcb40..8af068222e 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -647,7 +647,7 @@ declare const Test: { signFieldElement( messageHash: FieldConst, privateKey: ScalarConst, - isMainnet: boolean + networkId: string ): string; /** * Returns a dummy signature. @@ -663,6 +663,7 @@ declare const Test: { /** * Returns the commitment of a JSON transaction. */ + test(networkid: string): void; transactionCommitments( txJson: string, networkId: string From d72446838ae6b0c36a185fc3f3b2a7cb2f28c793 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 13:56:25 +0100 Subject: [PATCH 03/22] make custom network deriver for signature --- src/mina-signer/src/signature.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 58eeb59cce..9a27c6df34 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -153,7 +153,7 @@ function deriveNonce( let id = getNetworkId(networkId); let input = HashInput.append(message, { fields: [x, y, d], - packed: [[id, 40]], + packed: [id], }); let packedInput = packToFields(input); let inputBits = packedInput.map(Field.toBits).flat(); @@ -321,7 +321,7 @@ function hashMessageLegacy( const toBytePadded = (b: number) => ('000000000' + b.toString(2)).substr(-8); -function networkIdOfString(n: string) { +function networkIdOfString(n: string): [bigint, number] { let l = n.length; let acc = ''; for (let i = l - 1; i >= 0; i--) { @@ -329,15 +329,15 @@ function networkIdOfString(n: string) { let padded = toBytePadded(b); acc = acc.concat(padded); } - return BigInt('0b' + [...acc].reverse().join('')); + return [BigInt('0b' + acc), acc.length]; } -function getNetworkId(networkId: string) { +function getNetworkId(networkId: string): [bigint, number] { switch (networkId) { case 'mainnet': - return networkIdMainnet; + return [networkIdMainnet, 8]; case 'testnet': - return networkIdTestnet; + return [networkIdTestnet, 8]; default: return networkIdOfString(networkId); } From 9081fb84b25b08f32893e78fb647d9f9659a2ef0 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 14:01:26 +0100 Subject: [PATCH 04/22] fix types --- src/snarky.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 8af068222e..3e7daca766 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -663,7 +663,6 @@ declare const Test: { /** * Returns the commitment of a JSON transaction. */ - test(networkid: string): void; transactionCommitments( txJson: string, networkId: string From 44070bdeccfe2ce6e733a8af32597c45d19cb6a4 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 14:29:19 +0100 Subject: [PATCH 05/22] refac --- src/lib/account-update.ts | 1 - src/mina-signer/src/sign-zkapp-command.ts | 5 +-- src/mina-signer/src/signature.ts | 24 +++++++------ src/mina-signer/src/signature.unit-test.ts | 41 +++++++++++++--------- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index b06e1bf2fe..1a9bab0920 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -65,7 +65,6 @@ import { } from './mina/smart-contract-context.js'; import { assert } from './util/assert.js'; import { RandomId } from './provable-types/auxiliary.js'; -import { max } from 'src/bindings/crypto/bigint-helpers.js'; // external API export { diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 79f57ee932..cc0bcfde6b 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -35,6 +35,7 @@ export { accountUpdateFromFeePayer, isCallDepthValid, CallForest, + createCustomPrefix, }; function signZkappCommand( @@ -159,7 +160,7 @@ function accountUpdatesToCallForest( return forest; } -const createCustomBodyPrefix = (prefix: string) => { +const createCustomPrefix = (prefix: string) => { const maxLength = 20; const paddingChar = '*'; let length = prefix.length; @@ -179,7 +180,7 @@ const zkAppBodyPrefix = (network: string) => { case 'testnet': return prefixes.zkappBodyTestnet; default: - return createCustomBodyPrefix(network + 'ZkappBody'); + return createCustomPrefix(network + 'ZkappBody'); } }; diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 9a27c6df34..73f7844601 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -28,6 +28,7 @@ import { base58 } from '../../lib/base58.js'; import { versionBytes } from '../../bindings/crypto/constants.js'; import { Pallas } from '../../bindings/crypto/elliptic-curve.js'; import { NetworkId } from './types.js'; +import { createCustomPrefix } from './sign-zkapp-command.js'; export { sign, @@ -189,12 +190,7 @@ function hashMessage( ): Scalar { let { x, y } = publicKey; let input = HashInput.append(message, { fields: [x, y, r] }); - - let chain = ''; - if (networkId === 'mainnet') chain = prefixes.signatureMainnet; - else if (networkId === 'testnet') chain = prefixes.signatureTestnet; - else chain = 'otherSignature******'; - return hashWithPrefix(chain, packToFields(input)); + return hashWithPrefix(signaturePrefix(networkId), packToFields(input)); } /** @@ -312,10 +308,7 @@ function hashMessageLegacy( ): Scalar { let { x, y } = publicKey; let input = HashInputLegacy.append(message, { fields: [x, y, r], bits: [] }); - let prefix = - networkId === 'mainnet' - ? prefixes.signatureMainnet - : prefixes.signatureTestnet; + let prefix = signaturePrefix(networkId); return HashLegacy.hashWithPrefix(prefix, packToFieldsLegacy(input)); } @@ -342,3 +335,14 @@ function getNetworkId(networkId: string): [bigint, number] { return networkIdOfString(networkId); } } + +const signaturePrefix = (network: string) => { + switch (network) { + case 'mainnet': + return prefixes.signatureMainnet; + case 'testnet': + return prefixes.signatureTestnet; + default: + return createCustomPrefix(network + 'Signature'); + } +}; diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index e3161cbc2b..50999cc89f 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -23,26 +23,29 @@ function checkConsistentSingle( msg: Field, key: PrivateKey, keySnarky: PrivateKeySnarky, - pk: PublicKey + pk: PublicKey, + networkId: string ) { - let sigTest = signFieldElement(msg, key, 'testnet'); - let sigMain = signFieldElement(msg, key, 'mainnet'); + let sig = signFieldElement(msg, key, networkId); + // verify - let okTestnetTestnet = verifyFieldElement(sigTest, msg, pk, 'testnet'); - let okMainnetTestnet = verifyFieldElement(sigMain, msg, pk, 'testnet'); - let okTestnetMainnet = verifyFieldElement(sigTest, msg, pk, 'mainnet'); - let okMainnetMainnet = verifyFieldElement(sigMain, msg, pk, 'mainnet'); - expect(okTestnetTestnet).toEqual(true); - expect(okMainnetTestnet).toEqual(false); - expect(okTestnetMainnet).toEqual(false); - expect(okMainnetMainnet).toEqual(true); + expect(verifyFieldElement(sig, msg, pk, networkId)).toEqual(true); + + // verify against different network + expect( + verifyFieldElement( + sig, + msg, + pk, + networkId === 'mainnet' ? 'testnet' : 'mainnet' + ) + ).toEqual(false); + // consistent with OCaml let msgMl = FieldConst.fromBigint(msg); let keyMl = Ml.fromPrivateKey(keySnarky); - let actualTest = Test.signature.signFieldElement(msgMl, keyMl, 'testnet'); - let actualMain = Test.signature.signFieldElement(msgMl, keyMl, 'mainnet'); - expect(Signature.toBase58(sigTest)).toEqual(actualTest); - expect(Signature.toBase58(sigMain)).toEqual(actualMain); + let actualTest = Test.signature.signFieldElement(msgMl, keyMl, networkId); + expect(Signature.toBase58(sig)).toEqual(actualTest); } // check that various multi-field hash inputs can be verified @@ -96,12 +99,16 @@ for (let i = 0; i < 10; i++) { // hard coded single field elements let hardcoded = [0n, 1n, 2n, p - 1n]; for (let x of hardcoded) { - checkConsistentSingle(x, key, keySnarky, publicKey); + checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, 'other'); } // random single field elements for (let i = 0; i < 10; i++) { let x = randomFields[i]; - checkConsistentSingle(x, key, keySnarky, publicKey); + checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); + checkConsistentSingle(x, key, keySnarky, publicKey, 'other'); } // hard-coded multi-element hash inputs let messages: HashInput[] = [ From f4625c1e2c303dda0005f3cb3ddd8b16a1292e0f Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 14:34:50 +0100 Subject: [PATCH 06/22] fix legacy test --- src/mina-signer/src/signature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 73f7844601..70e04dd7aa 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -277,7 +277,7 @@ function deriveNonceLegacy( ): Scalar { let { x, y } = publicKey; let scalarBits = Scalar.toBits(privateKey); - let id = getNetworkId(networkId); + let id = getNetworkId(networkId)[0]; let idBits = bytesToBits([Number(id)]); let input = HashInputLegacy.append(message, { fields: [x, y], From eef6849db3cfba187c6e7cc42eba741f26ab0c0f Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 14:40:59 +0100 Subject: [PATCH 07/22] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a6b6800186..1beb2fec84 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a6b6800186752b3cf5c9a29b7eb167e494784286 +Subproject commit 1beb2fec847e18225adf6dd3687c3459550fe676 From 02450c482d447967b6fb82732fc56143f6bfb1fb Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 14:54:44 +0100 Subject: [PATCH 08/22] refac derivers --- src/lib/account-update.ts | 32 ++++------------------ src/mina-signer/src/sign-zkapp-command.ts | 26 +----------------- src/mina-signer/src/signature.ts | 33 ++++++++++++++++++++--- 3 files changed, 35 insertions(+), 56 deletions(-) diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index 1a9bab0920..5f20c2f20b 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -41,7 +41,11 @@ import { protocolVersions, } from '../bindings/crypto/constants.js'; import { MlArray } from './ml/base.js'; -import { Signature, signFieldElement } from '../mina-signer/src/signature.js'; +import { + Signature, + signFieldElement, + zkAppBodyPrefix, +} from '../mina-signer/src/signature.js'; import { MlFieldConstArray } from './ml/fields.js'; import { accountUpdatesToCallForest, @@ -104,32 +108,6 @@ const TransactionVersion = { current: () => UInt32.from(protocolVersions.txnVersion), }; -// TODO FIX ME PLS - -const createCustomBodyPrefix = (prefix: string) => { - const maxLength = 20; - const paddingChar = '*'; - let length = prefix.length; - - if (length <= maxLength) { - let diff = maxLength - length; - return prefix + paddingChar.repeat(diff); - } else { - return prefix.substring(0, maxLength); - } -}; - -const zkAppBodyPrefix = (network: string) => { - switch (network) { - case 'mainnet': - return prefixes.zkappBodyMainnet; - case 'testnet': - return prefixes.zkappBodyTestnet; - default: - return createCustomBodyPrefix(network + 'ZkappBody'); - } -}; - type ZkappProverData = { transaction: ZkappCommand; accountUpdate: AccountUpdate; diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index cc0bcfde6b..c70f8556ff 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -15,6 +15,7 @@ import { Signature, signFieldElement, verifyFieldElement, + zkAppBodyPrefix, } from './signature.js'; import { mocks } from '../../bindings/crypto/constants.js'; import { NetworkId } from './types.js'; @@ -35,7 +36,6 @@ export { accountUpdateFromFeePayer, isCallDepthValid, CallForest, - createCustomPrefix, }; function signZkappCommand( @@ -160,30 +160,6 @@ function accountUpdatesToCallForest( return forest; } -const createCustomPrefix = (prefix: string) => { - const maxLength = 20; - const paddingChar = '*'; - let length = prefix.length; - - if (length <= maxLength) { - let diff = maxLength - length; - return prefix + paddingChar.repeat(diff); - } else { - return prefix.substring(0, maxLength); - } -}; - -const zkAppBodyPrefix = (network: string) => { - switch (network) { - case 'mainnet': - return prefixes.zkappBodyMainnet; - case 'testnet': - return prefixes.zkappBodyTestnet; - default: - return createCustomPrefix(network + 'ZkappBody'); - } -}; - function accountUpdateHash(update: AccountUpdate, networkId: NetworkId) { assertAuthorizationKindValid(update); let input = AccountUpdate.toInput(update); diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 70e04dd7aa..37600b0e81 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -28,7 +28,6 @@ import { base58 } from '../../lib/base58.js'; import { versionBytes } from '../../bindings/crypto/constants.js'; import { Pallas } from '../../bindings/crypto/elliptic-curve.js'; import { NetworkId } from './types.js'; -import { createCustomPrefix } from './sign-zkapp-command.js'; export { sign, @@ -40,6 +39,8 @@ export { signLegacy, verifyLegacy, deriveNonce, + signaturePrefix, + zkAppBodyPrefix, }; const networkIdMainnet = 0x01n; @@ -151,7 +152,7 @@ function deriveNonce( ): Scalar { let { x, y } = publicKey; let d = Field(privateKey); - let id = getNetworkId(networkId); + let id = getNetworkIdHashInput(networkId); let input = HashInput.append(message, { fields: [x, y, d], packed: [id], @@ -277,7 +278,7 @@ function deriveNonceLegacy( ): Scalar { let { x, y } = publicKey; let scalarBits = Scalar.toBits(privateKey); - let id = getNetworkId(networkId)[0]; + let id = getNetworkIdHashInput(networkId)[0]; let idBits = bytesToBits([Number(id)]); let input = HashInputLegacy.append(message, { fields: [x, y], @@ -325,7 +326,7 @@ function networkIdOfString(n: string): [bigint, number] { return [BigInt('0b' + acc), acc.length]; } -function getNetworkId(networkId: string): [bigint, number] { +function getNetworkIdHashInput(networkId: string): [bigint, number] { switch (networkId) { case 'mainnet': return [networkIdMainnet, 8]; @@ -336,6 +337,19 @@ function getNetworkId(networkId: string): [bigint, number] { } } +const createCustomPrefix = (prefix: string) => { + const maxLength = 20; + const paddingChar = '*'; + let length = prefix.length; + + if (length <= maxLength) { + let diff = maxLength - length; + return prefix + paddingChar.repeat(diff); + } else { + return prefix.substring(0, maxLength); + } +}; + const signaturePrefix = (network: string) => { switch (network) { case 'mainnet': @@ -346,3 +360,14 @@ const signaturePrefix = (network: string) => { return createCustomPrefix(network + 'Signature'); } }; + +const zkAppBodyPrefix = (network: string) => { + switch (network) { + case 'mainnet': + return prefixes.zkappBodyMainnet; + case 'testnet': + return prefixes.zkappBodyTestnet; + default: + return createCustomPrefix(network + 'ZkappBody'); + } +}; From 33abba1cd6febfa660bb461e3afa25417a181b3b Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 15:03:22 +0100 Subject: [PATCH 09/22] remove explicit network id declartion --- src/mina-signer/src/types.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/mina-signer/src/types.ts b/src/mina-signer/src/types.ts index 6591bcaf91..e1b7a52c32 100644 --- a/src/mina-signer/src/types.ts +++ b/src/mina-signer/src/types.ts @@ -11,12 +11,6 @@ export type PrivateKey = string; export type Signature = SignatureJson; export type NetworkId = string; -export const NetworkID = { - Mainnet: 'mainnet', - Testnet: 'testnet', - Other: (other: string) => other, -}; - export type Keypair = { readonly privateKey: PrivateKey; readonly publicKey: PublicKey; From 90bb55800ae6a67d9b9e700840e08f6431a7440f Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Sun, 18 Feb 2024 15:10:37 +0100 Subject: [PATCH 10/22] dont use deprecated function --- src/mina-signer/src/signature.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 37600b0e81..1da40bfaa4 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -313,14 +313,14 @@ function hashMessageLegacy( return HashLegacy.hashWithPrefix(prefix, packToFieldsLegacy(input)); } -const toBytePadded = (b: number) => ('000000000' + b.toString(2)).substr(-8); +const numberToBytePadded = (b: number) => b.toString(2).padStart(8, '0'); function networkIdOfString(n: string): [bigint, number] { let l = n.length; let acc = ''; for (let i = l - 1; i >= 0; i--) { let b = n.charCodeAt(i); - let padded = toBytePadded(b); + let padded = numberToBytePadded(b); acc = acc.concat(padded); } return [BigInt('0b' + acc), acc.length]; From 3cb66fb27eabdec6191b9c926813b138ab4de72b Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Mon, 19 Feb 2024 13:22:26 +0100 Subject: [PATCH 11/22] add optional network parameter to signature --- src/lib/signature.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 261aab8cc2..9c80c0a834 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -235,7 +235,11 @@ class Signature extends CircuitValue { * Signs a message using a {@link PrivateKey}. * @returns a {@link Signature} */ - static create(privKey: PrivateKey, msg: Field[]): Signature { + static create( + privKey: PrivateKey, + msg: Field[], + networkId?: string + ): Signature { const publicKey = PublicKey.fromPrivateKey(privKey).toGroup(); const d = privKey.s; const kPrime = Scalar.fromBigInt( @@ -243,7 +247,7 @@ class Signature extends CircuitValue { { fields: msg.map((f) => f.toBigInt()) }, { x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() }, BigInt(d.toJSON()), - 'testnet' + networkId ?? 'testnet' ) ); let { x: r, y: ry } = Group.generator.scale(kPrime); From 105320dbd0b8fc31c6abbf022c6778a4324b9e08 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Mon, 19 Feb 2024 13:25:53 +0100 Subject: [PATCH 12/22] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0cba5c1aa..2a59c77398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/3b5f7c7...HEAD) +### Added + +- Support for custom network identifiers other than `mainnet` or `testnet` https://github.com/o1-labs/o1js/pull/1444 + - New optional argument `networkId?: string` in `Signature.create(privKey: PrivateKey, msg: Field[], networkId?: string)` to reflect support for custom network identifiers. + ## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) ### Breaking changes From 90764e564af70c0dd5d0e4f2b46ab8e2f3acd0f9 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Mon, 19 Feb 2024 13:31:40 +0100 Subject: [PATCH 13/22] fix verify --- CHANGELOG.md | 2 +- src/lib/signature.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a59c77398..556cd8e80c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Support for custom network identifiers other than `mainnet` or `testnet` https://github.com/o1-labs/o1js/pull/1444 - - New optional argument `networkId?: string` in `Signature.create(privKey: PrivateKey, msg: Field[], networkId?: string)` to reflect support for custom network identifiers. + - New optional argument `networkId?: string` toin `Signature.create()` and `Signature.verify()` to reflect support for custom network identifiers. ## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 9c80c0a834..30ee60d0e1 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -4,6 +4,7 @@ import { hashWithPrefix } from './hash.js'; import { deriveNonce, Signature as SignatureBigint, + signaturePrefix, } from '../mina-signer/src/signature.js'; import { Bool as BoolBigint } from '../provable/field-bigint.js'; import { @@ -253,7 +254,7 @@ class Signature extends CircuitValue { let { x: r, y: ry } = Group.generator.scale(kPrime); const k = ry.toBits()[0].toBoolean() ? kPrime.neg() : kPrime; let h = hashWithPrefix( - prefixes.signatureTestnet, + signaturePrefix(networkId ?? 'testnet'), msg.concat([publicKey.x, publicKey.y, r]) ); // TODO: Scalar.fromBits interprets the input as a "shifted scalar" @@ -267,10 +268,10 @@ class Signature extends CircuitValue { * Verifies the {@link Signature} using a message and the corresponding {@link PublicKey}. * @returns a {@link Bool} */ - verify(publicKey: PublicKey, msg: Field[]): Bool { + verify(publicKey: PublicKey, msg: Field[], networkId?: string): Bool { const point = publicKey.toGroup(); let h = hashWithPrefix( - prefixes.signatureTestnet, + signaturePrefix(networkId ?? 'testnet'), msg.concat([point.x, point.y, this.r]) ); // TODO: Scalar.fromBits interprets the input as a "shifted scalar" From 2c3a6f2e3c5036ed374e96b1e628c5401d8e8795 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Mon, 19 Feb 2024 13:31:54 +0100 Subject: [PATCH 14/22] typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 556cd8e80c..e290278623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Support for custom network identifiers other than `mainnet` or `testnet` https://github.com/o1-labs/o1js/pull/1444 - - New optional argument `networkId?: string` toin `Signature.create()` and `Signature.verify()` to reflect support for custom network identifiers. + - New optional argument `networkId?: string` to `Signature.create()` and `Signature.verify()` to reflect support for custom network identifiers. ## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) From cd43ef7994cfa8f6ab7cf60c9fe861b7080db370 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 09:02:44 +0100 Subject: [PATCH 15/22] revert sig type --- CHANGELOG.md | 1 - src/lib/signature.ts | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e290278623..195463582c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Support for custom network identifiers other than `mainnet` or `testnet` https://github.com/o1-labs/o1js/pull/1444 - - New optional argument `networkId?: string` to `Signature.create()` and `Signature.verify()` to reflect support for custom network identifiers. ## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 30ee60d0e1..bdc0450248 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -243,18 +243,21 @@ class Signature extends CircuitValue { ): Signature { const publicKey = PublicKey.fromPrivateKey(privKey).toGroup(); const d = privKey.s; + // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' + // there's no consequences in practice and the signatures can be used with any network + // if there needs to be a custom nonce, include it in the message itself const kPrime = Scalar.fromBigInt( deriveNonce( { fields: msg.map((f) => f.toBigInt()) }, { x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() }, BigInt(d.toJSON()), - networkId ?? 'testnet' + 'testnet' ) ); let { x: r, y: ry } = Group.generator.scale(kPrime); const k = ry.toBits()[0].toBoolean() ? kPrime.neg() : kPrime; let h = hashWithPrefix( - signaturePrefix(networkId ?? 'testnet'), + signaturePrefix('testnet'), msg.concat([publicKey.x, publicKey.y, r]) ); // TODO: Scalar.fromBits interprets the input as a "shifted scalar" @@ -270,8 +273,11 @@ class Signature extends CircuitValue { */ verify(publicKey: PublicKey, msg: Field[], networkId?: string): Bool { const point = publicKey.toGroup(); + // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' + // there's no consequences in practice and the signatures can be used with any network + // if there needs to be a custom nonce, include it in the message itself let h = hashWithPrefix( - signaturePrefix(networkId ?? 'testnet'), + signaturePrefix('testnet'), msg.concat([point.x, point.y, this.r]) ); // TODO: Scalar.fromBits interprets the input as a "shifted scalar" From 1a60fd1012686f87465ddf7f3072e715369bc3c9 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 10:14:23 +0100 Subject: [PATCH 16/22] revert sig type --- src/lib/signature.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index bdc0450248..c830f21319 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -236,11 +236,7 @@ class Signature extends CircuitValue { * Signs a message using a {@link PrivateKey}. * @returns a {@link Signature} */ - static create( - privKey: PrivateKey, - msg: Field[], - networkId?: string - ): Signature { + static create(privKey: PrivateKey, msg: Field[]): Signature { const publicKey = PublicKey.fromPrivateKey(privKey).toGroup(); const d = privKey.s; // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' @@ -271,7 +267,7 @@ class Signature extends CircuitValue { * Verifies the {@link Signature} using a message and the corresponding {@link PublicKey}. * @returns a {@link Bool} */ - verify(publicKey: PublicKey, msg: Field[], networkId?: string): Bool { + verify(publicKey: PublicKey, msg: Field[]): Bool { const point = publicKey.toGroup(); // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' // there's no consequences in practice and the signatures can be used with any network From 3c799af8ed655b7d0171cccffae3b660401ef340 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 10:19:31 +0100 Subject: [PATCH 17/22] fix build --- src/lib/account-update.ts | 8 +++---- src/mina-signer/mina-signer.ts | 24 +++++-------------- src/mina-signer/src/random-transaction.ts | 4 +++- src/mina-signer/src/sign-legacy.unit-test.ts | 3 ++- .../src/sign-zkapp-command.unit-test.ts | 6 ++--- src/mina-signer/src/signature.ts | 14 +++++------ src/mina-signer/src/signature.unit-test.ts | 13 ++++++---- src/mina-signer/src/types.ts | 2 +- 8 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index 5f20c2f20b..4c6220ad82 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -1019,18 +1019,16 @@ class AccountUpdate implements Types.AccountUpdate { // consistency between JS & OCaml hashing on *every single account update // proof* we create. It will give us 100% confidence that the two // implementations are equivalent, and catch regressions quickly + let networkId = activeInstance.getNetworkId(); if (Provable.inCheckedComputation()) { let input = Types.AccountUpdate.toInput(this); - return hashWithPrefix( - zkAppBodyPrefix(activeInstance.getNetworkId()), - packToFields(input) - ); + return hashWithPrefix(zkAppBodyPrefix(networkId), packToFields(input)); } else { let json = Types.AccountUpdate.toJSON(this); return Field( Test.hashFromJson.accountUpdate( JSON.stringify(json), - activeInstance.getNetworkId() + typeof networkId === 'string' ? networkId : networkId.custom ) ); } diff --git a/src/mina-signer/mina-signer.ts b/src/mina-signer/mina-signer.ts index b2e7ddf494..c1356b52e0 100644 --- a/src/mina-signer/mina-signer.ts +++ b/src/mina-signer/mina-signer.ts @@ -41,13 +41,8 @@ const defaultValidUntil = '4294967295'; class Client { private network: NetworkId; - constructor(options: { network: NetworkId }) { - if (!options?.network) { - throw Error('Invalid Specified Network'); - } - const specifiedNetwork = options.network.toLowerCase(); - - this.network = specifiedNetwork; + constructor({ network }: { network: NetworkId }) { + this.network = network; } /** @@ -120,13 +115,9 @@ class Client { * @param privateKey The private key used for signing * @returns The signed field elements */ - signFields( - fields: bigint[], - privateKey: Json.PrivateKey, - network?: NetworkId - ): Signed { + signFields(fields: bigint[], privateKey: Json.PrivateKey): Signed { let privateKey_ = PrivateKey.fromBase58(privateKey); - let signature = sign({ fields }, privateKey_, network ?? 'testnet'); + let signature = sign({ fields }, privateKey_, 'testnet'); return { signature: Signature.toBase58(signature), publicKey: PublicKey.toBase58(PrivateKey.toPublicKey(privateKey_)), @@ -141,15 +132,12 @@ class Client { * @returns True if the `signedFields` contains a valid signature matching * the fields and publicKey. */ - verifyFields( - { data, signature, publicKey }: Signed, - network?: NetworkId - ) { + verifyFields({ data, signature, publicKey }: Signed) { return verify( Signature.fromBase58(signature), { fields: data }, PublicKey.fromBase58(publicKey), - network ?? 'testnet' + 'testnet' ); } diff --git a/src/mina-signer/src/random-transaction.ts b/src/mina-signer/src/random-transaction.ts index 4709b7e433..d86270790b 100644 --- a/src/mina-signer/src/random-transaction.ts +++ b/src/mina-signer/src/random-transaction.ts @@ -141,6 +141,8 @@ const RandomTransaction = { zkappCommand, zkappCommandAndFeePayerKey, zkappCommandJson, - networkId: Random.oneOf('testnet', 'mainnet', 'other'), + networkId: Random.oneOf('testnet', 'mainnet', { + custom: 'other', + }), accountUpdateWithCallDepth: accountUpdate, }; diff --git a/src/mina-signer/src/sign-legacy.unit-test.ts b/src/mina-signer/src/sign-legacy.unit-test.ts index 403c57287d..f745914460 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -29,7 +29,8 @@ let networks: NetworkId[] = ['testnet', 'mainnet']; for (let network of networks) { let i = 0; - let reference = signatures[network]; + let reference = + signatures[typeof network === 'string' ? network : network.custom]; for (let payment of payments) { let signature = signPayment(payment, privateKey, network); diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 1345bc116a..34b34e9aeb 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -146,7 +146,7 @@ test( let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); let ocamlCommitments = Test.hashFromJson.transactionCommitments( JSON.stringify(zkappCommandJson), - networkId + typeof networkId === 'string' ? networkId : networkId.custom ); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); let commitment = callForestHash(callForest, networkId); @@ -194,7 +194,7 @@ test( // tx commitment let ocamlCommitments = Test.hashFromJson.transactionCommitments( JSON.stringify(zkappCommandJson), - networkId + typeof networkId === 'string' ? networkId : networkId.custom ); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); let commitment = callForestHash(callForest, networkId); @@ -244,7 +244,7 @@ test( let sigOCaml = Test.signature.signFieldElement( ocamlCommitments.fullCommitment, Ml.fromPrivateKey(feePayerKeySnarky), - networkId + typeof networkId === 'string' ? networkId : networkId.custom ); expect(Signature.toBase58(sigFieldElements)).toEqual(sigOCaml); diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 1da40bfaa4..03f11b0811 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -326,14 +326,14 @@ function networkIdOfString(n: string): [bigint, number] { return [BigInt('0b' + acc), acc.length]; } -function getNetworkIdHashInput(networkId: string): [bigint, number] { - switch (networkId) { +function getNetworkIdHashInput(network: NetworkId): [bigint, number] { + switch (typeof network === 'string' ? network : network.custom) { case 'mainnet': return [networkIdMainnet, 8]; case 'testnet': return [networkIdTestnet, 8]; default: - return networkIdOfString(networkId); + return networkIdOfString(network as string); } } @@ -350,8 +350,8 @@ const createCustomPrefix = (prefix: string) => { } }; -const signaturePrefix = (network: string) => { - switch (network) { +const signaturePrefix = (network: NetworkId) => { + switch (typeof network === 'string' ? network : network.custom) { case 'mainnet': return prefixes.signatureMainnet; case 'testnet': @@ -361,8 +361,8 @@ const signaturePrefix = (network: string) => { } }; -const zkAppBodyPrefix = (network: string) => { - switch (network) { +const zkAppBodyPrefix = (network: NetworkId) => { + switch (typeof network === 'string' ? network : network.custom) { case 'mainnet': return prefixes.zkappBodyMainnet; case 'testnet': diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 50999cc89f..165f8fcfec 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -17,6 +17,7 @@ import { AccountUpdate } from '../../bindings/mina-transaction/gen/transaction-b import { HashInput } from '../../bindings/lib/provable-bigint.js'; import { Ml } from '../../lib/ml/conversion.js'; import { FieldConst } from '../../lib/field.js'; +import { NetworkId } from './types.js'; // check consistency with OCaml, where we expose the function to sign 1 field element with "testnet" function checkConsistentSingle( @@ -24,7 +25,7 @@ function checkConsistentSingle( key: PrivateKey, keySnarky: PrivateKeySnarky, pk: PublicKey, - networkId: string + networkId: NetworkId ) { let sig = signFieldElement(msg, key, networkId); @@ -44,7 +45,11 @@ function checkConsistentSingle( // consistent with OCaml let msgMl = FieldConst.fromBigint(msg); let keyMl = Ml.fromPrivateKey(keySnarky); - let actualTest = Test.signature.signFieldElement(msgMl, keyMl, networkId); + let actualTest = Test.signature.signFieldElement( + msgMl, + keyMl, + typeof networkId === 'string' ? networkId : networkId.custom + ); expect(Signature.toBase58(sig)).toEqual(actualTest); } @@ -101,14 +106,14 @@ for (let i = 0; i < 10; i++) { for (let x of hardcoded) { checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); - checkConsistentSingle(x, key, keySnarky, publicKey, 'other'); + checkConsistentSingle(x, key, keySnarky, publicKey, { custom: 'other' }); } // random single field elements for (let i = 0; i < 10; i++) { let x = randomFields[i]; checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); - checkConsistentSingle(x, key, keySnarky, publicKey, 'other'); + checkConsistentSingle(x, key, keySnarky, publicKey, { custom: 'other' }); } // hard-coded multi-element hash inputs let messages: HashInput[] = [ diff --git a/src/mina-signer/src/types.ts b/src/mina-signer/src/types.ts index e1b7a52c32..6133704784 100644 --- a/src/mina-signer/src/types.ts +++ b/src/mina-signer/src/types.ts @@ -9,7 +9,7 @@ export type Field = number | bigint | string; export type PublicKey = string; export type PrivateKey = string; export type Signature = SignatureJson; -export type NetworkId = string; +export type NetworkId = 'mainnet' | 'testnet' | { custom: string }; export type Keypair = { readonly privateKey: PrivateKey; From d311e926607e052f46840e282a10f8598e88d5ce Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 10:23:24 +0100 Subject: [PATCH 18/22] add NetworkID.toString --- src/mina-signer/src/sign-legacy.unit-test.ts | 4 ++-- src/mina-signer/src/signature.ts | 6 +++--- src/mina-signer/src/types.ts | 6 ++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/mina-signer/src/sign-legacy.unit-test.ts b/src/mina-signer/src/sign-legacy.unit-test.ts index f745914460..4a714f3287 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -21,6 +21,7 @@ import { Field } from '../../provable/field-bigint.js'; import { Random, test } from '../../lib/testing/property.js'; import { RandomTransaction } from './random-transaction.js'; import { NetworkId } from './types.js'; +import { Network } from 'src/lib/precondition.js'; let { privateKey, publicKey } = keypair; let networks: NetworkId[] = ['testnet', 'mainnet']; @@ -29,8 +30,7 @@ let networks: NetworkId[] = ['testnet', 'mainnet']; for (let network of networks) { let i = 0; - let reference = - signatures[typeof network === 'string' ? network : network.custom]; + let reference = signatures[NetworkId.toString(network)]; for (let payment of payments) { let signature = signPayment(payment, privateKey, network); diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 03f11b0811..c159de2e2e 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -327,7 +327,7 @@ function networkIdOfString(n: string): [bigint, number] { } function getNetworkIdHashInput(network: NetworkId): [bigint, number] { - switch (typeof network === 'string' ? network : network.custom) { + switch (NetworkId.toString(network)) { case 'mainnet': return [networkIdMainnet, 8]; case 'testnet': @@ -351,7 +351,7 @@ const createCustomPrefix = (prefix: string) => { }; const signaturePrefix = (network: NetworkId) => { - switch (typeof network === 'string' ? network : network.custom) { + switch (NetworkId.toString(network)) { case 'mainnet': return prefixes.signatureMainnet; case 'testnet': @@ -362,7 +362,7 @@ const signaturePrefix = (network: NetworkId) => { }; const zkAppBodyPrefix = (network: NetworkId) => { - switch (typeof network === 'string' ? network : network.custom) { + switch (NetworkId.toString(network)) { case 'mainnet': return prefixes.zkappBodyMainnet; case 'testnet': diff --git a/src/mina-signer/src/types.ts b/src/mina-signer/src/types.ts index 6133704784..87739e059b 100644 --- a/src/mina-signer/src/types.ts +++ b/src/mina-signer/src/types.ts @@ -11,6 +11,12 @@ export type PrivateKey = string; export type Signature = SignatureJson; export type NetworkId = 'mainnet' | 'testnet' | { custom: string }; +export const NetworkId = { + toString(network: NetworkId) { + return typeof network === 'string' ? network : network.custom; + }, +}; + export type Keypair = { readonly privateKey: PrivateKey; readonly publicKey: PublicKey; From 1685902836ee5ae62ec33a697a50bd8ef1ac8c51 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 10:25:14 +0100 Subject: [PATCH 19/22] simplify --- src/lib/account-update.ts | 3 ++- src/mina-signer/src/sign-zkapp-command.unit-test.ts | 6 +++--- src/mina-signer/src/signature.unit-test.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index 4c6220ad82..e36f51078c 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -69,6 +69,7 @@ import { } from './mina/smart-contract-context.js'; import { assert } from './util/assert.js'; import { RandomId } from './provable-types/auxiliary.js'; +import { NetworkId } from '../mina-signer/src/types.js'; // external API export { @@ -1028,7 +1029,7 @@ class AccountUpdate implements Types.AccountUpdate { return Field( Test.hashFromJson.accountUpdate( JSON.stringify(json), - typeof networkId === 'string' ? networkId : networkId.custom + NetworkId.toString(networkId) ) ); } diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 34b34e9aeb..04f2946bc6 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -146,7 +146,7 @@ test( let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); let ocamlCommitments = Test.hashFromJson.transactionCommitments( JSON.stringify(zkappCommandJson), - typeof networkId === 'string' ? networkId : networkId.custom + NetworkId.toString(networkId) ); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); let commitment = callForestHash(callForest, networkId); @@ -194,7 +194,7 @@ test( // tx commitment let ocamlCommitments = Test.hashFromJson.transactionCommitments( JSON.stringify(zkappCommandJson), - typeof networkId === 'string' ? networkId : networkId.custom + NetworkId.toString(networkId) ); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); let commitment = callForestHash(callForest, networkId); @@ -244,7 +244,7 @@ test( let sigOCaml = Test.signature.signFieldElement( ocamlCommitments.fullCommitment, Ml.fromPrivateKey(feePayerKeySnarky), - typeof networkId === 'string' ? networkId : networkId.custom + NetworkId.toString(networkId) ); expect(Signature.toBase58(sigFieldElements)).toEqual(sigOCaml); diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 165f8fcfec..ae8384cd24 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -48,7 +48,7 @@ function checkConsistentSingle( let actualTest = Test.signature.signFieldElement( msgMl, keyMl, - typeof networkId === 'string' ? networkId : networkId.custom + NetworkId.toString(networkId) ); expect(Signature.toBase58(sig)).toEqual(actualTest); } From ecf67e5c9c00c715e1c0ae6b2a528e691664da44 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 10:38:14 +0100 Subject: [PATCH 20/22] cleanup --- src/mina-signer/src/sign-legacy.unit-test.ts | 1 - src/mina-signer/src/sign-zkapp-command.unit-test.ts | 8 ++++++++ src/mina-signer/src/signature.ts | 11 +++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/mina-signer/src/sign-legacy.unit-test.ts b/src/mina-signer/src/sign-legacy.unit-test.ts index 4a714f3287..61a9de37d6 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -21,7 +21,6 @@ import { Field } from '../../provable/field-bigint.js'; import { Random, test } from '../../lib/testing/property.js'; import { RandomTransaction } from './random-transaction.js'; import { NetworkId } from './types.js'; -import { Network } from 'src/lib/precondition.js'; let { privateKey, publicKey } = keypair; let networks: NetworkId[] = ['testnet', 'mainnet']; diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 04f2946bc6..ced276d9ed 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -112,6 +112,14 @@ test( let hashSnarky = accountUpdateSnarky.hash(); let hash = accountUpdateHash(accountUpdate, networkId); expect(hash).toEqual(hashSnarky.toBigInt()); + /* + // check against different network hash + expect(hash).not.toEqual( + accountUpdateHash( + accountUpdate, + NetworkId.toString(networkId) === 'mainnet' ? 'testnet' : 'mainnet' + ) + ); */ } ); diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index c159de2e2e..9a94f01cd4 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -327,13 +327,14 @@ function networkIdOfString(n: string): [bigint, number] { } function getNetworkIdHashInput(network: NetworkId): [bigint, number] { - switch (NetworkId.toString(network)) { + let s = NetworkId.toString(network); + switch (s) { case 'mainnet': return [networkIdMainnet, 8]; case 'testnet': return [networkIdTestnet, 8]; default: - return networkIdOfString(network as string); + return networkIdOfString(s); } } @@ -351,7 +352,8 @@ const createCustomPrefix = (prefix: string) => { }; const signaturePrefix = (network: NetworkId) => { - switch (NetworkId.toString(network)) { + let s = NetworkId.toString(network); + switch (s) { case 'mainnet': return prefixes.signatureMainnet; case 'testnet': @@ -362,7 +364,8 @@ const signaturePrefix = (network: NetworkId) => { }; const zkAppBodyPrefix = (network: NetworkId) => { - switch (NetworkId.toString(network)) { + let s = NetworkId.toString(network); + switch (s) { case 'mainnet': return prefixes.zkappBodyMainnet; case 'testnet': From 5840e767d3ef57b48accce4315c1fb1529af8aa3 Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 10:43:19 +0100 Subject: [PATCH 21/22] fix tests --- src/lib/account-update.ts | 8 +++++--- src/mina-signer/src/signature.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index e36f51078c..beb297c26c 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -1020,16 +1020,18 @@ class AccountUpdate implements Types.AccountUpdate { // consistency between JS & OCaml hashing on *every single account update // proof* we create. It will give us 100% confidence that the two // implementations are equivalent, and catch regressions quickly - let networkId = activeInstance.getNetworkId(); if (Provable.inCheckedComputation()) { let input = Types.AccountUpdate.toInput(this); - return hashWithPrefix(zkAppBodyPrefix(networkId), packToFields(input)); + return hashWithPrefix( + zkAppBodyPrefix(activeInstance.getNetworkId()), + packToFields(input) + ); } else { let json = Types.AccountUpdate.toJSON(this); return Field( Test.hashFromJson.accountUpdate( JSON.stringify(json), - NetworkId.toString(networkId) + NetworkId.toString(activeInstance.getNetworkId()) ) ); } diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index 9a94f01cd4..a80ef3e80e 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -359,7 +359,7 @@ const signaturePrefix = (network: NetworkId) => { case 'testnet': return prefixes.signatureTestnet; default: - return createCustomPrefix(network + 'Signature'); + return createCustomPrefix(s + 'Signature'); } }; @@ -371,6 +371,6 @@ const zkAppBodyPrefix = (network: NetworkId) => { case 'testnet': return prefixes.zkappBodyTestnet; default: - return createCustomPrefix(network + 'ZkappBody'); + return createCustomPrefix(s + 'ZkappBody'); } }; From 5b9bdcfebb8ee738a3d3207632240449ae1abfaf Mon Sep 17 00:00:00 2001 From: Florian Kluge Date: Tue, 20 Feb 2024 10:44:28 +0100 Subject: [PATCH 22/22] add check against different network hash --- src/mina-signer/src/sign-zkapp-command.unit-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index ced276d9ed..a6db9a9327 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -112,14 +112,14 @@ test( let hashSnarky = accountUpdateSnarky.hash(); let hash = accountUpdateHash(accountUpdate, networkId); expect(hash).toEqual(hashSnarky.toBigInt()); - /* + // check against different network hash expect(hash).not.toEqual( accountUpdateHash( accountUpdate, NetworkId.toString(networkId) === 'mainnet' ? 'testnet' : 'mainnet' ) - ); */ + ); } );