@nori-zk/ethprocessor
Version:
zkApp for verifying SP1 Helios Nori proof and storing latest execution state root on Mina
142 lines • 6.29 kB
JavaScript
import { EthProcessor } from './EthProcessor.js';
import { EthVerifier, EthInput, Bytes32 } from './EthVerifier.js';
import { ethers } from 'ethers';
import { NodeProofLeft } from '@nori-zk/proof-conversion';
import { AccountUpdate, Mina, PrivateKey, UInt64, fetchAccount, } from 'o1js';
export class MinaEthProcessorSubmitter {
constructor(type = 'plonk') {
/*const ZK_APP_ADDRESS = process.env.ZK_APP_ADDRESS;
if (ZK_APP_ADDRESS === undefined) {
throw 'ZK_APP_ADDRESS env var is not defined exiting';
}*/
this.type = type;
const SENDER_PRIVATE_KEY = process.env.SENDER_PRIVATE_KEY;
if (!SENDER_PRIVATE_KEY) {
throw 'SENDER_PRIVATE_KEY env var is not define exiting';
}
this.senderPrivateKey = PrivateKey.fromBase58(SENDER_PRIVATE_KEY);
const NETWORK = process.env.NETWORK;
if (!NETWORK || !['devnet', 'mainnet', 'lightnet'].includes(NETWORK)) {
throw 'NETWORK env var is not defined or wrong, options are devnet, mainnet, lightnet';
}
this.network = NETWORK;
this.testMode = NETWORK === 'lightnet';
if (this.testMode && !process.env.ZKAPP_PRIVATE_KEY) {
// This makes sure the local tests work dont remove it
this.zkAppPrivateKey = PrivateKey.random();
this.zkApp = new EthProcessor(this.zkAppPrivateKey.toPublicKey());
}
else {
const ZKAPP_PRIVATE_KEY = process.env.ZKAPP_PRIVATE_KEY;
if (!ZKAPP_PRIVATE_KEY) {
throw 'ZKAPP_PRIVATE_KEY env var is not define exiting';
}
this.zkAppPrivateKey = PrivateKey.fromBase58(process.env.ZKAPP_PRIVATE_KEY);
this.zkApp = new EthProcessor(this.zkAppPrivateKey.toPublicKey());
//this.zkApp = new EthProcessor(PublicKey.fromBase58(ZK_APP_ADDRESS));
}
this.txFee = Number(process.env.TX_FEE || 0.1) * 1e9;
console.log('Loaded constants from .env');
}
async networkSetUp() {
const MINA_RPC_NETWORK_URL = process.env.MINA_RPC_NETWORK_URL ||
'https://api.minascan.io/node/devnet/v1/graphql';
const networkId = this.network === 'mainnet' ? 'mainnet' : 'testnet';
const Network = Mina.Network({
networkId,
mina: MINA_RPC_NETWORK_URL,
});
Mina.setActiveInstance(Network);
console.log('Finished Mina network setup');
}
async compileContracts() {
try {
console.log('Compiling verifier contract');
const { verificationKey: vk } = await EthVerifier.compile(); // Future opt cache: Cache.FileSystemDefault,
console.log('Verifier contract vk hash compiled:', vk.hash.toString());
const pVK = (await EthProcessor.compile()).verificationKey; // Future opt cache: Cache.FileSystemDefault,
console.log('EthProcessor contract vk hash:', pVK.hash.toString());
console.log('Contracts compiled.');
}
catch (err) {
console.error(`Error compiling contracts: ${err}`);
}
}
async deployContract() {
const deployTx = await Mina.transaction({ sender: this.senderPrivateKey.toPublicKey(), fee: this.txFee }, async () => {
AccountUpdate.fundNewAccount(this.senderPrivateKey.toPublicKey());
await this.zkApp.deploy();
});
console.log('Deploy transaction created successfully.');
await deployTx.prove();
await deployTx
.sign([this.senderPrivateKey, this.zkAppPrivateKey])
.send()
.wait();
console.log('EthProcessor deployed successfully.');
}
async createProof(proofArguments) {
const { sp1PlonkProof, conversionOutputProof } = proofArguments;
const rawProof = await NodeProofLeft.fromJSON(conversionOutputProof.proofData);
const ethSP1Proof = sp1PlonkProof;
// Decode proof values.
const defaultEncoder = ethers.AbiCoder.defaultAbiCoder();
const decoded = defaultEncoder.decode([
'bytes32',
'bytes32',
'bytes32',
'uint64',
'bytes32',
'uint64',
'bytes32',
'bytes32',
], new Uint8Array(Buffer.from(ethSP1Proof.public_values.buffer.data)));
// Create input for verification.
const input = new EthInput({
executionStateRoot: Bytes32.fromHex(decoded[0].slice(2)),
newHeader: Bytes32.fromHex(decoded[1].slice(2)),
nextSyncCommitteeHash: Bytes32.fromHex(decoded[2].slice(2)),
newHead: UInt64.from(decoded[3]),
prevHeader: Bytes32.fromHex(decoded[4].slice(2)),
prevHead: UInt64.from(decoded[5]),
syncCommitteeHash: Bytes32.fromHex(decoded[6].slice(2)),
startSyncComitteHash: Bytes32.fromHex(decoded[7].slice(2)),
});
// Compute and verify proof.
console.log('Computing proof.');
return EthVerifier.compute(input, rawProof);
}
async submit(ethProof) {
console.log('Creating update transaction.');
try {
await fetchAccount({ publicKey: this.zkApp.address });
await fetchAccount({
publicKey: this.senderPrivateKey.toPublicKey(),
});
const updateTx = await Mina.transaction({
sender: this.senderPrivateKey.toPublicKey(),
fee: this.txFee,
}, async () => {
await this.zkApp.update(ethProof);
});
await updateTx.prove();
console.log('Transaction proven.');
const tx = await updateTx.sign([this.senderPrivateKey]).send();
console.log(`Transaction sent${this.testMode ? ' to testMode.' : '.'}`);
const txId = tx.data.sendZkapp.zkapp.id;
const txHash = tx.data.sendZkapp.zkapp.hash;
if (!txId) {
throw new Error('txId is undefined');
}
return {
txId,
txHash,
};
}
catch (err) {
console.error(`Error submitting proof: ${err}`);
throw err;
}
}
}
//# sourceMappingURL=proofSubmitter.js.map