UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

176 lines 10.4 kB
import { expect } from 'expect'; import { mocks } from '../../bindings/crypto/constants.js'; import { AccountUpdate, Field, ZkappCommand, } from '../../bindings/mina-transaction/gen/v1/transaction-bigint.js'; import * as TypesSnarky from '../../bindings/mina-transaction/gen/v1/transaction.js'; import { AccountUpdate as AccountUpdateSnarky, ZkappCommand as ZkappCommandSnarky, } from '../../lib/mina/v1/account-update.js'; import { FieldConst } from '../../lib/provable/core/fieldvar.js'; import { packToFields as packToFieldsSnarky } from '../../lib/provable/crypto/poseidon.js'; import { Network, setActiveInstance } from '../../lib/mina/v1/mina.js'; import { Ml, MlHashInput } from '../../lib/ml/conversion.js'; import { PrivateKey as PrivateKeySnarky, PublicKey as PublicKeySnarky, } from '../../lib/provable/crypto/signature.js'; import { Random, test, withHardCoded } from '../../lib/testing/property.js'; import { PrivateKey, PublicKey } from './curve-bigint.js'; import { hashWithPrefix, packToFields, prefixes } from './poseidon-bigint.js'; import { Pickles, Test } from '../../bindings.js'; import { Memo } from './memo.js'; import { RandomTransaction } from './random-transaction.js'; import { accountUpdateFromFeePayer, accountUpdateHash, accountUpdatesToCallForest, callForestHash, feePayerHash, isCallDepthValid, signZkappCommand, verifyZkappCommandSignature, } from './sign-zkapp-command.js'; import { Signature, signFieldElement, verifyFieldElement } from './signature.js'; import { NetworkId } from './types.js'; let mlTest = await Test(); // monkey-patch bigint to json BigInt.prototype.toJSON = function () { return this.toString(); }; let { parse, stringify } = JSON; const toJSON = (x) => parse(stringify(x)); // public key roundtrip & consistency w/ OCaml serialization test(Random.json.publicKey, (publicKeyBase58) => { let pkSnarky = PublicKeySnarky.fromBase58(publicKeyBase58); let pk = PublicKey.fromJSON(publicKeyBase58); expect(pk.x).toEqual(pkSnarky.x.toBigInt()); expect(pk.isOdd).toEqual(pkSnarky.isOdd.toBoolean()); expect(PublicKey.toJSON(pk)).toEqual(publicKeyBase58); }); // empty account update let dummy = AccountUpdate.empty(); let dummySnarky = AccountUpdateSnarky.dummy(); expect(AccountUpdate.toJSON(dummy)).toEqual(AccountUpdateSnarky.toJSON(dummySnarky)); let dummyInput = AccountUpdate.toInput(dummy); let dummyInputSnarky = MlHashInput.from(mlTest.hashInputFromJson.body(JSON.stringify(AccountUpdateSnarky.toJSON(dummySnarky).body))); expect(stringify(dummyInput.fields)).toEqual(stringify(dummyInputSnarky.fields)); expect(stringify(dummyInput.packed)).toEqual(stringify(dummyInputSnarky.packed)); test(Random.accountUpdate, RandomTransaction.networkId, (accountUpdate, networkId) => { const minaInstance = Network({ networkId, mina: 'http://localhost:8080/graphql', }); fixVerificationKey(accountUpdate); // example account update let accountUpdateJson = AccountUpdate.toJSON(accountUpdate); // account update hash let accountUpdateSnarky = AccountUpdateSnarky.fromJSON(accountUpdateJson); let inputSnarky = TypesSnarky.AccountUpdate.toInput(accountUpdateSnarky); let input = AccountUpdate.toInput(accountUpdate); expect(toJSON(input.fields)).toEqual(toJSON(inputSnarky.fields)); expect(toJSON(input.packed)).toEqual(toJSON(inputSnarky.packed)); let packed = packToFields(input); let packedSnarky = packToFieldsSnarky(inputSnarky); expect(toJSON(packed)).toEqual(toJSON(packedSnarky)); setActiveInstance(minaInstance); let hashSnarky = accountUpdateSnarky.hash(); let hash = accountUpdateHash(accountUpdate, networkId); expect(hash).toEqual(hashSnarky.toBigInt()); // check against different network hash expect(hash).not.toEqual(accountUpdateHash(accountUpdate, NetworkId.toString(networkId) === 'mainnet' ? 'devnet' : 'mainnet')); expect(hash).not.toEqual(accountUpdateHash(accountUpdate, NetworkId.toString(networkId) === 'mainnet' ? 'testnet' : 'mainnet')); }); // private key to/from base58 test(Random.json.privateKey, (feePayerKeyBase58) => { let feePayerKey = PrivateKey.fromBase58(feePayerKeyBase58); let feePayerKeySnarky = PrivateKeySnarky.fromBase58(feePayerKeyBase58); let feePayerCompressed = feePayerKeySnarky.s.toFieldsCompressed(); expect(feePayerKey).toEqual(feePayerCompressed.field.toBigInt()); expect(PrivateKey.toBase58(feePayerKey)).toEqual(feePayerKeyBase58); }); // memo let memoGenerator = withHardCoded(Random.json.memoString, 'hello world'); test(memoGenerator, (memoString) => { let memo = Memo.fromString(memoString); let memoBase58 = Memo.toBase58(memo); let memoBase581 = mlTest.encoding.memoToBase58(memoString); expect(memoBase58).toEqual(memoBase581); let memoRecovered = Memo.fromBase58(memoBase58); expect(memoRecovered).toEqual(memo); }); // zkapp transaction - basic properties & commitment test(RandomTransaction.zkappCommand, RandomTransaction.networkId, (zkappCommand, networkId, assert) => { zkappCommand.accountUpdates.forEach(fixVerificationKey); assert(isCallDepthValid(zkappCommand)); let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); let ocamlCommitments = mlTest.hashFromJson.transactionCommitments(JSON.stringify(zkappCommandJson), NetworkId.toString(networkId)); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); let commitment = callForestHash(callForest, networkId); expect(commitment).toEqual(FieldConst.toBigint(ocamlCommitments.commitment)); }); // invalid zkapp transactions test.negative(RandomTransaction.zkappCommandJson.invalid, Random.json.privateKey, RandomTransaction.networkId, (zkappCommand, feePayerKey, networkId) => { signZkappCommand(zkappCommand, feePayerKey, networkId); }); // zkapp transaction test(RandomTransaction.zkappCommandAndFeePayerKey, RandomTransaction.networkId, (zkappCommandAndFeePayerKey, networkId) => { const { feePayerKey, zkappCommand } = zkappCommandAndFeePayerKey; zkappCommand.accountUpdates.forEach(fixVerificationKey); let feePayerKeyBase58 = PrivateKey.toBase58(feePayerKey); let feePayerKeySnarky = PrivateKeySnarky.fromBase58(feePayerKeyBase58); let feePayerAddress = PrivateKey.toPublicKey(feePayerKey); let { feePayer, memo: memoBase58 } = zkappCommand; feePayer.authorization = Signature.toBase58(Signature.dummy()); let zkappCommandJson = ZkappCommand.toJSON(zkappCommand); // o1js fromJSON -> toJSON roundtrip, + consistency with mina-signer let zkappCommandSnarky = ZkappCommandSnarky.fromJSON(zkappCommandJson); let zkappCommandJsonSnarky = ZkappCommandSnarky.toJSON(zkappCommandSnarky); expect(JSON.stringify(zkappCommandJson)).toEqual(JSON.stringify(zkappCommandJsonSnarky)); let recoveredZkappCommand = ZkappCommand.fromJSON(zkappCommandJson); expect(recoveredZkappCommand).toEqual(zkappCommand); // tx commitment let ocamlCommitments = mlTest.hashFromJson.transactionCommitments(JSON.stringify(zkappCommandJson), NetworkId.toString(networkId)); let callForest = accountUpdatesToCallForest(zkappCommand.accountUpdates); let commitment = callForestHash(callForest, networkId); expect(commitment).toEqual(FieldConst.toBigint(ocamlCommitments.commitment)); let memo = Memo.fromBase58(memoBase58); let memoHash = Memo.hash(memo); let memoHashSnarky = mlTest.encoding.memoHashBase58(memoBase58); expect(memoHash).toEqual(FieldConst.toBigint(memoHashSnarky)); let feePayerAccountUpdate = accountUpdateFromFeePayer(feePayer); let feePayerJson = AccountUpdate.toJSON(feePayerAccountUpdate); let feePayerInput = AccountUpdate.toInput(feePayerAccountUpdate); let feePayerInput1 = MlHashInput.from(mlTest.hashInputFromJson.body(JSON.stringify(feePayerJson.body))); expect(stringify(feePayerInput.fields)).toEqual(stringify(feePayerInput1.fields)); expect(stringify(feePayerInput.packed)).toEqual(stringify(feePayerInput1.packed)); let feePayerDigest = feePayerHash(feePayer, networkId); expect(feePayerDigest).toEqual(FieldConst.toBigint(ocamlCommitments.feePayerHash)); let fullCommitment = hashWithPrefix(prefixes.accountUpdateCons, [ memoHash, feePayerDigest, commitment, ]); expect(fullCommitment).toEqual(FieldConst.toBigint(ocamlCommitments.fullCommitment)); // signature let sigFieldElements = signFieldElement(fullCommitment, feePayerKey, networkId); let sigOCaml = mlTest.signature.signFieldElement(ocamlCommitments.fullCommitment, Ml.fromPrivateKey(feePayerKeySnarky), NetworkId.toString(networkId)); expect(Signature.toBase58(sigFieldElements)).toEqual(sigOCaml); let verify = (s, id) => verifyFieldElement(s, fullCommitment, feePayerAddress, id); expect(verify(sigFieldElements, networkId)).toEqual(true); 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); expect(sig.feePayer.authorization).toEqual(sigOCaml); let feePayerAddressBase58 = PublicKey.toBase58(feePayerAddress); expect(verifyZkappCommandSignature(sig, feePayerAddressBase58, networkId)).toEqual(true); expect(verifyZkappCommandSignature(sig, feePayerAddressBase58, networkId === 'mainnet' ? 'testnet' : 'mainnet')).toEqual(false); expect(verifyZkappCommandSignature(sig, feePayerAddressBase58, networkId === 'mainnet' ? 'devnet' : 'mainnet')).toEqual(false); }); console.log('to/from json, hashes & signatures are consistent! 🎉'); function fixVerificationKey(a) { // ensure verification key is valid if (a.body.update.verificationKey.isSome) { let [, data, hash] = Pickles.dummyVerificationKey(); a.body.update.verificationKey.value = { data, hash: FieldConst.toBigint(hash), }; } else { a.body.update.verificationKey.value = { data: '', hash: Field(0), }; } fixVerificationKeyHash(a); } function fixVerificationKeyHash(a) { a.body.authorizationKind.verificationKeyHash = Field(mocks.dummyVerificationKeyHash); } //# sourceMappingURL=sign-zkapp-command.unit-test.js.map