@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
JavaScript
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