diff --git a/CHANGELOG.md b/CHANGELOG.md index bde92e484f..b0cba5c1aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Security_ in case of vulnerabilities. --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/834a44002...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/3b5f7c7...HEAD) + +## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) ### Breaking changes @@ -35,6 +37,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `TokenAccountUpdateIterator`, a primitive to iterate over all token account updates in a transaction https://github.com/o1-labs/o1js/pull/1398 - this is used to implement `TokenContract` under the hood +### Fixed + +- Mainnet support. https://github.com/o1-labs/o1js/pull/1437 + ## [0.16.0](https://github.com/o1-labs/o1js/compare/e5d1e0f...834a44002) ### Breaking changes diff --git a/package-lock.json b/package-lock.json index 7596decd36..5aab1ef0a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.16.0", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.16.0", + "version": "0.16.1", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index b9dc29d6f0..8d00104b7c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.16.0", + "version": "0.16.1", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ diff --git a/src/bindings b/src/bindings index 7c9feffb58..a6b6800186 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7c9feffb589deed29ce5606a135aeca5515c3a90 +Subproject commit a6b6800186752b3cf5c9a29b7eb167e494784286 diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index ff2e7c38d3..f8673c7ad4 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -1017,10 +1017,20 @@ class AccountUpdate implements Types.AccountUpdate { // implementations are equivalent, and catch regressions quickly if (Provable.inCheckedComputation()) { let input = Types.AccountUpdate.toInput(this); - return hashWithPrefix(prefixes.body, packToFields(input)); + return hashWithPrefix( + activeInstance.getNetworkId() === 'mainnet' + ? prefixes.zkappBodyMainnet + : prefixes.zkappBodyTestnet, + packToFields(input) + ); } else { let json = Types.AccountUpdate.toJSON(this); - return Field(Test.hashFromJson.accountUpdate(JSON.stringify(json))); + return Field( + Test.hashFromJson.accountUpdate( + JSON.stringify(json), + activeInstance.getNetworkId() + ) + ); } } @@ -1048,7 +1058,8 @@ class AccountUpdate implements Types.AccountUpdate { forest, (a) => a.hash(), Poseidon.hashWithPrefix, - emptyHash + emptyHash, + activeInstance.getNetworkId() ); return { accountUpdate, calls }; } @@ -1386,7 +1397,13 @@ class AccountUpdate implements Types.AccountUpdate { // call forest stuff function hashAccountUpdate(update: AccountUpdate) { - return genericHash(AccountUpdate, prefixes.body, update); + return genericHash( + AccountUpdate, + activeInstance.getNetworkId() === 'mainnet' + ? prefixes.zkappBodyMainnet + : prefixes.zkappBodyTestnet, + update + ); } class HashedAccountUpdate extends Hashed.create( @@ -1983,7 +2000,8 @@ function addMissingSignatures( ): ZkappCommandSigned { let additionalPublicKeys = additionalKeys.map((sk) => sk.toPublicKey()); let { commitment, fullCommitment } = transactionCommitments( - TypesBigint.ZkappCommand.fromJSON(ZkappCommand.toJSON(zkappCommand)) + TypesBigint.ZkappCommand.fromJSON(ZkappCommand.toJSON(zkappCommand)), + activeInstance.getNetworkId() ); function addFeePayerSignature(accountUpdate: FeePayerUnsigned): FeePayer { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index a64163cb9b..44c7830517 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -391,7 +391,8 @@ function LocalBlockchain({ let zkappCommandJson = ZkappCommand.toJSON(txn.transaction); let commitments = transactionCommitments( - TypesBigint.ZkappCommand.fromJSON(zkappCommandJson) + TypesBigint.ZkappCommand.fromJSON(zkappCommandJson), + minaNetworkId ); if (enforceTransactionLimits) verifyTransactionLimits(txn.transaction); diff --git a/src/lib/mina/token/forest-iterator.unit-test.ts b/src/lib/mina/token/forest-iterator.unit-test.ts index 4a00bb57f7..d93158b9ca 100644 --- a/src/lib/mina/token/forest-iterator.unit-test.ts +++ b/src/lib/mina/token/forest-iterator.unit-test.ts @@ -56,7 +56,7 @@ test.custom({ timeBudget: 1000 })( (flatUpdatesBigint) => { // reference: bigint callforest hash from mina-signer let forestBigint = accountUpdatesToCallForest(flatUpdatesBigint); - let expectedHash = callForestHash(forestBigint); + let expectedHash = callForestHash(forestBigint, 'testnet'); let flatUpdates = flatUpdatesBigint.map(accountUpdateFromBigint); let forest = AccountUpdateForest.fromFlatArray(flatUpdates); diff --git a/src/mina-signer/package-lock.json b/src/mina-signer/package-lock.json index 55178b872b..7b97ce6a84 100644 --- a/src/mina-signer/package-lock.json +++ b/src/mina-signer/package-lock.json @@ -1,12 +1,12 @@ { "name": "mina-signer", - "version": "3.0.0", + "version": "3.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mina-signer", - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "dependencies": { "blakejs": "^1.2.1", diff --git a/src/mina-signer/package.json b/src/mina-signer/package.json index 8152789289..f6efdbefab 100644 --- a/src/mina-signer/package.json +++ b/src/mina-signer/package.json @@ -1,7 +1,7 @@ { "name": "mina-signer", "description": "Node API for signing transactions on various networks for Mina Protocol", - "version": "3.0.0", + "version": "3.0.1", "type": "module", "scripts": { "build": "tsc -p ../../tsconfig.mina-signer.json", diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 40e983e4fa..e5a6985f08 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -44,7 +44,10 @@ function signZkappCommand( ): Json.ZkappCommand { let zkappCommand = ZkappCommand.fromJSON(zkappCommand_); - let { commitment, fullCommitment } = transactionCommitments(zkappCommand); + let { commitment, fullCommitment } = transactionCommitments( + zkappCommand, + networkId + ); let privateKey = PrivateKey.fromBase58(privateKeyBase58); let publicKey = zkappCommand.feePayer.body.publicKey; @@ -71,7 +74,10 @@ function verifyZkappCommandSignature( ) { let zkappCommand = ZkappCommand.fromJSON(zkappCommand_); - let { commitment, fullCommitment } = transactionCommitments(zkappCommand); + let { commitment, fullCommitment } = transactionCommitments( + zkappCommand, + networkId + ); let publicKey = PublicKey.fromBase58(publicKeyBase58); // verify fee payer signature @@ -108,14 +114,17 @@ function verifyAccountUpdateSignature( return verifyFieldElement(signature, usedCommitment, publicKey, networkId); } -function transactionCommitments(zkappCommand: ZkappCommand) { +function transactionCommitments( + zkappCommand: ZkappCommand, + networkId: NetworkId +) { if (!isCallDepthValid(zkappCommand)) { throw Error('zkapp command: invalid call depth'); } let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); - let commitment = callForestHash(callForest); + let commitment = callForestHash(callForest, networkId); let memoHash = Memo.hash(Memo.fromBase58(zkappCommand.memo)); - let feePayerDigest = feePayerHash(zkappCommand.feePayer); + let feePayerDigest = feePayerHash(zkappCommand.feePayer, networkId); let fullCommitment = hashWithPrefix(prefixes.accountUpdateCons, [ memoHash, feePayerDigest, @@ -150,22 +159,44 @@ function accountUpdatesToCallForest( return forest; } -function accountUpdateHash(update: AccountUpdate) { +const zkAppBodyPrefix = (network: string) => { + switch (network) { + case 'mainnet': + return prefixes.zkappBodyMainnet; + case 'testnet': + return prefixes.zkappBodyTestnet; + + default: + return 'ZkappBody' + network; + } +}; + +function accountUpdateHash(update: AccountUpdate, networkId: NetworkId) { assertAuthorizationKindValid(update); let input = AccountUpdate.toInput(update); let fields = packToFields(input); - return hashWithPrefix(prefixes.body, fields); + return hashWithPrefix(zkAppBodyPrefix(networkId), fields); } -function callForestHash(forest: CallForest): bigint { - return callForestHashGeneric(forest, accountUpdateHash, hashWithPrefix, 0n); +function callForestHash( + forest: CallForest, + networkId: NetworkId +): bigint { + return callForestHashGeneric( + forest, + accountUpdateHash, + hashWithPrefix, + 0n, + networkId + ); } function callForestHashGeneric( forest: CallForest, - hash: (a: A) => F, + hash: (a: A, networkId: NetworkId) => F, hashWithPrefix: (prefix: string, input: F[]) => F, - emptyHash: F + emptyHash: F, + networkId: NetworkId ): F { let stackHash = emptyHash; for (let callTree of [...forest].reverse()) { @@ -173,9 +204,10 @@ function callForestHashGeneric( callTree.children, hash, hashWithPrefix, - emptyHash + emptyHash, + networkId ); - let treeHash = hash(callTree.accountUpdate); + let treeHash = hash(callTree.accountUpdate, networkId); let nodeHash = hashWithPrefix(prefixes.accountUpdateNode, [ treeHash, calls, @@ -193,9 +225,9 @@ type FeePayer = ZkappCommand['feePayer']; function createFeePayer(feePayer: FeePayer['body']): FeePayer { return { authorization: '', body: feePayer }; } -function feePayerHash(feePayer: FeePayer) { +function feePayerHash(feePayer: FeePayer, networkId: NetworkId) { let accountUpdate = accountUpdateFromFeePayer(feePayer); - return accountUpdateHash(accountUpdate); + return accountUpdateHash(accountUpdate, networkId); } function accountUpdateFromFeePayer({ 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 f8a4b51fd1..b11d9a02c9 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -1,14 +1,5 @@ import { expect } from 'expect'; -import { Ledger, Test, Pickles } from '../../snarky.js'; -import { - PrivateKey as PrivateKeySnarky, - PublicKey as PublicKeySnarky, -} from '../../lib/signature.js'; -import { - AccountUpdate as AccountUpdateSnarky, - ZkappCommand as ZkappCommandSnarky, -} from '../../lib/account-update.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; +import { mocks } from '../../bindings/crypto/constants.js'; import { AccountUpdate, Field, @@ -16,6 +7,28 @@ import { ZkappCommand, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import * as TypesSnarky from '../../bindings/mina-transaction/gen/transaction.js'; +import { + AccountUpdate as AccountUpdateSnarky, + ZkappCommand as ZkappCommandSnarky, +} from '../../lib/account-update.js'; +import { FieldConst } from '../../lib/field.js'; +import { packToFields as packToFieldsSnarky } from '../../lib/hash.js'; +import { Network, setActiveInstance } from '../../lib/mina.js'; +import { Ml, MlHashInput } from '../../lib/ml/conversion.js'; +import { + PrivateKey as PrivateKeySnarky, + PublicKey as PublicKeySnarky, +} from '../../lib/signature.js'; +import { Random, test, withHardCoded } from '../../lib/testing/property.js'; +import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; +import { + hashWithPrefix, + packToFields, + prefixes, +} from '../../provable/poseidon-bigint.js'; +import { Pickles, Test } from '../../snarky.js'; +import { Memo } from './memo.js'; +import { RandomTransaction } from './random-transaction.js'; import { accountUpdateFromFeePayer, accountUpdateHash, @@ -26,23 +39,11 @@ import { signZkappCommand, verifyZkappCommandSignature, } from './sign-zkapp-command.js'; -import { - hashWithPrefix, - packToFields, - prefixes, -} from '../../provable/poseidon-bigint.js'; -import { packToFields as packToFieldsSnarky } from '../../lib/hash.js'; -import { Memo } from './memo.js'; import { Signature, signFieldElement, verifyFieldElement, } from './signature.js'; -import { Random, test, withHardCoded } from '../../lib/testing/property.js'; -import { RandomTransaction } from './random-transaction.js'; -import { Ml, MlHashInput } from '../../lib/ml/conversion.js'; -import { FieldConst } from '../../lib/field.js'; -import { mocks } from '../../bindings/crypto/constants.js'; import { NetworkId } from './types.js'; // monkey-patch bigint to json @@ -82,6 +83,15 @@ expect(stringify(dummyInput.packed)).toEqual( ); test(Random.accountUpdate, (accountUpdate) => { + const testnetMinaInstance = Network({ + networkId: 'testnet', + mina: 'http://localhost:8080/graphql', + }); + const mainnetMinaInstance = Network({ + networkId: 'mainnet', + mina: 'http://localhost:8080/graphql', + }); + fixVerificationKey(accountUpdate); // example account update @@ -99,9 +109,14 @@ test(Random.accountUpdate, (accountUpdate) => { let packedSnarky = packToFieldsSnarky(inputSnarky); expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); - let hash = accountUpdateHash(accountUpdate); - let hashSnarky = accountUpdateSnarky.hash(); - expect(hash).toEqual(hashSnarky.toBigInt()); + let hashTestnet = accountUpdateHash(accountUpdate, 'testnet'); + let hashMainnet = accountUpdateHash(accountUpdate, 'mainnet'); + setActiveInstance(testnetMinaInstance); + let hashSnarkyTestnet = accountUpdateSnarky.hash(); + setActiveInstance(mainnetMinaInstance); + let hashSnarkyMainnet = accountUpdateSnarky.hash(); + expect(hashTestnet).toEqual(hashSnarkyTestnet.toBigInt()); + expect(hashMainnet).toEqual(hashSnarkyMainnet.toBigInt()); }); // private key to/from base58 @@ -131,10 +146,11 @@ test(RandomTransaction.zkappCommand, (zkappCommand, assert) => { assert(isCallDepthValid(zkappCommand)); let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); let ocamlCommitments = Test.hashFromJson.transactionCommitments( - JSON.stringify(zkappCommandJson) + JSON.stringify(zkappCommandJson), + 'testnet' ); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); - let commitment = callForestHash(callForest); + let commitment = callForestHash(callForest, 'testnet'); expect(commitment).toEqual(FieldConst.toBigint(ocamlCommitments.commitment)); }); @@ -151,7 +167,9 @@ test.negative( // zkapp transaction test( RandomTransaction.zkappCommandAndFeePayerKey, - ({ feePayerKey, zkappCommand }) => { + RandomTransaction.networkId, + (zkappCommandAndFeePayerKey, networkId) => { + const { feePayerKey, zkappCommand } = zkappCommandAndFeePayerKey; zkappCommand.accountUpdates.forEach(fixVerificationKey); let feePayerKeyBase58 = PrivateKey.toBase58(feePayerKey); @@ -173,10 +191,11 @@ test( // tx commitment let ocamlCommitments = Test.hashFromJson.transactionCommitments( - JSON.stringify(zkappCommandJson) + JSON.stringify(zkappCommandJson), + networkId ); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); - let commitment = callForestHash(callForest); + let commitment = callForestHash(callForest, networkId); expect(commitment).toEqual( FieldConst.toBigint(ocamlCommitments.commitment) ); @@ -200,7 +219,7 @@ test( stringify(feePayerInput1.packed) ); - let feePayerDigest = feePayerHash(feePayer); + let feePayerDigest = feePayerHash(feePayer, networkId); expect(feePayerDigest).toEqual( FieldConst.toBigint(ocamlCommitments.feePayerHash) ); @@ -215,55 +234,42 @@ test( ); // signature - let sigTestnet = signFieldElement(fullCommitment, feePayerKey, 'testnet'); - let sigMainnet = signFieldElement(fullCommitment, feePayerKey, 'mainnet'); - let sigTestnetOcaml = Test.signature.signFieldElement( - ocamlCommitments.fullCommitment, - Ml.fromPrivateKey(feePayerKeySnarky), - false + let sigFieldElements = signFieldElement( + fullCommitment, + feePayerKey, + networkId ); - let sigMainnetOcaml = Test.signature.signFieldElement( + let sigOCaml = Test.signature.signFieldElement( ocamlCommitments.fullCommitment, Ml.fromPrivateKey(feePayerKeySnarky), - true + networkId === 'mainnet' ? true : false ); - expect(Signature.toBase58(sigTestnet)).toEqual(sigTestnetOcaml); - expect(Signature.toBase58(sigMainnet)).toEqual(sigMainnetOcaml); + + expect(Signature.toBase58(sigFieldElements)).toEqual(sigOCaml); let verify = (s: Signature, id: NetworkId) => verifyFieldElement(s, fullCommitment, feePayerAddress, id); - expect(verify(sigTestnet, 'testnet')).toEqual(true); - expect(verify(sigTestnet, 'mainnet')).toEqual(false); - expect(verify(sigMainnet, 'testnet')).toEqual(false); - expect(verify(sigMainnet, 'mainnet')).toEqual(true); + + expect(verify(sigFieldElements, networkId)).toEqual(true); + expect( + verify(sigFieldElements, networkId === 'mainnet' ? 'testnet' : 'mainnet') + ).toEqual(false); // full end-to-end test: sign a zkapp transaction - let sTest = signZkappCommand( - zkappCommandJson, - feePayerKeyBase58, - 'testnet' - ); - expect(sTest.feePayer.authorization).toEqual(sigTestnetOcaml); - let sMain = signZkappCommand( - zkappCommandJson, - feePayerKeyBase58, - 'mainnet' - ); - expect(sMain.feePayer.authorization).toEqual(sigMainnetOcaml); + let sig = signZkappCommand(zkappCommandJson, feePayerKeyBase58, networkId); + expect(sig.feePayer.authorization).toEqual(sigOCaml); let feePayerAddressBase58 = PublicKey.toBase58(feePayerAddress); expect( - verifyZkappCommandSignature(sTest, feePayerAddressBase58, 'testnet') + verifyZkappCommandSignature(sig, feePayerAddressBase58, networkId) ).toEqual(true); expect( - verifyZkappCommandSignature(sTest, feePayerAddressBase58, 'mainnet') + verifyZkappCommandSignature( + sig, + feePayerAddressBase58, + networkId === 'mainnet' ? 'testnet' : 'mainnet' + ) ).toEqual(false); - expect( - verifyZkappCommandSignature(sMain, feePayerAddressBase58, 'testnet') - ).toEqual(false); - expect( - verifyZkappCommandSignature(sMain, feePayerAddressBase58, 'mainnet') - ).toEqual(true); } ); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 2c47b9dc63..51a43dcb40 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -659,11 +659,14 @@ declare const Test: { accountUpdate(json: string): MlArray; }; hashFromJson: { - accountUpdate(json: string): FieldConst; + accountUpdate(json: string, networkId: string): FieldConst; /** * Returns the commitment of a JSON transaction. */ - transactionCommitments(txJson: string): { + transactionCommitments( + txJson: string, + networkId: string + ): { commitment: FieldConst; fullCommitment: FieldConst; feePayerHash: FieldConst;