@nori-zk/mina-token-bridge
Version:
A Mina zk-program contract allowing users to mint tokens on Nori Bridge.
140 lines • 6.86 kB
JavaScript
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