UNPKG

@nori-zk/mina-token-bridge

Version:

A Mina zk-program contract allowing users to mint tokens on Nori Bridge.

387 lines (386 loc) 22.8 kB
var _TokenMintWorker_minaPrivateKey, _TokenMintWorker_mintProofCache; import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib"; import { computeDepositAttestation } from '../../depositAttestation.js'; import { compileEcdsaEthereum, compileEcdsaSigPresentationVerifier, createEcdsaMinaCredential, createEcdsaSigPresentation, createEcdsaSigPresentationRequest, getSecretHashFromPresentationJson, ProvableEcdsaSigPresentation, } from '../../credentialAttestation.js'; import { EthDepositProgram, EthDepositProgramInput, EthDepositProgramProofType, } from '../../e2ePrerequisites.js'; import { EthVerifier, ContractDepositAttestor } from '@nori-zk/o1js-zk-utils'; import { AccountUpdate, fetchAccount, Field, Mina, PrivateKey, PublicKey, Transaction, } from 'o1js'; import { NoriStorageInterface } from '../../NoriStorageInterface.js'; import { FungibleToken } from '../../TokenBase.js'; import { NoriTokenController, } from '../../NoriTokenController.js'; import { Presentation } from 'mina-attestations'; // FIXME make a setter for senderPublicKey and perhaps noriAddressBase58 export class TokenMintWorker { constructor() { /// WALLET METHOD DONT USE IN FRONT END // Initialise methods _TokenMintWorker_minaPrivateKey.set(this, void 0); // Here another MOCK for mint but split into two stages statefulMintProof and signAndSendStatefulMintProof // This will be removed when we have a working version of WALLET_signAndSend _TokenMintWorker_mintProofCache.set(this, void 0); } async WALLET_setMinaPrivateKey(minaPrivateKeyBase58) { if (__classPrivateFieldGet(this, _TokenMintWorker_minaPrivateKey, "f")) throw new Error('Mina private key has already been set.'); __classPrivateFieldSet(this, _TokenMintWorker_minaPrivateKey, PrivateKey.fromBase58(minaPrivateKeyBase58), "f"); } // Credential methods async WALLET_computeEcdsaSigPresentation(presentationRequestJson, credentialJson) { console.time('getPresentation'); const presentationJson = await createEcdsaSigPresentation(presentationRequestJson, credentialJson, __classPrivateFieldGet(this, _TokenMintWorker_minaPrivateKey, "f")); console.timeEnd('getPresentation'); // 46.801s return presentationJson; } /*private deserializeTransaction(serializedTransaction: string) { const { tx, blindingValues, length } = JSON.parse( serializedTransaction ); const parsedTx = JSON.parse(tx); const transaction = Mina.Transaction.fromJSON( parsedTx ) as Mina.Transaction<false, false>; if (length !== txNew.transaction.accountUpdates.length) { throw new Error('New Transaction length mismatch'); } if (length !== transaction.transaction.accountUpdates.length) { throw new Error('Serialized Transaction length mismatch'); } for (let i = 0; i < length; i++) { transaction.transaction.accountUpdates[i].lazyAuthorization = txNew.transaction.accountUpdates[i].lazyAuthorization; if (blindingValues[i] !== '') ( transaction.transaction.accountUpdates[i] .lazyAuthorization as any ).blindingValue = Field.fromJSON(blindingValues[i]); } return transaction; }*/ /*private deserializeTransaction(serializedTransaction: string) { const txJSON = JSON.parse(serializedTransaction); const payload = { transaction, onlySign: true, feePayer: { fee: fee, memo: memo, }, }; }*/ // Sign and send transaction // THIS DOES NOT WORK ATM async WALLET_signAndSend(provedTxJsonStr) { if (!__classPrivateFieldGet(this, _TokenMintWorker_minaPrivateKey, "f")) throw new Error('#minaPrivateKey is undefined please call setMinaPrivateKey first'); const tx = Transaction.fromJSON(JSON.parse(provedTxJsonStr)); const result = await tx.sign([__classPrivateFieldGet(this, _TokenMintWorker_minaPrivateKey, "f")]).send().wait(); return { txHash: result.hash }; } // CREDENTIAL METHODS ****************************************************************************** //credential (compile) async compileCredentialDeps() { // Compile programs / contracts console.time('compileEcdsaEthereum'); await compileEcdsaEthereum(); console.timeEnd('compileEcdsaEthereum'); // 1:20.330 (m:ss.mmm) console.time('compilePresentationVerifier'); await compileEcdsaSigPresentationVerifier(); console.timeEnd('compilePresentationVerifier'); // 11.507s } // Credential methods async computeCredential(secret, ethSecretSignature, ethWalletAddress, senderPublicKeyBase58) { console.log('senderPublicKeyBase58', senderPublicKeyBase58); const senderPublicKey = PublicKey.fromBase58(senderPublicKeyBase58); console.time('createCredential'); const credentialJson = await createEcdsaMinaCredential(ethSecretSignature, ethWalletAddress, senderPublicKey, secret); console.timeEnd('createCredential'); // 2:02.513 (m:ss.mmm) return credentialJson; } async computeEcdsaSigPresentationRequest(zkAppPublicKeyBase58) { const zkAppPublicKey = PublicKey.fromBase58(zkAppPublicKeyBase58); console.time('getPresentationRequest'); const presentationRequestJson = await createEcdsaSigPresentationRequest(zkAppPublicKey); console.timeEnd('getPresentationRequest'); // 1.348ms return presentationRequestJson; } // DEPOSIT METHODS ****************************************************************************** //attesation e2e desposit Attestation eth verifier (compile) //ethDepositProof async compileEthDepositProgramDeps() { console.time('ContractDepositAttestor compile'); const { verificationKey: contractDepositAttestorVerificationKey } = await ContractDepositAttestor.compile({ forceRecompile: true }); console.timeEnd('ContractDepositAttestor compile'); console.log(`ContractDepositAttestor contract compiled vk: '${contractDepositAttestorVerificationKey.hash}'.`); console.time('EthVerifier compile'); const { verificationKey: ethVerifierVerificationKey } = await EthVerifier.compile({ forceRecompile: true }); console.timeEnd('EthVerifier compile'); console.log(`EthVerifier compiled vk: '${ethVerifierVerificationKey.hash}'.`); // EthDepositProgram console.time('EthDepositProgram compile'); const { verificationKey: EthDepositProgramVerificationKey } = await EthDepositProgram.compile({ forceRecompile: true, }); console.timeEnd('EthDepositProgram compile'); console.log(`EthDepositProgram compiled vk: '${EthDepositProgramVerificationKey.hash}'.`); } async computeEthDeposit(presentationJson, depositBlockNumber, ethAddressLowerHex) { const { credentialAttestationBEHex, credentialAttestationHashField } = getSecretHashFromPresentationJson(presentationJson); const { depositAttestationProof, ethVerifierProof, despositSlotRaw } = await computeDepositAttestation(depositBlockNumber, ethAddressLowerHex, credentialAttestationBEHex); const e2ePrerequisitesInput = new EthDepositProgramInput({ credentialAttestationHash: credentialAttestationHashField, }); console.log('Computing e2e'); console.time('EthDepositProgram.compute'); const ethDepositProof = await EthDepositProgram.compute(e2ePrerequisitesInput, ethVerifierProof, depositAttestationProof); console.timeEnd('EthDepositProgram.compute'); return { despositSlotRaw, ethDepositProofJson: ethDepositProof.proof.toJSON(), }; } // Mina setup ****************************************************************************** async minaSetup(options) { const Network = Mina.Network(options); Mina.setActiveInstance(Network); } // Storage setup ****************************************************************************** async fetchAccounts(accounts) { await Promise.all(accounts.map((addr) => fetchAccount({ publicKey: addr }))); } // Balance utils // getBalanceOf async getBalanceOf( //noriTokenControllerAddressBase58: string, noriTokenBaseBase58, minaSenderPublicKeyBase58) { const minaSenderPublicKey = PublicKey.fromBase58(minaSenderPublicKeyBase58); const noriTokenBaseAddress = PublicKey.fromBase58(noriTokenBaseBase58); const noriTokenBase = new FungibleToken(noriTokenBaseAddress); /*const storage = new NoriStorageInterface( minaSenderPublicKey, noriTokenController.deriveTokenId() );*/ await fetchAccount({ publicKey: minaSenderPublicKey, tokenId: noriTokenBase.deriveTokenId(), }); const balanceOf = await noriTokenBase.getBalanceOf(minaSenderPublicKey); console.log('balanceOf raw', balanceOf); console.log('balanceOf string', balanceOf.toString()); return balanceOf.toBigInt().toString(); } async mintedSoFar(noriTokenControllerAddressBase58, minaSenderPublicKeyBase58) { const minaSenderPublicKey = PublicKey.fromBase58(minaSenderPublicKeyBase58); const noriTokenControllerAddress = PublicKey.fromBase58(noriTokenControllerAddressBase58); const noriTokenController = new NoriTokenController(noriTokenControllerAddress); const storage = new NoriStorageInterface(minaSenderPublicKey, noriTokenController.deriveTokenId()); await fetchAccount({ publicKey: minaSenderPublicKey, tokenId: noriTokenController.deriveTokenId(), }); const userKeyHash = await storage.userKeyHash.fetch(); if (!userKeyHash) throw new Error('userKeyHash was falsey. Perhaps this account is not set up?'); const mintedSoFar = await storage.mintedSoFar.fetch(); return mintedSoFar.toBigInt().toString(); } // Determine if we need to setupStorage (as it only needs to be done once per account). async needsToSetupStorage(noriTokenControllerAddressBase58, minaSenderPublicKeyBase58) { try { const minaSenderPublicKey = PublicKey.fromBase58(minaSenderPublicKeyBase58); const noriTokenControllerAddress = PublicKey.fromBase58(noriTokenControllerAddressBase58); const noriTokenController = new NoriTokenController(noriTokenControllerAddress); const storage = new NoriStorageInterface(minaSenderPublicKey, noriTokenController.deriveTokenId()); await fetchAccount({ publicKey: minaSenderPublicKey, tokenId: noriTokenController.deriveTokenId(), }); const userKeyHash = await storage.userKeyHash.fetch(); if (!userKeyHash) throw new Error('userKeyHash was falsey'); const mintedSoFar = await storage.mintedSoFar.fetch(); console.log('mintedSoFar', mintedSoFar.toBigInt()); return false; } catch (e) { const error = e; console.error(`Error determining if we needed to setup storage. Going to assume that we do need to.`, error); // But perhaps this could error for other reasons?! return true; } } async setupStorage(userPublicKeyBase58, noriAddressBase58, txFee, storageInterfaceVerificationKeySafe) { //const userPrivateKey = PrivateKey.fromBase58(userPrivateKeyBase58); const userPublicKey = PublicKey.fromBase58(userPublicKeyBase58); // userPrivateKey.toPublicKey(); const noriAddress = PublicKey.fromBase58(noriAddressBase58); const { hashStr: storageInterfaceVerificationKeyHashStr, data } = storageInterfaceVerificationKeySafe; const storageInterfaceVerificationKeyHashBigInt = BigInt(storageInterfaceVerificationKeyHashStr); const hash = new Field(storageInterfaceVerificationKeyHashBigInt); const storageInterfaceVerificationKey = { data, hash }; console.log(`Setting up storage for user: ${userPublicKey.toBase58()}`); //await fetchAccount({ publicKey: userPublicKey }); // DO we need to do this is we are not proving here??? // FIXME do we need await this.fetchAccounts([userPublicKey, noriAddress]); // Note we could have another method to not have to do this multiple times, but keeping it stateless for now. const noriTokenControllerInst = new NoriTokenController(noriAddress); const setupTx = await Mina.transaction({ sender: userPublicKey, fee: txFee }, async () => { AccountUpdate.fundNewAccount(userPublicKey, 1); await noriTokenControllerInst.setUpStorage(userPublicKey, storageInterfaceVerificationKey); }); const provedTx = await setupTx.prove(); return provedTx.toJSON(); } // This will be removed when we have a working version of WALLET_signAndSend async MOCK_setupStorage(userPublicKeyBase58, noriAddressBase58, txFee, storageInterfaceVerificationKeySafe) { //const userPrivateKey = PrivateKey.fromBase58(userPrivateKeyBase58); const userPublicKey = PublicKey.fromBase58(userPublicKeyBase58); // userPrivateKey.toPublicKey(); const noriAddress = PublicKey.fromBase58(noriAddressBase58); const { hashStr: storageInterfaceVerificationKeyHashStr, data } = storageInterfaceVerificationKeySafe; const storageInterfaceVerificationKeyHashBigInt = BigInt(storageInterfaceVerificationKeyHashStr); const hash = new Field(storageInterfaceVerificationKeyHashBigInt); const storageInterfaceVerificationKey = { data, hash }; console.log(`Setting up storage for user: ${userPublicKey.toBase58()}`); //await fetchAccount({ publicKey: userPublicKey }); // DO we need to do this is we are not proving here??? // FIXME do we need await this.fetchAccounts([userPublicKey, noriAddress]); // Note we could have another method to not have to do this multiple times, but keeping it stateless for now. const noriTokenControllerInst = new NoriTokenController(noriAddress); const setupTx = await Mina.transaction({ sender: userPublicKey, fee: txFee }, async () => { AccountUpdate.fundNewAccount(userPublicKey, 1); await noriTokenControllerInst.setUpStorage(userPublicKey, storageInterfaceVerificationKey); }); const provedTx = await setupTx.prove(); const tx = await provedTx.sign([__classPrivateFieldGet(this, _TokenMintWorker_minaPrivateKey, "f")]).send(); const result = await tx.wait(); console.log('Storage setup completed successfully'); return { txHash: result.hash }; } // MINTER ****************************************************************************** async compileMinterDeps() { console.time('compileNoriStorageInterface'); const { verificationKey: noriStorageInterfaceVerificationKey } = await NoriStorageInterface.compile(); console.timeEnd('compileNoriStorageInterface'); console.log(`NoriStorageInterface compiled vk: '${noriStorageInterfaceVerificationKey.hash}'.`); console.time('compileFungibleToken'); const { verificationKey: fungibleTokenVerificationKey } = await FungibleToken.compile(); console.timeEnd('compileFungibleToken'); console.log(`FungibleToken compiled vk: '${fungibleTokenVerificationKey.hash}'.`); console.time('compileNoriTokenController'); const { verificationKey: noriTokenControllerVerificationKey } = await NoriTokenController.compile(); console.timeEnd('compileNoriTokenController'); console.log(`NoriTokenController compiled vk: '${noriTokenControllerVerificationKey.hash}'.`); const noriStorageInterfaceVerificationKeyHashField = noriStorageInterfaceVerificationKey.hash; const noriStorageInterfaceVerificationKeyHashBigInt = noriStorageInterfaceVerificationKeyHashField.toBigInt(); const noriStorageInterfaceVerificationKeyHashStr = noriStorageInterfaceVerificationKeyHashBigInt.toString(); return { data: noriStorageInterfaceVerificationKey.data, hashStr: noriStorageInterfaceVerificationKeyHashStr, }; } async mint(userPublicKeyBase58, noriAddressBase58, proofDataJson, //userPrivateKey: PrivateKey, txFee, fundNewAccount = true) { const userPublicKey = PublicKey.fromBase58(userPublicKeyBase58); const noriAddress = PublicKey.fromBase58(noriAddressBase58); // Reconstruct MintProofData const { ethDepositProofJson, presentationProofStr } = proofDataJson; const ethDepositProof = await EthDepositProgramProofType.fromJSON(ethDepositProofJson); const presentationProof = ProvableEcdsaSigPresentation.from(Presentation.fromJSON(presentationProofStr)); const proofData = { ethDepositProof, presentationProof, }; console.log(`Minting tokens for user: ${userPublicKeyBase58}`); //await fetchAccount({ publicKey: userPublicKey }); // DO we need to do this is we are not proving here??? await this.fetchAccounts([userPublicKey, noriAddress]); // Note we could have another method to not have to do this multiple times, but keeping it stateless for now. const noriTokenControllerInst = new NoriTokenController(noriAddress); const mintTx = await Mina.transaction({ sender: userPublicKey, fee: txFee }, async () => { if (fundNewAccount) { AccountUpdate.fundNewAccount(userPublicKey, 1); } const realProofData = proofData; await noriTokenControllerInst.noriMint(realProofData.ethDepositProof, realProofData.presentationProof); }); const provedTx = await mintTx.prove(); return provedTx.toJSON(); } // This will be removed when we have a working version of WALLET_signAndSend async MOCK_mint(userPublicKeyBase58, noriAddressBase58, proofDataJson, //userPrivateKey: PrivateKey, txFee, fundNewAccount = true) { const userPublicKey = PublicKey.fromBase58(userPublicKeyBase58); const noriAddress = PublicKey.fromBase58(noriAddressBase58); // Reconstruct MintProofData const { ethDepositProofJson, presentationProofStr } = proofDataJson; const ethDepositProof = await EthDepositProgramProofType.fromJSON(ethDepositProofJson); const presentationProof = ProvableEcdsaSigPresentation.from(Presentation.fromJSON(presentationProofStr)); const proofData = { ethDepositProof, presentationProof, }; console.log(`Minting tokens for user: ${userPublicKeyBase58}`); //await fetchAccount({ publicKey: userPublicKey }); // DO we need to do this is we are not proving here??? await this.fetchAccounts([userPublicKey, noriAddress]); // Note we could have another method to not have to do this multiple times, but keeping it stateless for now. const noriTokenControllerInst = new NoriTokenController(noriAddress); const mintTx = await Mina.transaction({ sender: userPublicKey, fee: txFee }, async () => { if (fundNewAccount) { AccountUpdate.fundNewAccount(userPublicKey, 1); } const realProofData = proofData; await noriTokenControllerInst.noriMint(realProofData.ethDepositProof, realProofData.presentationProof); }); const provedTx = await mintTx.prove(); const tx = await provedTx.sign([__classPrivateFieldGet(this, _TokenMintWorker_minaPrivateKey, "f")]).send(); const result = await tx.wait(); // Fetch updated balance /*await fetchAccount({ publicKey: userPublicKey, tokenId: noriTokenControllerInst.deriveTokenId(), });*/ //const balance = await this.#tokenBase.getBalanceOf(userPublicKey); console.log('Minting completed successfully'); return { txHash: result.hash }; } // Compile all deps async compileAll() { await this.compileCredentialDeps(); await this.compileEthDepositProgramDeps(); return this.compileMinterDeps(); } async MOCK_computeMintProofAndCache(userPublicKeyBase58, noriAddressBase58, proofDataJson, txFee, fundNewAccount = true) { const userPublicKey = PublicKey.fromBase58(userPublicKeyBase58); const noriAddress = PublicKey.fromBase58(noriAddressBase58); // Reconstruct MintProofData const { ethDepositProofJson, presentationProofStr } = proofDataJson; const ethDepositProof = await EthDepositProgramProofType.fromJSON(ethDepositProofJson); const presentationProof = ProvableEcdsaSigPresentation.from(Presentation.fromJSON(presentationProofStr)); const proofData = { ethDepositProof, presentationProof, }; console.log(`Minting tokens for user: ${userPublicKeyBase58}`); //await fetchAccount({ publicKey: userPublicKey }); // DO we need to do this is we are not proving here??? await this.fetchAccounts([userPublicKey, noriAddress]); // Note we could have another method to not have to do this multiple times, but keeping it stateless for now. const noriTokenControllerInst = new NoriTokenController(noriAddress); const mintTx = await Mina.transaction({ sender: userPublicKey, fee: txFee }, async () => { if (fundNewAccount) { AccountUpdate.fundNewAccount(userPublicKey, 1); } const realProofData = proofData; await noriTokenControllerInst.noriMint(realProofData.ethDepositProof, realProofData.presentationProof); }); const provedTx = await mintTx.prove(); __classPrivateFieldSet(this, _TokenMintWorker_mintProofCache, provedTx, "f"); } async WALLET_MOCK_signAndSendMintProofCache() { const tx = await __classPrivateFieldGet(this, _TokenMintWorker_mintProofCache, "f") .sign([__classPrivateFieldGet(this, _TokenMintWorker_minaPrivateKey, "f")]) .send(); const result = await tx.wait(); return { txHash: result.hash }; } } _TokenMintWorker_minaPrivateKey = new WeakMap(), _TokenMintWorker_mintProofCache = new WeakMap(); //# sourceMappingURL=worker.js.map