UNPKG

@nori-zk/proof-conversion

Version:

Verifying zkVM proofs inside o1js circuits, to generate Mina compatible proof

372 lines 19 kB
import { Bytes, Gadgets, UInt8, UInt32 } from 'o1js'; import { FrC } from '../../towers/index.js'; import { Hash, Struct } from 'o1js'; import { FpC } from '../../towers/index.js'; import { bytesToWord, provableBn254BaseFieldToBytes, provableBn254ScalarFieldToBytes, wordToBytes, } from '../../sha/utils.js'; import { shaToFr } from './sha_to_fr.js'; const Bytes741Padding = [ UInt8.from(0x80n), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0), UInt8.from(0x17n), UInt8.from(0x28n), ]; const gammaSizeInBytes = () => { let size = 0; size += 5; // for gamma_separator size += 3 * 2 * 32; // 3 permutation polys * 2 coordinates * 32 bytes for each coord size += 6 * 2 * 32; // 6 selector polys * 2 coordinates * 32 bytes each coord size += 5 * 32; // SP1 v6: 5 public inputs each of 32 bytes (was 2 in SP1 v5) size += 3 * 2 * 32; // 1 custom gate triple (l, r, o) * 2 coordinates * 32 bytes each coord return size; }; const sizeBetaBytes = () => { let size = 0; size += 4; // for beta_separator size += 32; // for bytes of gamma_hash return size; }; const sizeAlphaBytes = () => { let size = 0; size += 5; // for alpha_separator size += 32; // for bytes of beta_hash size += 2 * 32; // for qcp_0 cm size += 2 * 32; // for grand_product_zm return size; }; const sizeZetaBytes = () => { let size = 0; size += 4; // for zeta_separator size += 32; // for bytes of alpha_hash size += 3 * 2 * 32; // 3 h_i chunks return size; }; const sizeGammaKzgBytes = () => { let size = 0; size += 5; // for gamma_separator size += 32; // for bytes of zeta_digest size += 1 * 2 * 32; // for linearized commitment size += 3 * 2 * 32; // for [l, r, o] commitments size += 2 * 2 * 32; // for [s1, s2] commitments size += 1 * 2 * 32; // for qcp_0 commitment size += 7 * 32; // for according openings of [linearized, l, r, o, s1, s2, qco_0] openings size += 32; // for shifted grand product opening return size; }; const sizeRandomBytes = () => { let size = 0; size += 4 * 2 * 32; // cm, batch, grand, batch shifted size += 2 * 32; // zeta and gamma return size; }; export class BytesGamma extends Bytes(gammaSizeInBytes()) { } export class BytesBeta extends Bytes(sizeBetaBytes()) { } export class BytesAlpha extends Bytes(sizeAlphaBytes()) { } export class BytesZeta extends Bytes(sizeZetaBytes()) { } export class BytesGammaKzg extends Bytes(sizeGammaKzgBytes()) { } export class BytesRandomKzg extends Bytes(sizeRandomBytes()) { } export class Bytes32 extends Bytes(32) { } function init() { return { gamma_digest: new Bytes32(Array.from({ length: 32 }, () => UInt8.from(0n))), gamma: FrC.from(0n), beta_digest: new Bytes32(Array.from({ length: 32 }, () => UInt8.from(0n))), beta: FrC.from(0n), alpha_digest: new Bytes32(Array.from({ length: 32 }, () => UInt8.from(0n))), alpha: FrC.from(0n), zeta_digest: new Bytes32(Array.from({ length: 32 }, () => UInt8.from(0n))), zeta: FrC.from(0n), gamma_kzg_digest: new Bytes32(Array.from({ length: 32 }, () => UInt8.from(0n))), gamma_kzg: FrC.from(0n), }; } class Sp1PlonkFiatShamir extends Struct({ gamma_digest: Bytes32.provable, gamma: FrC.provable, beta_digest: Bytes32.provable, beta: FrC.provable, alpha_digest: Bytes32.provable, alpha: FrC.provable, zeta_digest: Bytes32.provable, zeta: FrC.provable, gamma_kzg_digest: Bytes32.provable, gamma_kzg: FrC.provable, }) { constructor(fs) { super(fs); } static empty() { return new Sp1PlonkFiatShamir(init()); } deepClone() { return new Sp1PlonkFiatShamir({ gamma_digest: new Bytes32([...this.gamma_digest.bytes]), gamma: FrC.from(this.gamma.toBigInt()), beta_digest: new Bytes32([...this.beta_digest.bytes]), beta: FrC.from(this.beta.toBigInt()), alpha_digest: new Bytes32([...this.alpha_digest.bytes]), alpha: FrC.from(this.alpha.toBigInt()), zeta_digest: new Bytes32([...this.zeta_digest.bytes]), zeta: FrC.from(this.zeta.toBigInt()), gamma_kzg_digest: new Bytes32([...this.gamma_kzg_digest.bytes]), gamma_kzg: FrC.from(this.gamma_kzg.toBigInt()), }); } // pi0F = sp1_vkey_hash, pi1F = committed_values_digest (derived from public_values) // SP1 v6: pi2F = exit_code, pi3F = vk_root, pi4F = proof_nonce (from public_inputs[2..4]) squeezeGamma(proof, pi0F, pi1F, pi2F, pi3F, pi4F, vk) { const gamma_separator = FpC.from(0x67616d6d61n); // TODO: we can read this from file let separator_bytes = provableBn254BaseFieldToBytes(gamma_separator); // gamma is 39 bits, so we leave only 40 bits (to keep it multiple of 8) // and we cut the rest (256 - 40) bits which is 27 bytes let cm_bytes = separator_bytes.slice(27, 32); const s1x = provableBn254BaseFieldToBytes(vk.qs1_x); cm_bytes = cm_bytes.concat(s1x); const s1y = provableBn254BaseFieldToBytes(vk.qs1_y); cm_bytes = cm_bytes.concat(s1y); const s2x = provableBn254BaseFieldToBytes(vk.qs2_x); cm_bytes = cm_bytes.concat(s2x); const s2y = provableBn254BaseFieldToBytes(vk.qs2_y); cm_bytes = cm_bytes.concat(s2y); const s3x = provableBn254BaseFieldToBytes(vk.qs3_x); cm_bytes = cm_bytes.concat(s3x); const s3y = provableBn254BaseFieldToBytes(vk.qs3_y); cm_bytes = cm_bytes.concat(s3y); const qlx = provableBn254BaseFieldToBytes(vk.ql_x); cm_bytes = cm_bytes.concat(qlx); const qly = provableBn254BaseFieldToBytes(vk.ql_y); cm_bytes = cm_bytes.concat(qly); const qrx = provableBn254BaseFieldToBytes(vk.qr_x); cm_bytes = cm_bytes.concat(qrx); const qry = provableBn254BaseFieldToBytes(vk.qr_y); cm_bytes = cm_bytes.concat(qry); const qmx = provableBn254BaseFieldToBytes(vk.qm_x); cm_bytes = cm_bytes.concat(qmx); const qmy = provableBn254BaseFieldToBytes(vk.qm_y); cm_bytes = cm_bytes.concat(qmy); const qox = provableBn254BaseFieldToBytes(vk.qo_x); cm_bytes = cm_bytes.concat(qox); const qoy = provableBn254BaseFieldToBytes(vk.qo_y); cm_bytes = cm_bytes.concat(qoy); const qkx = provableBn254BaseFieldToBytes(vk.qk_x); cm_bytes = cm_bytes.concat(qkx); const qky = provableBn254BaseFieldToBytes(vk.qk_y); cm_bytes = cm_bytes.concat(qky); const qcp_0_x = provableBn254BaseFieldToBytes(vk.qcp_0_x); cm_bytes = cm_bytes.concat(qcp_0_x); const qcp_0_y = provableBn254BaseFieldToBytes(vk.qcp_0_y); cm_bytes = cm_bytes.concat(qcp_0_y); // SP1 v6: 5 public inputs (was 2 in SP1 v5) cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(pi0F)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(pi1F)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(pi2F)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(pi3F)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(pi4F)); // there is one gate, so we have just 1 [l, r, o] const lx = provableBn254BaseFieldToBytes(proof.l_com_x); cm_bytes = cm_bytes.concat(lx); const ly = provableBn254BaseFieldToBytes(proof.l_com_y); cm_bytes = cm_bytes.concat(ly); const rx = provableBn254BaseFieldToBytes(proof.r_com_x); cm_bytes = cm_bytes.concat(rx); const ry = provableBn254BaseFieldToBytes(proof.r_com_y); cm_bytes = cm_bytes.concat(ry); const ox = provableBn254BaseFieldToBytes(proof.o_com_x); cm_bytes = cm_bytes.concat(ox); const oy = provableBn254BaseFieldToBytes(proof.o_com_y); cm_bytes = cm_bytes.concat(oy); // assert(cm_bytes.length === gammaSizeInBytes()); this.gamma_digest = Hash.SHA2_256.hash(new BytesGamma(cm_bytes)); this.gamma = shaToFr(this.gamma_digest); } squeezeBeta() { const beta_separator = FpC.from(0x62657461n); let separator_bytes = provableBn254BaseFieldToBytes(beta_separator); // beta is 32 bits and we cut the rest (256 - 32) bits which is 28 bytes let cm_bytes = separator_bytes.slice(28, 32); cm_bytes = cm_bytes.concat(this.gamma_digest.bytes); // assert(cm_bytes.length === sizeBetaBytes()) this.beta_digest = Hash.SHA2_256.hash(new BytesBeta(cm_bytes)); this.beta = shaToFr(this.beta_digest); } squeezeAlpha(proof) { const alpha_separator = FpC.from(0x616c706861n); let separator_bytes = provableBn254BaseFieldToBytes(alpha_separator); // alpha is 39 bits, so we leave only 40 bits (to keep it multiple of 8) // and we cut the rest (256 - 40) bits which is 27 bytes let cm_bytes = separator_bytes.slice(27, 32); cm_bytes = cm_bytes.concat(this.beta_digest.bytes); const qcp_0_x = provableBn254BaseFieldToBytes(proof.qcp_0_wire_x); cm_bytes = cm_bytes.concat(qcp_0_x); const qcp_0_y = provableBn254BaseFieldToBytes(proof.qcp_0_wire_y); cm_bytes = cm_bytes.concat(qcp_0_y); const grand_product_x = provableBn254BaseFieldToBytes(proof.grand_product_x); cm_bytes = cm_bytes.concat(grand_product_x); const grand_product_y = provableBn254BaseFieldToBytes(proof.grand_product_y); cm_bytes = cm_bytes.concat(grand_product_y); this.alpha_digest = Hash.SHA2_256.hash(new BytesAlpha(cm_bytes)); this.alpha = shaToFr(this.alpha_digest); } squeezeZeta(proof) { const zeta_separator = FpC.from(0x7a657461n); let separator_bytes = provableBn254BaseFieldToBytes(zeta_separator); // zeta is 31 bits, so we leave only 32 bits (to keep it multiple of 8) // and we cut the rest (256 - 32) bits which is 28 bytes let cm_bytes = separator_bytes.slice(28, 32); cm_bytes = cm_bytes.concat(this.alpha_digest.bytes); const h0_x = provableBn254BaseFieldToBytes(proof.h0_x); cm_bytes = cm_bytes.concat(h0_x); const h0_y = provableBn254BaseFieldToBytes(proof.h0_y); cm_bytes = cm_bytes.concat(h0_y); const h1_x = provableBn254BaseFieldToBytes(proof.h1_x); cm_bytes = cm_bytes.concat(h1_x); const h1_y = provableBn254BaseFieldToBytes(proof.h1_y); cm_bytes = cm_bytes.concat(h1_y); const h2_x = provableBn254BaseFieldToBytes(proof.h2_x); cm_bytes = cm_bytes.concat(h2_x); const h2_y = provableBn254BaseFieldToBytes(proof.h2_y); cm_bytes = cm_bytes.concat(h2_y); this.zeta_digest = Hash.SHA2_256.hash(new BytesZeta(cm_bytes)); this.zeta = shaToFr(this.zeta_digest); } squeezeGammaKzg(proof, vk, linearized_cm_x, linearized_cm_y, linearized_opening) { const gamma_separator = FpC.from(0x67616d6d61n); let separator_bytes = provableBn254BaseFieldToBytes(gamma_separator); // gamma is 39 bits, so we leave only 40 bits (to keep it multiple of 8) // and we cut the rest (256 - 40) bits which is 27 bytes let cm_bytes = separator_bytes.slice(27, 32); // note that here they compute challenge from zeta_reduced and not unreduced as before cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(this.zeta)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(linearized_cm_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(linearized_cm_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.l_com_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.l_com_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.r_com_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.r_com_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.o_com_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.o_com_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs1_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs1_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs2_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs2_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qcp_0_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qcp_0_y)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(linearized_opening)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.l_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.r_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.o_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.s1_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.s2_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.qcp_0_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.grand_product_at_omega_zeta)); this.gamma_kzg_digest = Hash.SHA2_256.hash(new BytesGammaKzg(cm_bytes)); this.gamma_kzg = shaToFr(this.gamma_kzg_digest); } squeezeRandomForKzg(proof, cm_x, cm_y) { let cm_bytes = provableBn254BaseFieldToBytes(cm_x); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(cm_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.batch_opening_at_zeta_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.batch_opening_at_zeta_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.grand_product_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.grand_product_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.batch_opening_at_zeta_omega_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.batch_opening_at_zeta_omega_y)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(this.zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(this.gamma_kzg)); const random_digest = Hash.SHA2_256.hash(new BytesRandomKzg(cm_bytes)); return shaToFr(random_digest); } // just delay digest to fr // TODO: make this a generic function for all challenges gammaKzgDigest_part0(proof, vk, linearized_cm_x, linearized_cm_y, linearized_opening) { const gamma_separator = FpC.from(0x67616d6d61n); let separator_bytes = provableBn254BaseFieldToBytes(gamma_separator); // gamma is 39 bits, so we leave only 40 bits (to keep it multiple of 8) // and we cut the rest (256 - 40) bits which is 27 bytes let cm_bytes = separator_bytes.slice(27, 32); // note that here they compute challenge from zeta_reduced and not unreduced as before cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(this.zeta)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(linearized_cm_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(linearized_cm_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.l_com_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.l_com_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.r_com_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.r_com_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.o_com_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(proof.o_com_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs1_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs1_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs2_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qs2_y)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qcp_0_x)); cm_bytes = cm_bytes.concat(provableBn254BaseFieldToBytes(vk.qcp_0_y)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(linearized_opening)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.l_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.r_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.o_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.s1_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.s2_at_zeta)); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.qcp_0_at_zeta).slice(0, 27)); const chunks = []; for (let i = 0; i < cm_bytes.length; i += 4) { const chunk = UInt32.Unsafe.fromField(bytesToWord(cm_bytes.slice(i, i + 4).reverse())); chunks.push(chunk); } let H = Gadgets.SHA256.initialState; for (let i = 0; i < 11; i++) { const messageBlock = chunks.slice(16 * i, 16 * (i + 1)); let W = Gadgets.SHA256.createMessageSchedule(messageBlock); H = Gadgets.SHA256.compression(H, W); } return H; // this.gamma_kzg_digest = Hash.SHA2_256.hash(new BytesGammaKzg(cm_bytes)); } gammaKzgDigest_part1(proof, H) { let cm_bytes = provableBn254ScalarFieldToBytes(proof.qcp_0_at_zeta).slice(27, 32); cm_bytes = cm_bytes.concat(provableBn254ScalarFieldToBytes(proof.grand_product_at_omega_zeta)); cm_bytes = cm_bytes.concat(Bytes741Padding); const chunks = []; for (let i = 0; i < cm_bytes.length; i += 4) { const chunk = UInt32.Unsafe.fromField(bytesToWord(cm_bytes.slice(i, i + 4).reverse())); chunks.push(chunk); } const messageBlock = chunks; let W = Gadgets.SHA256.createMessageSchedule(messageBlock); H = Gadgets.SHA256.compression(H, W); this.gamma_kzg_digest = Bytes.from(H.map((x) => wordToBytes(x.value, 4).reverse()).flat()); } squeezeGammaKzgFromDigest() { this.gamma_kzg = shaToFr(this.gamma_kzg_digest); } } export { Sp1PlonkFiatShamir }; //# sourceMappingURL=index.js.map