From a89c8cd37a230a191526778d89e21b8ec9fc4f54 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 28 Nov 2024 16:31:38 +0100 Subject: [PATCH 01/23] implement conditional recursion --- src/lib/proof-system/zkprogram.ts | 100 +++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 2cb0222b3..9c88ac64e 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -55,6 +55,7 @@ import { emptyWitness } from '../provable/types/util.js'; import { InferValue } from '../../bindings/lib/provable-generic.js'; import { DeclaredProof, ZkProgramContext } from './zkprogram-context.js'; import { mapObject, mapToObject, zip } from '../util/arrays.js'; +import { Bool } from '../provable/bool.js'; // public API export { @@ -246,6 +247,13 @@ function ZkProgram< PrivateInputs[I] >; }; + proveRecursivelyIf: { + [I in keyof Config['methods']]: ConditionalRecursiveProver< + InferProvableOrUndefined>, + InferProvableOrVoid>, + PrivateInputs[I] + >; + }; proofsEnabled: boolean; setProofsEnabled(proofsEnabled: boolean): void; } & { @@ -468,11 +476,30 @@ function ZkProgram< let regularRecursiveProvers = mapObject(regularProvers, (prover, key) => { return async function proveRecursively_( + conditionAndConfig: Bool | { condition: Bool; domainLog2?: number }, publicInput: PublicInput, ...args: TupleToInstances ) { + let condition = + conditionAndConfig instanceof Bool + ? conditionAndConfig + : conditionAndConfig.condition; + // create the base proof in a witness block let proof = await Provable.witnessAsync(SelfProof, async () => { + let cond = condition.toBoolean(); + if (!cond) { + let publicOutput = ProvableType.synthesize(publicOutputType); + return SelfProof.dummy( + publicInput, + publicOutput, + maxProofsVerified, + conditionAndConfig instanceof Bool + ? undefined + : conditionAndConfig.domainLog2 + ); + } + // move method args to constants let constInput = Provable.toConstant(publicInputType, publicInput); let constArgs = zip(args, methods[key].privateInputs).map( @@ -489,26 +516,55 @@ function ZkProgram< // declare and verify the proof, and return its public output proof.declare(); - proof.verify(); + proof.verifyIf(condition); return proof.publicOutput; }; }); - type RecursiveProver_ = RecursiveProver< - PublicInput, - PublicOutput, - PrivateInputs[K] - >; + type RecursiveProvers = { - [K in MethodKey]: RecursiveProver_; + [K in MethodKey]: RecursiveProver< + PublicInput, + PublicOutput, + PrivateInputs[K] + >; }; - let proveRecursively: RecursiveProvers = mapToObject(methodKeys, (key) => { - if (publicInputType === Undefined || publicInputType === Void) { - return ((...args: any) => - regularRecursiveProvers[key](undefined as any, ...args)) as any; - } else { - return regularRecursiveProvers[key] as any; + type ConditionalRecursiveProvers = { + [K in MethodKey]: ConditionalRecursiveProver< + PublicInput, + PublicOutput, + PrivateInputs[K] + >; + }; + + let proveRecursively: RecursiveProvers = mapObject( + regularRecursiveProvers, + (prover): RecursiveProvers[MethodKey] => { + if (!hasPublicInput) { + return ((...args: any) => + prover(new Bool(true), undefined as any, ...args)) as any; + } else { + return ((pi: PublicInput, ...args: any) => + prover(new Bool(true), pi, ...args)) as any; + } } - }); + ); + let proveRecursivelyIf: ConditionalRecursiveProvers = mapObject( + regularRecursiveProvers, + (prover): ConditionalRecursiveProvers[MethodKey] => { + if (!hasPublicInput) { + return (( + condition: Bool | { condition: Bool; domainLog2?: number }, + ...args: any + ) => prover(condition, undefined as any, ...args)) as any; + } else { + return (( + condition: Bool | { condition: Bool; domainLog2?: number }, + pi: PublicInput, + ...args: any + ) => prover(condition, pi, ...args)) as any; + } + } + ); async function digest() { let methodsMeta = await analyzeMethods(); @@ -546,6 +602,7 @@ function ZkProgram< }, proveRecursively, + proveRecursivelyIf, }, provers ); @@ -1162,6 +1219,21 @@ type RecursiveProver< ...args: TupleToInstances ) => Promise; +type ConditionalRecursiveProver< + PublicInput, + PublicOutput, + Args extends Tuple +> = PublicInput extends undefined + ? ( + condition: Bool | { condition: Bool; domainLog2?: number }, + ...args: TupleToInstances + ) => Promise + : ( + condition: Bool | { condition: Bool; domainLog2?: number }, + publicInput: PublicInput, + ...args: TupleToInstances + ) => Promise; + type ProvableOrUndefined = A extends undefined ? typeof Undefined : ToProvable; From 4a902400f8b8bde106b9f08595962e9a7f8c0fcf Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 28 Nov 2024 16:32:49 +0100 Subject: [PATCH 02/23] self-recursive hash chain example --- src/examples/zkprogram/hash-chain.ts | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/examples/zkprogram/hash-chain.ts diff --git a/src/examples/zkprogram/hash-chain.ts b/src/examples/zkprogram/hash-chain.ts new file mode 100644 index 000000000..3032e9007 --- /dev/null +++ b/src/examples/zkprogram/hash-chain.ts @@ -0,0 +1,59 @@ +/** + * This shows how to prove a preimage of an arbitrarily long chain of hashes using ZkProgram, i.e. + * "I know x such that hash^n(x) = y". + * + * We implement this as a self-recursive ZkProgram, using `proveRecursivelyIf()` + */ +import { assert, Bool, Field, Poseidon, Provable, ZkProgram } from 'o1js'; + +const HASHES_PER_PROOF = 30; + +const hashChain = ZkProgram({ + name: 'hash-chain', + publicInput: Field, + publicOutput: Field, + + methods: { + chain: { + privateInputs: [Field], + + async method(x: Field, n: Field) { + Provable.log('hashChain (start method)', n); + let y = x; + let k = Field(0); + let reachedN = Bool(false); + + for (let i = 0; i < HASHES_PER_PROOF; i++) { + reachedN = k.equals(n); + y = Provable.if(reachedN, y, Poseidon.hash([y])); + k = Provable.if(reachedN, n, k.add(1)); + } + + // we have y = hash^k(x) + // now do z = hash^(n-k)(y) = hash^n(x) by calling this method recursively + // except if we have k = n, then ignore the output and use y + let z: Field = await hashChain.proveRecursivelyIf.chain( + reachedN.not(), + y, + n.sub(k) + ); + z = Provable.if(reachedN, y, z); + Provable.log('hashChain (start proving)', n); + return { publicOutput: z }; + }, + }, + }, +}); + +await hashChain.compile(); + +let n = 100; +let x = Field.random(); + +let { proof } = await hashChain.chain(x, Field(n)); + +assert(await hashChain.verify(proof), 'Proof invalid'); + +// check that the output is correct +let z = Array.from({ length: n }, () => 0).reduce((y) => Poseidon.hash([y]), x); +proof.publicOutput.assertEquals(z, 'Output is incorrect'); From aa90a40ef207d143cc08693e36f1cd8d4201d674 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 28 Nov 2024 16:43:50 +0100 Subject: [PATCH 03/23] changelog --- CHANGELOG.md | 3 ++- src/examples/zkprogram/hash-chain.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 159d68def..bc1747b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- API for recursively proving a ZkProgram method from within another https://github.com/o1-labs/o1js/pull/1931 +- APIs for recursively proving a ZkProgram method from within another https://github.com/o1-labs/o1js/pull/1931 https://github.com/o1-labs/o1js/pull/1932 - `program.proveRecursively.(...args): Promise` + - `program.proveRecursivelyIf.(condition, ...args): Promise` - This also works within the same program, as long as the return value is type-annotated - Add `enforceTransactionLimits` parameter on Network https://github.com/o1-labs/o1js/issues/1910 - Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922 diff --git a/src/examples/zkprogram/hash-chain.ts b/src/examples/zkprogram/hash-chain.ts index 3032e9007..28071db53 100644 --- a/src/examples/zkprogram/hash-chain.ts +++ b/src/examples/zkprogram/hash-chain.ts @@ -57,3 +57,5 @@ assert(await hashChain.verify(proof), 'Proof invalid'); // check that the output is correct let z = Array.from({ length: n }, () => 0).reduce((y) => Poseidon.hash([y]), x); proof.publicOutput.assertEquals(z, 'Output is incorrect'); + +console.log('Finished hash chain proof'); From 72eba8a1850c18944507781cdec0d3754ad45edb Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 28 Nov 2024 16:57:20 +0100 Subject: [PATCH 04/23] make n also a public input --- src/examples/zkprogram/hash-chain.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/examples/zkprogram/hash-chain.ts b/src/examples/zkprogram/hash-chain.ts index 28071db53..b009602e3 100644 --- a/src/examples/zkprogram/hash-chain.ts +++ b/src/examples/zkprogram/hash-chain.ts @@ -1,23 +1,33 @@ /** - * This shows how to prove a preimage of an arbitrarily long chain of hashes using ZkProgram, i.e. - * "I know x such that hash^n(x) = y". + * This shows how to prove an arbitrarily long chain of hashes using ZkProgram, i.e. + * `hash^n(x) = y`. * * We implement this as a self-recursive ZkProgram, using `proveRecursivelyIf()` */ -import { assert, Bool, Field, Poseidon, Provable, ZkProgram } from 'o1js'; +import { + assert, + Bool, + Field, + Poseidon, + Provable, + Struct, + ZkProgram, +} from 'o1js'; const HASHES_PER_PROOF = 30; +class HashChainSpec extends Struct({ x: Field, n: Field }) {} + const hashChain = ZkProgram({ name: 'hash-chain', - publicInput: Field, + publicInput: HashChainSpec, publicOutput: Field, methods: { chain: { - privateInputs: [Field], + privateInputs: [], - async method(x: Field, n: Field) { + async method({ x, n }: HashChainSpec) { Provable.log('hashChain (start method)', n); let y = x; let k = Field(0); @@ -34,8 +44,7 @@ const hashChain = ZkProgram({ // except if we have k = n, then ignore the output and use y let z: Field = await hashChain.proveRecursivelyIf.chain( reachedN.not(), - y, - n.sub(k) + { x: y, n: n.sub(k) } ); z = Provable.if(reachedN, y, z); Provable.log('hashChain (start proving)', n); @@ -50,7 +59,7 @@ await hashChain.compile(); let n = 100; let x = Field.random(); -let { proof } = await hashChain.chain(x, Field(n)); +let { proof } = await hashChain.chain({ x, n: Field(n) }); assert(await hashChain.verify(proof), 'Proof invalid'); From d383fe0504c70d5eb81ea89bd7914193a9538c38 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 29 Nov 2024 15:46:58 +0100 Subject: [PATCH 05/23] fixup --- src/lib/proof-system/zkprogram.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 31d8f42cf..c39d39e55 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -502,6 +502,7 @@ function ZkProgram< let cond = condition.toBoolean(); if (!cond) { let publicOutput = ProvableType.synthesize(publicOutputType); + let maxProofsVerified = compileOutput?.maxProofsVerified!; return SelfProof.dummy( publicInput, publicOutput, From d68b785fe7b240311b459c823c98f1aa9d2646a2 Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:04:12 -0700 Subject: [PATCH 06/23] add devnet to networkId type, update mina defaults to use devnet instead of testnet in instance and localblockchain --- src/lib/mina/local-blockchain.ts | 2 +- src/lib/mina/mina-instance.ts | 2 +- src/lib/mina/mina.ts | 2 +- src/mina-signer/src/types.ts | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/mina/local-blockchain.ts b/src/lib/mina/local-blockchain.ts index 2a80874c3..4d049cac0 100644 --- a/src/lib/mina/local-blockchain.ts +++ b/src/lib/mina/local-blockchain.ts @@ -106,7 +106,7 @@ async function LocalBlockchain({ const originalProofsEnabled = proofsEnabled; return { - getNetworkId: () => 'testnet' as NetworkId, + getNetworkId: () => 'devnet' as NetworkId, proofsEnabled, getNetworkConstants() { return { diff --git a/src/lib/mina/mina-instance.ts b/src/lib/mina/mina-instance.ts index 77bc328ba..640953936 100644 --- a/src/lib/mina/mina-instance.ts +++ b/src/lib/mina/mina-instance.ts @@ -117,7 +117,7 @@ let activeInstance: Mina = { fetchActions: noActiveInstance, getActions: noActiveInstance, proofsEnabled: true, - getNetworkId: () => 'testnet', + getNetworkId: () => 'devnet', }; /** diff --git a/src/lib/mina/mina.ts b/src/lib/mina/mina.ts index 67edd659e..c7ea26484 100644 --- a/src/lib/mina/mina.ts +++ b/src/lib/mina/mina.ts @@ -117,7 +117,7 @@ function Network( } | string ): Mina { - let minaNetworkId: NetworkId = 'testnet'; + let minaNetworkId: NetworkId = 'devnet'; let minaGraphqlEndpoint: string; let archiveEndpoint: string; let lightnetAccountManagerEndpoint: string; diff --git a/src/mina-signer/src/types.ts b/src/mina-signer/src/types.ts index 6eb7e75c8..f3692cf03 100644 --- a/src/mina-signer/src/types.ts +++ b/src/mina-signer/src/types.ts @@ -9,7 +9,9 @@ export type Field = number | bigint | string; export type PublicKey = string; export type PrivateKey = string; export type Signature = SignatureJson; -export type NetworkId = 'mainnet' | 'testnet' | { custom: string }; + +// testnet is deprecated in favor of devnet +export type NetworkId = 'mainnet' | 'devnet' | 'testnet' | { custom: string }; export const NetworkId = { toString(network: NetworkId) { From 526881d9ee734d076ebbcf58cc078eb438a38cbf Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:33:42 -0700 Subject: [PATCH 07/23] update signatures to treat devnet and testnet as identical --- src/lib/mina/token/forest-iterator.unit-test.ts | 3 ++- src/lib/provable/crypto/signature.ts | 10 +++++----- src/mina-signer/src/signature.ts | 9 ++++++--- src/mina-signer/src/signature.unit-test.ts | 17 +++++++++++------ 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/lib/mina/token/forest-iterator.unit-test.ts b/src/lib/mina/token/forest-iterator.unit-test.ts index 97f0409ca..18c696674 100644 --- a/src/lib/mina/token/forest-iterator.unit-test.ts +++ b/src/lib/mina/token/forest-iterator.unit-test.ts @@ -16,6 +16,7 @@ import { import assert from 'assert'; import { Field, Bool } from '../../provable/wrapped.js'; import { PublicKey } from '../../provable/crypto/signature.js'; +import { NetworkId } from '../../../mina-signer/index.js'; // RANDOM NUMBER GENERATORS for account updates @@ -56,7 +57,7 @@ test.custom({ timeBudget: 1000 })( (flatUpdatesBigint) => { // reference: bigint callforest hash from mina-signer let forestBigint = accountUpdatesToCallForest(flatUpdatesBigint); - let expectedHash = callForestHash(forestBigint, 'testnet'); + let expectedHash = callForestHash(forestBigint, 'devnet'); let flatUpdates = flatUpdatesBigint.map(accountUpdateFromBigint); let forest = AccountUpdateForest.fromFlatArray(flatUpdates); diff --git a/src/lib/provable/crypto/signature.ts b/src/lib/provable/crypto/signature.ts index d1f0cced8..d5ed209d8 100644 --- a/src/lib/provable/crypto/signature.ts +++ b/src/lib/provable/crypto/signature.ts @@ -266,7 +266,7 @@ class Signature extends CircuitValue { let publicKey = PublicKey.fromPrivateKey(privKey).toGroup(); let d = privKey.s; - // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' + // we chose an arbitrary prefix for the signature // 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 kPrime = Scalar.from( @@ -274,14 +274,14 @@ class Signature extends CircuitValue { { fields: msg.map((f) => f.toBigInt()) }, { x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() }, d.toBigInt(), - 'testnet' + 'devnet' ) ); let { x: r, y: ry } = Group.generator.scale(kPrime); let k = ry.isOdd().toBoolean() ? kPrime.neg() : kPrime; let h = hashWithPrefix( - signaturePrefix('testnet'), + signaturePrefix('devnet'), msg.concat([publicKey.x, publicKey.y, r]) ); let e = Scalar.fromField(h); @@ -296,11 +296,11 @@ class Signature extends CircuitValue { verify(publicKey: PublicKey, msg: Field[]): Bool { let point = publicKey.toGroup(); - // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' + // we chose an arbitrary prefix for the signature // 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('testnet'), + signaturePrefix('devnet'), msg.concat([point.x, point.y, this.r]) ); diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index bd3d38497..64df48d99 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -44,7 +44,7 @@ export { }; const networkIdMainnet = 0x01n; -const networkIdTestnet = 0x00n; +const networkIdDevnet = 0x00n; type Signature = { r: Field; s: Scalar }; type SignatureJson = { field: string; scalar: string }; @@ -111,7 +111,7 @@ function verifyFieldElement( * @param privateKey The `privateKey` represents an element of the Pallas scalar field, and should be given as a native bigint. * It can be converted from the base58 string representation using {@link PrivateKey.fromBase58}. * - * @param networkId The `networkId` is either "testnet" or "mainnet" and ensures that testnet transactions can + * @param networkId The `networkId` is either "devnet"/"testnet" or "mainnet" and ensures that testnet transactions can * never be used as valid mainnet transactions. * * @see {@link deriveNonce} and {@link hashMessage} for details on how the nonce and hash are computed. @@ -331,8 +331,9 @@ function getNetworkIdHashInput(network: NetworkId): [bigint, number] { switch (s) { case 'mainnet': return [networkIdMainnet, 8]; + case 'devnet': case 'testnet': - return [networkIdTestnet, 8]; + return [networkIdDevnet, 8]; default: return networkIdOfString(s); } @@ -356,6 +357,7 @@ const signaturePrefix = (network: NetworkId) => { switch (s) { case 'mainnet': return prefixes.signatureMainnet; + case 'devnet': case 'testnet': return prefixes.signatureTestnet; default: @@ -368,6 +370,7 @@ const zkAppBodyPrefix = (network: NetworkId) => { switch (s) { case 'mainnet': return prefixes.zkappBodyMainnet; + case 'devnet': case 'testnet': return prefixes.zkappBodyTestnet; default: diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 0565a1b9a..64dc027cf 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -56,16 +56,21 @@ function checkConsistentSingle( // check that various multi-field hash inputs can be verified function checkCanVerify(msg: HashInput, key: PrivateKey, pk: PublicKey) { + let sigDev = sign(msg, key, 'devnet'); let sigTest = sign(msg, key, 'testnet'); let sigMain = sign(msg, key, 'mainnet'); // verify - let okTestnetTestnet = verify(sigTest, msg, pk, 'testnet'); - let okMainnetTestnet = verify(sigMain, msg, pk, 'testnet'); - let okTestnetMainnet = verify(sigTest, msg, pk, 'mainnet'); + let okTestnetDevnet = verify(sigTest, msg, pk, 'devnet'); + let okDevnetTestnet = verify(sigDev, msg, pk, 'testnet'); + let okDevnetDevnet = verify(sigDev, msg, pk, 'devnet'); + let okMainnetDevnet = verify(sigMain, msg, pk, 'devnet'); + let okDevnetMainnet = verify(sigDev, msg, pk, 'mainnet'); let okMainnetMainnet = verify(sigMain, msg, pk, 'mainnet'); - expect(okTestnetTestnet).toEqual(true); - expect(okMainnetTestnet).toEqual(false); - expect(okTestnetMainnet).toEqual(false); + expect(okTestnetDevnet).toEqual(true); + expect(okDevnetTestnet).toEqual(true); + expect(okDevnetDevnet).toEqual(true); + expect(okMainnetDevnet).toEqual(false); + expect(okDevnetMainnet).toEqual(false); expect(okMainnetMainnet).toEqual(true); } From 28ea4f9a55a3e9cab12a209ca40973f03c81f0a7 Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:59:01 -0700 Subject: [PATCH 08/23] replace hardcoded instances of testnet with devnet in signing code, constants being generated for the signatures are unchanged --- src/mina-signer/mina-signer.ts | 4 +- src/mina-signer/src/random-transaction.ts | 2 +- src/mina-signer/src/sign-legacy.unit-test.ts | 12 +++- .../src/sign-zkapp-command.unit-test.ts | 16 ++++++ src/mina-signer/src/signature.unit-test.ts | 8 +++ .../src/test-vectors/legacySignatures.ts | 56 +++++++++++++++++++ 6 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/mina-signer/mina-signer.ts b/src/mina-signer/mina-signer.ts index d6b9793a4..29d53757e 100644 --- a/src/mina-signer/mina-signer.ts +++ b/src/mina-signer/mina-signer.ts @@ -152,7 +152,7 @@ class Client { */ signFields(fields: bigint[], privateKey: Json.PrivateKey): Signed { let privateKey_ = PrivateKey.fromBase58(privateKey); - let signature = sign({ fields }, privateKey_, 'testnet'); + let signature = sign({ fields }, privateKey_, 'devnet'); return { signature: Signature.toBase58(signature), publicKey: PublicKey.toBase58(PrivateKey.toPublicKey(privateKey_)), @@ -172,7 +172,7 @@ class Client { Signature.fromBase58(signature), { fields: data }, PublicKey.fromBase58(publicKey), - 'testnet' + 'devnet' ); } diff --git a/src/mina-signer/src/random-transaction.ts b/src/mina-signer/src/random-transaction.ts index ac475348e..87d286fd2 100644 --- a/src/mina-signer/src/random-transaction.ts +++ b/src/mina-signer/src/random-transaction.ts @@ -141,7 +141,7 @@ const RandomTransaction = { zkappCommand, zkappCommandAndFeePayerKey, zkappCommandJson, - networkId: Random.oneOf('testnet', 'mainnet', { + networkId: Random.oneOf('testnet', 'mainnet', 'devnet', { 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 365494368..2a54a56cf 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -23,7 +23,7 @@ import { RandomTransaction } from './random-transaction.js'; import { NetworkId } from './types.js'; let { privateKey, publicKey } = keypair; -let networks: NetworkId[] = ['testnet', 'mainnet']; +let networks: NetworkId[] = ['devnet', 'testnet', 'mainnet']; // test hard-coded cases against reference signature @@ -73,16 +73,26 @@ test( verifyPayment(payment, sig, publicKey, network); // valid signatures & verification matrix + let devnet = signPayment(payment, privateKey, 'devnet'); let testnet = signPayment(payment, privateKey, 'testnet'); let mainnet = signPayment(payment, privateKey, 'mainnet'); + assert(verify(devnet, 'testnet') === true); + assert(verify(testnet, 'devnet') === true); + assert(verify(testnet, 'testnet') === true); assert(verify(testnet, 'mainnet') === false); assert(verify(mainnet, 'testnet') === false); + + assert(verify(devnet, 'devnet') === true); + assert(verify(devnet, 'mainnet') === false); + assert(verify(mainnet, 'devnet') === false); assert(verify(mainnet, 'mainnet') === true); // fails when signing with wrong private key let testnetWrong = signPayment(payment, otherKey, 'testnet'); + let devnetWrong = signPayment(payment, otherKey, 'devnet'); let mainnetWrong = signPayment(payment, otherKey, 'mainnet'); + assert(verify(devnetWrong, 'devnet') === false); assert(verify(testnetWrong, 'testnet') === false); assert(verify(mainnetWrong, 'mainnet') === false); } 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 e0f1ccacd..d833a6b5d 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,12 @@ test( expect(hash).toEqual(hashSnarky.toBigInt()); // check against different network hash + expect(hash).not.toEqual( + accountUpdateHash( + accountUpdate, + NetworkId.toString(networkId) === 'mainnet' ? 'devnet' : 'mainnet' + ) + ); expect(hash).not.toEqual( accountUpdateHash( accountUpdate, @@ -262,6 +268,9 @@ test( expect( verify(sigFieldElements, networkId === 'mainnet' ? 'testnet' : 'mainnet') ).toEqual(false); + expect( + verify(sigFieldElements, networkId === 'mainnet' ? 'devnet' : 'mainnet') + ).toEqual(false); // full end-to-end test: sign a zkapp transaction let sig = signZkappCommand(zkappCommandJson, feePayerKeyBase58, networkId); @@ -278,6 +287,13 @@ test( networkId === 'mainnet' ? 'testnet' : 'mainnet' ) ).toEqual(false); + expect( + verifyZkappCommandSignature( + sig, + feePayerAddressBase58, + networkId === 'mainnet' ? 'devnet' : 'mainnet' + ) + ).toEqual(false); } ); diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 64dc027cf..98055fea8 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -42,6 +42,14 @@ function checkConsistentSingle( networkId === 'mainnet' ? 'testnet' : 'mainnet' ) ).toEqual(false); + expect( + verifyFieldElement( + sig, + msg, + pk, + networkId === 'mainnet' ? 'devnet' : 'mainnet' + ) + ).toEqual(false); // consistent with OCaml let msgMl = FieldConst.fromBigint(msg); diff --git a/src/mina-signer/src/test-vectors/legacySignatures.ts b/src/mina-signer/src/test-vectors/legacySignatures.ts index 709058747..db17f153e 100644 --- a/src/mina-signer/src/test-vectors/legacySignatures.ts +++ b/src/mina-signer/src/test-vectors/legacySignatures.ts @@ -91,6 +91,62 @@ let strings = [ * - the 3 strings. */ let signatures: { [k: string]: { field: string; scalar: string }[] } = { + devnet: [ + { + field: + '3925887987173883783388058255268083382298769764463609405200521482763932632383', + scalar: + '445615701481226398197189554290689546503290167815530435382795701939759548136', + }, + { + field: + '11838925242791061185900891854974280922359055483441419242429642295065318643984', + scalar: + '5057044820006008308046028014628135487302791372585541488835641418654652928805', + }, + { + field: + '13570419670106759824217358880396743605262660069048455950202130815805728575057', + scalar: + '2256128221267944805514947515637443480133552241968312777663034361688965989223', + }, + { + field: + '18603328765572408555868399359399411973012220541556204196884026585115374044583', + scalar: + '17076342019359061119005549736934690084415105419939473687106079907606137611470', + }, + { + field: + '1786373894608285187089973929748850875336413409295396991315429715474432640801', + scalar: + '10435258496141097615588833319454104720521911644724923418749752896069542389757', + }, + { + field: + '11710586766419351067338319607483640291676872446372400739329190129174446858072', + scalar: + '21663533922934564101122062377096487451020504743791218020915919810997397884837', + }, + { + field: + '11583775536286847540414661987230057163492736306749717851628536966882998258109', + scalar: + '14787360096063782022566783796923142259879388947509616216546009448340181956495', + }, + { + field: + '24809097509137086694730479515383937245108109696879845335879579016397403384488', + scalar: + '23723859937408726087117568974923795978435877847592289069941156359435022279156', + }, + { + field: + '23803497755408154859878117448681790665144834176143832235351783889976460433296', + scalar: + '21219917886278462345652813021708727397787183083051040637716760620250038837684', + }, + ], testnet: [ { field: From 53abba62067898155c086795e3b3e8e40deb7f38 Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:35:13 -0700 Subject: [PATCH 09/23] update mina-signer tests to include devnet client --- src/mina-signer/tests/client.test.ts | 7 ++- src/mina-signer/tests/message.test.ts | 71 ++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/mina-signer/tests/client.test.ts b/src/mina-signer/tests/client.test.ts index 4ca384a2d..e620429ee 100644 --- a/src/mina-signer/tests/client.test.ts +++ b/src/mina-signer/tests/client.test.ts @@ -13,7 +13,12 @@ describe('Client Class Initialization', () => { expect(client).toBeDefined(); }); - it('should throw an error if a value that is not `mainnet` or `testnet` is specified', () => { + it('should accept `devnet` as a valid network parameter', () => { + client = new Client({ network: 'devnet' }); + expect(client).toBeDefined(); + }); + + it('should throw an error if a value that is not `mainnet`, `devnet`, or `testnet` is specified', () => { try { //@ts-ignore client = new Client({ network: 'new-network' }); diff --git a/src/mina-signer/tests/message.test.ts b/src/mina-signer/tests/message.test.ts index bd9586fb1..451972f1b 100644 --- a/src/mina-signer/tests/message.test.ts +++ b/src/mina-signer/tests/message.test.ts @@ -37,6 +37,14 @@ describe('Message', () => { expect(client.verifyTransaction(message)).toEqual(true); }); + it('does not verify a signed message from `devnet`', () => { + const message = client.signMessage('hello', privateKey); + const devnetClient = new Client({ network: 'devnet' }); + const invalidMessage = devnetClient.verifyMessage(message); + expect(invalidMessage).toBeFalsy(); + expect(devnetClient.verifyTransaction(message)).toEqual(false); + }); + it('does not verify a signed message from `testnet`', () => { const message = client.signMessage('hello', privateKey); const testnetClient = new Client({ network: 'testnet' }); @@ -46,12 +54,12 @@ describe('Message', () => { }); }); - describe('Testnet network', () => { + describe('Devnet network', () => { let client: Client; let privateKey: PrivateKey; beforeAll(async () => { - client = new Client({ network: 'testnet' }); + client = new Client({ network: 'devnet' }); ({ privateKey } = client.genKeys()); }); @@ -89,4 +97,63 @@ describe('Message', () => { expect(mainnetClient.verifyTransaction(message)).toEqual(false); }); }); + + describe('Testnet network', () => { + let testnetClient: Client; + let devnetClient: Client; + let privateKey: PrivateKey; + + beforeAll(async () => { + testnetClient = new Client({ network: 'testnet' }); + devnetClient = new Client({ network: 'devnet' }); + ({ privateKey } = devnetClient.genKeys()); + }); + + it('generates the same signatures as devnet', () => { + const testnetMessage = testnetClient.signMessage('hello', privateKey); + const devnetMessage = devnetClient.signMessage('hello', privateKey); + expect(testnetMessage).toEqual(devnetMessage); + }); + + + it('generates the same signatures as devnet using signTransaction', () => { + const testnetMessage = testnetClient.signTransaction('hello', privateKey); + const devnetMessage = devnetClient.signTransaction('hello', privateKey); + expect(testnetMessage).toEqual(devnetMessage); + }); + + it('verifies a signed message from devnet and vice versa', () => { + const testnetMessage = testnetClient.signMessage('hello', privateKey); + const devnetMessage = devnetClient.signMessage('hello', privateKey); + + const verifiedDevnetMessage = testnetClient.verifyMessage(devnetMessage); + const verifiedTestnetMessage = devnetClient.verifyMessage(testnetMessage); + expect(verifiedDevnetMessage).toBeTruthy(); + expect(verifiedTestnetMessage).toBeTruthy(); + + expect(testnetClient.verifyTransaction(devnetMessage)).toEqual(true); + expect(devnetClient.verifyTransaction(testnetMessage)).toEqual(true); + }); + + it('verifies a signed message generated by signTransaction from devnet and vice versa', () => { + const testnetMessage = testnetClient.signTransaction('hello', privateKey); + const devnetMessage = devnetClient.signTransaction('hello', privateKey); + + const verifiedDevnetMessage = testnetClient.verifyMessage(devnetMessage); + const verifiedTestnetMessage = devnetClient.verifyMessage(testnetMessage); + expect(verifiedDevnetMessage).toBeTruthy(); + expect(verifiedTestnetMessage).toBeTruthy(); + + expect(testnetClient.verifyTransaction(devnetMessage)).toEqual(true); + expect(devnetClient.verifyTransaction(testnetMessage)).toEqual(true); + }); + + it('does not verify a signed message from `mainnet`', () => { + const message = testnetClient.signMessage('hello', privateKey); + const mainnetClient = new Client({ network: 'mainnet' }); + const invalidMessage = mainnetClient.verifyMessage(message); + expect(invalidMessage).toBeFalsy(); + expect(mainnetClient.verifyTransaction(message)).toEqual(false); + }); + }); }); From ddb1d66c0fc8c85a53eb8794b8ba400d98cea0c8 Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Wed, 4 Dec 2024 07:54:06 -0700 Subject: [PATCH 10/23] add tests for devnet mina-signer --- src/mina-signer/src/signature.unit-test.ts | 4 +- src/mina-signer/tests/message.test.ts | 2 +- src/mina-signer/tests/payment.test.ts | 208 +++++++++++++++++- .../tests/stake-delegation.test.ts | 130 ++++++++++- src/mina-signer/tests/zkapp.unit-test.ts | 2 +- 5 files changed, 340 insertions(+), 6 deletions(-) diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index 98055fea8..3bd81efcd 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -33,7 +33,7 @@ function checkConsistentSingle( // verify expect(verifyFieldElement(sig, msg, pk, networkId)).toEqual(true); - // verify against different network + // if the signature was generated with networkId=mainnet, the signature should not verify against testnet or devnet expect( verifyFieldElement( sig, @@ -118,6 +118,7 @@ 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, 'devnet'); checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); checkConsistentSingle(x, key, keySnarky, publicKey, { custom: 'other' }); @@ -125,6 +126,7 @@ for (let i = 0; i < 10; i++) { // random single field elements for (let i = 0; i < 10; i++) { let x = randomFields[i]; + checkConsistentSingle(x, key, keySnarky, publicKey, 'devnet'); checkConsistentSingle(x, key, keySnarky, publicKey, 'testnet'); checkConsistentSingle(x, key, keySnarky, publicKey, 'mainnet'); checkConsistentSingle(x, key, keySnarky, publicKey, { custom: 'other' }); diff --git a/src/mina-signer/tests/message.test.ts b/src/mina-signer/tests/message.test.ts index 451972f1b..0b5b6f6fd 100644 --- a/src/mina-signer/tests/message.test.ts +++ b/src/mina-signer/tests/message.test.ts @@ -134,7 +134,7 @@ describe('Message', () => { expect(testnetClient.verifyTransaction(devnetMessage)).toEqual(true); expect(devnetClient.verifyTransaction(testnetMessage)).toEqual(true); }); - + it('verifies a signed message generated by signTransaction from devnet and vice versa', () => { const testnetMessage = testnetClient.signTransaction('hello', privateKey); const devnetMessage = devnetClient.signTransaction('hello', privateKey); diff --git a/src/mina-signer/tests/payment.test.ts b/src/mina-signer/tests/payment.test.ts index de84939d6..34fbf24ab 100644 --- a/src/mina-signer/tests/payment.test.ts +++ b/src/mina-signer/tests/payment.test.ts @@ -103,6 +103,23 @@ describe('Payment', () => { expect(hashedPayment).toBeDefined(); }); + it('does not verify a signed payment from `devnet`', () => { + const payment = client.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetClient = new Client({ network: 'devnet' }); + const invalidPayment = devnetClient.verifyPayment(payment); + expect(invalidPayment).toBeFalsy(); + expect(devnetClient.verifyTransaction(payment)).toEqual(false); + }); + it('does not verify a signed payment from `testnet`', () => { const payment = client.signPayment( { @@ -121,12 +138,12 @@ describe('Payment', () => { }); }); - describe('Testnet network', () => { + describe('Devnet network', () => { let client: Client; let keypair: Keypair; beforeAll(async () => { - client = new Client({ network: 'testnet' }); + client = new Client({ network: 'devnet' }); keypair = client.genKeys(); }); @@ -239,4 +256,191 @@ describe('Payment', () => { expect(mainnetClient.verifyTransaction(payment)).toEqual(false); }); }); + describe('Testnet network', () => { + let testnetClient: Client; + let devnetClient: Client; + let keypair: Keypair; + + beforeAll(async () => { + testnetClient = new Client({ network: 'testnet' }); + devnetClient = new Client({ network: 'devnet' }); + keypair = testnetClient.genKeys(); + }); + + it('generates the same signed payment as devnet', () => { + const testnetPayment = testnetClient.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetPayment = devnetClient.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + expect(testnetPayment).toEqual(devnetPayment); + }); + + it('generates the same signed transaction by using signTransaction as a devnet client', () => { + const testnetTransaction = testnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetTransaction = devnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + expect(testnetTransaction).toEqual(devnetTransaction); + }); + + it('devnet client verifies a signed testnet payment and vice versa', () => { + const testnetPayment = testnetClient.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetPayment = devnetClient.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const verifiedDevnetPayment = testnetClient.verifyPayment(devnetPayment); + expect(verifiedDevnetPayment).toBeTruthy(); + expect(testnetClient.verifyTransaction(devnetPayment)).toEqual(true); + + const verifiedTestnetPayment = devnetClient.verifyPayment(testnetPayment); + expect(verifiedTestnetPayment).toBeTruthy(); + expect(devnetClient.verifyTransaction(testnetPayment)).toEqual(true); + }); + it('devnet client verifies a signed testnet payment generated with signTransaction and vice versa', () => { + const testnetPayment = testnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetPayment = devnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const verifiedDevnetPayment = testnetClient.verifyPayment(devnetPayment); + expect(verifiedDevnetPayment).toBeTruthy(); + expect(testnetClient.verifyTransaction(devnetPayment)).toEqual(true); + + const verifiedTestnetPayment = devnetClient.verifyPayment(testnetPayment); + expect(verifiedTestnetPayment).toBeTruthy(); + expect(devnetClient.verifyTransaction(testnetPayment)).toEqual(true); + }); + + it('generates same signed payment hash as devnet', () => { + const testnetPayment = testnetClient.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const hashedTestnetPayment = testnetClient.hashPayment(testnetPayment); + const devnetPayment = devnetClient.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const hashedDevnetPayment = devnetClient.hashPayment(devnetPayment); + expect(hashedTestnetPayment).toEqual(hashedDevnetPayment); + }); + + it('generates same signed payment hash as devnet for payment generated with signTransaction', () => { + const testnetPayment = testnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const hashedTestnetPayment = testnetClient.hashPayment(testnetPayment); + const devnetPayment = devnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const hashedDevnetPayment = devnetClient.hashPayment(devnetPayment); + expect(hashedTestnetPayment).toEqual(hashedDevnetPayment); + }); + + it('does not verify a signed payment from `mainnet`', () => { + const payment = testnetClient.signPayment( + { + to: keypair.publicKey, + from: keypair.publicKey, + amount: '1', + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const mainnetClient = new Client({ network: 'mainnet' }); + const invalidPayment = mainnetClient.verifyPayment(payment); + expect(invalidPayment).toBeFalsy(); + expect(mainnetClient.verifyTransaction(payment)).toEqual(false); + }); + }); }); diff --git a/src/mina-signer/tests/stake-delegation.test.ts b/src/mina-signer/tests/stake-delegation.test.ts index 16cdf0d8f..58d273040 100644 --- a/src/mina-signer/tests/stake-delegation.test.ts +++ b/src/mina-signer/tests/stake-delegation.test.ts @@ -114,7 +114,7 @@ describe('Stake Delegation', () => { }); }); - describe('Testnet network', () => { + describe('Devnet network', () => { let client: Client; let keypair: Keypair; @@ -211,4 +211,132 @@ describe('Stake Delegation', () => { expect(mainnetClient.verifyTransaction(delegation)).toEqual(false); }); }); + describe('Testnet network', () => { + let testnetClient: Client; + let devnetClient: Client; + let keypair: Keypair; + + beforeAll(async () => { + testnetClient = new Client({ network: 'testnet' }); + devnetClient = new Client({ network: 'devnet' }); + keypair = testnetClient.genKeys(); + }); + + it('generates the same signed stake delegation as devnet', () => { + const testnetDelegation = testnetClient.signStakeDelegation( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetDelegation = testnetClient.signStakeDelegation( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + expect(testnetDelegation).toEqual(devnetDelegation); + }); + it('generates the same signed stake delegation as devnet using signTransaction', () => { + const testnetDelegation = testnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetDelegation = testnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + expect(testnetDelegation).toEqual(devnetDelegation); + }); + + + it('verifies a signed delegation from devnet and vice versa', () => { + const testnetDelegation = testnetClient.signStakeDelegation( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetDelegation = testnetClient.signStakeDelegation( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const verifiedDevnetDelegation = testnetClient.verifyStakeDelegation(devnetDelegation); + expect(verifiedDevnetDelegation).toBeTruthy(); + expect(testnetClient.verifyTransaction(devnetDelegation)).toEqual(true); + + const verifiedTestnetDelegation = devnetClient.verifyStakeDelegation(testnetDelegation); + expect(verifiedTestnetDelegation).toBeTruthy(); + expect(devnetClient.verifyTransaction(testnetDelegation)).toEqual(true); + }); + + + it('verifies a signed delegation generated by signTransaction from devnet and vice versa', () => { + const testnetDelegation = testnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const devnetDelegation = testnetClient.signTransaction( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const verifiedDevnetDelegation = testnetClient.verifyStakeDelegation(devnetDelegation); + expect(verifiedDevnetDelegation).toBeTruthy(); + expect(testnetClient.verifyTransaction(devnetDelegation)).toEqual(true); + + const verifiedTestnetDelegation = devnetClient.verifyStakeDelegation(testnetDelegation); + expect(verifiedTestnetDelegation).toBeTruthy(); + expect(devnetClient.verifyTransaction(testnetDelegation)).toEqual(true); + }); + + it('does not verify a signed message from `mainnet`', () => { + const delegation = testnetClient.signStakeDelegation( + { + to: keypair.publicKey, + from: keypair.publicKey, + fee: '1', + nonce: '0', + }, + keypair.privateKey + ); + const mainnetClient = new Client({ network: 'mainnet' }); + const invalidMessage = mainnetClient.verifyStakeDelegation(delegation); + expect(invalidMessage).toBeFalsy(); + expect(mainnetClient.verifyTransaction(delegation)).toEqual(false); + }); + }); }); diff --git a/src/mina-signer/tests/zkapp.unit-test.ts b/src/mina-signer/tests/zkapp.unit-test.ts index 1b3ffb66c..6580df4a8 100644 --- a/src/mina-signer/tests/zkapp.unit-test.ts +++ b/src/mina-signer/tests/zkapp.unit-test.ts @@ -8,7 +8,7 @@ import { PrivateKey } from '../../lib/provable/crypto/signature.js'; import { Signature } from '../src/signature.js'; import { mocks } from '../../bindings/crypto/constants.js'; -const client = new Client({ network: 'testnet' }); +const client = new Client({ network: 'devnet' }); let { publicKey, privateKey } = client.genKeys(); let dummy = ZkappCommand.toJSON(ZkappCommand.empty()); From fa811ab3560c907e73b2f7eca025644c85e9114e Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:05:52 -0700 Subject: [PATCH 11/23] update bindings to accept devnet as string network id --- CHANGELOG.md | 1 + generate-keys.js | 2 +- src/bindings | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89592148..83e2ee810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Compiling stuck in the browser for recursive zkprograms https://github.com/o1-labs/o1js/pull/1906 - Error message in `rangeCheck16` gadget https://github.com/o1-labs/o1js/pull/1920 +- Deprecate `testnet` `networkId` in favor of `devnet` https://github.com/o1-labs/o1js/pull/1938 ## [2.1.0](https://github.com/o1-labs/o1js/compare/b04520d...e1bac02) - 2024-11-13 diff --git a/generate-keys.js b/generate-keys.js index b70294888..b638d116a 100755 --- a/generate-keys.js +++ b/generate-keys.js @@ -1,6 +1,6 @@ #!/usr/bin/env node import Client from './dist/node/mina-signer/mina-signer.js'; -let client = new Client({ network: 'testnet' }); +let client = new Client({ network: 'devnet' }); console.log(client.genKeys()); diff --git a/src/bindings b/src/bindings index 2c62a9a75..317c73252 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 2c62a9a755f1b128f89cc2131814df7157f68109 +Subproject commit 317c73252b85702570ca95e85943cf749727074c From 4b926eaee1ad706550d7c064209dd34ce51a4072 Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:17:58 -0700 Subject: [PATCH 12/23] remove testnet from comment --- 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 64df48d99..784927e4c 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -111,7 +111,7 @@ function verifyFieldElement( * @param privateKey The `privateKey` represents an element of the Pallas scalar field, and should be given as a native bigint. * It can be converted from the base58 string representation using {@link PrivateKey.fromBase58}. * - * @param networkId The `networkId` is either "devnet"/"testnet" or "mainnet" and ensures that testnet transactions can + * @param networkId The `networkId` is either "devnet" or "mainnet" and ensures that testnet transactions can * never be used as valid mainnet transactions. * * @see {@link deriveNonce} and {@link hashMessage} for details on how the nonce and hash are computed. From 79f1fd18f93d9c9643b89f89cbc91211583ae50f Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Sun, 15 Dec 2024 19:35:26 -0700 Subject: [PATCH 13/23] rebuild bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 317c73252..0c908a2c1 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 317c73252b85702570ca95e85943cf749727074c +Subproject commit 0c908a2c1dd2c9c444a2f65aaf52dcdcc616a4b5 From 92dd1a34df1381e31ba1a094125c9f6225a6ddf4 Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Sun, 15 Dec 2024 19:53:39 -0700 Subject: [PATCH 14/23] remove duplicate test vectors in mina signer --- src/mina-signer/src/sign-legacy.unit-test.ts | 4 +- .../src/test-vectors/legacySignatures.ts | 56 ------------------- 2 files changed, 3 insertions(+), 57 deletions(-) diff --git a/src/mina-signer/src/sign-legacy.unit-test.ts b/src/mina-signer/src/sign-legacy.unit-test.ts index 2a54a56cf..30cf19574 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -29,7 +29,9 @@ let networks: NetworkId[] = ['devnet', 'testnet', 'mainnet']; for (let network of networks) { let i = 0; - let reference = signatures[NetworkId.toString(network)]; + let reference = NetworkId.toString(network) === 'testnet' + ? signatures['devnet'] + : signatures[NetworkId.toString(network)]; for (let payment of payments) { let signature = signPayment(payment, privateKey, network); diff --git a/src/mina-signer/src/test-vectors/legacySignatures.ts b/src/mina-signer/src/test-vectors/legacySignatures.ts index db17f153e..7cb559ce6 100644 --- a/src/mina-signer/src/test-vectors/legacySignatures.ts +++ b/src/mina-signer/src/test-vectors/legacySignatures.ts @@ -147,62 +147,6 @@ let signatures: { [k: string]: { field: string; scalar: string }[] } = { '21219917886278462345652813021708727397787183083051040637716760620250038837684', }, ], - testnet: [ - { - field: - '3925887987173883783388058255268083382298769764463609405200521482763932632383', - scalar: - '445615701481226398197189554290689546503290167815530435382795701939759548136', - }, - { - field: - '11838925242791061185900891854974280922359055483441419242429642295065318643984', - scalar: - '5057044820006008308046028014628135487302791372585541488835641418654652928805', - }, - { - field: - '13570419670106759824217358880396743605262660069048455950202130815805728575057', - scalar: - '2256128221267944805514947515637443480133552241968312777663034361688965989223', - }, - { - field: - '18603328765572408555868399359399411973012220541556204196884026585115374044583', - scalar: - '17076342019359061119005549736934690084415105419939473687106079907606137611470', - }, - { - field: - '1786373894608285187089973929748850875336413409295396991315429715474432640801', - scalar: - '10435258496141097615588833319454104720521911644724923418749752896069542389757', - }, - { - field: - '11710586766419351067338319607483640291676872446372400739329190129174446858072', - scalar: - '21663533922934564101122062377096487451020504743791218020915919810997397884837', - }, - { - field: - '11583775536286847540414661987230057163492736306749717851628536966882998258109', - scalar: - '14787360096063782022566783796923142259879388947509616216546009448340181956495', - }, - { - field: - '24809097509137086694730479515383937245108109696879845335879579016397403384488', - scalar: - '23723859937408726087117568974923795978435877847592289069941156359435022279156', - }, - { - field: - '23803497755408154859878117448681790665144834176143832235351783889976460433296', - scalar: - '21219917886278462345652813021708727397787183083051040637716760620250038837684', - }, - ], mainnet: [ { field: From 1a903d0b610ac94f8c08a46455cf8234b00f4952 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 17 Dec 2024 17:04:27 +0100 Subject: [PATCH 15/23] add back recursive conditional provers --- src/lib/proof-system/recursive.ts | 102 ++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/src/lib/proof-system/recursive.ts b/src/lib/proof-system/recursive.ts index 23dbd4186..d23ae101a 100644 --- a/src/lib/proof-system/recursive.ts +++ b/src/lib/proof-system/recursive.ts @@ -5,6 +5,7 @@ import { Tuple } from '../util/types.js'; import { Proof } from './proof.js'; import { mapObject, mapToObject, zip } from '../util/arrays.js'; import { Undefined, Void } from './zkprogram.js'; +import { Bool } from '../provable/bool.js'; export { Recursive }; @@ -38,7 +39,13 @@ function Recursive< InferProvable, InferProvable, PrivateInputs[Key] - >; + > & { + if: ConditionalRecursiveProver< + InferProvable, + InferProvable, + PrivateInputs[Key] + >; + }; } { type PublicInput = InferProvable; type PublicOutput = InferProvable; @@ -62,11 +69,17 @@ function Recursive< let methodKeys: MethodKey[] = Object.keys(methods); - let regularRecursiveProvers = mapObject(zkprogram, (prover, key) => { + let regularRecursiveProvers = mapToObject(methodKeys, (key) => { return async function proveRecursively_( + conditionAndConfig: Bool | { condition: Bool; domainLog2?: number }, publicInput: PublicInput, ...args: TupleToInstances - ) { + ): Promise { + let condition = + conditionAndConfig instanceof Bool + ? conditionAndConfig + : conditionAndConfig.condition; + // create the base proof in a witness block let proof = await Provable.witnessAsync(SelfProof, async () => { // move method args to constants @@ -77,6 +90,24 @@ function Recursive< let constArgs = zip(args, privateInputs[key]).map(([arg, type]) => Provable.toConstant(type, arg) ); + + if (!condition.toBoolean()) { + let publicOutput: PublicOutput = + ProvableType.synthesize(publicOutputType); + let maxProofsVerified: 0 | 1 | 2 = + (await zkprogram.maxProofsVerified()) as any; // TODO + return SelfProof.dummy( + publicInput, + publicOutput, + maxProofsVerified, + conditionAndConfig instanceof Bool + ? undefined + : conditionAndConfig.domainLog2 + ); + } + + let prover = zkprogram[key]; + if (hasPublicInput) { let { proof } = await prover(constInput, ...constArgs); return proof; @@ -93,32 +124,48 @@ function Recursive< // declare and verify the proof, and return its public output proof.declare(); - proof.verify(); + proof.verifyIf(condition); return proof.publicOutput; }; }); - type RecursiveProver_ = RecursiveProver< - PublicInput, - PublicOutput, - PrivateInputs[K] - >; - type RecursiveProvers = { - [K in MethodKey]: RecursiveProver_; - }; - let proveRecursively: RecursiveProvers = mapToObject( - methodKeys, - (key: MethodKey) => { + return mapObject( + regularRecursiveProvers, + ( + prover + ): RecursiveProver & { + if: ConditionalRecursiveProver< + PublicInput, + PublicOutput, + PrivateInputs[MethodKey] + >; + } => { if (!hasPublicInput) { - return ((...args: any) => - regularRecursiveProvers[key](undefined as any, ...args)) as any; + return Object.assign( + ((...args: any) => + prover(new Bool(true), undefined as any, ...args)) as any, + { + if: ( + condition: Bool | { condition: Bool; domainLog2?: number }, + ...args: any + ) => prover(condition, undefined as any, ...args), + } + ); } else { - return regularRecursiveProvers[key] as any; + return Object.assign( + ((pi: PublicInput, ...args: any) => + prover(new Bool(true), pi, ...args)) as any, + { + if: ( + condition: Bool | { condition: Bool; domainLog2?: number }, + pi: PublicInput, + ...args: any + ) => prover(condition, pi, ...args), + } + ); } } ); - - return proveRecursively; } type RecursiveProver< @@ -132,6 +179,21 @@ type RecursiveProver< ...args: TupleToInstances ) => Promise; +type ConditionalRecursiveProver< + PublicInput, + PublicOutput, + Args extends Tuple +> = PublicInput extends undefined + ? ( + condition: Bool | { condition: Bool; domainLog2?: number }, + ...args: TupleToInstances + ) => Promise + : ( + condition: Bool | { condition: Bool; domainLog2?: number }, + publicInput: PublicInput, + ...args: TupleToInstances + ) => Promise; + type TupleToInstances = { [I in keyof T]: InferProvable; }; From bff1f194591142744a3c3cc6d55715d0974fcfaa Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 17 Dec 2024 17:10:15 +0100 Subject: [PATCH 16/23] fix after merging other pr --- src/lib/proof-system/recursive.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/proof-system/recursive.ts b/src/lib/proof-system/recursive.ts index d23ae101a..bdc6f1031 100644 --- a/src/lib/proof-system/recursive.ts +++ b/src/lib/proof-system/recursive.ts @@ -26,6 +26,7 @@ function Recursive< ...args: any ) => Promise<{ publicOutput: InferProvable }>; }; + maxProofsVerified: () => Promise<0 | 1 | 2>; } & { [Key in keyof PrivateInputs]: (...args: any) => Promise<{ proof: Proof< @@ -94,8 +95,7 @@ function Recursive< if (!condition.toBoolean()) { let publicOutput: PublicOutput = ProvableType.synthesize(publicOutputType); - let maxProofsVerified: 0 | 1 | 2 = - (await zkprogram.maxProofsVerified()) as any; // TODO + let maxProofsVerified = await zkprogram.maxProofsVerified(); return SelfProof.dummy( publicInput, publicOutput, From 6890754b2040c205e67a0898bdec3a306356aa83 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 17 Dec 2024 17:10:24 +0100 Subject: [PATCH 17/23] adapt hash chain example --- src/examples/zkprogram/hash-chain.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/examples/zkprogram/hash-chain.ts b/src/examples/zkprogram/hash-chain.ts index b009602e3..53bc574cf 100644 --- a/src/examples/zkprogram/hash-chain.ts +++ b/src/examples/zkprogram/hash-chain.ts @@ -7,6 +7,7 @@ import { assert, Bool, + Experimental, Field, Poseidon, Provable, @@ -42,10 +43,10 @@ const hashChain = ZkProgram({ // we have y = hash^k(x) // now do z = hash^(n-k)(y) = hash^n(x) by calling this method recursively // except if we have k = n, then ignore the output and use y - let z: Field = await hashChain.proveRecursivelyIf.chain( - reachedN.not(), - { x: y, n: n.sub(k) } - ); + let z: Field = await hashChainRecursive.chain.if(reachedN.not(), { + x: y, + n: n.sub(k), + }); z = Provable.if(reachedN, y, z); Provable.log('hashChain (start proving)', n); return { publicOutput: z }; @@ -53,6 +54,7 @@ const hashChain = ZkProgram({ }, }, }); +let hashChainRecursive = Experimental.Recursive(hashChain); await hashChain.compile(); From 3fdc3f1f42f0a838e832338f40194b40dc13981d Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 18 Dec 2024 13:36:34 +0100 Subject: [PATCH 18/23] update bindings without the huge diff --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 317c73252..e3a31e1e9 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 317c73252b85702570ca95e85943cf749727074c +Subproject commit e3a31e1e9cb136aae2509b4cfd1a03f84c1d443e From b4d2d1d842750fbe6b55abe1cc0031e32caf87bc Mon Sep 17 00:00:00 2001 From: Geometer1729 <16kuhnb@gmail.com> Date: Thu, 19 Dec 2024 16:20:05 -0500 Subject: [PATCH 19/23] fix git diff to include .wasm files --- .github/workflows/build-bindings.yml | 2 +- flake.lock | 4 ++-- src/bindings | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-bindings.yml b/.github/workflows/build-bindings.yml index 4f66ee7c6..b4cd18b16 100644 --- a/.github/workflows/build-bindings.yml +++ b/.github/workflows/build-bindings.yml @@ -50,7 +50,7 @@ jobs: run: | cd src/bindings git add . - git diff HEAD > ../../bindings.patch + git diff HEAD --textconv --text > ../../bindings.patch - name: Upload patch uses: actions/upload-artifact@v4 with: diff --git a/flake.lock b/flake.lock index 49061b354..6fe1645e4 100644 --- a/flake.lock +++ b/flake.lock @@ -265,8 +265,8 @@ "utils": "utils" }, "locked": { - "lastModified": 1733429866, - "narHash": "sha256-/ZEGYdZ2hLjBwdEzG/BIjlDehOjuGWmBqzD55nXfZoY=", + "lastModified": 1734626051, + "narHash": "sha256-4LLy5VMM5TK4LpQXEBiA8ziwYyqGx2ZAuunXGCDpraQ=", "path": "src/mina", "type": "path" }, diff --git a/src/bindings b/src/bindings index e05efb999..ba0915cd0 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit e05efb9999dc83107127d6758904e2512eb8582d +Subproject commit ba0915cd0c3ec4bebc46346e5282c2c599c739f8 From 2769561a910abd76a0ec065067574a54a76bf659 Mon Sep 17 00:00:00 2001 From: Geometer1729 <16kuhnb@gmail.com> Date: Thu, 19 Dec 2024 16:37:20 -0500 Subject: [PATCH 20/23] rescramble bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index ba0915cd0..6af47f810 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ba0915cd0c3ec4bebc46346e5282c2c599c739f8 +Subproject commit 6af47f8108656e32c5017adf4e966b005c4f609b From 00b8671e3a98085a72b8b2da6ef700cb5a9348ba Mon Sep 17 00:00:00 2001 From: Geometer1729 <16kuhnb@gmail.com> Date: Thu, 19 Dec 2024 16:47:29 -0500 Subject: [PATCH 21/23] apply patch from github --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 6af47f810..043f47adf 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 6af47f8108656e32c5017adf4e966b005c4f609b +Subproject commit 043f47adf7cd9a48eda4736a2a37df30e433de55 From f9ab3ab1ffc0828f675d108ed2dbad23a10818eb Mon Sep 17 00:00:00 2001 From: hattyhattington17 <181872047+hattyhattington17@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:28:08 -0700 Subject: [PATCH 22/23] apply bindings ci patch --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 120fb8d54..14ce3b8f5 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 120fb8d54725c4b96bfb7847ddfaf6dfb9344cf7 +Subproject commit 14ce3b8f5e23dbcd9afe3d53304ee84cfc92076b From 8383bb67450ebad9f88ab3fc07ba415799df39ed Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 8 Jan 2025 08:21:13 +0100 Subject: [PATCH 23/23] prune imports and changelog fix --- CHANGELOG.md | 2 +- src/lib/proof-system/zkprogram.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf58f4d7..8c3f662cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - APIs for recursively proving a ZkProgram method from within another https://github.com/o1-labs/o1js/pull/1931 https://github.com/o1-labs/o1js/pull/1932 - `let recursive = Experimental.Recursive(program);` - `recursive.(...args): Promise` - - `recursive..if.(condition, ...args): Promise` + - `recursive..if(condition, ...args): Promise` - This also works within the same program, as long as the return value is type-annotated - Add `enforceTransactionLimits` parameter on Network https://github.com/o1-labs/o1js/issues/1910 - Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922 diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 766f7b03f..01c04322c 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -30,7 +30,6 @@ import { unsetSrsCache, } from '../../bindings/crypto/bindings/srs.js'; import { - ProvablePure, ProvableType, ProvableTypePure, ToProvable, @@ -55,8 +54,7 @@ import { import { emptyWitness } from '../provable/types/util.js'; import { InferValue } from '../../bindings/lib/provable-generic.js'; import { DeclaredProof, ZkProgramContext } from './zkprogram-context.js'; -import { mapObject, mapToObject, zip } from '../util/arrays.js'; -import { Bool } from '../provable/bool.js'; +import { mapObject, mapToObject } from '../util/arrays.js'; // public API export {