UNPKG

@nori-zk/mina-token-bridge

Version:

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

140 lines 6.86 kB
import { EcdsaEthereum } from 'mina-attestations/imported'; import { Bytes, declareMethods, Field, SmartContract, } from 'o1js'; import { Credential, DynamicBytes, DynamicString, Operation, Presentation, PresentationRequest, PresentationSpec, } from 'mina-attestations'; import { wordToBytes } from '@nori-zk/proof-conversion/min'; // Define the max secret length that we can support. const secretMaxLength = 20; // Define secret string type const SecretString = DynamicString({ maxLength: secretMaxLength }); // Define EcdsaCredential type export const EcdsaCredential = await EcdsaEthereum.Credential({ maxMessageLength: secretMaxLength, // maxMessageLength is a misnomer by mina-attestations }); // Define EcdsaCredentialPresentation Spec let ecdsaCredentialPresentationSpec = PresentationSpec( // This does credential.verify() (we know its a valid credential, a wrapper around it) { credential: EcdsaCredential.spec }, ({ credential }) => ({ outputClaim: Operation.record({ owner: Operation.owner, // Mina issuer: Operation.publicInput(credential), // Eth messageHash: Operation.hash(Operation.property(credential, 'message'), Operation.owner), }), })); // Precompile ecdsaCredentialPresentationSpec let EcdsaSigPresentationSpecPreCompile = await Presentation.precompile(ecdsaCredentialPresentationSpec); // Define ProvableEcdsaSigPresentation export class ProvableEcdsaSigPresentation extends EcdsaSigPresentationSpecPreCompile.ProvablePresentation { } // Define ProvableEcdsaSigPresentation verifier smart contract export class EcdsaSigPresentationVerifier extends SmartContract { async verifyPresentation(presentation) { // verify the presentation, and receive its claims for further validation and usage let { claims, outputClaim } = presentation.verify({ publicKey: this.address, tokenId: this.tokenId, methodName: 'verifyPresentation', }); return outputClaim; } } // o1js hackery declareMethods(EcdsaSigPresentationVerifier, { verifyPresentation: [ProvableEcdsaSigPresentation], // TODO bad TS interface }); // Compile util functions // Compile EcdsaEthereum export async function compileEcdsaEthereum(cache) { const ecdsaEthereumOptions = { maxMessageLength: secretMaxLength, // maxMessageLength is a misnomer by mina-attestations proofsEnabled: true, }; // Note that cache does not really exist here FIXME! const ecdsaCredentialOptions = { proofsEnabled: true }; if (cache) { ecdsaCredentialOptions.cache = cache; } await EcdsaEthereum.compileDependencies({ maxMessageLength: secretMaxLength, // maxMessageLength is a misnomer by mina-attestations proofsEnabled: true, }); await EcdsaCredential.compile(ecdsaCredentialOptions); } // Compile EcdsaSigPresentationVerifier export async function compileEcdsaSigPresentationVerifier(cache) { await EcdsaSigPresentationVerifier.compile({ cache: cache }); } // Methods // Create EcdsaMinaCredential export async function createEcdsaMinaCredential(ethSignature, ethWalletAddress, minaPubKey, secret) { if (secret.length > secretMaxLength) throw new Error(`Secret provided has length '${secret.valueOf}' which is greater than the max supported secret length '${secretMaxLength}'.`); const Message = DynamicBytes({ maxLength: secretMaxLength }); const message = secret; // create credential (which verifies the signature) let { signature, parityBit } = EcdsaEthereum.parseSignature(ethSignature); let credential = await EcdsaCredential.create({ owner: minaPubKey, publicInput: { signerAddress: EcdsaEthereum.parseAddress(ethWalletAddress), }, privateInput: { message: Message.fromString(message), signature, parityBit, }, }); await Credential.validate(credential); const credentialJson = Credential.toJSON(credential); console.log('✅ Created credential:', credentialJson); return credentialJson; } // Create EcdsaSigPresentationRequest export async function createEcdsaSigPresentationRequest(zkAppAddress) { // ZKAPP VERIFIER, outside circuit: request a presentation let request = PresentationRequest.zkAppFromCompiled(EcdsaSigPresentationSpecPreCompile, {}, // createdAt: UInt64.from(Date.now()) // TODO { // this added context ensures that the presentation can't be used outside the target zkApp publicKey: zkAppAddress, methodName: 'verifyPresentation', //tokenId: new Field(0), // Or TokenId.default() from o1js // Will need to be derived from the instance of the ZKApps we have TokenController contract //network // mainnet devnet }); let presentationRequestJson = PresentationRequest.toJSON(request); console.log('✅ Created presentation request:', presentationRequestJson); return presentationRequestJson; } // Create EcdsaSigPresentation export async function createEcdsaSigPresentation(presentationRequestJson, credentialJson, owner) { const presentationRequest = PresentationRequest.fromJSON('zk-app', presentationRequestJson); const credential = await Credential.fromJSON(credentialJson); // Context is a WalletContext which is an R extends PresentationContext. // not sure what its value should be here. const presentation = await Presentation.create(owner, { request: presentationRequest, credentials: [credential], context: undefined, }); const presentationJson = Presentation.toJSON(presentation); console.log('✅ Created presentation:', presentationJson); return presentationJson; } // Verify EcdsaSigPresentation export async function verifyEcdsaSigPresentation(presentationJson, zkAppAddress) { let presentation = Presentation.fromJSON(presentationJson); return new EcdsaSigPresentationVerifier(zkAppAddress).verifyPresentation(ProvableEcdsaSigPresentation.from(presentation)); } // Hash secret export function hashSecret(secret) { const secretString = SecretString.from(secret); return secretString.hash(); } export function getSecretHashFromPresentationJson(presentationJson) { const presentation = JSON.parse(presentationJson); const messageHashString = presentation.outputClaim.value.messageHash.value; const messageHashBigInt = BigInt(messageHashString); const credentialAttestationHashField = Field.from(messageHashBigInt); const beAttestationHashBytes = Bytes.from(wordToBytes(credentialAttestationHashField, 32).reverse()); const credentialAttestationBEHex = `0x${beAttestationHashBytes.toHex()}`; return { credentialAttestationBEHex, credentialAttestationHashField, credentialAttestationBigInt: messageHashBigInt }; } //# sourceMappingURL=credentialAttestation.js.map