diff --git a/.github/workflows/build-bindings.yml b/.github/workflows/build-bindings.yml index 4f66ee7c66..b4cd18b163 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/CHANGELOG.md b/CHANGELOG.md index 208ae656e9..06370b1aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,9 +26,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - `ZkProgram` to support non-pure provable types as inputs and outputs https://github.com/o1-labs/o1js/pull/1828 -- 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 - `let recursive = Experimental.Recursive(program);` - `recursive.(...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 @@ -44,6 +45,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/flake.lock b/flake.lock index 7653c47f47..142eb21dc0 100644 --- a/flake.lock +++ b/flake.lock @@ -265,8 +265,8 @@ "utils": "utils" }, "locked": { - "lastModified": 1734361061, - "narHash": "sha256-3e82deloDgZaV7hB8P6NfMEavRyrH/pB/E7AqWbxOs8=", + "lastModified": 1736283575, + "narHash": "sha256-aI4sxplaKydvPfvk98Qws35HHi2n9BMPKQqmVfP7zZA=", "path": "src/mina", "type": "path" }, diff --git a/generate-keys.js b/generate-keys.js index b702948882..b638d116a7 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/examples/zkprogram/hash-chain.ts b/src/examples/zkprogram/hash-chain.ts new file mode 100644 index 0000000000..53bc574cf0 --- /dev/null +++ b/src/examples/zkprogram/hash-chain.ts @@ -0,0 +1,72 @@ +/** + * 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, + Experimental, + 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: HashChainSpec, + publicOutput: Field, + + methods: { + chain: { + privateInputs: [], + + async method({ x, n }: HashChainSpec) { + 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 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 }; + }, + }, + }, +}); +let hashChainRecursive = Experimental.Recursive(hashChain); + +await hashChain.compile(); + +let n = 100; +let x = Field.random(); + +let { proof } = await hashChain.chain({ x, n: 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'); + +console.log('Finished hash chain proof'); diff --git a/src/lib/mina/local-blockchain.ts b/src/lib/mina/local-blockchain.ts index 2a80874c32..4d049cac09 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 77bc328ba9..640953936b 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 67edd659e5..c7ea264848 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/lib/mina/token/forest-iterator.unit-test.ts b/src/lib/mina/token/forest-iterator.unit-test.ts index 97f0409ca3..18c6966743 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/proof-system/recursive.ts b/src/lib/proof-system/recursive.ts index f7f0a4a3f1..bdc6f10316 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 }; @@ -25,6 +26,7 @@ function Recursive< ...args: any ) => Promise<{ publicOutput: InferProvable }>; }; + maxProofsVerified: () => Promise<0 | 1 | 2>; } & { [Key in keyof PrivateInputs]: (...args: any) => Promise<{ proof: Proof< @@ -38,7 +40,13 @@ function Recursive< InferProvable, InferProvable, PrivateInputs[Key] - >; + > & { + if: ConditionalRecursiveProver< + InferProvable, + InferProvable, + PrivateInputs[Key] + >; + }; } { type PublicInput = InferProvable; type PublicOutput = InferProvable; @@ -64,9 +72,15 @@ function Recursive< 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 @@ -78,6 +92,20 @@ function Recursive< Provable.toConstant(type, arg) ); + if (!condition.toBoolean()) { + let publicOutput: PublicOutput = + ProvableType.synthesize(publicOutputType); + let maxProofsVerified = await zkprogram.maxProofsVerified(); + return SelfProof.dummy( + publicInput, + publicOutput, + maxProofsVerified, + conditionAndConfig instanceof Bool + ? undefined + : conditionAndConfig.domainLog2 + ); + } + let prover = zkprogram[key]; if (hasPublicInput) { @@ -96,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< @@ -135,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; }; diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index b6e39ee960..01c04322c9 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,7 +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 { mapObject, mapToObject } from '../util/arrays.js'; // public API export { diff --git a/src/lib/provable/crypto/signature.ts b/src/lib/provable/crypto/signature.ts index d1f0cced8a..d5ed209d81 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/mina-signer.ts b/src/mina-signer/mina-signer.ts index d6b9793a43..29d53757e0 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 ac475348e5..87d286fd28 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 3654943682..30cf195743 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -23,13 +23,15 @@ 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 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); @@ -73,16 +75,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 e0f1ccacdc..d833a6b5da 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.ts b/src/mina-signer/src/signature.ts index bd3d384978..784927e4ca 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" 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 0565a1b9af..3bd81efcd3 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, @@ -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); @@ -56,16 +64,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); } @@ -105,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' }); @@ -112,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/src/test-vectors/legacySignatures.ts b/src/mina-signer/src/test-vectors/legacySignatures.ts index 7090587475..7cb559ce6d 100644 --- a/src/mina-signer/src/test-vectors/legacySignatures.ts +++ b/src/mina-signer/src/test-vectors/legacySignatures.ts @@ -91,7 +91,7 @@ let strings = [ * - the 3 strings. */ let signatures: { [k: string]: { field: string; scalar: string }[] } = { - testnet: [ + devnet: [ { field: '3925887987173883783388058255268083382298769764463609405200521482763932632383', diff --git a/src/mina-signer/src/types.ts b/src/mina-signer/src/types.ts index 6eb7e75c80..f3692cf03a 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) { diff --git a/src/mina-signer/tests/client.test.ts b/src/mina-signer/tests/client.test.ts index 4ca384a2d2..e620429ee2 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 bd9586fb19..0b5b6f6fd2 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); + }); + }); }); diff --git a/src/mina-signer/tests/payment.test.ts b/src/mina-signer/tests/payment.test.ts index de84939d67..34fbf24abe 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 16cdf0d8f6..58d273040c 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 1b3ffb66c3..6580df4a8c 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());