@nori-zk/mina-token-bridge
Version:
Nori ethereum state settelment and nETH token bridge zkApp
168 lines (167 loc) • 7.17 kB
TypeScript
/**
* SCRAM — Signature Commit-Reveal Authentication Mechanism
*
* Short:
* SCRAM is a scheme where a Mina signature is Poseidon-hashed into a
* commitment (codeChallenge). The codeChallenge is stored on-chain within
* the Eth deposit smart contract as the key in the `lockedTokens` map.
* Later, the claimant supplies the original
* signature, publicKey, and message as private witnesses inside a ZK proof
* on the Mina side. The ZK circuit verifies both that the signature is
* valid for the claimed (publicKey, message) pair AND that its Poseidon
* hash equals the stored codeChallenge — without exposing the signature
* publicly.
*
* Definitions:
* - signature: Mina Signature produced by signing a message with the
* signer's private key.
* - publicKey: Mina PublicKey corresponding to the signer's private key.
* - message: Field[] — the message that was signed.
* - codeChallenge : Field = Poseidon(signature.toFields()) — the commitment
* stored on the Eth smart contract as the deposit key.
*
* Off-chain (committer):
* 1. Sign a message with the signer's Mina private key.
* 2. Compute codeChallenge = Poseidon(signature.toFields()).
* 3. Submit the codeChallenge to the Eth smart contract's lockTokens
* function, where it becomes the deposit key in `lockedTokens`.
*
* On-chain Mina (ZK verification):
* 1. Claimant supplies the original signature, publicKey, and message
* as private witnesses to the ZK circuit.
* 2. Circuit verifies signature.verify(publicKey, message), proving key
* ownership and message integrity.
* 3. Circuit computes Poseidon(signature.toFields()) and asserts equality
* with the stored codeChallenge.
* 4. The signature is never exposed publicly — all verification occurs
* provably inside the circuit.
*
* Security:
* - Commitment is binding: only the holder of the original signature can
* produce a preimage that hashes to the stored codeChallenge.
* - Identity is verified: the signature must be valid for the claimed
* publicKey and message, preventing substitution.
* - Zero-knowledge: the signature witness is never revealed publicly,
* preserving privacy of the commitment preimage.
* - Poseidon is collision-resistant in the Mina field, so forging a
* different signature with the same hash is computationally infeasible.
* - Note: SCRAM does not enforce any constraint on what the message is.
* Any domain separation or message binding is the caller's responsibility.
*
*/
import { Signature, Field, type PublicKey } from 'o1js';
declare const SCRAMWitness_base: (new (value: {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
}) => {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
}) & {
_isStruct: true;
} & Omit<import("o1js/dist/node/lib/provable/types/provable-intf.js").Provable<{
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
}, {
signature: any;
message: bigint[];
}>, "fromFields"> & {
fromFields: (fields: import("o1js/dist/node/lib/provable/field.js").Field[]) => {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
};
} & {
fromValue: (value: {
signature: Signature | {
r: Field | bigint;
s: import("o1js").Scalar | bigint;
};
message: import("o1js/dist/node/lib/provable/field.js").Field[] | bigint[];
}) => {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
};
toInput: (x: {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
}) => {
fields?: Field[] | undefined;
packed?: [Field, number][] | undefined;
};
toJSON: (x: {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
}) => {
signature: any;
message: string[];
};
fromJSON: (x: {
signature: any;
message: string[];
}) => {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
};
empty: () => {
signature: Signature;
message: import("o1js/dist/node/lib/provable/field.js").Field[];
};
};
/**
* The user-supplied private witness for SCRAM verification.
*
* Contains only the inputs that are not computed or derived on-chain:
* - signature: the Mina Signature that was committed to.
* - message: the Field[128] message that was signed.
*
* The codeChallenge is read from on-chain state and the publicKey is
* derived from `this.sender` — neither is user input.
*/
export declare class SCRAMWitness extends SCRAMWitness_base {
}
/**
* Computes the SCRAM codeChallenge (commitment) from a Mina signature.
*
* @param signature - Mina Signature to commit to.
* @returns Field representing the codeChallenge.
*
* Conceptually:
* The signature is decomposed into fields and hashed via Poseidon to
* produce a commitment. This codeChallenge is submitted to the Eth smart
* contract's lockTokens function as the deposit key. The original
* signature is required to open the commitment later inside a ZK proof
* on the Mina side.
*/
export declare function createCodeChallenge(signature: Signature): import("o1js/dist/node/lib/provable/field.js").Field;
/**
* Verifies a SCRAM codeChallenge against a privately witnessed signature.
*
* @param challenge - Stored codeChallenge to verify against.
* @param signature - The Mina Signature supplied as a private witness.
* @param publicKey - Mina PublicKey of the original signer.
* @param message - The message Field[] that was signed.
*
* Conceptually:
* Two independent checks are performed inside the ZK circuit:
* 1. Signature validity — the signature must verify against the supplied
* publicKey and message, proving key ownership and message integrity.
* 2. Commitment match — the Poseidon hash of the signature must equal the
* stored codeChallenge, proving this is the exact signature that was
* committed to.
* The signature is never exposed publicly.
*/
export declare function verifyCodeChallenge(codeChallenge: Field, signature: Signature, publicKey: PublicKey, message: Field[]): void;
/**
* Converts a SCRAM codeChallenge Field into a big-endian hex string.
*
* @param codeChallenge - The Poseidon Field representing the codeChallenge.
* @returns A 0x-prefixed hexadecimal string representing the codeChallenge in
* big-endian byte order, suitable for off-chain use or contract arguments.
*
* Conceptually:
* - The codeChallenge Field is first converted into a 32-byte word.
* - The bytes are reversed to switch from little-endian (internal representation)
* to big-endian.
* - The resulting bytes are serialized as a hex string with a 0x prefix.
*/
export declare function codeChallengeFieldToBEHex(codeChallenge: Field): string;
export {};