@nori-zk/mina-token-bridge
Version:
A Mina zk-program contract allowing users to mint tokens on Nori Bridge.
131 lines • 7.43 kB
JavaScript
import { EthProof, ContractDepositAttestorProof, ContractDepositAttestor, EthVerifier, } from '@nori-zk/o1js-zk-utils';
import { AccountUpdate, Field, Mina, Provable, Struct, ZkProgram, } from 'o1js';
import { EcdsaSigPresentationVerifier, ProvableEcdsaSigPresentation, } from './credentialAttestation.js';
import { Presentation } from 'mina-attestations';
export class EthDepositProgramInput extends Struct({
credentialAttestationHash: Field,
}) {
}
export class EthDepositProgramOutput extends Struct({
totalLocked: Field,
storageDepositRoot: Field,
attestationHash: Field,
}) {
}
export const EthDepositProgram = ZkProgram({
name: 'EthDepositProgram',
publicInput: EthDepositProgramInput,
publicOutput: EthDepositProgramOutput,
methods: {
compute: {
privateInputs: [EthProof, ContractDepositAttestorProof],
async method(input, ethVerifierProof, contractDepositAttestorProof) {
ethVerifierProof.verify();
contractDepositAttestorProof.verify();
// Extract roots from public inputs
const depositAttestationProofRoot = contractDepositAttestorProof.publicOutput;
const ethVerifierStorageProofRootBytes = ethVerifierProof.publicInput.verifiedContractDepositsRoot
.bytes; // I think the is BE
// Convert verifiedContractDepositsRoot from bytes to field
let ethVerifierStorageProofRoot = new Field(0);
// FIXME
// Turn into a LE field?? This seems wierd as on the rust side we have fixed_bytes[..32].copy_from_slice(&root.to_bytes());
// And here we re-interpret the BE as LE!
// But it does pass the test! And otherwise fails.
for (let i = 31; i >= 0; i--) {
ethVerifierStorageProofRoot = ethVerifierStorageProofRoot
.mul(256)
.add(ethVerifierStorageProofRootBytes[i].value);
}
// Assert roots
Provable.asProver(() => {
Provable.log('depositAttestationProofRoot', 'ethVerifierStorageProofRoot', depositAttestationProofRoot, ethVerifierStorageProofRoot);
});
depositAttestationProofRoot.assertEquals(ethVerifierStorageProofRoot);
// Mock attestation assert
const contractDepositAttestorPublicInputs = contractDepositAttestorProof.publicInput.value;
// Convert contractDepositAttestorPublicInputs.attestationHash from bytes into a field
const contractDepositAttestorProofCredentialBytes = contractDepositAttestorPublicInputs.attestationHash.bytes;
let contractDepositAttestorProofCredential = new Field(0);
// Turn into field
for (let i = 0; i < 32; i++) {
contractDepositAttestorProofCredential =
contractDepositAttestorProofCredential
.mul(256)
.add(contractDepositAttestorProofCredentialBytes[i]
.value);
}
Provable.asProver(() => {
Provable.log('input.credentialAttestationHash', 'contractDepositAttestorProofCredential', input.credentialAttestationHash, contractDepositAttestorProofCredential);
});
input.credentialAttestationHash.assertEquals(contractDepositAttestorProofCredential);
Provable.asProver(() => {
console.log(contractDepositAttestorPublicInputs.value.bytes.map((byte) => byte.toBigInt()));
});
// Turn totalLocked into a field
const totalLockedBytes = contractDepositAttestorPublicInputs.value.bytes;
let totalLocked = new Field(0);
/*for (let i = 31; i >= 0; i--) {
totalLocked = totalLocked
.mul(256)
.add(totalLockedBytes[i].value);
}*/
for (let i = 0; i < 32; i++) {
totalLocked = totalLocked
.mul(256)
.add(totalLockedBytes[i].value);
}
// Perhaps flip this??
// We interpret contractDepositAttestorProofCredential to BE so why not this??
const storageDepositRoot = ethVerifierStorageProofRoot;
const attestationHash = contractDepositAttestorProofCredential;
return {
publicOutput: new EthDepositProgramOutput({
totalLocked,
storageDepositRoot,
attestationHash,
}),
};
},
},
},
});
// E2EPrerequisitesProgram
export const EthDepositProgramProof = ZkProgram.Proof(EthDepositProgram);
export class EthDepositProgramProofType extends EthDepositProgramProof {
}
export async function compilePreRequisites() {
// TODO optimise not all of these need to be compiled immediately
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}'.`);
console.time('E2EPrerequisitesProgram compile');
const { verificationKey: e2ePrerequisitesVerificationKey } = await EthDepositProgram.compile({ forceRecompile: true });
console.timeEnd('E2EPrerequisitesProgram compile');
console.log(`E2EPrerequisitesProgram contract compiled vk: '${e2ePrerequisitesVerificationKey.hash}'.`);
}
export async function deployAndVerifyEcdsaSigPresentationVerifier(zkAppPrivateKey, senderPrivateKey, presentationJSON) {
const senderPublicKey = senderPrivateKey.toPublicKey();
const zkAppPublicKey = zkAppPrivateKey.toPublicKey();
const zkApp = new EcdsaSigPresentationVerifier(zkAppPublicKey);
const deployTx = await Mina.transaction({ sender: senderPublicKey, fee: 0.01 * 1e9 }, async () => {
AccountUpdate.fundNewAccount(senderPublicKey);
await zkApp.deploy();
const presentation = Presentation.fromJSON(presentationJSON);
const provablePresentation = ProvableEcdsaSigPresentation.from(presentation);
const claims = await zkApp.verifyPresentation(provablePresentation);
console.log('✅ ProvablePresentation verified!');
console.log('ProvableEcdsaSigPresentation claims:', claims);
});
console.log('Deploy transaction created successfully. Proving...');
await deployTx.prove();
console.log('Transaction proved. Signing and sending the transaction...');
await deployTx.sign([senderPrivateKey, zkAppPrivateKey]).send().wait();
console.log('✅ EcdsaSigPresentationVerifier deployed and verified successfully.');
}
//# sourceMappingURL=e2ePrerequisites.js.map