From 72419be4b935a439eb4da6b572119658295aaf7b Mon Sep 17 00:00:00 2001 From: samdelaney Date: Sun, 11 Jan 2026 17:22:14 -0800 Subject: [PATCH 01/25] preinputs.ak --- zkp/lib/common/common.ak | 5 +++- zkp/lib/plonk/preinputs.ak | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 zkp/lib/plonk/preinputs.ak diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index d3b2ea8..2df5841 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -6,6 +6,9 @@ use aiken/primitive/bytearray pub const bls12_381_prime = 52435875175126190479447740508185965837690552500527637822603658699938581184513 +pub const bls12_381_base_prime = + 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 + // Additional curve constants pub const bls12_381_r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 @@ -131,7 +134,7 @@ pub fn mod_sqrt(a: Field) -> Option { } } -fn mod_pow(base: Field, exp: Field) -> Field { +pub fn mod_pow(base: Field, exp: Field) -> Field { if exp == 0 { 1 } else { diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak new file mode 100644 index 0000000..61278ee --- /dev/null +++ b/zkp/lib/plonk/preinputs.ak @@ -0,0 +1,57 @@ +use aiken/builtin +use aiken/collection/list +use common/common.{Field, mod_pow} + +pub type PlonkPreInputs { + n_public: Int, + power: Int, + k1: Field, + k2: Field, + q_m: ByteArray, + q_l: ByteArray, + q_r: ByteArray, + q_o: ByteArray, + q_c: ByteArray, + s_sig1: ByteArray, + s_sig2: ByteArray, + s_sig3: ByteArray, + x2: ByteArray, + generator: Field, +} + +pub type PlonkPreInputsFast { + n: Int, + power: Int, + k1: Field, + k2: Field, + q_m: ByteArray, + q_l: ByteArray, + q_r: ByteArray, + q_o: ByteArray, + q_c: ByteArray, + s_sig1: ByteArray, + s_sig2: ByteArray, + s_sig3: ByteArray, + x2: ByteArray, + generators: List, +} + +pub fn convert_to_fast_pre_inputs(pre_inputs: PlonkPreInputs) -> PlonkPreInputsFast { + let ppif = PlonkPreInputsFast { + n: mod_pow(2, pre_inputs.power), + power: pre_inputs.power, + k1: pre_inputs.k1, + k2: pre_inputs.k2, + q_m: pre_inputs.q_m, + q_l: pre_inputs.q_l, + q_r: pre_inputs.q_r, + q_o: pre_inputs.q_o, + q_c: pre_inputs.q_c, + s_sig1: pre_inputs.s_sig1, + s_sig2: pre_inputs.s_sig2, + s_sig3: pre_inputs.s_sig3, + x2: pre_inputs.x2, + generators: list.map(list.range(0, pre_inputs.n_public), fn(n) { mod_pow(pre_inputs.generator, n) }), + } + ppif +} \ No newline at end of file From 96a86c4dc15ccf1a1115df1ae6b1130bb6fc855a Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 12 Jan 2026 08:37:33 -0800 Subject: [PATCH 02/25] plonk proof preparation --- zkp/lib/common/common.ak | 4 +++ zkp/lib/plonk/preinputs.ak | 40 ++++----------------- zkp/lib/plonk/proofs.ak | 74 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 zkp/lib/plonk/proofs.ak diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index 2df5841..f300c9e 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -58,6 +58,10 @@ pub type VerificationError { } // Common utility functions for field operations +pub fn make_scalar(a: Int) -> Field { + a % bls12_381_prime +} + pub fn mod_add(a: Field, b: Field) -> Field { ( a + b ) % bls12_381_prime } diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 61278ee..67ba14c 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -1,9 +1,7 @@ -use aiken/builtin use aiken/collection/list use common/common.{Field, mod_pow} pub type PlonkPreInputs { - n_public: Int, power: Int, k1: Field, k2: Field, @@ -16,42 +14,18 @@ pub type PlonkPreInputs { s_sig2: ByteArray, s_sig3: ByteArray, x2: ByteArray, - generator: Field, } -pub type PlonkPreInputsFast { +pub type PreparedPlonkPreInputs { n: Int, - power: Int, - k1: Field, - k2: Field, - q_m: ByteArray, - q_l: ByteArray, - q_r: ByteArray, - q_o: ByteArray, - q_c: ByteArray, - s_sig1: ByteArray, - s_sig2: ByteArray, - s_sig3: ByteArray, - x2: ByteArray, generators: List, + inputs: PlonkPreInputs, } -pub fn convert_to_fast_pre_inputs(pre_inputs: PlonkPreInputs) -> PlonkPreInputsFast { - let ppif = PlonkPreInputsFast { - n: mod_pow(2, pre_inputs.power), - power: pre_inputs.power, - k1: pre_inputs.k1, - k2: pre_inputs.k2, - q_m: pre_inputs.q_m, - q_l: pre_inputs.q_l, - q_r: pre_inputs.q_r, - q_o: pre_inputs.q_o, - q_c: pre_inputs.q_c, - s_sig1: pre_inputs.s_sig1, - s_sig2: pre_inputs.s_sig2, - s_sig3: pre_inputs.s_sig3, - x2: pre_inputs.x2, - generators: list.map(list.range(0, pre_inputs.n_public), fn(n) { mod_pow(pre_inputs.generator, n) }), +pub fn plonk_prepare_preinputs(inputs: PlonkPreInputs, generator: Field, n_public: Int) -> PreparedPlonkPreInputs { + PreparedPlonkPreInputs { + n: mod_pow(2, inputs.power), + generators: list.map(list.range(0, n_public), fn(n) { mod_pow(generator, n) }), + inputs: inputs, } - ppif } \ No newline at end of file diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak new file mode 100644 index 0000000..5a4bbc9 --- /dev/null +++ b/zkp/lib/plonk/proofs.ak @@ -0,0 +1,74 @@ +use aiken/builtin.{blake2b_224} +use common/common.{Field, bls12_381_prime, make_scalar, mod_inv} +use aiken/collection/list +use aiken/primitive/bytearray +use plonk/preinputs.{PreparedPlonkPreInputs} + +pub type PlonkProof { + commitment_a: ByteArray, + commitment_b: ByteArray, + commitment_c: ByteArray, + commitment_z: ByteArray, + t_low: ByteArray, + t_mid: ByteArray, + t_high: ByteArray, + w_omega: ByteArray, + w_omega_zeta: ByteArray, + a_eval: Field, + b_eval: Field, + c_eval: Field, + s_sig1_p: Field, + s_sig2_p: Field, + z_omega: Field, +} + +pub type PreparedPlonkProof { + proof: PlonkProof, + lagrange_inverses: List, +} + +pub fn plonk_prepare_proof(preinputs: PreparedPlonkPreInputs, pub_inputs: List, proof: PlonkProof) -> PreparedPlonkProof { + let beta_inputs = [ + preinputs.inputs.q_m, + preinputs.inputs.q_l, + preinputs.inputs.q_r, + preinputs.inputs.q_o, + preinputs.inputs.q_c, + preinputs.inputs.s_sig1, + preinputs.inputs.s_sig2, + preinputs.inputs.s_sig3, + list.foldl(pub_inputs, #"", fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }), + proof.commitment_a, + proof.commitment_b, + proof.commitment_c + ] + let beta_bs = blake2b_224( + list.foldl(beta_inputs, #"", bytearray.concat) + ) + let beta = make_scalar(bytearray.to_int_big_endian(beta_bs) % bls12_381_prime) + let gamma_bs = blake2b_224(bytearray.from_int_big_endian(beta, 32)) + let gamma = make_scalar(bytearray.to_int_big_endian(gamma_bs) % bls12_381_prime) + let alpha_bs = blake2b_224(list.foldl([ + bytearray.from_int_big_endian(beta, 32), + bytearray.from_int_big_endian(gamma, 32), + proof.commitment_z + ], #"", bytearray.concat)) + let alpha = make_scalar(bytearray.to_int_big_endian(alpha_bs) % bls12_381_prime) + let zeta_bs = blake2b_224(list.foldl([ + bytearray.from_int_big_endian(alpha, 32), + proof.t_low, + proof.t_mid, + proof.t_high + ], #"", bytearray.concat)) + let zeta = make_scalar(bytearray.to_int_big_endian(zeta_bs) % bls12_381_prime) + let lagrange_inverses = list.map(preinputs.generators, fn(x) { + let nzx = make_scalar(preinputs.n) * (zeta - x) + expect Some(inv) = mod_inv(nzx, bls12_381_prime) + inv + }) + + PreparedPlonkProof { + proof, + lagrange_inverses, + } +} \ No newline at end of file From c073320202ee650add347e441ab93e540cb2a295 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Sun, 18 Jan 2026 14:17:37 -0800 Subject: [PATCH 03/25] Scalar -> Field cleanup --- zkp/aiken.lock | 4 ++-- zkp/aiken.toml | 4 ++-- zkp/lib/common/common.ak | 15 +++++++++++++ zkp/lib/plonk/plonk.ak | 20 ++++++++--------- zkp/lib/plonk/preinputs.ak | 15 +++++++------ zkp/lib/plonk/proofs.ak | 46 +++++++++++++++++--------------------- 6 files changed, 57 insertions(+), 47 deletions(-) diff --git a/zkp/aiken.lock b/zkp/aiken.lock index 21e7fb4..37b9b2e 100644 --- a/zkp/aiken.lock +++ b/zkp/aiken.lock @@ -3,12 +3,12 @@ [[requirements]] name = "aiken-lang/stdlib" -version = "v2.1.0" +version = "3.0.0" source = "github" [[packages]] name = "aiken-lang/stdlib" -version = "v2.1.0" +version = "3.0.0" requirements = [] source = "github" diff --git a/zkp/aiken.toml b/zkp/aiken.toml index f1ce231..d7cf62b 100644 --- a/zkp/aiken.toml +++ b/zkp/aiken.toml @@ -1,6 +1,6 @@ name = "adao/zkp" version = "0.0.0" -compiler = "v1.1.19" +compiler = "v1.1.21" plutus = "v3" license = "Apache-2.0" description = "Aiken contracts for project 'adao/zkp'" @@ -12,7 +12,7 @@ platform = "github" [[dependencies]] name = "aiken-lang/stdlib" -version = "v2.1.0" +version = "3.0.0" source = "github" [config] diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index f300c9e..0119fde 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -1,6 +1,8 @@ use aiken/collection/list use aiken/crypto use aiken/primitive/bytearray +use aiken/builtin.{blake2b_224} +use aiken/crypto/bls12_381/scalar // Constants for BLS12-381 pub const bls12_381_prime = @@ -160,6 +162,19 @@ fn mod_pow_loop(b, e, result) { } } +// Blake2b-224 utilities + +pub fn concat_and_blake2b_224(inputs: List) -> ByteArray { + let concatenated = list.foldl(inputs, #"", bytearray.concat) + blake2b_224(concatenated) +} + +// Scalar utilities + +pub fn scalar_bs_rt(bs: ByteArray) -> ByteArray { + scalar.to_bytes(scalar.from_bytes(bs)) +} + // Point operations (expanding on existing ones) pub fn g1_generator() -> G1Point { // BLS12-381 G1 generator coordinates diff --git a/zkp/lib/plonk/plonk.ak b/zkp/lib/plonk/plonk.ak index 3a11a69..c5bf309 100644 --- a/zkp/lib/plonk/plonk.ak +++ b/zkp/lib/plonk/plonk.ak @@ -339,19 +339,19 @@ pub fn compute_challenges( // Compute beta and gamma challenges let ts2 = ts1 // Convert the transcript hash to a scalar field element - expect Some(beta) = - scalar.from_bytearray_big_endian( + let beta = + scalar.from_bytes( generate_transcript(bytearray.concat(ts2, #[1])), ) - expect Some(gamma) = - scalar.from_bytearray_big_endian( + let gamma = + scalar.from_bytes( generate_transcript(bytearray.concat(ts2, #[2])), ) // Compute alpha challenge let ts3 = bytearray.concat(ts2, proof.z) - expect Some(alpha) = - scalar.from_bytearray_big_endian(generate_transcript(ts3)) + let alpha = + scalar.from_bytes(generate_transcript(ts3)) // Compute zeta challenge let ts4 = @@ -359,14 +359,14 @@ pub fn compute_challenges( ts3, bytearray.concat(proof.t1, bytearray.concat(proof.t2, proof.t3)), ) - expect Some(zeta) = scalar.from_bytearray_big_endian(generate_transcript(ts4)) + let zeta = scalar.from_bytes(generate_transcript(ts4)) // Compute v and u challenges let ts5 = - bytearray.concat(ts4, scalar.to_bytearray_big_endian(proof.eval_a, 32)) - expect Some(v) = scalar.from_bytearray_big_endian(generate_transcript(ts5)) + bytearray.concat(ts4, scalar.to_bytes(proof.eval_a)) + let v = scalar.from_bytes(generate_transcript(ts5)) expect Some(u) = - scalar.from_bytearray_big_endian( + scalar.from_bytes( generate_transcript(bytearray.concat(ts5, #[1])), ) diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 67ba14c..83fc680 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -1,10 +1,11 @@ use aiken/collection/list -use common/common.{Field, mod_pow} +use aiken/crypto/bls12_381/scalar.{Scalar, scale} +use aiken/crypto/bitwise.{State} pub type PlonkPreInputs { power: Int, - k1: Field, - k2: Field, + k1: Scalar, + k2: Scalar, q_m: ByteArray, q_l: ByteArray, q_r: ByteArray, @@ -18,14 +19,14 @@ pub type PlonkPreInputs { pub type PreparedPlonkPreInputs { n: Int, - generators: List, + generators: List>, inputs: PlonkPreInputs, } -pub fn plonk_prepare_preinputs(inputs: PlonkPreInputs, generator: Field, n_public: Int) -> PreparedPlonkPreInputs { +pub fn plonk_prepare_preinputs(inputs: PlonkPreInputs, generator: State, n_public: Int) -> PreparedPlonkPreInputs { PreparedPlonkPreInputs { - n: mod_pow(2, inputs.power), - generators: list.map(list.range(0, n_public), fn(n) { mod_pow(generator, n) }), + n: scalar.to_int(scale(scalar.from_int(2), inputs.power)), + generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), inputs: inputs, } } \ No newline at end of file diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak index 5a4bbc9..ad0bce5 100644 --- a/zkp/lib/plonk/proofs.ak +++ b/zkp/lib/plonk/proofs.ak @@ -1,8 +1,10 @@ use aiken/builtin.{blake2b_224} -use common/common.{Field, bls12_381_prime, make_scalar, mod_inv} +use common/common.{scalar_bs_rt, concat_and_blake2b_224} use aiken/collection/list use aiken/primitive/bytearray use plonk/preinputs.{PreparedPlonkPreInputs} +use aiken/crypto/bls12_381/scalar.{Scalar} +use aiken/crypto/bitwise.{State} pub type PlonkProof { commitment_a: ByteArray, @@ -14,17 +16,17 @@ pub type PlonkProof { t_high: ByteArray, w_omega: ByteArray, w_omega_zeta: ByteArray, - a_eval: Field, - b_eval: Field, - c_eval: Field, - s_sig1_p: Field, - s_sig2_p: Field, - z_omega: Field, + a_eval: Int, + b_eval: Int, + c_eval: Int, + s_sig1_p: Int, + s_sig2_p: Int, + z_omega: Int, } pub type PreparedPlonkProof { proof: PlonkProof, - lagrange_inverses: List, + lagrange_inverses: List>, } pub fn plonk_prepare_proof(preinputs: PreparedPlonkPreInputs, pub_inputs: List, proof: PlonkProof) -> PreparedPlonkProof { @@ -42,29 +44,21 @@ pub fn plonk_prepare_proof(preinputs: PreparedPlonkPreInputs, pub_inputs: List Date: Fri, 23 Jan 2026 15:23:18 -0800 Subject: [PATCH 04/25] PLONK implementation --- zkp/lib/common/common.ak | 14 +- zkp/lib/plonk/plonk.ak | 374 ------------------------------------- zkp/lib/plonk/preinputs.ak | 4 +- zkp/lib/plonk/verifier.ak | 238 +++++++++++++++++++++++ 4 files changed, 252 insertions(+), 378 deletions(-) delete mode 100644 zkp/lib/plonk/plonk.ak create mode 100644 zkp/lib/plonk/verifier.ak diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index 0119fde..11b60e6 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -2,7 +2,9 @@ use aiken/collection/list use aiken/crypto use aiken/primitive/bytearray use aiken/builtin.{blake2b_224} -use aiken/crypto/bls12_381/scalar +use aiken/crypto/bls12_381/scalar.{Scalar} +use aiken/crypto/bitwise.{State} +use aiken/crypto/bls12_381/g1 // Constants for BLS12-381 pub const bls12_381_prime = @@ -175,7 +177,15 @@ pub fn scalar_bs_rt(bs: ByteArray) -> ByteArray { scalar.to_bytes(scalar.from_bytes(bs)) } -// Point operations (expanding on existing ones) +pub fn scalar_sum(scalars: List>) -> State { + list.foldl(scalars, scalar.from_int(0), fn(acc, x) { scalar.add(acc, x) }) +} + +// G1 operations (expanding on existing ones) +pub fn g1_sum(points: List) -> G1Element { + list.foldl(points, g1.zero, g1.add) +} + pub fn g1_generator() -> G1Point { // BLS12-381 G1 generator coordinates Point { diff --git a/zkp/lib/plonk/plonk.ak b/zkp/lib/plonk/plonk.ak deleted file mode 100644 index c5bf309..0000000 --- a/zkp/lib/plonk/plonk.ak +++ /dev/null @@ -1,374 +0,0 @@ -use aiken/crypto -use aiken/crypto/bls12_381/g1 -use aiken/crypto/bls12_381/scalar -use aiken/primitive/bytearray -use common/common - -// PLONK specific types -pub type PlonkVerificationKey { - n: Int, - // Size of the circuit - q_m: List, - // Multiplication selector polynomial - q_l: List, - // Left selector polynomial - q_r: List, - // Right selector polynomial - q_o: List, - // Output selector polynomial - q_c: List, - // Constants selector polynomial - sigma1: List, - sigma2: List, - sigma3: List, - s1: ByteArray, - s2: ByteArray, - s3: ByteArray, -} - -pub type PlonkProof { - // Commitments - a: ByteArray, - b: ByteArray, - c: ByteArray, - z: ByteArray, - t1: ByteArray, - t2: ByteArray, - t3: ByteArray, - // Evaluations - eval_a: scalar.Scalar, - eval_b: scalar.Scalar, - eval_c: scalar.Scalar, - eval_s1: scalar.Scalar, - eval_s2: scalar.Scalar, - eval_z_omega: scalar.Scalar, -} - -pub type BoolOrPlonkError { - BPB(Bool) - BPP(PlonkError) -} - -pub type BoolOrVerificationError { - BVB(Bool) - BVV(common.VerificationError) -} - -pub type G1PointOrPlonkError { - G(ByteArray) - P(PlonkError) -} - -pub type PlonkError { - InvalidProofFormat - InvalidVerificationKey - PairingCheckFailed - PolynomialCommitmentError - InvalidPublicInput - BatchOpeningFailed - InvalidProofElements -} - -// Additional PLONK specific types -pub type PlonkChallenges { - beta: scalar.Scalar, - gamma: scalar.Scalar, - alpha: scalar.Scalar, - zeta: scalar.Scalar, - v: scalar.Scalar, - u: scalar.Scalar, -} - -pub type PlonkEvaluations { - a: scalar.Scalar, - b: scalar.Scalar, - c: scalar.Scalar, - s1: scalar.Scalar, - s2: scalar.Scalar, - z_omega: scalar.Scalar, -} - -// Main verification function -pub fn verify( - vk: PlonkVerificationKey, - proof: PlonkProof, - public_inputs: List, -) -> BoolOrPlonkError { - // 1. Compute challenges from transcript - let challenges = compute_challenges(vk, proof, public_inputs) - - // 2. Compute public input polynomial evaluation - let pi = compute_pi(public_inputs, challenges) - - // 3. Verify polynomial commitments - when verify_polynomial_commitments(vk, proof, challenges) is { - BPP(_err) -> BPP(PolynomialCommitmentError) - BPB(valid) -> - if !valid { - BPP(PolynomialCommitmentError) - } else { - // 4. Verify batch opening proof - when verify_batch_opening(vk, proof, challenges, pi) is { - BPP(_err) -> BPP(PairingCheckFailed) - BPB(valid) -> - if valid { - BPB(True) - } else { - BPP(PairingCheckFailed) - } - } - } - } -} - -/// Verify the polynomial commitments -pub fn verify_polynomial_commitments( - vk: PlonkVerificationKey, - proof: PlonkProof, - challenges: PlonkChallenges, -) -> BoolOrPlonkError { - // First, validate that proof elements are valid curve points - when validate_proof_elements(proof) is { - BVB(True) -> - // Verify the permutation argument - verify_permutation_argument(vk, proof, challenges) - BVB(False) -> BPP(InvalidProofElements) - BVV(_) -> BPP(InvalidProofElements) - } -} - -pub fn compute_linearization_polynomial( - _vk: PlonkVerificationKey, - proof: PlonkProof, - _challenge_beta: scalar.Scalar, - _challenge_gamma: scalar.Scalar, -) -> G1PointOrPlonkError { - // Extract the commitments from the proof - let a_commitment = proof.a - // Compute the linearization polynomial - // This is a placeholder implementation - G(a_commitment) -} - -/// Verify the permutation argument -pub fn verify_permutation_argument( - _vk: PlonkVerificationKey, - proof: PlonkProof, - challenges: PlonkChallenges, -) -> BoolOrPlonkError { - // Verify that z(ω·X) has the correct relationship with z(X) - // This checks that z(X) is a valid permutation polynomial - // 1. Compute the permutation challenges - let beta = challenges.beta - let gamma = challenges.gamma - // 2. Check that z(ω·X) = z(X) · (a(X) + β·σ1(X) + γ) · (b(X) + β·σ2(X) + γ) · (c(X) + β·σ3(X) + γ) / ((a(X) + β·id1(X) + γ) · (b(X) + β·id2(X) + γ) · (c(X) + β·id3(X) + γ)) - // For now, we'll implement a simplified check that just verifies that z(ω·X) is related to z(X) - // A complete implementation would verify the full permutation argument - // Check that eval_z_omega is consistent with the permutation argument - let expected_z_omega = - scalar.mul( - proof.eval_z_omega, - scalar.add( - scalar.add( - scalar.mul(proof.eval_a, beta), - scalar.mul(proof.eval_s1, gamma), - ), - scalar.one, - ), - ) - // Compare with a simplified expected value - // In a complete implementation, this would be a more complex check - if expected_z_omega == scalar.mul(proof.eval_z_omega, beta) { - BPB(True) - } else { - BPP(PolynomialCommitmentError) - } -} - -/// Compute the linearization polynomial -pub fn compute_linearization( - proof: PlonkProof, - challenges: PlonkChallenges, -) -> ByteArray { - // 1. Compute quotient terms - let l1 = - scalar.mul( - challenges.alpha, - scalar.add( - scalar.mul(proof.eval_a, proof.eval_b), - scalar.mul(proof.eval_c, challenges.gamma), - ), - ) - - // 2. Compute permutation terms - // Compute z_h = zeta^2 - 1 - let z_h = scalar.sub(scalar.mul(challenges.zeta, challenges.zeta), scalar.one) - - let perm = - scalar.mul( - proof.eval_z_omega, - scalar.add( - scalar.add( - scalar.mul(proof.eval_a, challenges.beta), - scalar.mul(proof.eval_s1, challenges.gamma), - ), - scalar.one, - ), - ) - // 3. Compute the linearization polynomial commitment - // Combine the terms using the challenges - let l_scalar = scalar.add(scalar.add(l1, z_h), perm) - // Scale the proof.a commitment by the computed scalar - // This is a simplified version - a complete implementation would combine multiple commitments - g1.compress(g1.scale(g1.decompress(proof.a), l_scalar)) -} - -/// Compute public input polynomial -fn compute_pi( - public_inputs: List, - challenges: PlonkChallenges, -) -> scalar.Scalar { - when public_inputs is { - [] -> scalar.zero - [input, ..rest] -> { - let pi = scalar.mul(input, challenges.zeta) - when rest is { - [] -> pi - [input2, ..rest2] -> { - let pi2 = - scalar.mul(input2, scalar.mul(challenges.zeta, challenges.zeta)) - when rest2 is { - [] -> scalar.add(pi, pi2) - [input3, ..] -> { - let pi3 = - scalar.mul( - input3, - scalar.mul( - scalar.mul(challenges.zeta, challenges.zeta), - challenges.zeta, - ), - ) - scalar.add(scalar.add(pi, pi2), pi3) - } - } - } - } - } - } -} - -/// Verify batch opening proof -fn verify_batch_opening( - _vk: PlonkVerificationKey, - proof: PlonkProof, - challenges: PlonkChallenges, - pi: scalar.Scalar, -) -> BoolOrPlonkError { - // 1. Compute opening challenge - let u = challenges.u - // 2. Combine all polynomial evaluations with the public input - // Calculate the combined evaluation that will be used for verification - expect _eval_combined = - scalar.add( - scalar.add( - scalar.add( - scalar.mul(proof.eval_a, u), - scalar.mul(proof.eval_b, scalar.mul(u, u)), - ), - scalar.add( - scalar.mul(proof.eval_c, scalar.mul(scalar.mul(u, u), u)), - scalar.mul( - proof.eval_s1, - scalar.mul(scalar.mul(scalar.mul(u, u), u), u), - ), - ), - ), - pi, - ) - // 3. Combine all commitments - let a_point = g1.decompress(proof.a) - let b_point = g1.decompress(proof.b) - let c_point = g1.decompress(proof.c) - let z_point = g1.decompress(proof.z) - let commit_combined_point = - g1.add( - g1.add( - g1.add(g1.scale(a_point, u), g1.scale(b_point, scalar.mul(u, u))), - g1.add( - g1.scale(c_point, scalar.mul(scalar.mul(u, u), u)), - g1.scale(z_point, scalar.mul(scalar.mul(scalar.mul(u, u), u), u)), - ), - ), - // Add linearization polynomial commitment - g1.decompress(compute_linearization(proof, challenges)), - ) - // 4. Perform a simplified pairing check - // In a complete implementation, this would use the full pairing check - // For now, we'll just check that the commitments are consistent - // Scale the commitments by the challenge - let scaled_commit_point = g1.scale(commit_combined_point, challenges.v) - // Check if the scaled commitments are equal - if g1.equal(scaled_commit_point, commit_combined_point) { - BPB(True) - } else { - BPP(BatchOpeningFailed) - } -} - -/// Generate transcript for Fiat-Shamir -fn generate_transcript(data: ByteArray) -> ByteArray { - crypto.blake2b_256(data) -} - -// Helper function to validate proof elements -fn validate_proof_elements(_proof: PlonkProof) -> BoolOrVerificationError { - // For now, we'll assume all proof elements are valid - // In a real implementation, we would check that they are valid G1 points - BVB(True) -} - -/// Compute challenges from transcript -pub fn compute_challenges( - _vk: PlonkVerificationKey, - proof: PlonkProof, - _public_inputs: List, -) -> PlonkChallenges { - // Generate transcript for Fiat-Shamir - let ts1 = bytearray.concat(proof.a, bytearray.concat(proof.b, proof.c)) - - // Compute beta and gamma challenges - let ts2 = ts1 - // Convert the transcript hash to a scalar field element - let beta = - scalar.from_bytes( - generate_transcript(bytearray.concat(ts2, #[1])), - ) - let gamma = - scalar.from_bytes( - generate_transcript(bytearray.concat(ts2, #[2])), - ) - - // Compute alpha challenge - let ts3 = bytearray.concat(ts2, proof.z) - let alpha = - scalar.from_bytes(generate_transcript(ts3)) - - // Compute zeta challenge - let ts4 = - bytearray.concat( - ts3, - bytearray.concat(proof.t1, bytearray.concat(proof.t2, proof.t3)), - ) - let zeta = scalar.from_bytes(generate_transcript(ts4)) - - // Compute v and u challenges - let ts5 = - bytearray.concat(ts4, scalar.to_bytes(proof.eval_a)) - let v = scalar.from_bytes(generate_transcript(ts5)) - expect Some(u) = - scalar.from_bytes( - generate_transcript(bytearray.concat(ts5, #[1])), - ) - - PlonkChallenges { beta, gamma, alpha, zeta, v, u } -} diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 83fc680..472fe0e 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -4,8 +4,8 @@ use aiken/crypto/bitwise.{State} pub type PlonkPreInputs { power: Int, - k1: Scalar, - k2: Scalar, + k1: State, + k2: State, q_m: ByteArray, q_l: ByteArray, q_r: ByteArray, diff --git a/zkp/lib/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak new file mode 100644 index 0000000..2c53bac --- /dev/null +++ b/zkp/lib/plonk/verifier.ak @@ -0,0 +1,238 @@ +use plonk/proofs.{PreparedPlonkProof, PlonkProof} +use plonk/preinputs.{PreparedPlonkPreInputs, PlonkPreInputs} +use common/common.{concat_and_blake2b_224, bls12_381_prime, g1_sum, scalar_sum} +use aiken/collection/list +use aiken/crypto/bls12_381/g1 +use aiken/crypto/bls12_381/g2 +use aiken/crypto/bls12_381/scalar.{Scalar, scale2} +use aiken/crypto/bls12_381/pairing.{miller_loop, final_exponentiation} +use aiken/primitive/bytearray +use aiken/builtin.{blake2b_224} +use aiken/crypto/bitwise.{State} + +pub fn verify_plonk(preinputs: PreparedPlonkPreInputs, pub_inputs: List, preproof: PreparedPlonkProof) -> Bool { + let proof: PlonkProof = preproof.proof + let inputs: PlonkPreInputs = preinputs.inputs + + // uncompress the inputs + let q_m: G1Element = g1.decompress(inputs.q_m) + let q_l: G1Element = g1.decompress(inputs.q_l) + let q_r: G1Element = g1.decompress(inputs.q_r) + let q_o: G1Element = g1.decompress(inputs.q_o) + let q_c: G1Element = g1.decompress(inputs.q_c) + let s_sig1: G1Element = g1.decompress(inputs.s_sig1) + let s_sig2: G1Element = g1.decompress(inputs.s_sig2) + let s_sig3: G1Element = g1.decompress(inputs.s_sig3) + let x2: G2Element = g2.decompress(inputs.x2) + + // uncompress the proof commitments + let comm_a: G1Element = g1.decompress(proof.commitment_a) + let comm_b: G1Element = g1.decompress(proof.commitment_b) + let comm_c: G1Element = g1.decompress(proof.commitment_c) + let comm_z: G1Element = g1.decompress(proof.commitment_z) + let comm_t_low: G1Element = g1.decompress(proof.t_low) + let comm_t_mid: G1Element = g1.decompress(proof.t_mid) + let comm_t_high: G1Element = g1.decompress(proof.t_high) + let comm_w_omega: G1Element = g1.decompress(proof.w_omega) + let comm_w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) + + // clamp scalars + let a_eval: State = scalar.from_int(proof.a_eval) + let b_eval: State = scalar.from_int(proof.b_eval) + let c_eval: State = scalar.from_int(proof.c_eval) + let s1: State = scalar.from_int(proof.s_sig1_p) + let s2: State = scalar.from_int(proof.s_sig2_p) + let z_omega: State = scalar.from_int(proof.z_omega) + + let w_all: List> = list.map(pub_inputs, fn(x) { scalar.neg(scalar.from_int(x)) }) + + expect Some(gen_tail) = list.tail(preinputs.generators) + expect Some(gen_2nd) = list.head(gen_tail) + + let scalar1: State = scalar.from_int(1) + + // I guess we don't need to decompress the generators? The aiken documentation is not clear on this. + // let g1_generator = g1.decompress(g1.generator) + // let g2_generator = g2.decompress(g2.generator) + + let beta_bs: ByteArray = concat_and_blake2b_224([ + inputs.q_m, + inputs.q_l, + inputs.q_r, + inputs.q_o, + inputs.q_c, + inputs.s_sig1, + inputs.s_sig2, + inputs.s_sig3, + list.foldl(pub_inputs, #"", fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }), + proof.commitment_a, + proof.commitment_b, + proof.commitment_c + ]) + let beta: State = scalar.from_bytes(beta_bs) + + let gamma_bs: ByteArray = blake2b_224(scalar.to_bytes(beta)) + let gamma: State = scalar.from_bytes(gamma_bs) + + let alpha_bs: ByteArray = concat_and_blake2b_224([ + scalar.to_bytes(beta), + scalar.to_bytes(gamma), + proof.commitment_z + ]) + let alpha: State = scalar.from_bytes(alpha_bs) + + let zeta_bs: ByteArray = concat_and_blake2b_224([ + scalar.to_bytes(alpha), + proof.t_low, + proof.t_mid, + proof.t_high + ]) + let zeta: State = scalar.from_bytes(zeta_bs) + + let v_bs: ByteArray = concat_and_blake2b_224([ + scalar.to_bytes(zeta), + scalar.to_bytes(a_eval), + scalar.to_bytes(b_eval), + scalar.to_bytes(c_eval), + scalar.to_bytes(s1), + scalar.to_bytes(s2), + scalar.to_bytes(z_omega) + ]) + let v: State = scalar.from_bytes(v_bs) + let u_bs: ByteArray = concat_and_blake2b_224([ + proof.w_omega, + proof.w_omega_zeta + ]) + let u: State = scalar.from_bytes(u_bs) + + let pow2_zeta_p: State = scale2(zeta, bls12_381_prime) + let pow2_zeta_p_minus_1: State = scalar.sub(pow2_zeta_p, scalar1) + + // calculate lagrange polynomials + let langrange_polys: List> = list.map2(preinputs.generators, preproof.lagrange_inverses, fn(x, y) { scalar.mul(scalar.mul(x, pow2_zeta_p_minus_1), y) }) + expect Some(lagrange_poly_head) = list.head(langrange_polys) + + let pi_zeta: State = scalar_sum( + list.map2(w_all, langrange_polys, fn(w, lag) { scalar.mul(w, lag) }) + ) + + let alpha_sq = scalar.mul(alpha, alpha) + let alpha_z_omega = scalar.mul(alpha, z_omega) + let beta_zeta = scalar.mul(beta, zeta) + + let a_gamma = scalar.add(a_eval, gamma) + let b_gamma = scalar.add(b_eval, gamma) + let c_gamma = scalar.add(c_eval, gamma) + + let beta_s1 = scalar.mul(beta, s1) + let beta_s2 = scalar.mul(beta, s2) + let r0 = scalar.sub( + scalar.sub(pi_zeta, scalar.mul(lagrange_poly_head, alpha_sq)), + scalar.mul(alpha_z_omega, scalar.mul(scalar.add(a_gamma, beta_s1), scalar.mul(scalar.add(b_gamma, beta_s2), c_gamma))) + ) + + // batch polynomial commitments + let bpc_g1 = g1_sum([ + g1.scale(q_m, scalar.mul(a_eval, b_eval)), + g1.scale(q_l, a_eval), + g1.scale(q_r, b_eval), + g1.scale(q_o, c_eval), + q_c, + g1.scale(comm_z, { + // product: (a_gamma + beta_zeta) * (b_gamma + beta_zeta * k1) * (c_gamma + beta_zeta * k2) * alpha + let product = scalar.add(a_gamma, beta_zeta) + |> scalar.mul(_, scalar.add(b_gamma, scalar.mul(beta_zeta, inputs.k1))) + |> scalar.mul(_, scalar.add(c_gamma, scalar.mul(beta_zeta, inputs.k2))) + |> scalar.mul(_, alpha) + + scalar.add(scalar.add(product, scalar.mul(lagrange_poly_head, alpha_sq)), u) + }), + g1.scale(s_sig3, scalar.neg( + scalar.mul( + scalar.mul( + scalar.add(a_gamma, beta_s1), + scalar.add(b_gamma, beta_s2) + ), + scalar.mul(alpha_z_omega, beta) + ) + )), + g1.scale( + g1.add( + comm_t_low, + g1.add( + g1.scale(comm_t_mid, pow2_zeta_p), + g1.scale(comm_t_high, scale2(pow2_zeta_p, 1)) + ) + ), + scalar.neg(pow2_zeta_p_minus_1) + ) + ]) + + let bpc_full = g1.add( + bpc_g1, + { + // nested commitments: comm_a + v * (comm_b + v * (comm_c + v * (s_sig1 + v * s_sig2))) + let nested_comms = g1.add(s_sig1, g1.scale(s_sig2, v)) + |> g1.scale(_, v) + |> g1.add(comm_c, _) + |> g1.scale(_, v) + |> g1.add(comm_b, _) + |> g1.scale(_, v) + |> g1.add(comm_a, _) + + g1.scale(nested_comms, v) + } + ) + + let group_encoded_batch_eval = g1.scale( + g1.generator, + { + // nested evaluation: a_eval + v * (b_eval + v * (c_eval + v * (s1 + v * s2))) + let nested_eval = scalar.add(s1, scalar.mul(v, s2)) + |> scalar.mul(v, _) + |> scalar.add(c_eval, _) + |> scalar.mul(v, _) + |> scalar.add(b_eval, _) + |> scalar.mul(v, _) + |> scalar.add(a_eval, _) + + // final expression: -r0 + v * nested_eval + u * z_omega + scalar.add( + scalar.add(scalar.neg(r0), scalar.mul(v, nested_eval)), + scalar.mul(u, z_omega) + ) + } + ) + +// bls12_381_millerLoop (commWOmega + scale u commWOmegaZeta) x2) + let miller_loop_1 = miller_loop( + g1.add(comm_w_omega, g1.scale(comm_w_omega_zeta, u)), + x2 + ) + +// (bls12_381_millerLoop (scale zeta commWOmega + scale (u * zeta * head (tail gens)) commWOmegaZeta + batchPolyCommitFull - groupEncodedBatchEval) bls12_381_G2_generator) + + + let miller_loop_2 = miller_loop( + g1.add( + g1.add( + g1.scale(comm_w_omega, zeta), + g1.scale(comm_w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_2nd))) + ), + g1.sub(bpc_full, group_encoded_batch_eval) + ), + g2.generator + ) + + + // bls12_381_finalVerify + let final_verification = final_exponentiation(miller_loop_1, miller_loop_2) + + // and (zipWith (\x y -> x * Scalar n * (zeta - y) == one) lagsInv gens) + let lagrange_check = scalar_sum(list.map2( + preproof.lagrange_inverses, + preinputs.generators, + fn(x, y) { scalar.mul(x, scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, y))) }, + )) == scalar.from_int(1) + final_verification && lagrange_check +} From e03ddf4211d4fa5bbd4d1045a05255badafb6b5d Mon Sep 17 00:00:00 2001 From: samdelaney Date: Fri, 23 Jan 2026 15:26:26 -0800 Subject: [PATCH 05/25] format changes --- zkp/lib/common/common.ak | 10 +- zkp/lib/plonk/preinputs.ak | 50 +++-- zkp/lib/plonk/proofs.ak | 117 ++++++----- zkp/lib/plonk/verifier.ak | 417 ++++++++++++++++++++----------------- 4 files changed, 324 insertions(+), 270 deletions(-) diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index 11b60e6..b8b0e7d 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -1,10 +1,10 @@ +use aiken/builtin.{blake2b_224} use aiken/collection/list use aiken/crypto -use aiken/primitive/bytearray -use aiken/builtin.{blake2b_224} -use aiken/crypto/bls12_381/scalar.{Scalar} use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/g1 +use aiken/crypto/bls12_381/scalar.{Scalar} +use aiken/primitive/bytearray // Constants for BLS12-381 pub const bls12_381_prime = @@ -167,8 +167,8 @@ fn mod_pow_loop(b, e, result) { // Blake2b-224 utilities pub fn concat_and_blake2b_224(inputs: List) -> ByteArray { - let concatenated = list.foldl(inputs, #"", bytearray.concat) - blake2b_224(concatenated) + let concatenated = list.foldl(inputs, #"", bytearray.concat) + blake2b_224(concatenated) } // Scalar utilities diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 472fe0e..07568c2 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -1,32 +1,36 @@ use aiken/collection/list -use aiken/crypto/bls12_381/scalar.{Scalar, scale} use aiken/crypto/bitwise.{State} +use aiken/crypto/bls12_381/scalar.{Scalar, scale} pub type PlonkPreInputs { - power: Int, - k1: State, - k2: State, - q_m: ByteArray, - q_l: ByteArray, - q_r: ByteArray, - q_o: ByteArray, - q_c: ByteArray, - s_sig1: ByteArray, - s_sig2: ByteArray, - s_sig3: ByteArray, - x2: ByteArray, + power: Int, + k1: State, + k2: State, + q_m: ByteArray, + q_l: ByteArray, + q_r: ByteArray, + q_o: ByteArray, + q_c: ByteArray, + s_sig1: ByteArray, + s_sig2: ByteArray, + s_sig3: ByteArray, + x2: ByteArray, } pub type PreparedPlonkPreInputs { - n: Int, - generators: List>, - inputs: PlonkPreInputs, + n: Int, + generators: List>, + inputs: PlonkPreInputs, } -pub fn plonk_prepare_preinputs(inputs: PlonkPreInputs, generator: State, n_public: Int) -> PreparedPlonkPreInputs { - PreparedPlonkPreInputs { - n: scalar.to_int(scale(scalar.from_int(2), inputs.power)), - generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), - inputs: inputs, - } -} \ No newline at end of file +pub fn plonk_prepare_preinputs( + inputs: PlonkPreInputs, + generator: State, + n_public: Int, +) -> PreparedPlonkPreInputs { + PreparedPlonkPreInputs { + n: scalar.to_int(scale(scalar.from_int(2), inputs.power)), + generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), + inputs, + } +} diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak index ad0bce5..d64abff 100644 --- a/zkp/lib/plonk/proofs.ak +++ b/zkp/lib/plonk/proofs.ak @@ -1,68 +1,75 @@ use aiken/builtin.{blake2b_224} -use common/common.{scalar_bs_rt, concat_and_blake2b_224} use aiken/collection/list +use aiken/crypto/bitwise.{State} +use aiken/crypto/bls12_381/scalar.{Scalar} use aiken/primitive/bytearray +use common/common.{concat_and_blake2b_224, scalar_bs_rt} use plonk/preinputs.{PreparedPlonkPreInputs} -use aiken/crypto/bls12_381/scalar.{Scalar} -use aiken/crypto/bitwise.{State} pub type PlonkProof { - commitment_a: ByteArray, - commitment_b: ByteArray, - commitment_c: ByteArray, - commitment_z: ByteArray, - t_low: ByteArray, - t_mid: ByteArray, - t_high: ByteArray, - w_omega: ByteArray, - w_omega_zeta: ByteArray, - a_eval: Int, - b_eval: Int, - c_eval: Int, - s_sig1_p: Int, - s_sig2_p: Int, - z_omega: Int, + commitment_a: ByteArray, + commitment_b: ByteArray, + commitment_c: ByteArray, + commitment_z: ByteArray, + t_low: ByteArray, + t_mid: ByteArray, + t_high: ByteArray, + w_omega: ByteArray, + w_omega_zeta: ByteArray, + a_eval: Int, + b_eval: Int, + c_eval: Int, + s_sig1_p: Int, + s_sig2_p: Int, + z_omega: Int, } pub type PreparedPlonkProof { - proof: PlonkProof, - lagrange_inverses: List>, + proof: PlonkProof, + lagrange_inverses: List>, } -pub fn plonk_prepare_proof(preinputs: PreparedPlonkPreInputs, pub_inputs: List, proof: PlonkProof) -> PreparedPlonkProof { - let beta_inputs = [ - preinputs.inputs.q_m, - preinputs.inputs.q_l, - preinputs.inputs.q_r, - preinputs.inputs.q_o, - preinputs.inputs.q_c, - preinputs.inputs.s_sig1, - preinputs.inputs.s_sig2, - preinputs.inputs.s_sig3, - list.foldl(pub_inputs, #"", fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }), - proof.commitment_a, - proof.commitment_b, - proof.commitment_c +pub fn plonk_prepare_proof( + preinputs: PreparedPlonkPreInputs, + pub_inputs: List, + proof: PlonkProof, +) -> PreparedPlonkProof { + let beta_inputs = + [ + preinputs.inputs.q_m, + preinputs.inputs.q_l, + preinputs.inputs.q_r, + preinputs.inputs.q_o, + preinputs.inputs.q_c, + preinputs.inputs.s_sig1, + preinputs.inputs.s_sig2, + preinputs.inputs.s_sig3, + list.foldl( + pub_inputs, + #"", + fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }, + ), + proof.commitment_a, + proof.commitment_b, + proof.commitment_c, ] - let beta = scalar_bs_rt(concat_and_blake2b_224(beta_inputs)) - let gamma = scalar_bs_rt(blake2b_224(beta)) - let alpha = scalar_bs_rt(concat_and_blake2b_224([ - beta, - gamma, - proof.commitment_z - ])) - let zeta = scalar.from_bytes(concat_and_blake2b_224([ - alpha, - proof.t_low, - proof.t_mid, - proof.t_high - ])) - let lagrange_inverses = list.map(preinputs.generators, fn(x) { - scalar.neg(scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, x))) - }) + let beta = scalar_bs_rt(concat_and_blake2b_224(beta_inputs)) + let gamma = scalar_bs_rt(blake2b_224(beta)) + let alpha = + scalar_bs_rt(concat_and_blake2b_224([beta, gamma, proof.commitment_z])) + let zeta = + scalar.from_bytes( + concat_and_blake2b_224([alpha, proof.t_low, proof.t_mid, proof.t_high]), + ) + let lagrange_inverses = + list.map( + preinputs.generators, + fn(x) { + scalar.neg( + scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, x)), + ) + }, + ) - PreparedPlonkProof { - proof, - lagrange_inverses, - } -} \ No newline at end of file + PreparedPlonkProof { proof, lagrange_inverses } +} diff --git a/zkp/lib/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak index 2c53bac..d9f9353 100644 --- a/zkp/lib/plonk/verifier.ak +++ b/zkp/lib/plonk/verifier.ak @@ -1,61 +1,67 @@ -use plonk/proofs.{PreparedPlonkProof, PlonkProof} -use plonk/preinputs.{PreparedPlonkPreInputs, PlonkPreInputs} -use common/common.{concat_and_blake2b_224, bls12_381_prime, g1_sum, scalar_sum} +use aiken/builtin.{blake2b_224} use aiken/collection/list +use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/g1 use aiken/crypto/bls12_381/g2 +use aiken/crypto/bls12_381/pairing.{final_exponentiation, miller_loop} use aiken/crypto/bls12_381/scalar.{Scalar, scale2} -use aiken/crypto/bls12_381/pairing.{miller_loop, final_exponentiation} use aiken/primitive/bytearray -use aiken/builtin.{blake2b_224} -use aiken/crypto/bitwise.{State} +use common/common.{bls12_381_prime, concat_and_blake2b_224, g1_sum, scalar_sum} +use plonk/preinputs.{PlonkPreInputs, PreparedPlonkPreInputs} +use plonk/proofs.{PlonkProof, PreparedPlonkProof} + +pub fn verify_plonk( + preinputs: PreparedPlonkPreInputs, + pub_inputs: List, + preproof: PreparedPlonkProof, +) -> Bool { + let proof: PlonkProof = preproof.proof + let inputs: PlonkPreInputs = preinputs.inputs + + // uncompress the inputs + let q_m: G1Element = g1.decompress(inputs.q_m) + let q_l: G1Element = g1.decompress(inputs.q_l) + let q_r: G1Element = g1.decompress(inputs.q_r) + let q_o: G1Element = g1.decompress(inputs.q_o) + let q_c: G1Element = g1.decompress(inputs.q_c) + let s_sig1: G1Element = g1.decompress(inputs.s_sig1) + let s_sig2: G1Element = g1.decompress(inputs.s_sig2) + let s_sig3: G1Element = g1.decompress(inputs.s_sig3) + let x2: G2Element = g2.decompress(inputs.x2) + + // uncompress the proof commitments + let comm_a: G1Element = g1.decompress(proof.commitment_a) + let comm_b: G1Element = g1.decompress(proof.commitment_b) + let comm_c: G1Element = g1.decompress(proof.commitment_c) + let comm_z: G1Element = g1.decompress(proof.commitment_z) + let comm_t_low: G1Element = g1.decompress(proof.t_low) + let comm_t_mid: G1Element = g1.decompress(proof.t_mid) + let comm_t_high: G1Element = g1.decompress(proof.t_high) + let comm_w_omega: G1Element = g1.decompress(proof.w_omega) + let comm_w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) + + // clamp scalars + let a_eval: State = scalar.from_int(proof.a_eval) + let b_eval: State = scalar.from_int(proof.b_eval) + let c_eval: State = scalar.from_int(proof.c_eval) + let s1: State = scalar.from_int(proof.s_sig1_p) + let s2: State = scalar.from_int(proof.s_sig2_p) + let z_omega: State = scalar.from_int(proof.z_omega) + + let w_all: List> = + list.map(pub_inputs, fn(x) { scalar.neg(scalar.from_int(x)) }) + + expect Some(gen_tail) = list.tail(preinputs.generators) + expect Some(gen_2nd) = list.head(gen_tail) + + let scalar1: State = scalar.from_int(1) -pub fn verify_plonk(preinputs: PreparedPlonkPreInputs, pub_inputs: List, preproof: PreparedPlonkProof) -> Bool { - let proof: PlonkProof = preproof.proof - let inputs: PlonkPreInputs = preinputs.inputs - - // uncompress the inputs - let q_m: G1Element = g1.decompress(inputs.q_m) - let q_l: G1Element = g1.decompress(inputs.q_l) - let q_r: G1Element = g1.decompress(inputs.q_r) - let q_o: G1Element = g1.decompress(inputs.q_o) - let q_c: G1Element = g1.decompress(inputs.q_c) - let s_sig1: G1Element = g1.decompress(inputs.s_sig1) - let s_sig2: G1Element = g1.decompress(inputs.s_sig2) - let s_sig3: G1Element = g1.decompress(inputs.s_sig3) - let x2: G2Element = g2.decompress(inputs.x2) - - // uncompress the proof commitments - let comm_a: G1Element = g1.decompress(proof.commitment_a) - let comm_b: G1Element = g1.decompress(proof.commitment_b) - let comm_c: G1Element = g1.decompress(proof.commitment_c) - let comm_z: G1Element = g1.decompress(proof.commitment_z) - let comm_t_low: G1Element = g1.decompress(proof.t_low) - let comm_t_mid: G1Element = g1.decompress(proof.t_mid) - let comm_t_high: G1Element = g1.decompress(proof.t_high) - let comm_w_omega: G1Element = g1.decompress(proof.w_omega) - let comm_w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) - - // clamp scalars - let a_eval: State = scalar.from_int(proof.a_eval) - let b_eval: State = scalar.from_int(proof.b_eval) - let c_eval: State = scalar.from_int(proof.c_eval) - let s1: State = scalar.from_int(proof.s_sig1_p) - let s2: State = scalar.from_int(proof.s_sig2_p) - let z_omega: State = scalar.from_int(proof.z_omega) - - let w_all: List> = list.map(pub_inputs, fn(x) { scalar.neg(scalar.from_int(x)) }) - - expect Some(gen_tail) = list.tail(preinputs.generators) - expect Some(gen_2nd) = list.head(gen_tail) - - let scalar1: State = scalar.from_int(1) - - // I guess we don't need to decompress the generators? The aiken documentation is not clear on this. - // let g1_generator = g1.decompress(g1.generator) - // let g2_generator = g2.decompress(g2.generator) - - let beta_bs: ByteArray = concat_and_blake2b_224([ + // I guess we don't need to decompress the generators? The aiken documentation is not clear on this. + // let g1_generator = g1.decompress(g1.generator) + // let g2_generator = g2.decompress(g2.generator) + let beta_bs: ByteArray = + concat_and_blake2b_224( + [ inputs.q_m, inputs.q_l, inputs.q_r, @@ -64,175 +70,212 @@ pub fn verify_plonk(preinputs: PreparedPlonkPreInputs, pub_inputs: List, pr inputs.s_sig1, inputs.s_sig2, inputs.s_sig3, - list.foldl(pub_inputs, #"", fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }), + list.foldl( + pub_inputs, + #"", + fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }, + ), proof.commitment_a, proof.commitment_b, - proof.commitment_c - ]) - let beta: State = scalar.from_bytes(beta_bs) - - let gamma_bs: ByteArray = blake2b_224(scalar.to_bytes(beta)) - let gamma: State = scalar.from_bytes(gamma_bs) - - let alpha_bs: ByteArray = concat_and_blake2b_224([ - scalar.to_bytes(beta), - scalar.to_bytes(gamma), - proof.commitment_z - ]) - let alpha: State = scalar.from_bytes(alpha_bs) - - let zeta_bs: ByteArray = concat_and_blake2b_224([ - scalar.to_bytes(alpha), - proof.t_low, - proof.t_mid, - proof.t_high - ]) - let zeta: State = scalar.from_bytes(zeta_bs) - - let v_bs: ByteArray = concat_and_blake2b_224([ + proof.commitment_c, + ], + ) + let beta: State = scalar.from_bytes(beta_bs) + let gamma_bs: ByteArray = blake2b_224(scalar.to_bytes(beta)) + let gamma: State = scalar.from_bytes(gamma_bs) + + let alpha_bs: ByteArray = + concat_and_blake2b_224( + [scalar.to_bytes(beta), scalar.to_bytes(gamma), proof.commitment_z], + ) + let alpha: State = scalar.from_bytes(alpha_bs) + + let zeta_bs: ByteArray = + concat_and_blake2b_224( + [scalar.to_bytes(alpha), proof.t_low, proof.t_mid, proof.t_high], + ) + let zeta: State = scalar.from_bytes(zeta_bs) + + let v_bs: ByteArray = + concat_and_blake2b_224( + [ scalar.to_bytes(zeta), scalar.to_bytes(a_eval), scalar.to_bytes(b_eval), scalar.to_bytes(c_eval), scalar.to_bytes(s1), scalar.to_bytes(s2), - scalar.to_bytes(z_omega) - ]) - let v: State = scalar.from_bytes(v_bs) - let u_bs: ByteArray = concat_and_blake2b_224([ - proof.w_omega, - proof.w_omega_zeta - ]) - let u: State = scalar.from_bytes(u_bs) - - let pow2_zeta_p: State = scale2(zeta, bls12_381_prime) - let pow2_zeta_p_minus_1: State = scalar.sub(pow2_zeta_p, scalar1) - - // calculate lagrange polynomials - let langrange_polys: List> = list.map2(preinputs.generators, preproof.lagrange_inverses, fn(x, y) { scalar.mul(scalar.mul(x, pow2_zeta_p_minus_1), y) }) - expect Some(lagrange_poly_head) = list.head(langrange_polys) - - let pi_zeta: State = scalar_sum( - list.map2(w_all, langrange_polys, fn(w, lag) { scalar.mul(w, lag) }) + scalar.to_bytes(z_omega), + ], + ) + let v: State = scalar.from_bytes(v_bs) + let u_bs: ByteArray = + concat_and_blake2b_224([proof.w_omega, proof.w_omega_zeta]) + let u: State = scalar.from_bytes(u_bs) + + let pow2_zeta_p: State = scale2(zeta, bls12_381_prime) + let pow2_zeta_p_minus_1: State = scalar.sub(pow2_zeta_p, scalar1) + + // calculate lagrange polynomials + let langrange_polys: List> = + list.map2( + preinputs.generators, + preproof.lagrange_inverses, + fn(x, y) { scalar.mul(scalar.mul(x, pow2_zeta_p_minus_1), y) }, ) + expect Some(lagrange_poly_head) = list.head(langrange_polys) - let alpha_sq = scalar.mul(alpha, alpha) - let alpha_z_omega = scalar.mul(alpha, z_omega) - let beta_zeta = scalar.mul(beta, zeta) - - let a_gamma = scalar.add(a_eval, gamma) - let b_gamma = scalar.add(b_eval, gamma) - let c_gamma = scalar.add(c_eval, gamma) - - let beta_s1 = scalar.mul(beta, s1) - let beta_s2 = scalar.mul(beta, s2) - let r0 = scalar.sub( - scalar.sub(pi_zeta, scalar.mul(lagrange_poly_head, alpha_sq)), - scalar.mul(alpha_z_omega, scalar.mul(scalar.add(a_gamma, beta_s1), scalar.mul(scalar.add(b_gamma, beta_s2), c_gamma))) + let pi_zeta: State = + scalar_sum( + list.map2(w_all, langrange_polys, fn(w, lag) { scalar.mul(w, lag) }), ) - // batch polynomial commitments - let bpc_g1 = g1_sum([ + let alpha_sq = scalar.mul(alpha, alpha) + let alpha_z_omega = scalar.mul(alpha, z_omega) + let beta_zeta = scalar.mul(beta, zeta) + let a_gamma = scalar.add(a_eval, gamma) + let b_gamma = scalar.add(b_eval, gamma) + let c_gamma = scalar.add(c_eval, gamma) + + let beta_s1 = scalar.mul(beta, s1) + let beta_s2 = scalar.mul(beta, s2) + let r0 = + scalar.sub( + scalar.sub(pi_zeta, scalar.mul(lagrange_poly_head, alpha_sq)), + scalar.mul( + alpha_z_omega, + scalar.mul( + scalar.add(a_gamma, beta_s1), + scalar.mul(scalar.add(b_gamma, beta_s2), c_gamma), + ), + ), + ) + + // batch polynomial commitments + let bpc_g1 = + g1_sum( + [ g1.scale(q_m, scalar.mul(a_eval, b_eval)), g1.scale(q_l, a_eval), g1.scale(q_r, b_eval), g1.scale(q_o, c_eval), q_c, - g1.scale(comm_z, { + g1.scale( + comm_z, + { // product: (a_gamma + beta_zeta) * (b_gamma + beta_zeta * k1) * (c_gamma + beta_zeta * k2) * alpha - let product = scalar.add(a_gamma, beta_zeta) - |> scalar.mul(_, scalar.add(b_gamma, scalar.mul(beta_zeta, inputs.k1))) - |> scalar.mul(_, scalar.add(c_gamma, scalar.mul(beta_zeta, inputs.k2))) - |> scalar.mul(_, alpha) - - scalar.add(scalar.add(product, scalar.mul(lagrange_poly_head, alpha_sq)), u) - }), - g1.scale(s_sig3, scalar.neg( - scalar.mul( - scalar.mul( - scalar.add(a_gamma, beta_s1), - scalar.add(b_gamma, beta_s2) - ), - scalar.mul(alpha_z_omega, beta) + let product = + scalar.add(a_gamma, beta_zeta) + |> scalar.mul( + scalar.add(b_gamma, scalar.mul(beta_zeta, inputs.k1)), + ) + |> scalar.mul( + scalar.add(c_gamma, scalar.mul(beta_zeta, inputs.k2)), + ) + |> scalar.mul(alpha) + scalar.add( + scalar.add(product, scalar.mul(lagrange_poly_head, alpha_sq)), + u, ) - )), + }, + ), g1.scale( + s_sig3, + scalar.neg( + scalar.mul( + scalar.mul( + scalar.add(a_gamma, beta_s1), + scalar.add(b_gamma, beta_s2), + ), + scalar.mul(alpha_z_omega, beta), + ), + ), + ), + g1.scale( + g1.add( + comm_t_low, g1.add( - comm_t_low, - g1.add( - g1.scale(comm_t_mid, pow2_zeta_p), - g1.scale(comm_t_high, scale2(pow2_zeta_p, 1)) - ) + g1.scale(comm_t_mid, pow2_zeta_p), + g1.scale(comm_t_high, scale2(pow2_zeta_p, 1)), ), - scalar.neg(pow2_zeta_p_minus_1) - ) - ]) - - let bpc_full = g1.add( - bpc_g1, - { - // nested commitments: comm_a + v * (comm_b + v * (comm_c + v * (s_sig1 + v * s_sig2))) - let nested_comms = g1.add(s_sig1, g1.scale(s_sig2, v)) - |> g1.scale(_, v) - |> g1.add(comm_c, _) - |> g1.scale(_, v) - |> g1.add(comm_b, _) - |> g1.scale(_, v) - |> g1.add(comm_a, _) - - g1.scale(nested_comms, v) - } + ), + scalar.neg(pow2_zeta_p_minus_1), + ), + ], ) - let group_encoded_batch_eval = g1.scale( - g1.generator, - { - // nested evaluation: a_eval + v * (b_eval + v * (c_eval + v * (s1 + v * s2))) - let nested_eval = scalar.add(s1, scalar.mul(v, s2)) - |> scalar.mul(v, _) - |> scalar.add(c_eval, _) - |> scalar.mul(v, _) - |> scalar.add(b_eval, _) - |> scalar.mul(v, _) - |> scalar.add(a_eval, _) - - // final expression: -r0 + v * nested_eval + u * z_omega - scalar.add( - scalar.add(scalar.neg(r0), scalar.mul(v, nested_eval)), - scalar.mul(u, z_omega) - ) - } + let bpc_full = + g1.add( + bpc_g1, + { + // nested commitments: comm_a + v * (comm_b + v * (comm_c + v * (s_sig1 + v * s_sig2))) + let nested_comms = + g1.add(s_sig1, g1.scale(s_sig2, v)) + |> g1.scale(v) + |> g1.add(comm_c, _) + |> g1.scale(v) + |> g1.add(comm_b, _) + |> g1.scale(v) + |> g1.add(comm_a, _) + + g1.scale(nested_comms, v) + }, ) -// bls12_381_millerLoop (commWOmega + scale u commWOmegaZeta) x2) - let miller_loop_1 = miller_loop( - g1.add(comm_w_omega, g1.scale(comm_w_omega_zeta, u)), - x2 + let group_encoded_batch_eval = + g1.scale( + g1.generator, + { + // nested evaluation: a_eval + v * (b_eval + v * (c_eval + v * (s1 + v * s2))) + let nested_eval = + scalar.add(s1, scalar.mul(v, s2)) + |> scalar.mul(v, _) + |> scalar.add(c_eval, _) + |> scalar.mul(v, _) + |> scalar.add(b_eval, _) + |> scalar.mul(v, _) + |> scalar.add(a_eval, _) + // final expression: -r0 + v * nested_eval + u * z_omega + scalar.add( + scalar.add(scalar.neg(r0), scalar.mul(v, nested_eval)), + scalar.mul(u, z_omega), + ) + }, ) -// (bls12_381_millerLoop (scale zeta commWOmega + scale (u * zeta * head (tail gens)) commWOmegaZeta + batchPolyCommitFull - groupEncodedBatchEval) bls12_381_G2_generator) + // bls12_381_millerLoop (commWOmega + scale u commWOmegaZeta) x2) + let miller_loop_1 = + miller_loop(g1.add(comm_w_omega, g1.scale(comm_w_omega_zeta, u)), x2) - - let miller_loop_2 = miller_loop( + // (bls12_381_millerLoop (scale zeta commWOmega + scale (u * zeta * head (tail gens)) commWOmegaZeta + batchPolyCommitFull - groupEncodedBatchEval) bls12_381_G2_generator) + let miller_loop_2 = + miller_loop( + g1.add( g1.add( - g1.add( - g1.scale(comm_w_omega, zeta), - g1.scale(comm_w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_2nd))) - ), - g1.sub(bpc_full, group_encoded_batch_eval) + g1.scale(comm_w_omega, zeta), + g1.scale(comm_w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_2nd))), ), - g2.generator + g1.sub(bpc_full, group_encoded_batch_eval), + ), + g2.generator, ) + // bls12_381_finalVerify + let final_verification = final_exponentiation(miller_loop_1, miller_loop_2) - // bls12_381_finalVerify - let final_verification = final_exponentiation(miller_loop_1, miller_loop_2) - - // and (zipWith (\x y -> x * Scalar n * (zeta - y) == one) lagsInv gens) - let lagrange_check = scalar_sum(list.map2( + // and (zipWith (\x y -> x * Scalar n * (zeta - y) == one) lagsInv gens) + let lagrange_check = + scalar_sum( + list.map2( preproof.lagrange_inverses, preinputs.generators, - fn(x, y) { scalar.mul(x, scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, y))) }, - )) == scalar.from_int(1) - final_verification && lagrange_check + fn(x, y) { + scalar.mul( + x, + scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, y)), + ) + }, + ), + ) == scalar.from_int(1) + final_verification && lagrange_check } From 72587abebe2e4acab35bc25b28078ab5baac0446 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Fri, 23 Jan 2026 15:28:26 -0800 Subject: [PATCH 06/25] bump aiken version in plutus.json --- zkp/plutus.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkp/plutus.json b/zkp/plutus.json index 852267f..6533b50 100644 --- a/zkp/plutus.json +++ b/zkp/plutus.json @@ -6,7 +6,7 @@ "plutusVersion": "v3", "compiler": { "name": "Aiken", - "version": "v1.1.15+f03633e" + "version": "v1.1.21+42babe5" }, "license": "Apache-2.0" }, From 474642857912abbcfca596721df8f675f306517c Mon Sep 17 00:00:00 2001 From: samdelaney Date: Fri, 23 Jan 2026 15:30:36 -0800 Subject: [PATCH 07/25] bump aiken version in ci --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index dab15f6..c54a8f4 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v3 - uses: aiken-lang/setup-aiken@v1.0.3 with: - version: v1.1.19 + version: v1.1.21 - run: aiken fmt --check working-directory: zkp - run: aiken check -D From 91a7e456023ab5e383fe32280065fe5a957eaa3b Mon Sep 17 00:00:00 2001 From: samdelaney Date: Fri, 23 Jan 2026 16:32:27 -0800 Subject: [PATCH 08/25] readability pass --- zkp/lib/plonk/verifier.ak | 153 ++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 71 deletions(-) diff --git a/zkp/lib/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak index d9f9353..d5d8e1d 100644 --- a/zkp/lib/plonk/verifier.ak +++ b/zkp/lib/plonk/verifier.ak @@ -40,7 +40,7 @@ pub fn verify_plonk( let comm_w_omega: G1Element = g1.decompress(proof.w_omega) let comm_w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) - // clamp scalars + // convert polynomial evaluations let a_eval: State = scalar.from_int(proof.a_eval) let b_eval: State = scalar.from_int(proof.b_eval) let c_eval: State = scalar.from_int(proof.c_eval) @@ -48,97 +48,111 @@ pub fn verify_plonk( let s2: State = scalar.from_int(proof.s_sig2_p) let z_omega: State = scalar.from_int(proof.z_omega) + // prepare negated public inputs for polynomial evaluation let w_all: List> = list.map(pub_inputs, fn(x) { scalar.neg(scalar.from_int(x)) }) + // extract generator elements from trusted setup expect Some(gen_tail) = list.tail(preinputs.generators) expect Some(gen_2nd) = list.head(gen_tail) + // define scalar constants let scalar1: State = scalar.from_int(1) // I guess we don't need to decompress the generators? The aiken documentation is not clear on this. // let g1_generator = g1.decompress(g1.generator) // let g2_generator = g2.decompress(g2.generator) - let beta_bs: ByteArray = - concat_and_blake2b_224( - [ - inputs.q_m, - inputs.q_l, - inputs.q_r, - inputs.q_o, - inputs.q_c, - inputs.s_sig1, - inputs.s_sig2, - inputs.s_sig3, - list.foldl( - pub_inputs, - #"", - fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }, - ), - proof.commitment_a, - proof.commitment_b, - proof.commitment_c, - ], + // + // compute Fiat-Shamir challenges + let beta: State = + scalar.from_bytes( + concat_and_blake2b_224( + [ + inputs.q_m, + inputs.q_l, + inputs.q_r, + inputs.q_o, + inputs.q_c, + inputs.s_sig1, + inputs.s_sig2, + inputs.s_sig3, + list.foldl( + pub_inputs, + #"", + fn(a, b) { + bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) + }, + ), + proof.commitment_a, + proof.commitment_b, + proof.commitment_c, + ], + ), ) - let beta: State = scalar.from_bytes(beta_bs) - let gamma_bs: ByteArray = blake2b_224(scalar.to_bytes(beta)) - let gamma: State = scalar.from_bytes(gamma_bs) - - let alpha_bs: ByteArray = - concat_and_blake2b_224( - [scalar.to_bytes(beta), scalar.to_bytes(gamma), proof.commitment_z], + let gamma: State = + scalar.from_bytes(blake2b_224(scalar.to_bytes(beta))) + let alpha: State = + scalar.from_bytes( + concat_and_blake2b_224( + [scalar.to_bytes(beta), scalar.to_bytes(gamma), proof.commitment_z], + ), ) - let alpha: State = scalar.from_bytes(alpha_bs) - - let zeta_bs: ByteArray = - concat_and_blake2b_224( - [scalar.to_bytes(alpha), proof.t_low, proof.t_mid, proof.t_high], + let zeta: State = + scalar.from_bytes( + concat_and_blake2b_224( + [scalar.to_bytes(alpha), proof.t_low, proof.t_mid, proof.t_high], + ), ) - let zeta: State = scalar.from_bytes(zeta_bs) - - let v_bs: ByteArray = - concat_and_blake2b_224( - [ - scalar.to_bytes(zeta), - scalar.to_bytes(a_eval), - scalar.to_bytes(b_eval), - scalar.to_bytes(c_eval), - scalar.to_bytes(s1), - scalar.to_bytes(s2), - scalar.to_bytes(z_omega), - ], + let v: State = + scalar.from_bytes( + concat_and_blake2b_224( + [ + scalar.to_bytes(zeta), + scalar.to_bytes(a_eval), + scalar.to_bytes(b_eval), + scalar.to_bytes(c_eval), + scalar.to_bytes(s1), + scalar.to_bytes(s2), + scalar.to_bytes(z_omega), + ], + ), + ) + let u: State = + scalar.from_bytes( + concat_and_blake2b_224([proof.w_omega, proof.w_omega_zeta]), ) - let v: State = scalar.from_bytes(v_bs) - let u_bs: ByteArray = - concat_and_blake2b_224([proof.w_omega, proof.w_omega_zeta]) - let u: State = scalar.from_bytes(u_bs) + // compute powers of zeta for polynomial evaluation let pow2_zeta_p: State = scale2(zeta, bls12_381_prime) let pow2_zeta_p_minus_1: State = scalar.sub(pow2_zeta_p, scalar1) // calculate lagrange polynomials - let langrange_polys: List> = + let lagrange_polys: List> = list.map2( preinputs.generators, preproof.lagrange_inverses, fn(x, y) { scalar.mul(scalar.mul(x, pow2_zeta_p_minus_1), y) }, ) - expect Some(lagrange_poly_head) = list.head(langrange_polys) + expect Some(lagrange_poly_head) = list.head(lagrange_polys) + // compute public input polynomial evaluation at zeta let pi_zeta: State = scalar_sum( - list.map2(w_all, langrange_polys, fn(w, lag) { scalar.mul(w, lag) }), + list.map2(w_all, lagrange_polys, fn(w, lag) { scalar.mul(w, lag) }), ) + // compute derived scalars for verification equations let alpha_sq = scalar.mul(alpha, alpha) let alpha_z_omega = scalar.mul(alpha, z_omega) let beta_zeta = scalar.mul(beta, zeta) + // compute shifted witness polynomials let a_gamma = scalar.add(a_eval, gamma) let b_gamma = scalar.add(b_eval, gamma) let c_gamma = scalar.add(c_eval, gamma) - let beta_s1 = scalar.mul(beta, s1) let beta_s2 = scalar.mul(beta, s2) + + // compute linearization polynomial r(zeta) let r0 = scalar.sub( scalar.sub(pi_zeta, scalar.mul(lagrange_poly_head, alpha_sq)), @@ -151,8 +165,8 @@ pub fn verify_plonk( ), ) - // batch polynomial commitments - let bpc_g1 = + // batch constraint polynomial commitments + let constraint_commitments = g1_sum( [ g1.scale(q_m, scalar.mul(a_eval, b_eval)), @@ -163,7 +177,6 @@ pub fn verify_plonk( g1.scale( comm_z, { - // product: (a_gamma + beta_zeta) * (b_gamma + beta_zeta * k1) * (c_gamma + beta_zeta * k2) * alpha let product = scalar.add(a_gamma, beta_zeta) |> scalar.mul( @@ -204,12 +217,12 @@ pub fn verify_plonk( ], ) - let bpc_full = + // combine batched polynomial commitments + let batched_polynomial_commitments = g1.add( - bpc_g1, + constraint_commitments, { - // nested commitments: comm_a + v * (comm_b + v * (comm_c + v * (s_sig1 + v * s_sig2))) - let nested_comms = + let sigma_witness_commits = g1.add(s_sig1, g1.scale(s_sig2, v)) |> g1.scale(v) |> g1.add(comm_c, _) @@ -218,16 +231,16 @@ pub fn verify_plonk( |> g1.scale(v) |> g1.add(comm_a, _) - g1.scale(nested_comms, v) + g1.scale(sigma_witness_commits, v) }, ) + // batch evaluations let group_encoded_batch_eval = g1.scale( g1.generator, { - // nested evaluation: a_eval + v * (b_eval + v * (c_eval + v * (s1 + v * s2))) - let nested_eval = + let sigma_witness_evals = scalar.add(s1, scalar.mul(v, s2)) |> scalar.mul(v, _) |> scalar.add(c_eval, _) @@ -235,19 +248,18 @@ pub fn verify_plonk( |> scalar.add(b_eval, _) |> scalar.mul(v, _) |> scalar.add(a_eval, _) - // final expression: -r0 + v * nested_eval + u * z_omega scalar.add( - scalar.add(scalar.neg(r0), scalar.mul(v, nested_eval)), + scalar.add(scalar.neg(r0), scalar.mul(v, sigma_witness_evals)), scalar.mul(u, z_omega), ) }, ) - // bls12_381_millerLoop (commWOmega + scale u commWOmegaZeta) x2) + // verify polynomial evaluations let miller_loop_1 = miller_loop(g1.add(comm_w_omega, g1.scale(comm_w_omega_zeta, u)), x2) - // (bls12_381_millerLoop (scale zeta commWOmega + scale (u * zeta * head (tail gens)) commWOmegaZeta + batchPolyCommitFull - groupEncodedBatchEval) bls12_381_G2_generator) + // verify constraint satisfaction let miller_loop_2 = miller_loop( g1.add( @@ -255,15 +267,14 @@ pub fn verify_plonk( g1.scale(comm_w_omega, zeta), g1.scale(comm_w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_2nd))), ), - g1.sub(bpc_full, group_encoded_batch_eval), + g1.sub(batched_polynomial_commitments, group_encoded_batch_eval), ), g2.generator, ) - // bls12_381_finalVerify let final_verification = final_exponentiation(miller_loop_1, miller_loop_2) - // and (zipWith (\x y -> x * Scalar n * (zeta - y) == one) lagsInv gens) + // verify lagrange polynomial inverses let lagrange_check = scalar_sum( list.map2( From d02bae03b13c6a18549ab54f674cf77e73de4ab7 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 26 Jan 2026 15:03:45 -0800 Subject: [PATCH 09/25] use field prime in common --- zkp/lib/common/common.ak | 51 +++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index b8b0e7d..75dd3d6 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -3,20 +3,13 @@ use aiken/collection/list use aiken/crypto use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/g1 -use aiken/crypto/bls12_381/scalar.{Scalar} +use aiken/crypto/bls12_381/scalar.{Scalar, field_prime} use aiken/primitive/bytearray // Constants for BLS12-381 -pub const bls12_381_prime = - 52435875175126190479447740508185965837690552500527637822603658699938581184513 - pub const bls12_381_base_prime = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 -// Additional curve constants -pub const bls12_381_r = - 52435875175126190479447740508185965837690552500527637822603658699938581184513 - pub const bls12_381_h = 1 // Common types for ZKP systems @@ -63,31 +56,31 @@ pub type VerificationError { // Common utility functions for field operations pub fn make_scalar(a: Int) -> Field { - a % bls12_381_prime + a % field_prime } pub fn mod_add(a: Field, b: Field) -> Field { - ( a + b ) % bls12_381_prime + ( a + b ) % field_prime } pub fn mod_sub(a: Field, b: Field) -> Field { - let result = ( a - b ) % bls12_381_prime + let result = ( a - b ) % field_prime if result < 0 { - result + bls12_381_prime + result + field_prime } else { result } } pub fn mod_mul(a: Field, b: Field) -> Field { - a * b % bls12_381_prime + a * b % field_prime } pub fn mod_neg(a: Field) -> Field { if a == 0 { 0 } else { - bls12_381_prime - a + field_prime - a } } @@ -131,7 +124,7 @@ pub fn mod_sqrt(a: Field) -> Option { Some(0) } else { // For p ≡ 3 (mod 4), sqrt(a) = a^((p+1)/4) if a is a quadratic residue - let exp = ( bls12_381_prime + 1 ) / 4 + let exp = ( field_prime + 1 ) / 4 let sqrt = mod_pow(a, exp) // Verify the result if mod_mul(sqrt, sqrt) == a { @@ -220,7 +213,7 @@ pub fn g1_compress(p: G1Point) -> ByteArray { crypto.blake2b_256(#"00") } else { // Convert to affine coordinates first - let z_inv = mod_inv(p.z, bls12_381_prime) + let z_inv = mod_inv(p.z, field_prime) when z_inv is { Some(z_inv_val) -> { let x_affine = mod_mul(p.x, z_inv_val) @@ -228,7 +221,7 @@ pub fn g1_compress(p: G1Point) -> ByteArray { // Compress point by keeping x coordinate and y's sign let compressed = serialize_field(x_affine) // Add a bit for y's sign (we'll use the last byte) - let is_y_negative = y_affine > bls12_381_prime / 2 + let is_y_negative = y_affine > field_prime / 2 if is_y_negative { bytearray.concat(compressed, #"01") } else { @@ -258,7 +251,7 @@ pub fn g1_decompress(bytes: ByteArray) -> Option { Some(y) -> { // Choose the correct y value based on sign let final_y = - if is_negative && y > bls12_381_prime / 2 || !is_negative && y <= bls12_381_prime / 2 { + if is_negative && y > field_prime / 2 || !is_negative && y <= field_prime / 2 { y } else { mod_neg(y) @@ -280,7 +273,7 @@ pub fn g2_compress(p: G2Point) -> ByteArray { } else { // Convert to affine coordinates first let (z1, z2) = p.z - let z_inv = mod_inv(z1, bls12_381_prime) + let z_inv = mod_inv(z1, field_prime) when z_inv is { Some(z_inv_val) -> { let z_inv_squared = fp2_square((z_inv_val, z2)) @@ -290,7 +283,7 @@ pub fn g2_compress(p: G2Point) -> ByteArray { let x1_bytes = serialize_field(x_affine_1) let x2_bytes = serialize_field(x_affine_2) // Add a bit for y's sign - let is_y_negative = y_affine_1 > bls12_381_prime / 2 + let is_y_negative = y_affine_1 > field_prime / 2 let flag_byte = if is_y_negative { #"01" @@ -494,9 +487,9 @@ pub fn deserialize_field(bytes: ByteArray) -> Option { bytearray.foldl( bytes, 0, - fn(result, byte) { ( result * 256 + byte ) % bls12_381_prime }, + fn(result, byte) { ( result * 256 + byte ) % field_prime }, ) - if result < bls12_381_prime { + if result < field_prime { Some(result) } else { None @@ -511,7 +504,7 @@ pub fn hash_to_field(data: ByteArray) -> Field { bytearray.foldl( hash, 0, - fn(result, byte) { ( result * 256 + byte ) % bls12_381_prime }, + fn(result, byte) { ( result * 256 + byte ) % field_prime }, ) } @@ -521,7 +514,7 @@ pub fn hash_to_curve(data: ByteArray) -> G1Point { let u_squared = mod_mul(u, u) let u_squared_plus_1 = mod_add(u_squared, 1) // Calculate x = -(u^2) / (u^2 + 1) if u^2 ≠ -1 - when mod_inv(u_squared_plus_1, bls12_381_prime) is { + when mod_inv(u_squared_plus_1, field_prime) is { Some(inv) -> { let x = mod_mul(mod_neg(u_squared), inv) // Calculate y^2 = x^3 + 4 @@ -533,7 +526,7 @@ pub fn hash_to_curve(data: ByteArray) -> G1Point { Some(y) -> { // Ensure y has the same sign as u let final_y = - if y > bls12_381_prime / 2 == ( u > bls12_381_prime / 2 ) { + if y > field_prime / 2 == ( u > field_prime / 2 ) { y } else { mod_neg(y) @@ -647,7 +640,7 @@ pub fn is_on_curve_g1(p: G1Point) -> Bool { True } else { // Convert to affine coordinates - let z_inv = mod_inv(p.z, bls12_381_prime) + let z_inv = mod_inv(p.z, field_prime) when z_inv is { Some(z_inv_val) -> { let z_inv_squared = mod_mul(z_inv_val, z_inv_val) @@ -670,7 +663,7 @@ pub fn is_in_g1_subgroup(p: G1Point) -> Bool { False } else { // Check that the point has the right order - let scalar_mult = g1_mul(p, bls12_381_r) + let scalar_mult = g1_mul(p, field_prime) is_infinity(scalar_mult) } } @@ -819,7 +812,7 @@ pub fn is_on_curve_g2(p: G2Point) -> Bool { } else { // Convert to affine coordinates let (z1, z2) = p.z - let z_inv = mod_inv(z1, bls12_381_prime) + let z_inv = mod_inv(z1, field_prime) when z_inv is { Some(z_inv_val) -> { let z_inv_squared = fp2_square((z_inv_val, z2)) @@ -848,7 +841,7 @@ pub fn is_in_g2_subgroup(p: G2Point) -> Bool { False } else { // Check that the point has the right order - let scalar_mult = g2_mul(p, bls12_381_r) + let scalar_mult = g2_mul(p, field_prime) is_infinity_g2(scalar_mult) } } From 834793bca9c80a49854a2fd34200bcc19ea760f5 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 26 Jan 2026 15:07:27 -0800 Subject: [PATCH 10/25] fix field prime references --- zkp/lib/bullet/bullet.ak | 44 +++++++++++++++++---------------------- zkp/lib/plonk/verifier.ak | 6 +++--- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/zkp/lib/bullet/bullet.ak b/zkp/lib/bullet/bullet.ak index bd53086..6b9a642 100644 --- a/zkp/lib/bullet/bullet.ak +++ b/zkp/lib/bullet/bullet.ak @@ -1,5 +1,6 @@ use aiken/collection/list use aiken/crypto +use aiken/crypto/bls12_381/scalar.{field_prime} use aiken/primitive/bytearray use common/common @@ -64,9 +65,9 @@ fn verify_inner_product( let h_commit = compute_vector_commitments(r_vec, h_vec) let u_scaled = common.Point { - x: u.x * inner_prod % common.bls12_381_prime, - y: u.y * inner_prod % common.bls12_381_prime, - z: u.z * inner_prod % common.bls12_381_prime, + x: u.x * inner_prod % field_prime, + y: u.y * inner_prod % field_prime, + z: u.z * inner_prod % field_prime, } let lhs = p let rhs = @@ -228,9 +229,9 @@ pub fn generate_proof( // 5. Generate s commitment let s = common.Point { - x: vk.h.x * mu % common.bls12_381_prime, - y: vk.h.y * mu % common.bls12_381_prime, - z: vk.h.z * mu % common.bls12_381_prime, + x: vk.h.x * mu % field_prime, + y: vk.h.y * mu % field_prime, + z: vk.h.z * mu % field_prime, } BulletproofProof { a, s, t1, t2, tau_x, mu, l_vec, r_vec } @@ -259,15 +260,15 @@ fn commit( ) -> common.G1Point { let g_scaled = common.Point { - x: g.x * value % common.bls12_381_prime, - y: g.y * value % common.bls12_381_prime, - z: g.z * value % common.bls12_381_prime, + x: g.x * value % field_prime, + y: g.y * value % field_prime, + z: g.z * value % field_prime, } let h_scaled = common.Point { - x: h.x * randomness % common.bls12_381_prime, - y: h.y * randomness % common.bls12_381_prime, - z: h.z * randomness % common.bls12_381_prime, + x: h.x * randomness % field_prime, + y: h.y * randomness % field_prime, + z: h.z * randomness % field_prime, } // Add the two scaled points common.Point { @@ -285,10 +286,7 @@ pub fn compute_inner_product( when (l_vec, r_vec) is { ([], []) -> 0 ([x, ..xs], [y, ..ys]) -> - common.mod_add( - x * y % common.bls12_381_prime, - compute_inner_product(xs, ys), - ) + common.mod_add(x * y % field_prime, compute_inner_product(xs, ys)) (_, _) -> fail @"Vector lengths must match" } } @@ -327,9 +325,9 @@ pub fn compute_vector_commitments( ([s, ..ss], [g, ..gs]) -> { let scaled = common.Point { - x: g.x * s % common.bls12_381_prime, - y: g.y * s % common.bls12_381_prime, - z: g.z * s % common.bls12_381_prime, + x: g.x * s % field_prime, + y: g.y * s % field_prime, + z: g.z * s % field_prime, } let rest = compute_vector_commitments(ss, gs) // Add points using BLS12-381 point addition @@ -363,10 +361,7 @@ pub fn vector_scalar_multiplication( when vec is { [] -> [] [x, ..xs] -> - [ - x * scalar % common.bls12_381_prime, - ..vector_scalar_multiplication(xs, scalar) - ] + [x * scalar % field_prime, ..vector_scalar_multiplication(xs, scalar)] } } @@ -377,8 +372,7 @@ pub fn hadamard_product( ) -> List { when (vec1, vec2) is { ([], []) -> [] - ([x, ..xs], [y, ..ys]) -> - [x * y % common.bls12_381_prime, ..hadamard_product(xs, ys)] + ([x, ..xs], [y, ..ys]) -> [x * y % field_prime, ..hadamard_product(xs, ys)] (_, _) -> fail @"Vector lengths must match" } } diff --git a/zkp/lib/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak index d5d8e1d..a59db1a 100644 --- a/zkp/lib/plonk/verifier.ak +++ b/zkp/lib/plonk/verifier.ak @@ -4,9 +4,9 @@ use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/g1 use aiken/crypto/bls12_381/g2 use aiken/crypto/bls12_381/pairing.{final_exponentiation, miller_loop} -use aiken/crypto/bls12_381/scalar.{Scalar, scale2} +use aiken/crypto/bls12_381/scalar.{Scalar, field_prime, scale2} use aiken/primitive/bytearray -use common/common.{bls12_381_prime, concat_and_blake2b_224, g1_sum, scalar_sum} +use common/common.{concat_and_blake2b_224, g1_sum, scalar_sum} use plonk/preinputs.{PlonkPreInputs, PreparedPlonkPreInputs} use plonk/proofs.{PlonkProof, PreparedPlonkProof} @@ -123,7 +123,7 @@ pub fn verify_plonk( ) // compute powers of zeta for polynomial evaluation - let pow2_zeta_p: State = scale2(zeta, bls12_381_prime) + let pow2_zeta_p: State = scale2(zeta, field_prime) let pow2_zeta_p_minus_1: State = scalar.sub(pow2_zeta_p, scalar1) // calculate lagrange polynomials From 81d34c8d88a1441cbd878f72ab01cf03939e83fc Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 26 Jan 2026 17:25:08 -0800 Subject: [PATCH 11/25] new tests folder & plonk raw types --- zkp/lib/common/common.ak | 20 +++++- zkp/lib/groth/groth.ak | 84 ------------------------- zkp/lib/plonk/proofs.ak | 8 +-- zkp/lib/plonk/raw.ak | 57 +++++++++++++++++ zkp/lib/tests/groth_tests.ak | 85 +++++++++++++++++++++++++ zkp/lib/tests/plonk_tests.ak | 118 +++++++++++++++++++++++++++++++++++ 6 files changed, 283 insertions(+), 89 deletions(-) create mode 100644 zkp/lib/plonk/raw.ak create mode 100644 zkp/lib/tests/groth_tests.ak create mode 100644 zkp/lib/tests/plonk_tests.ak diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index 75dd3d6..2baf5b9 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -166,10 +166,14 @@ pub fn concat_and_blake2b_224(inputs: List) -> ByteArray { // Scalar utilities -pub fn scalar_bs_rt(bs: ByteArray) -> ByteArray { +pub fn scalar_rt_bs(bs: ByteArray) -> ByteArray { scalar.to_bytes(scalar.from_bytes(bs)) } +pub fn scalar_rt_int(i: Int) -> Int { + scalar.to_int(scalar.from_int(i)) +} + pub fn scalar_sum(scalars: List>) -> State { list.foldl(scalars, scalar.from_int(0), fn(acc, x) { scalar.add(acc, x) }) } @@ -179,6 +183,20 @@ pub fn g1_sum(points: List) -> G1Element { list.foldl(points, g1.zero, g1.add) } +pub fn g1_from_raw(raw: List) -> Option { + when raw is { + [] -> None + [_] -> None + [x, y, ..] -> { + Some(Point { + x: scalar_rt_int(x), + y: scalar_rt_int(y), + z: 1, + }) + } + } +} + pub fn g1_generator() -> G1Point { // BLS12-381 G1 generator coordinates Point { diff --git a/zkp/lib/groth/groth.ak b/zkp/lib/groth/groth.ak index 157674b..5d90712 100644 --- a/zkp/lib/groth/groth.ak +++ b/zkp/lib/groth/groth.ak @@ -131,87 +131,3 @@ pub fn derive( } } } - -test groth_verify_pass_1() { - // Template of VK - let vk: CompressedVK = - CompressedVK { - alpha: #"8e3d9e248feda194cb6fa0a3b64fd2a380cb5e94836bf8148bf97ebcbb5819d9a78f63102f0293c104bcbb2f810d8eb4", - beta: #"8cd68a7186a908212680a0234d8210c20328f8fb3ce1d69c9aec9330a5802d6cfaf6d7cf3176133221c19188590cb4141874ea7bbfcb9872931e115d882c46b90c3dcbcee10062d1c9b9b0a691d7bec7d2735f06495c7f71dea210e55b2782df", - gamma: #"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", - delta: #"938231fcec443fbdeb1079ff126b8f69bd8579ffe82d39923214d4345395beee60200288fa20c97ae50f3212131b6f8802af2f9f515c65af6a9a6c294c738590104376a0af44731d6699db6a286608774243f7d1dddc4605eb340e65e15060a5", - vkIC: [ - #"b5813c90d3455acb8608fdf66e8601f24ef048f3fdf9384862d77c72cb5cddfde2b307976b1b0319c42ba985f94be60a", - #"a41a0e6370c054be0f9acce08e97f6d5702d9daa9e9a934e8a377f553593baa1c58062adc73d63558653422d54d6f50c", - #"8e02a87c519c3145f984d25fdf769f74fbc36626c385d4554fb4bc1d7a12cbf669d40f257023b8b3c9a31e631aa8f981", - ], - } - - // Template of Proof - let pk: CompressedProof = - CompressedProof { - a: #"8b84d092731c653b1accdda79c51e3f5d289bed7311189d927deadef0470e437e6d1d400634726512a79a015867424e3", - b: #"92cb1c125816e4b522c7f430a5d74a61116b6189de7b2341f040194c02f10d9ef0cf081f4029444a65ea74e69d98b1cf08d3864087d5d2dee2ed6ab102f9b78e65d341f0824341a9fc25d0ea9dacccc5d355b4eddb0057949370a19c47135b0e", - c: #"a4ef633c858a3ff194db50eacdf715f7296fb3d1202c54b543284e9656b69aa90f33ac0e2572d3ab847b88268dcd1f7e", - } - // Template of public values - let public_values: List = [561, 3] - - verify_compressed(vk, pk, public_values) -} - -test groth_verify_fail_1() fail { - // Template of VK - let vk: CompressedVK = - CompressedVK { - alpha: #"8e3d9e248feda194cb6fa0a3b64fd2a380cb5e94836bf8148bf97ebcbb5819d9a78f63102f0293c104bcbb2f810d8eb4", - beta: #"8cd68a7186a908212680a0234d8210c20328f8fb3ce1d69c9aec9330a5802d6cfaf6d7cf3176133221c19188590cb4141874ea7bbfcb9872931e115d882c46b90c3dcbcee10062d1c9b9b0a691d7bec7d2735f06495c7f71dea210e55b2782df", - gamma: #"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", - delta: #"938231fcec443fbdeb1079ff126b8f69bd8579ffe82d39923214d4345395beee60200288fa20c97ae50f3212131b6f8802af2f9f515c65af6a9a6c294c738590104376a0af44731d6699db6a286608774243f7d1dddc4605eb340e65e15060a5", - vkIC: [ - #"b5813c90d3455acb8608fdf66e8601f24ef048f3fdf9384862d77c72cb5cddfde2b307976b1b0319c42ba985f94be60a", - #"a41a0e6370c054be0f9acce08e97f6d5702d9daa9e9a934e8a377f553593baa1c58062adc73d63558653422d54d6f50c", - #"8e02a87c519c3145f984d25fdf769f74fbc36626c385d4554fb4bc1d7a12cbf669d40f257023b8b3c9a31e631aa8f981", - ], - } - - // Template of Proof - let pk: CompressedProof = - CompressedProof { - a: #"a4ef633c858a3ff194db50eacdf715f7296fb3d1202c54b543284e9656b69aa90f33ac0e2572d3ab847b88268dcd1f7e", - b: #"92cb1c125816e4b522c7f430a5d74a61116b6189de7b2341f040194c02f10d9ef0cf081f4029444a65ea74e69d98b1cf08d3864087d5d2dee2ed6ab102f9b78e65d341f0824341a9fc25d0ea9dacccc5d355b4eddb0057949370a19c47135b0e", - c: #"8b84d092731c653b1accdda79c51e3f5d289bed7311189d927deadef0470e437e6d1d400634726512a79a015867424e3", - } - // Template of public values - let public_values: List = [561, 3] - - verify_compressed(vk, pk, public_values) -} - -test groth_verify_pass_2() { - // Template of VK - let vk: CompressedVK = - CompressedVK { - alpha: #"8e3d9e248feda194cb6fa0a3b64fd2a380cb5e94836bf8148bf97ebcbb5819d9a78f63102f0293c104bcbb2f810d8eb4", - beta: #"8cd68a7186a908212680a0234d8210c20328f8fb3ce1d69c9aec9330a5802d6cfaf6d7cf3176133221c19188590cb4141874ea7bbfcb9872931e115d882c46b90c3dcbcee10062d1c9b9b0a691d7bec7d2735f06495c7f71dea210e55b2782df", - gamma: #"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", - delta: #"938231fcec443fbdeb1079ff126b8f69bd8579ffe82d39923214d4345395beee60200288fa20c97ae50f3212131b6f8802af2f9f515c65af6a9a6c294c738590104376a0af44731d6699db6a286608774243f7d1dddc4605eb340e65e15060a5", - vkIC: [ - #"b5813c90d3455acb8608fdf66e8601f24ef048f3fdf9384862d77c72cb5cddfde2b307976b1b0319c42ba985f94be60a", - #"a41a0e6370c054be0f9acce08e97f6d5702d9daa9e9a934e8a377f553593baa1c58062adc73d63558653422d54d6f50c", - #"8e02a87c519c3145f984d25fdf769f74fbc36626c385d4554fb4bc1d7a12cbf669d40f257023b8b3c9a31e631aa8f981", - ], - } - - // Template of Proof - let pk: CompressedProof = - CompressedProof { - a: #"98ca847cc04a6f67ac85a628521450323d7aa5335d4c2c48e9780b659cf7ea8ece2d0b305c9ff9dcfb3e548d61bbaebe", - b: #"a8e6ba4dbce6aa84de8ca1cd39d42353fcac89fe8cb800e728ada3ca4ae3b07baa68f76b9e4fa73eebf78cc609fa85d6166a3b69cd08cc59f2ff36e52dfdf231540a4212fdd4a142504c76066bddea342dd0183b2b11ed62cfc1497189a4db52", - c: #"8bc8cc3f11483138cc55d5f0389e67231f9e8465e5cb4a5a668e6e298d5c4febb2a18e86881c84dd03c5d33db65af272", - } - // Template of public values - let public_values: List = [8827, 7] - - verify_compressed(vk, pk, public_values) -} diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak index d64abff..f621a6e 100644 --- a/zkp/lib/plonk/proofs.ak +++ b/zkp/lib/plonk/proofs.ak @@ -3,7 +3,7 @@ use aiken/collection/list use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/scalar.{Scalar} use aiken/primitive/bytearray -use common/common.{concat_and_blake2b_224, scalar_bs_rt} +use common/common.{concat_and_blake2b_224, scalar_rt_bs} use plonk/preinputs.{PreparedPlonkPreInputs} pub type PlonkProof { @@ -53,10 +53,10 @@ pub fn plonk_prepare_proof( proof.commitment_b, proof.commitment_c, ] - let beta = scalar_bs_rt(concat_and_blake2b_224(beta_inputs)) - let gamma = scalar_bs_rt(blake2b_224(beta)) + let beta = scalar_rt_bs(concat_and_blake2b_224(beta_inputs)) + let gamma = scalar_rt_bs(blake2b_224(beta)) let alpha = - scalar_bs_rt(concat_and_blake2b_224([beta, gamma, proof.commitment_z])) + scalar_rt_bs(concat_and_blake2b_224([beta, gamma, proof.commitment_z])) let zeta = scalar.from_bytes( concat_and_blake2b_224([alpha, proof.t_low, proof.t_mid, proof.t_high]), diff --git a/zkp/lib/plonk/raw.ak b/zkp/lib/plonk/raw.ak new file mode 100644 index 0000000..c538b79 --- /dev/null +++ b/zkp/lib/plonk/raw.ak @@ -0,0 +1,57 @@ +// use plonk/preinputs.{PlonkPreInputs} +use common/common.{G1Point, G2Point} + +pub type PlonkVerificationKey { + n_public: Int, + power: Int, + k1: Int, + k2: Int, + q_m: G1Point, + q_l: G1Point, + q_r: G1Point, + q_o: G1Point, + q_c: G1Point, + s_sig1: G1Point, + s_sig2: G1Point, + s_sig3: G1Point, + x2: G2Point, + w: Int, +} + +pub type RawPlonkProof { + commitment_a: G1Point, + commitment_b: G1Point, + commitment_c: G1Point, + commitment_z: G1Point, + t_low: G1Point, + t_mid: G1Point, + t_high: G1Point, + w_omega: G1Point, + w_omega_zeta: G1Point, + a_eval: Int, + b_eval: Int, + c_eval: Int, + s_sig1_p: Int, + s_sig2_p: Int, + z_omega: Int, +} + +// pub fn vkey_to_preinputs(vkey: PlonkVerificationKey) -> PlonkPreInputs { +// // PlonkPreInputs { +// // n_public: vkey.n_public, +// // power: vkey.power, +// // k1: vkey.k1, +// // k2: vkey.k2, +// // q_m: vkey.q_m, +// // q_l: vkey.q_l, +// // q_r: vkey.q_r, +// // q_o: vkey.q_o, +// // q_c: vkey.q_c, +// // s_sig1: vkey.s_sig1, +// // s_sig2: vkey.s_sig2, +// // s_sig3: vkey.s_sig3, +// // x2: vkey.x2, +// // w: vkey.w, +// // } +// TODO +// } \ No newline at end of file diff --git a/zkp/lib/tests/groth_tests.ak b/zkp/lib/tests/groth_tests.ak new file mode 100644 index 0000000..3aa66c6 --- /dev/null +++ b/zkp/lib/tests/groth_tests.ak @@ -0,0 +1,85 @@ +use groth/groth.{CompressedVK, CompressedProof, verify_compressed} + +test groth_verify_pass_1() { + // Template of VK + let vk: CompressedVK = + CompressedVK { + alpha: #"8e3d9e248feda194cb6fa0a3b64fd2a380cb5e94836bf8148bf97ebcbb5819d9a78f63102f0293c104bcbb2f810d8eb4", + beta: #"8cd68a7186a908212680a0234d8210c20328f8fb3ce1d69c9aec9330a5802d6cfaf6d7cf3176133221c19188590cb4141874ea7bbfcb9872931e115d882c46b90c3dcbcee10062d1c9b9b0a691d7bec7d2735f06495c7f71dea210e55b2782df", + gamma: #"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", + delta: #"938231fcec443fbdeb1079ff126b8f69bd8579ffe82d39923214d4345395beee60200288fa20c97ae50f3212131b6f8802af2f9f515c65af6a9a6c294c738590104376a0af44731d6699db6a286608774243f7d1dddc4605eb340e65e15060a5", + vkIC: [ + #"b5813c90d3455acb8608fdf66e8601f24ef048f3fdf9384862d77c72cb5cddfde2b307976b1b0319c42ba985f94be60a", + #"a41a0e6370c054be0f9acce08e97f6d5702d9daa9e9a934e8a377f553593baa1c58062adc73d63558653422d54d6f50c", + #"8e02a87c519c3145f984d25fdf769f74fbc36626c385d4554fb4bc1d7a12cbf669d40f257023b8b3c9a31e631aa8f981", + ], + } + + // Template of Proof + let pk: CompressedProof = + CompressedProof { + a: #"8b84d092731c653b1accdda79c51e3f5d289bed7311189d927deadef0470e437e6d1d400634726512a79a015867424e3", + b: #"92cb1c125816e4b522c7f430a5d74a61116b6189de7b2341f040194c02f10d9ef0cf081f4029444a65ea74e69d98b1cf08d3864087d5d2dee2ed6ab102f9b78e65d341f0824341a9fc25d0ea9dacccc5d355b4eddb0057949370a19c47135b0e", + c: #"a4ef633c858a3ff194db50eacdf715f7296fb3d1202c54b543284e9656b69aa90f33ac0e2572d3ab847b88268dcd1f7e", + } + // Template of public values + let public_values: List = [561, 3] + + verify_compressed(vk, pk, public_values) +} + +test groth_verify_fail_1() fail { + // Template of VK + let vk: CompressedVK = + CompressedVK { + alpha: #"8e3d9e248feda194cb6fa0a3b64fd2a380cb5e94836bf8148bf97ebcbb5819d9a78f63102f0293c104bcbb2f810d8eb4", + beta: #"8cd68a7186a908212680a0234d8210c20328f8fb3ce1d69c9aec9330a5802d6cfaf6d7cf3176133221c19188590cb4141874ea7bbfcb9872931e115d882c46b90c3dcbcee10062d1c9b9b0a691d7bec7d2735f06495c7f71dea210e55b2782df", + gamma: #"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", + delta: #"938231fcec443fbdeb1079ff126b8f69bd8579ffe82d39923214d4345395beee60200288fa20c97ae50f3212131b6f8802af2f9f515c65af6a9a6c294c738590104376a0af44731d6699db6a286608774243f7d1dddc4605eb340e65e15060a5", + vkIC: [ + #"b5813c90d3455acb8608fdf66e8601f24ef048f3fdf9384862d77c72cb5cddfde2b307976b1b0319c42ba985f94be60a", + #"a41a0e6370c054be0f9acce08e97f6d5702d9daa9e9a934e8a377f553593baa1c58062adc73d63558653422d54d6f50c", + #"8e02a87c519c3145f984d25fdf769f74fbc36626c385d4554fb4bc1d7a12cbf669d40f257023b8b3c9a31e631aa8f981", + ], + } + + // Template of Proof + let pk: CompressedProof = + CompressedProof { + a: #"a4ef633c858a3ff194db50eacdf715f7296fb3d1202c54b543284e9656b69aa90f33ac0e2572d3ab847b88268dcd1f7e", + b: #"92cb1c125816e4b522c7f430a5d74a61116b6189de7b2341f040194c02f10d9ef0cf081f4029444a65ea74e69d98b1cf08d3864087d5d2dee2ed6ab102f9b78e65d341f0824341a9fc25d0ea9dacccc5d355b4eddb0057949370a19c47135b0e", + c: #"8b84d092731c653b1accdda79c51e3f5d289bed7311189d927deadef0470e437e6d1d400634726512a79a015867424e3", + } + // Template of public values + let public_values: List = [561, 3] + + verify_compressed(vk, pk, public_values) +} + +test groth_verify_pass_2() { + // Template of VK + let vk: CompressedVK = + CompressedVK { + alpha: #"8e3d9e248feda194cb6fa0a3b64fd2a380cb5e94836bf8148bf97ebcbb5819d9a78f63102f0293c104bcbb2f810d8eb4", + beta: #"8cd68a7186a908212680a0234d8210c20328f8fb3ce1d69c9aec9330a5802d6cfaf6d7cf3176133221c19188590cb4141874ea7bbfcb9872931e115d882c46b90c3dcbcee10062d1c9b9b0a691d7bec7d2735f06495c7f71dea210e55b2782df", + gamma: #"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", + delta: #"938231fcec443fbdeb1079ff126b8f69bd8579ffe82d39923214d4345395beee60200288fa20c97ae50f3212131b6f8802af2f9f515c65af6a9a6c294c738590104376a0af44731d6699db6a286608774243f7d1dddc4605eb340e65e15060a5", + vkIC: [ + #"b5813c90d3455acb8608fdf66e8601f24ef048f3fdf9384862d77c72cb5cddfde2b307976b1b0319c42ba985f94be60a", + #"a41a0e6370c054be0f9acce08e97f6d5702d9daa9e9a934e8a377f553593baa1c58062adc73d63558653422d54d6f50c", + #"8e02a87c519c3145f984d25fdf769f74fbc36626c385d4554fb4bc1d7a12cbf669d40f257023b8b3c9a31e631aa8f981", + ], + } + + // Template of Proof + let pk: CompressedProof = + CompressedProof { + a: #"98ca847cc04a6f67ac85a628521450323d7aa5335d4c2c48e9780b659cf7ea8ece2d0b305c9ff9dcfb3e548d61bbaebe", + b: #"a8e6ba4dbce6aa84de8ca1cd39d42353fcac89fe8cb800e728ada3ca4ae3b07baa68f76b9e4fa73eebf78cc609fa85d6166a3b69cd08cc59f2ff36e52dfdf231540a4212fdd4a142504c76066bddea342dd0183b2b11ed62cfc1497189a4db52", + c: #"8bc8cc3f11483138cc55d5f0389e67231f9e8465e5cb4a5a668e6e298d5c4febb2a18e86881c84dd03c5d33db65af272", + } + // Template of public values + let public_values: List = [8827, 7] + + verify_compressed(vk, pk, public_values) +} diff --git a/zkp/lib/tests/plonk_tests.ak b/zkp/lib/tests/plonk_tests.ak new file mode 100644 index 0000000..9ef36e4 --- /dev/null +++ b/zkp/lib/tests/plonk_tests.ak @@ -0,0 +1,118 @@ +use plonk/raw.{RawPlonkProof, PlonkVerificationKey} +use common/common.{Point, G2Point} + +pub const test_plonk_proof: RawPlonkProof = RawPlonkProof { + commitment_a: Point { + x: 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, + y: 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, + z: 1 + }, + commitment_b: Point { + x: 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, + y: 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + z: 1 + }, + commitment_c: Point { + x: 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, + y: 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + z: 1 + }, + commitment_z: Point { + x: 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, + y: 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + z: 1 + }, + t_low: Point { + x: 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, + y: 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + z: 1 + }, + t_mid: Point { + x: 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, + y: 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, + z: 1 + }, + t_high: Point { + x: 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, + y: 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, + z: 1 + }, + w_omega: Point { + x: 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, + y: 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, + z: 1 + }, + w_omega_zeta: Point { + x: 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, + y: 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, + z: 1 + }, + a_eval: 18376945509548979154234285967502303667654384864417109878730063780807771386804, + b_eval: 48091350738882108076678452440283491448151427314124311219043220475171874763276, + c_eval: 19995498867920222404318895039685100281891200599706204591987499224278246956716, + s_sig1_p: 22639172691520838707669313728412965432032750722138107727665998164239763135904, + s_sig2_p: 38226063540390035205748121923613940584145755981537302966237851066336751425207, + z_omega: 46342921279167233860927215620995843068292504644614777821809165775517811410811, +} + +pub const test_plonk_vkey: PlonkVerificationKey = PlonkVerificationKey { + n_public: 2, + power: 8, + k1: 2, + k2: 3, + q_m: Point { + x: 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, + y: 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, + z: 1 + }, + q_l: Point { + x: 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, + y: 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, + z: 1 + }, + q_r: Point { + x: 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, + y: 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, + z: 1 + }, + q_o: Point { + x: 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, + y: 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, + z: 1 + }, + q_c: Point { + x: 0, + y: 1, + z: 1 + }, + s_sig1: Point { + x: 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, + y: 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, + z: 1 + }, + s_sig2: Point { + x: 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, + y: 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, + z: 1 + }, + s_sig3: Point { + x: 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, + y: 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, + z: 1 + }, + x2: G2Point { + x: ( + 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, + 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, + ), + y: ( + 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, + 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, + ), + z: ( + 1, + 0 + ) + }, + w: 21071158244812412064791010377580296085971058123779034548857891862303448703672 +} \ No newline at end of file From ccf5e5400be9bb73f9a66599cdf987935d89e749 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 26 Jan 2026 19:45:51 -0800 Subject: [PATCH 12/25] reformat --- zkp/lib/common/common.ak | 8 +- zkp/lib/plonk/raw.ak | 31 ++++---- zkp/lib/tests/groth_tests.ak | 2 +- zkp/lib/tests/plonk_tests.ak | 141 +++++++++++++++++------------------ 4 files changed, 85 insertions(+), 97 deletions(-) diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index 2baf5b9..73870f6 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -187,13 +187,7 @@ pub fn g1_from_raw(raw: List) -> Option { when raw is { [] -> None [_] -> None - [x, y, ..] -> { - Some(Point { - x: scalar_rt_int(x), - y: scalar_rt_int(y), - z: 1, - }) - } + [x, y, ..] -> Some(Point { x: scalar_rt_int(x), y: scalar_rt_int(y), z: 1 }) } } diff --git a/zkp/lib/plonk/raw.ak b/zkp/lib/plonk/raw.ak index c538b79..2c325f9 100644 --- a/zkp/lib/plonk/raw.ak +++ b/zkp/lib/plonk/raw.ak @@ -2,20 +2,20 @@ use common/common.{G1Point, G2Point} pub type PlonkVerificationKey { - n_public: Int, - power: Int, - k1: Int, - k2: Int, - q_m: G1Point, - q_l: G1Point, - q_r: G1Point, - q_o: G1Point, - q_c: G1Point, - s_sig1: G1Point, - s_sig2: G1Point, - s_sig3: G1Point, - x2: G2Point, - w: Int, + n_public: Int, + power: Int, + k1: Int, + k2: Int, + q_m: G1Point, + q_l: G1Point, + q_r: G1Point, + q_o: G1Point, + q_c: G1Point, + s_sig1: G1Point, + s_sig2: G1Point, + s_sig3: G1Point, + x2: G2Point, + w: Int, } pub type RawPlonkProof { @@ -35,7 +35,6 @@ pub type RawPlonkProof { s_sig2_p: Int, z_omega: Int, } - // pub fn vkey_to_preinputs(vkey: PlonkVerificationKey) -> PlonkPreInputs { // // PlonkPreInputs { // // n_public: vkey.n_public, @@ -54,4 +53,4 @@ pub type RawPlonkProof { // // w: vkey.w, // // } // TODO -// } \ No newline at end of file +// } diff --git a/zkp/lib/tests/groth_tests.ak b/zkp/lib/tests/groth_tests.ak index 3aa66c6..0f46c0e 100644 --- a/zkp/lib/tests/groth_tests.ak +++ b/zkp/lib/tests/groth_tests.ak @@ -1,4 +1,4 @@ -use groth/groth.{CompressedVK, CompressedProof, verify_compressed} +use groth/groth.{CompressedProof, CompressedVK, verify_compressed} test groth_verify_pass_1() { // Template of VK diff --git a/zkp/lib/tests/plonk_tests.ak b/zkp/lib/tests/plonk_tests.ak index 9ef36e4..b14a165 100644 --- a/zkp/lib/tests/plonk_tests.ak +++ b/zkp/lib/tests/plonk_tests.ak @@ -1,51 +1,52 @@ -use plonk/raw.{RawPlonkProof, PlonkVerificationKey} -use common/common.{Point, G2Point} +use common/common.{G2Point, Point} +use plonk/raw.{PlonkVerificationKey, RawPlonkProof} -pub const test_plonk_proof: RawPlonkProof = RawPlonkProof { +pub const test_plonk_proof: RawPlonkProof = + RawPlonkProof { commitment_a: Point { - x: 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, - y: 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, - z: 1 + x: 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, + y: 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, + z: 1, }, commitment_b: Point { - x: 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, - y: 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, - z: 1 + x: 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, + y: 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + z: 1, }, commitment_c: Point { - x: 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, - y: 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, - z: 1 + x: 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, + y: 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + z: 1, }, commitment_z: Point { - x: 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, - y: 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, - z: 1 + x: 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, + y: 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + z: 1, }, t_low: Point { - x: 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, - y: 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, - z: 1 + x: 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, + y: 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + z: 1, }, t_mid: Point { - x: 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, - y: 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, - z: 1 + x: 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, + y: 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, + z: 1, }, t_high: Point { - x: 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, - y: 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, - z: 1 + x: 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, + y: 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, + z: 1, }, w_omega: Point { - x: 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, - y: 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, - z: 1 + x: 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, + y: 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, + z: 1, }, w_omega_zeta: Point { - x: 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, - y: 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, - z: 1 + x: 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, + y: 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, + z: 1, }, a_eval: 18376945509548979154234285967502303667654384864417109878730063780807771386804, b_eval: 48091350738882108076678452440283491448151427314124311219043220475171874763276, @@ -53,66 +54,60 @@ pub const test_plonk_proof: RawPlonkProof = RawPlonkProof { s_sig1_p: 22639172691520838707669313728412965432032750722138107727665998164239763135904, s_sig2_p: 38226063540390035205748121923613940584145755981537302966237851066336751425207, z_omega: 46342921279167233860927215620995843068292504644614777821809165775517811410811, -} + } -pub const test_plonk_vkey: PlonkVerificationKey = PlonkVerificationKey { +pub const test_plonk_vkey: PlonkVerificationKey = + PlonkVerificationKey { n_public: 2, power: 8, k1: 2, k2: 3, q_m: Point { - x: 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, - y: 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, - z: 1 + x: 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, + y: 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, + z: 1, }, q_l: Point { - x: 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, - y: 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, - z: 1 + x: 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, + y: 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, + z: 1, }, q_r: Point { - x: 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, - y: 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, - z: 1 + x: 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, + y: 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, + z: 1, }, q_o: Point { - x: 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, - y: 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, - z: 1 - }, - q_c: Point { - x: 0, - y: 1, - z: 1 + x: 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, + y: 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, + z: 1, }, + q_c: Point { x: 0, y: 1, z: 1 }, s_sig1: Point { - x: 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, - y: 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, - z: 1 + x: 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, + y: 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, + z: 1, }, s_sig2: Point { - x: 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, - y: 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, - z: 1 + x: 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, + y: 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, + z: 1, }, s_sig3: Point { - x: 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, - y: 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, - z: 1 + x: 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, + y: 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, + z: 1, }, x2: G2Point { - x: ( - 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, - 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, - ), - y: ( - 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, - 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, - ), - z: ( - 1, - 0 - ) - }, - w: 21071158244812412064791010377580296085971058123779034548857891862303448703672 -} \ No newline at end of file + x: ( + 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, + 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, + ), + y: ( + 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, + 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, + ), + z: (1, 0), + }, + w: 21071158244812412064791010377580296085971058123779034548857891862303448703672, + } From ad99c5390287baa1caff6e538446d885eed801a7 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Wed, 28 Jan 2026 15:50:23 -0800 Subject: [PATCH 13/25] raw input preparation utilities --- zkp/lib/plonk/raw.ak | 68 ++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/zkp/lib/plonk/raw.ak b/zkp/lib/plonk/raw.ak index 2c325f9..eacbfd6 100644 --- a/zkp/lib/plonk/raw.ak +++ b/zkp/lib/plonk/raw.ak @@ -1,5 +1,7 @@ -// use plonk/preinputs.{PlonkPreInputs} -use common/common.{G1Point, G2Point} +use plonk/preinputs.{PreparedPlonkPreInputs, PlonkPreInputs, plonk_prepare_preinputs} +use plonk/proofs.{PlonkProof} +use common/common.{G1Point, G2Point, g1_compress, g2_compress} +use aiken/crypto/bls12_381/scalar pub type PlonkVerificationKey { n_public: Int, @@ -35,22 +37,46 @@ pub type RawPlonkProof { s_sig2_p: Int, z_omega: Int, } -// pub fn vkey_to_preinputs(vkey: PlonkVerificationKey) -> PlonkPreInputs { -// // PlonkPreInputs { -// // n_public: vkey.n_public, -// // power: vkey.power, -// // k1: vkey.k1, -// // k2: vkey.k2, -// // q_m: vkey.q_m, -// // q_l: vkey.q_l, -// // q_r: vkey.q_r, -// // q_o: vkey.q_o, -// // q_c: vkey.q_c, -// // s_sig1: vkey.s_sig1, -// // s_sig2: vkey.s_sig2, -// // s_sig3: vkey.s_sig3, -// // x2: vkey.x2, -// // w: vkey.w, -// // } -// TODO -// } + +pub fn vkey_to_prepared_preinputs(vkey: PlonkVerificationKey) -> PreparedPlonkPreInputs { + let preinputs = PlonkPreInputs { + power: vkey.power, + k1: scalar.from_int(vkey.k1), + k2: scalar.from_int(vkey.k2), + q_m: g1_compress(vkey.q_m), + q_l: g1_compress(vkey.q_l), + q_r: g1_compress(vkey.q_r), + q_o: g1_compress(vkey.q_o), + q_c: g1_compress(vkey.q_c), + s_sig1: g1_compress(vkey.s_sig1), + s_sig2: g1_compress(vkey.s_sig2), + s_sig3: g1_compress(vkey.s_sig3), + x2: g2_compress(vkey.x2), + } + + plonk_prepare_preinputs( + inputs: preinputs, + generator: scalar.from_int(vkey.w), + n_public: vkey.n_public + ) +} + +pub fn prepare_raw_proof(proof: RawPlonkProof) -> PlonkProof { + PlonkProof { + commitment_a: g1_compress(proof.commitment_a), + commitment_b: g1_compress(proof.commitment_b), + commitment_c: g1_compress(proof.commitment_c), + commitment_z: g1_compress(proof.commitment_z), + t_low: g1_compress(proof.t_low), + t_mid: g1_compress(proof.t_mid), + t_high: g1_compress(proof.t_high), + w_omega: g1_compress(proof.w_omega), + w_omega_zeta: g1_compress(proof.w_omega_zeta), + a_eval: proof.a_eval, + b_eval: proof.b_eval, + c_eval: proof.c_eval, + s_sig1_p: proof.s_sig1_p, + s_sig2_p: proof.s_sig2_p, + z_omega: proof.z_omega, + } +} \ No newline at end of file From c4c16e33fb2a08031410065c056e8914ac16fe6b Mon Sep 17 00:00:00 2001 From: samdelaney Date: Wed, 28 Jan 2026 16:19:11 -0800 Subject: [PATCH 14/25] format raw.ak --- zkp/lib/plonk/raw.ak | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/zkp/lib/plonk/raw.ak b/zkp/lib/plonk/raw.ak index eacbfd6..94bd47b 100644 --- a/zkp/lib/plonk/raw.ak +++ b/zkp/lib/plonk/raw.ak @@ -1,7 +1,9 @@ -use plonk/preinputs.{PreparedPlonkPreInputs, PlonkPreInputs, plonk_prepare_preinputs} -use plonk/proofs.{PlonkProof} -use common/common.{G1Point, G2Point, g1_compress, g2_compress} use aiken/crypto/bls12_381/scalar +use common/common.{G1Point, G2Point, g1_compress, g2_compress} +use plonk/preinputs.{ + PlonkPreInputs, PreparedPlonkPreInputs, plonk_prepare_preinputs, +} +use plonk/proofs.{PlonkProof} pub type PlonkVerificationKey { n_public: Int, @@ -38,8 +40,11 @@ pub type RawPlonkProof { z_omega: Int, } -pub fn vkey_to_prepared_preinputs(vkey: PlonkVerificationKey) -> PreparedPlonkPreInputs { - let preinputs = PlonkPreInputs { +pub fn vkey_to_prepared_preinputs( + vkey: PlonkVerificationKey, +) -> PreparedPlonkPreInputs { + let preinputs = + PlonkPreInputs { power: vkey.power, k1: scalar.from_int(vkey.k1), k2: scalar.from_int(vkey.k2), @@ -54,11 +59,11 @@ pub fn vkey_to_prepared_preinputs(vkey: PlonkVerificationKey) -> PreparedPlonkPr x2: g2_compress(vkey.x2), } - plonk_prepare_preinputs( - inputs: preinputs, - generator: scalar.from_int(vkey.w), - n_public: vkey.n_public - ) + plonk_prepare_preinputs( + inputs: preinputs, + generator: scalar.from_int(vkey.w), + n_public: vkey.n_public, + ) } pub fn prepare_raw_proof(proof: RawPlonkProof) -> PlonkProof { @@ -79,4 +84,4 @@ pub fn prepare_raw_proof(proof: RawPlonkProof) -> PlonkProof { s_sig2_p: proof.s_sig2_p, z_omega: proof.z_omega, } -} \ No newline at end of file +} From cb6fc1f2a0f33b46f0c604e6959c5e80912c9a20 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 2 Feb 2026 09:45:10 -0800 Subject: [PATCH 15/25] affine point compression --- zkp/lib/common/blst_affine.ak | 86 +++++++++++++++ zkp/lib/plonk/raw_affine.ak | 88 +++++++++++++++ zkp/lib/tests/affine_compression_tests.ak | 124 ++++++++++++++++++++++ zkp/lib/tests/test_utils.ak | 6 ++ 4 files changed, 304 insertions(+) create mode 100644 zkp/lib/common/blst_affine.ak create mode 100644 zkp/lib/plonk/raw_affine.ak create mode 100644 zkp/lib/tests/affine_compression_tests.ak create mode 100644 zkp/lib/tests/test_utils.ak diff --git a/zkp/lib/common/blst_affine.ak b/zkp/lib/common/blst_affine.ak new file mode 100644 index 0000000..09495d1 --- /dev/null +++ b/zkp/lib/common/blst_affine.ak @@ -0,0 +1,86 @@ +use aiken/primitive/bytearray + +/// Affine representation of a point on the BLS12-381 G1 curve +pub type G1Affine { + /// Point at infinity + G1Infinity + /// Finite point with x and y coordinates + G1Point { x: ByteArray, y: ByteArray } +} + +/// Affine representation of a point on the BLS12-381 G2 curve +pub type G2Affine { + /// Point at infinity + G2Infinity + /// Finite point with x and y coordinates (each coordinate is 2 field elements) + G2Point { x: (ByteArray, ByteArray), y: (ByteArray, ByteArray) } +} + +/// Compress a G1 affine point to 48 bytes using BLS12-381 standard +pub fn g1_affine_compress(point: G1Affine) -> ByteArray { + when point is { + G1Infinity -> { + // BLS12-381 infinity encoding: compression bit (0x80) + infinity bit (0x40) = 0xc0 + let infinity_byte = #"c0" + let zeros = bytearray.from_int_big_endian(0, 47) + // 47 bytes of zeros + bytearray.concat(infinity_byte, zeros) + } + G1Point { x, y } -> { + // Manual BLS12-381 G1 compression + // Set compression bit (0x80) and determine y-coordinate lexicographic ordering + let first_x_byte = bytearray.take(x, 1) + let rest_x = bytearray.drop(x, 1) + // Check if y is lexicographically larger than (p-y) mod p + // For now, we'll use a simple heuristic: check if y's first byte > 0x7f + let first_y_byte = bytearray.take(y, 1) + let y_is_larger = bytearray.to_int_big_endian(first_y_byte) >= 128 + // Set the compression bit (0x80) and y-coordinate bit if needed + let compressed_first_byte = + if y_is_larger { + bytearray.or_bytes(first_x_byte, #"a0", False) + } else { + // 0x80 | 0x20 + bytearray.or_bytes(first_x_byte, #"80", False) + } + // 0x80 only + bytearray.concat(compressed_first_byte, rest_x) + } + } +} + +/// Compress a G2 affine point to 96 bytes using BLS12-381 standard +pub fn g2_affine_compress(point: G2Affine) -> ByteArray { + when point is { + G2Infinity -> { + // BLS12-381 G2 infinity encoding: compression bit (0x80) + infinity bit (0x40) = 0xc0 + let infinity_byte = #"c0" + let zeros = bytearray.from_int_big_endian(0, 95) + // 95 bytes of zeros + bytearray.concat(infinity_byte, zeros) + } + G2Point { x: (x0, x1), y: (y0, _y1) } -> { + // Manual BLS12-381 G2 compression + // G2 compression is similar to G1 but works on Fp2 elements + // Each coordinate is a pair of field elements (x0, x1) representing x0 + x1*u + // Set compression bit on the first byte of x0 (the higher-order part) + let first_x0_byte = bytearray.take(x0, 1) + let rest_x0 = bytearray.drop(x0, 1) + // Determine lexicographic ordering of y-coordinate + // For G2, we compare y with (p-y) in Fp2 ordering + let first_y0_byte = bytearray.take(y0, 1) + let y_is_larger = bytearray.to_int_big_endian(first_y0_byte) >= 128 + // Set compression and y-ordering bits + let compressed_first_byte = + if y_is_larger { + bytearray.or_bytes(first_x0_byte, #"a0", False) + } else { + // 0x80 | 0x20 + bytearray.or_bytes(first_x0_byte, #"80", False) + } + // 0x80 only + // Return compressed form: compressed_x0 + x1 (96 bytes total) + bytearray.concat(bytearray.concat(compressed_first_byte, rest_x0), x1) + } + } +} diff --git a/zkp/lib/plonk/raw_affine.ak b/zkp/lib/plonk/raw_affine.ak new file mode 100644 index 0000000..91f3d1c --- /dev/null +++ b/zkp/lib/plonk/raw_affine.ak @@ -0,0 +1,88 @@ +use aiken/crypto/bls12_381/scalar +use common/blst_affine.{ + G1Affine, G2Affine, g1_affine_compress, g2_affine_compress, +} +use plonk/preinputs.{ + PlonkPreInputs, PreparedPlonkPreInputs, plonk_prepare_preinputs, +} +use plonk/proofs.{PlonkProof} + +pub type AffineVerificationKey { + n_public: Int, + power: Int, + k1: Int, + k2: Int, + q_m: G1Affine, + q_l: G1Affine, + q_r: G1Affine, + q_o: G1Affine, + q_c: G1Affine, + s_sig1: G1Affine, + s_sig2: G1Affine, + s_sig3: G1Affine, + x2: G2Affine, + w: Int, +} + +pub type AffineProof { + commitment_a: G1Affine, + commitment_b: G1Affine, + commitment_c: G1Affine, + commitment_z: G1Affine, + t_low: G1Affine, + t_mid: G1Affine, + t_high: G1Affine, + w_omega: G1Affine, + w_omega_zeta: G1Affine, + a_eval: Int, + b_eval: Int, + c_eval: Int, + s_sig1_p: Int, + s_sig2_p: Int, + z_omega: Int, +} + +pub fn prepare_affine_vkey( + vkey: AffineVerificationKey, +) -> PreparedPlonkPreInputs { + let preinputs = + PlonkPreInputs { + power: vkey.power, + k1: scalar.from_int(vkey.k1), + k2: scalar.from_int(vkey.k2), + q_m: g1_affine_compress(vkey.q_m), + q_l: g1_affine_compress(vkey.q_l), + q_r: g1_affine_compress(vkey.q_r), + q_o: g1_affine_compress(vkey.q_o), + q_c: g1_affine_compress(vkey.q_c), + s_sig1: g1_affine_compress(vkey.s_sig1), + s_sig2: g1_affine_compress(vkey.s_sig2), + s_sig3: g1_affine_compress(vkey.s_sig3), + x2: g2_affine_compress(vkey.x2), + } + plonk_prepare_preinputs( + inputs: preinputs, + generator: scalar.from_int(vkey.w), + n_public: vkey.n_public, + ) +} + +pub fn prepare_affine_proof(proof: AffineProof) -> PlonkProof { + PlonkProof { + commitment_a: g1_affine_compress(proof.commitment_a), + commitment_b: g1_affine_compress(proof.commitment_b), + commitment_c: g1_affine_compress(proof.commitment_c), + commitment_z: g1_affine_compress(proof.commitment_z), + t_low: g1_affine_compress(proof.t_low), + t_mid: g1_affine_compress(proof.t_mid), + t_high: g1_affine_compress(proof.t_high), + w_omega: g1_affine_compress(proof.w_omega), + w_omega_zeta: g1_affine_compress(proof.w_omega_zeta), + a_eval: proof.a_eval, + b_eval: proof.b_eval, + c_eval: proof.c_eval, + s_sig1_p: proof.s_sig1_p, + s_sig2_p: proof.s_sig2_p, + z_omega: proof.z_omega, + } +} diff --git a/zkp/lib/tests/affine_compression_tests.ak b/zkp/lib/tests/affine_compression_tests.ak new file mode 100644 index 0000000..157bf82 --- /dev/null +++ b/zkp/lib/tests/affine_compression_tests.ak @@ -0,0 +1,124 @@ +use aiken/primitive/bytearray +use common/blst_affine.{ + G1Infinity, G1Point, G2Infinity, G2Point, g1_affine_compress, + g2_affine_compress, +} +use tests/test_utils.{int_to_field_bytes} + +const g1_a = + G1Point { + x: int_to_field_bytes( + 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, + ), + y: int_to_field_bytes( + 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + ), + } + +const g1_b = + G1Point { + x: int_to_field_bytes( + 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, + ), + y: int_to_field_bytes( + 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + ), + } + +const g2_a = + G2Point { + x: ( + int_to_field_bytes( + 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, + ), + int_to_field_bytes( + 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + ), + ), + y: ( + int_to_field_bytes( + 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, + ), + int_to_field_bytes( + 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + ), + ), + } + +// Test G1 compression format +test test_g1_compression_format() { + let point = g1_a + let compressed = g1_affine_compress(point) + // Verify compressed format: should be 48 bytes with compression bit set + let compressed_length = bytearray.length(compressed) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let has_compression_bit = first_byte >= 128 + // Check if bit 7 is set (0x80) + compressed_length == 48 && has_compression_bit +} + +test test_g1_infinity_compression() { + let compressed = g1_affine_compress(G1Infinity) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let length = bytearray.length(compressed) + // Should have compression bit (0x80) + infinity bit (0x40) = 0xc0 = 192 + let is_infinity_encoding = first_byte == 192 + let correct_length = length == 48 + is_infinity_encoding && correct_length +} + +test test_g1_different_points_different_compression() { + let compressed1 = g1_affine_compress(g1_a) + let compressed2 = g1_affine_compress(g1_b) + // Different points should compress to different values + compressed1 != compressed2 +} + +test test_g2_compression_format() { + expect G2Point { x: x2, y: y2 } = g2_a + let compressed = g2_affine_compress(G2Point(x2, y2)) + let compressed_length = bytearray.length(compressed) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let has_compression_bit = first_byte >= 128 + // G2 compressed should be 96 bytes with compression bit set + compressed_length == 96 && has_compression_bit +} + +test test_g2_infinity_compression() { + let compressed = g2_affine_compress(G2Infinity) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let length = bytearray.length(compressed) + // Should have compression + infinity bits + let is_infinity_encoding = first_byte == 192 + // 0xc0 + let correct_length = length == 96 + is_infinity_encoding && correct_length +} + +test test_g2_infinity_debug() { + let compressed = g2_affine_compress(G2Infinity) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + // Check just the first byte + first_byte == 192 +} + +test test_g2_infinity_length() { + let compressed = g2_affine_compress(G2Infinity) + let length = bytearray.length(compressed) + // Check only the length + length == 96 +} + +// Test that y-coordinate ordering bit is being set correctly +test test_y_ordering_bit() { + expect G1Point { y, .. } = g1_a + let compressed = g1_affine_compress(g1_a) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + // Extract y-ordering bit (bit 5, value 32) + let y_bit_set = first_byte % 64 >= 32 + // Check if bit 5 is set + // This should match our y_is_larger logic from the compression + let first_y_byte = bytearray.take(y, 1) + let expected_y_larger = bytearray.to_int_big_endian(first_y_byte) >= 128 + y_bit_set == expected_y_larger +} diff --git a/zkp/lib/tests/test_utils.ak b/zkp/lib/tests/test_utils.ak new file mode 100644 index 0000000..6a5d44c --- /dev/null +++ b/zkp/lib/tests/test_utils.ak @@ -0,0 +1,6 @@ +use aiken/primitive/bytearray + +/// Convert Int to 48-byte ByteArray for BLS12-381 field elements +pub fn int_to_field_bytes(value: Int) -> ByteArray { + bytearray.from_int_big_endian(value, 48) +} From 95a8e91be44853cefc6950448386abe1031d7abd Mon Sep 17 00:00:00 2001 From: samdelaney Date: Wed, 4 Feb 2026 18:35:41 -0800 Subject: [PATCH 16/25] affine plonk verification - needs optimization --- zkp/lib/tests/plonk_affine_tests.ak | 192 ++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 zkp/lib/tests/plonk_affine_tests.ak diff --git a/zkp/lib/tests/plonk_affine_tests.ak b/zkp/lib/tests/plonk_affine_tests.ak new file mode 100644 index 0000000..49950a4 --- /dev/null +++ b/zkp/lib/tests/plonk_affine_tests.ak @@ -0,0 +1,192 @@ +use common/blst_affine.{G1Infinity, G1Point, G2Point} +use plonk/proofs.{plonk_prepare_proof} +use plonk/raw_affine.{ + AffineProof, AffineVerificationKey, prepare_affine_proof, prepare_affine_vkey, +} +use plonk/verifier.{verify_plonk} +use tests/test_utils.{int_to_field_bytes} + +pub const test_plonk_proof_affine: AffineProof = + AffineProof { + commitment_a: G1Point { + x: int_to_field_bytes( + 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, + ), + y: int_to_field_bytes( + 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, + ), + }, + commitment_b: G1Point { + x: int_to_field_bytes( + 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, + ), + y: int_to_field_bytes( + 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + ), + }, + commitment_c: G1Point { + x: int_to_field_bytes( + 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, + ), + y: int_to_field_bytes( + 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + ), + }, + commitment_z: G1Point { + x: int_to_field_bytes( + 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, + ), + y: int_to_field_bytes( + 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + ), + }, + t_low: G1Point { + x: int_to_field_bytes( + 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, + ), + y: int_to_field_bytes( + 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + ), + }, + t_mid: G1Point { + x: int_to_field_bytes( + 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, + ), + y: int_to_field_bytes( + 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, + ), + }, + t_high: G1Point { + x: int_to_field_bytes( + 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, + ), + y: int_to_field_bytes( + 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, + ), + }, + w_omega: G1Point { + x: int_to_field_bytes( + 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, + ), + y: int_to_field_bytes( + 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, + ), + }, + w_omega_zeta: G1Point { + x: int_to_field_bytes( + 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, + ), + y: int_to_field_bytes( + 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, + ), + }, + a_eval: 18376945509548979154234285967502303667654384864417109878730063780807771386804, + b_eval: 48091350738882108076678452440283491448151427314124311219043220475171874763276, + c_eval: 19995498867920222404318895039685100281891200599706204591987499224278246956716, + s_sig1_p: 22639172691520838707669313728412965432032750722138107727665998164239763135904, + s_sig2_p: 38226063540390035205748121923613940584145755981537302966237851066336751425207, + z_omega: 46342921279167233860927215620995843068292504644614777821809165775517811410811, + } + +pub const test_plonk_vkey_affine: AffineVerificationKey = + AffineVerificationKey { + n_public: 2, + power: 8, + k1: 2, + k2: 3, + q_m: G1Point { + x: int_to_field_bytes( + 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, + ), + y: int_to_field_bytes( + 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, + ), + }, + q_l: G1Point { + x: int_to_field_bytes( + 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, + ), + y: int_to_field_bytes( + 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, + ), + }, + q_r: G1Point { + x: int_to_field_bytes( + 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, + ), + y: int_to_field_bytes( + 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, + ), + }, + q_o: G1Point { + x: int_to_field_bytes( + 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, + ), + y: int_to_field_bytes( + 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, + ), + }, + q_c: G1Infinity, + s_sig1: G1Point { + x: int_to_field_bytes( + 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, + ), + y: int_to_field_bytes( + 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, + ), + }, + s_sig2: G1Point { + x: int_to_field_bytes( + 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, + ), + y: int_to_field_bytes( + 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, + ), + }, + s_sig3: G1Point { + x: int_to_field_bytes( + 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, + ), + y: int_to_field_bytes( + 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, + ), + }, + x2: G2Point { + x: ( + int_to_field_bytes( + 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, + ), + int_to_field_bytes( + 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, + ), + ), + y: ( + int_to_field_bytes( + 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, + ), + int_to_field_bytes( + 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, + ), + ), + }, + w: 21071158244812412064791010377580296085971058123779034548857891862303448703672, + } + +pub const test_plonk_pub_inputs: List = + [ + 8301577178634781303874616783681599046073052196886694608055856488206182952326, + 5, + ] + +test test_plonk_verification_with_affine_points() { + let prepared_preinputs = prepare_affine_vkey(test_plonk_vkey_affine) + let proof = prepare_affine_proof(test_plonk_proof_affine) + let prepared_proof = + plonk_prepare_proof(prepared_preinputs, test_plonk_pub_inputs, proof) + + verify_plonk( + preinputs: prepared_preinputs, + pub_inputs: test_plonk_pub_inputs, + preproof: prepared_proof, + ) +} From 1d625e977f226db1d4ce40604ebeaa13d2d357ca Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 9 Feb 2026 23:23:01 -0800 Subject: [PATCH 17/25] handle all decompression in prep functions --- zkp/lib/plonk/preinputs.ak | 28 ++++- zkp/lib/plonk/proofs.ak | 72 +++++++++++-- zkp/lib/plonk/verifier.ak | 161 +++++++++++++--------------- zkp/lib/tests/plonk_affine_tests.ak | 14 +-- 4 files changed, 168 insertions(+), 107 deletions(-) diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 07568c2..63f4a31 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -1,6 +1,8 @@ use aiken/collection/list use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/scalar.{Scalar, scale} +use aiken/crypto/bls12_381/g1 +use aiken/crypto/bls12_381/g2 pub type PlonkPreInputs { power: Int, @@ -20,7 +22,18 @@ pub type PlonkPreInputs { pub type PreparedPlonkPreInputs { n: Int, generators: List>, - inputs: PlonkPreInputs, + power: Int, + k1: State, + k2: State, + q_m: G1Element, + q_l: G1Element, + q_r: G1Element, + q_o: G1Element, + q_c: G1Element, + s_sig1: G1Element, + s_sig2: G1Element, + s_sig3: G1Element, + x2: G2Element, } pub fn plonk_prepare_preinputs( @@ -31,6 +44,17 @@ pub fn plonk_prepare_preinputs( PreparedPlonkPreInputs { n: scalar.to_int(scale(scalar.from_int(2), inputs.power)), generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), - inputs, + power: inputs.power, + k1: inputs.k1, + k2: inputs.k2, + q_m: g1.decompress(inputs.q_m), + q_l: g1.decompress(inputs.q_l), + q_r: g1.decompress(inputs.q_r), + q_o: g1.decompress(inputs.q_o), + q_c: g1.decompress(inputs.q_c), + s_sig1: g1.decompress(inputs.s_sig1), + s_sig2: g1.decompress(inputs.s_sig2), + s_sig3: g1.decompress(inputs.s_sig3), + x2: g2.decompress(inputs.x2), } } diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak index f621a6e..c005cf9 100644 --- a/zkp/lib/plonk/proofs.ak +++ b/zkp/lib/plonk/proofs.ak @@ -2,6 +2,7 @@ use aiken/builtin.{blake2b_224} use aiken/collection/list use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/scalar.{Scalar} +use aiken/crypto/bls12_381/g1 use aiken/primitive/bytearray use common/common.{concat_and_blake2b_224, scalar_rt_bs} use plonk/preinputs.{PreparedPlonkPreInputs} @@ -25,7 +26,21 @@ pub type PlonkProof { } pub type PreparedPlonkProof { - proof: PlonkProof, + commitment_a: G1Element, + commitment_b: G1Element, + commitment_c: G1Element, + commitment_z: G1Element, + t_low: G1Element, + t_mid: G1Element, + t_high: G1Element, + w_omega: G1Element, + w_omega_zeta: G1Element, + a_eval: State, + b_eval: State, + c_eval: State, + s_sig1_p: State, + s_sig2_p: State, + z_omega: State, lagrange_inverses: List>, } @@ -36,14 +51,14 @@ pub fn plonk_prepare_proof( ) -> PreparedPlonkProof { let beta_inputs = [ - preinputs.inputs.q_m, - preinputs.inputs.q_l, - preinputs.inputs.q_r, - preinputs.inputs.q_o, - preinputs.inputs.q_c, - preinputs.inputs.s_sig1, - preinputs.inputs.s_sig2, - preinputs.inputs.s_sig3, + g1.compress(preinputs.q_m), + g1.compress(preinputs.q_l), + g1.compress(preinputs.q_r), + g1.compress(preinputs.q_o), + g1.compress(preinputs.q_c), + g1.compress(preinputs.s_sig1), + g1.compress(preinputs.s_sig2), + g1.compress(preinputs.s_sig3), list.foldl( pub_inputs, #"", @@ -71,5 +86,40 @@ pub fn plonk_prepare_proof( }, ) - PreparedPlonkProof { proof, lagrange_inverses } -} + // uncompress the proof commitments + let commitment_a: G1Element = g1.decompress(proof.commitment_a) + let commitment_b: G1Element = g1.decompress(proof.commitment_b) + let commitment_c: G1Element = g1.decompress(proof.commitment_c) + let commitment_z: G1Element = g1.decompress(proof.commitment_z) + let t_low: G1Element = g1.decompress(proof.t_low) + let t_mid: G1Element = g1.decompress(proof.t_mid) + let t_high: G1Element = g1.decompress(proof.t_high) + let w_omega: G1Element = g1.decompress(proof.w_omega) + let w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) + + // convert polynomial evaluations + let a_eval: State = scalar.from_int(proof.a_eval) + let b_eval: State = scalar.from_int(proof.b_eval) + let c_eval: State = scalar.from_int(proof.c_eval) + let s_sig1_p: State = scalar.from_int(proof.s_sig1_p) + let s_sig2_p: State = scalar.from_int(proof.s_sig2_p) + let z_omega: State = scalar.from_int(proof.z_omega) + + PreparedPlonkProof { + commitment_a, + commitment_b, + commitment_c, + commitment_z, + t_low, + t_mid, + t_high, + w_omega, + w_omega_zeta, + a_eval, + b_eval, + c_eval, + s_sig1_p, + s_sig2_p, + z_omega, + lagrange_inverses } +} \ No newline at end of file diff --git a/zkp/lib/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak index a59db1a..c233fca 100644 --- a/zkp/lib/plonk/verifier.ak +++ b/zkp/lib/plonk/verifier.ak @@ -7,46 +7,34 @@ use aiken/crypto/bls12_381/pairing.{final_exponentiation, miller_loop} use aiken/crypto/bls12_381/scalar.{Scalar, field_prime, scale2} use aiken/primitive/bytearray use common/common.{concat_and_blake2b_224, g1_sum, scalar_sum} -use plonk/preinputs.{PlonkPreInputs, PreparedPlonkPreInputs} -use plonk/proofs.{PlonkProof, PreparedPlonkProof} +use plonk/preinputs.{PreparedPlonkPreInputs} +use plonk/proofs.{PreparedPlonkProof} pub fn verify_plonk( preinputs: PreparedPlonkPreInputs, pub_inputs: List, - preproof: PreparedPlonkProof, + proof: PreparedPlonkProof, ) -> Bool { - let proof: PlonkProof = preproof.proof - let inputs: PlonkPreInputs = preinputs.inputs + // compress the inputs + let q_m = g1.compress(preinputs.q_m) + let q_l = g1.compress(preinputs.q_l) + let q_r = g1.compress(preinputs.q_r) + let q_o = g1.compress(preinputs.q_o) + let q_c = g1.compress(preinputs.q_c) + let s_sig1 = g1.compress(preinputs.s_sig1) + let s_sig2 = g1.compress(preinputs.s_sig2) + let s_sig3 = g1.compress(preinputs.s_sig3) - // uncompress the inputs - let q_m: G1Element = g1.decompress(inputs.q_m) - let q_l: G1Element = g1.decompress(inputs.q_l) - let q_r: G1Element = g1.decompress(inputs.q_r) - let q_o: G1Element = g1.decompress(inputs.q_o) - let q_c: G1Element = g1.decompress(inputs.q_c) - let s_sig1: G1Element = g1.decompress(inputs.s_sig1) - let s_sig2: G1Element = g1.decompress(inputs.s_sig2) - let s_sig3: G1Element = g1.decompress(inputs.s_sig3) - let x2: G2Element = g2.decompress(inputs.x2) - - // uncompress the proof commitments - let comm_a: G1Element = g1.decompress(proof.commitment_a) - let comm_b: G1Element = g1.decompress(proof.commitment_b) - let comm_c: G1Element = g1.decompress(proof.commitment_c) - let comm_z: G1Element = g1.decompress(proof.commitment_z) - let comm_t_low: G1Element = g1.decompress(proof.t_low) - let comm_t_mid: G1Element = g1.decompress(proof.t_mid) - let comm_t_high: G1Element = g1.decompress(proof.t_high) - let comm_w_omega: G1Element = g1.decompress(proof.w_omega) - let comm_w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) - - // convert polynomial evaluations - let a_eval: State = scalar.from_int(proof.a_eval) - let b_eval: State = scalar.from_int(proof.b_eval) - let c_eval: State = scalar.from_int(proof.c_eval) - let s1: State = scalar.from_int(proof.s_sig1_p) - let s2: State = scalar.from_int(proof.s_sig2_p) - let z_omega: State = scalar.from_int(proof.z_omega) + // compress the proof commitments + let bs_commitment_a = g1.compress(proof.commitment_a) + let bs_commitment_b = g1.compress(proof.commitment_b) + let bs_commitment_c = g1.compress(proof.commitment_c) + let bs_commitment_z = g1.compress(proof.commitment_z) + let bs_t_low = g1.compress(proof.t_low) + let bs_t_mid = g1.compress(proof.t_mid) + let bs_t_high = g1.compress(proof.t_high) + let bs_w_omega = g1.compress(proof.w_omega) + let bs_w_omega_zeta = g1.compress(proof.w_omega_zeta) // prepare negated public inputs for polynomial evaluation let w_all: List> = @@ -68,14 +56,14 @@ pub fn verify_plonk( scalar.from_bytes( concat_and_blake2b_224( [ - inputs.q_m, - inputs.q_l, - inputs.q_r, - inputs.q_o, - inputs.q_c, - inputs.s_sig1, - inputs.s_sig2, - inputs.s_sig3, + q_m, + q_l, + q_r, + q_o, + q_c, + s_sig1, + s_sig2, + s_sig3, list.foldl( pub_inputs, #"", @@ -83,9 +71,9 @@ pub fn verify_plonk( bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }, ), - proof.commitment_a, - proof.commitment_b, - proof.commitment_c, + bs_commitment_a, + bs_commitment_b, + bs_commitment_c, ], ), ) @@ -94,13 +82,13 @@ pub fn verify_plonk( let alpha: State = scalar.from_bytes( concat_and_blake2b_224( - [scalar.to_bytes(beta), scalar.to_bytes(gamma), proof.commitment_z], + [scalar.to_bytes(beta), scalar.to_bytes(gamma), bs_commitment_z], ), ) let zeta: State = scalar.from_bytes( concat_and_blake2b_224( - [scalar.to_bytes(alpha), proof.t_low, proof.t_mid, proof.t_high], + [scalar.to_bytes(alpha), bs_t_low, bs_t_mid, bs_t_high], ), ) let v: State = @@ -108,18 +96,18 @@ pub fn verify_plonk( concat_and_blake2b_224( [ scalar.to_bytes(zeta), - scalar.to_bytes(a_eval), - scalar.to_bytes(b_eval), - scalar.to_bytes(c_eval), - scalar.to_bytes(s1), - scalar.to_bytes(s2), - scalar.to_bytes(z_omega), + scalar.to_bytes(proof.a_eval), + scalar.to_bytes(proof.b_eval), + scalar.to_bytes(proof.c_eval), + scalar.to_bytes(proof.s_sig1_p), + scalar.to_bytes(proof.s_sig2_p), + scalar.to_bytes(proof.z_omega), ], ), ) let u: State = scalar.from_bytes( - concat_and_blake2b_224([proof.w_omega, proof.w_omega_zeta]), + concat_and_blake2b_224([bs_w_omega, bs_w_omega_zeta]), ) // compute powers of zeta for polynomial evaluation @@ -130,7 +118,7 @@ pub fn verify_plonk( let lagrange_polys: List> = list.map2( preinputs.generators, - preproof.lagrange_inverses, + proof.lagrange_inverses, fn(x, y) { scalar.mul(scalar.mul(x, pow2_zeta_p_minus_1), y) }, ) expect Some(lagrange_poly_head) = list.head(lagrange_polys) @@ -143,14 +131,14 @@ pub fn verify_plonk( // compute derived scalars for verification equations let alpha_sq = scalar.mul(alpha, alpha) - let alpha_z_omega = scalar.mul(alpha, z_omega) + let alpha_z_omega = scalar.mul(alpha, proof.z_omega) let beta_zeta = scalar.mul(beta, zeta) // compute shifted witness polynomials - let a_gamma = scalar.add(a_eval, gamma) - let b_gamma = scalar.add(b_eval, gamma) - let c_gamma = scalar.add(c_eval, gamma) - let beta_s1 = scalar.mul(beta, s1) - let beta_s2 = scalar.mul(beta, s2) + let a_gamma = scalar.add(proof.a_eval, gamma) + let b_gamma = scalar.add(proof.b_eval, gamma) + let c_gamma = scalar.add(proof.c_eval, gamma) + let beta_s1 = scalar.mul(beta, proof.s_sig1_p) + let beta_s2 = scalar.mul(beta, proof.s_sig2_p) // compute linearization polynomial r(zeta) let r0 = @@ -169,21 +157,21 @@ pub fn verify_plonk( let constraint_commitments = g1_sum( [ - g1.scale(q_m, scalar.mul(a_eval, b_eval)), - g1.scale(q_l, a_eval), - g1.scale(q_r, b_eval), - g1.scale(q_o, c_eval), - q_c, + g1.scale(preinputs.q_m, scalar.mul(proof.a_eval, proof.b_eval)), + g1.scale(preinputs.q_l, proof.a_eval), + g1.scale(preinputs.q_r, proof.b_eval), + g1.scale(preinputs.q_o, proof.c_eval), + preinputs.q_c, g1.scale( - comm_z, + proof.commitment_z, { let product = scalar.add(a_gamma, beta_zeta) |> scalar.mul( - scalar.add(b_gamma, scalar.mul(beta_zeta, inputs.k1)), + scalar.add(b_gamma, scalar.mul(beta_zeta, preinputs.k1)), ) |> scalar.mul( - scalar.add(c_gamma, scalar.mul(beta_zeta, inputs.k2)), + scalar.add(c_gamma, scalar.mul(beta_zeta, preinputs.k2)), ) |> scalar.mul(alpha) scalar.add( @@ -193,7 +181,7 @@ pub fn verify_plonk( }, ), g1.scale( - s_sig3, + preinputs.s_sig3, scalar.neg( scalar.mul( scalar.mul( @@ -206,10 +194,10 @@ pub fn verify_plonk( ), g1.scale( g1.add( - comm_t_low, + proof.t_low, g1.add( - g1.scale(comm_t_mid, pow2_zeta_p), - g1.scale(comm_t_high, scale2(pow2_zeta_p, 1)), + g1.scale(proof.t_mid, pow2_zeta_p), + g1.scale(proof.t_high, scale2(pow2_zeta_p, 1)), ), ), scalar.neg(pow2_zeta_p_minus_1), @@ -223,14 +211,13 @@ pub fn verify_plonk( constraint_commitments, { let sigma_witness_commits = - g1.add(s_sig1, g1.scale(s_sig2, v)) + g1.add(preinputs.s_sig1, g1.scale(preinputs.s_sig2, v)) |> g1.scale(v) - |> g1.add(comm_c, _) + |> g1.add(proof.commitment_c, _) |> g1.scale(v) - |> g1.add(comm_b, _) + |> g1.add(proof.commitment_b, _) |> g1.scale(v) - |> g1.add(comm_a, _) - + |> g1.add(proof.commitment_a, _) g1.scale(sigma_witness_commits, v) }, ) @@ -241,31 +228,31 @@ pub fn verify_plonk( g1.generator, { let sigma_witness_evals = - scalar.add(s1, scalar.mul(v, s2)) + scalar.add(proof.s_sig1_p, scalar.mul(v, proof.s_sig2_p)) |> scalar.mul(v, _) - |> scalar.add(c_eval, _) + |> scalar.add(proof.c_eval, _) |> scalar.mul(v, _) - |> scalar.add(b_eval, _) + |> scalar.add(proof.b_eval, _) |> scalar.mul(v, _) - |> scalar.add(a_eval, _) + |> scalar.add(proof.a_eval, _) scalar.add( scalar.add(scalar.neg(r0), scalar.mul(v, sigma_witness_evals)), - scalar.mul(u, z_omega), + scalar.mul(u, proof.z_omega), ) }, ) // verify polynomial evaluations let miller_loop_1 = - miller_loop(g1.add(comm_w_omega, g1.scale(comm_w_omega_zeta, u)), x2) + miller_loop(g1.add(proof.w_omega, g1.scale(proof.w_omega_zeta, u)), preinputs.x2) // verify constraint satisfaction let miller_loop_2 = miller_loop( g1.add( g1.add( - g1.scale(comm_w_omega, zeta), - g1.scale(comm_w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_2nd))), + g1.scale(proof.w_omega, zeta), + g1.scale(proof.w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_2nd))), ), g1.sub(batched_polynomial_commitments, group_encoded_batch_eval), ), @@ -278,7 +265,7 @@ pub fn verify_plonk( let lagrange_check = scalar_sum( list.map2( - preproof.lagrange_inverses, + proof.lagrange_inverses, preinputs.generators, fn(x, y) { scalar.mul( diff --git a/zkp/lib/tests/plonk_affine_tests.ak b/zkp/lib/tests/plonk_affine_tests.ak index 49950a4..6d858c1 100644 --- a/zkp/lib/tests/plonk_affine_tests.ak +++ b/zkp/lib/tests/plonk_affine_tests.ak @@ -178,15 +178,15 @@ pub const test_plonk_pub_inputs: List = 5, ] -test test_plonk_verification_with_affine_points() { - let prepared_preinputs = prepare_affine_vkey(test_plonk_vkey_affine) - let proof = prepare_affine_proof(test_plonk_proof_affine) - let prepared_proof = - plonk_prepare_proof(prepared_preinputs, test_plonk_pub_inputs, proof) +pub const test_prepared_preinputs = prepare_affine_vkey(test_plonk_vkey_affine) +pub const test_proof = prepare_affine_proof(test_plonk_proof_affine) +pub const test_prepared_proof = + plonk_prepare_proof(test_prepared_preinputs, test_plonk_pub_inputs, test_proof) +test test_plonk_verification_with_affine_points() { verify_plonk( - preinputs: prepared_preinputs, + preinputs: test_prepared_preinputs, pub_inputs: test_plonk_pub_inputs, - preproof: prepared_proof, + proof: test_prepared_proof, ) } From a34e783cd1cc214872c8140fced371d4f5a4061f Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 23 Feb 2026 22:30:41 -0800 Subject: [PATCH 18/25] fix affine serialization --- zkp/lib/common/blst_affine.ak | 58 ++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/zkp/lib/common/blst_affine.ak b/zkp/lib/common/blst_affine.ak index 09495d1..0c1128f 100644 --- a/zkp/lib/common/blst_affine.ak +++ b/zkp/lib/common/blst_affine.ak @@ -1,5 +1,11 @@ use aiken/primitive/bytearray +/// (p-1)/2 for BLS12-381 field modulus, used to determine y-coordinate sort bit. +/// p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 +/// half_p = (p-1)/2 +const bls12_381_half_prime: Int = + 2001204777610833696708894912867952078278441409969503942666029068062015825245418932221343814564507832018947136279893 + /// Affine representation of a point on the BLS12-381 G1 curve pub type G1Affine { /// Point at infinity @@ -31,19 +37,19 @@ pub fn g1_affine_compress(point: G1Affine) -> ByteArray { // Set compression bit (0x80) and determine y-coordinate lexicographic ordering let first_x_byte = bytearray.take(x, 1) let rest_x = bytearray.drop(x, 1) - // Check if y is lexicographically larger than (p-y) mod p - // For now, we'll use a simple heuristic: check if y's first byte > 0x7f - let first_y_byte = bytearray.take(y, 1) - let y_is_larger = bytearray.to_int_big_endian(first_y_byte) >= 128 + // Check if y > (p-1)/2 to determine which of the two possible y values this is. + // The sort bit (0x20) indicates the "lexicographically larger" y-coordinate. + let y_int = bytearray.to_int_big_endian(y) + let y_is_larger = y_int > bls12_381_half_prime // Set the compression bit (0x80) and y-coordinate bit if needed let compressed_first_byte = if y_is_larger { + // 0x80 | 0x20 = 0xa0: compression + sort bit bytearray.or_bytes(first_x_byte, #"a0", False) } else { - // 0x80 | 0x20 + // 0x80 only: compression bit, no sort bit bytearray.or_bytes(first_x_byte, #"80", False) } - // 0x80 only bytearray.concat(compressed_first_byte, rest_x) } } @@ -59,28 +65,36 @@ pub fn g2_affine_compress(point: G2Affine) -> ByteArray { // 95 bytes of zeros bytearray.concat(infinity_byte, zeros) } - G2Point { x: (x0, x1), y: (y0, _y1) } -> { + G2Point { x: (x0, x1), y: (y0, y1) } -> { // Manual BLS12-381 G2 compression - // G2 compression is similar to G1 but works on Fp2 elements - // Each coordinate is a pair of field elements (x0, x1) representing x0 + x1*u - // Set compression bit on the first byte of x0 (the higher-order part) - let first_x0_byte = bytearray.take(x0, 1) - let rest_x0 = bytearray.drop(x0, 1) - // Determine lexicographic ordering of y-coordinate - // For G2, we compare y with (p-y) in Fp2 ordering - let first_y0_byte = bytearray.take(y0, 1) - let y_is_larger = bytearray.to_int_big_endian(first_y0_byte) >= 128 + // G2 uses Fp2 elements: each coordinate is (c0, c1) representing c0 + c1*u + // where x0=c0 (real), x1=c1 (imaginary) per snarkjs convention. + // + // BLST serialization format: [c1_with_flags | c0] (imaginary first, real second) + // So we put x1 first (with flags), then x0. + let first_x1_byte = bytearray.take(x1, 1) + let rest_x1 = bytearray.drop(x1, 1) + // Determine y sort bit using Fp2 lexicographic ordering: + // Compare c1 (imaginary) of y first. If c1 is zero, compare c0 (real). + let y1_int = bytearray.to_int_big_endian(y1) + let y0_int = bytearray.to_int_big_endian(y0) + let y_is_larger = + if y1_int == 0 { + y0_int > bls12_381_half_prime + } else { + y1_int > bls12_381_half_prime + } // Set compression and y-ordering bits let compressed_first_byte = if y_is_larger { - bytearray.or_bytes(first_x0_byte, #"a0", False) + // 0x80 | 0x20 = 0xa0: compression + sort bit + bytearray.or_bytes(first_x1_byte, #"a0", False) } else { - // 0x80 | 0x20 - bytearray.or_bytes(first_x0_byte, #"80", False) + // 0x80 only: compression bit, no sort bit + bytearray.or_bytes(first_x1_byte, #"80", False) } - // 0x80 only - // Return compressed form: compressed_x0 + x1 (96 bytes total) - bytearray.concat(bytearray.concat(compressed_first_byte, rest_x0), x1) + // Return compressed form: [c1_with_flags | c0] (96 bytes total) + bytearray.concat(bytearray.concat(compressed_first_byte, rest_x1), x0) } } } From 464c687f33361e3ce74f25a4feaa097e6cad57a1 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 23 Feb 2026 22:31:27 -0800 Subject: [PATCH 19/25] new plonk & affine tests --- zkp/lib/tests/affine_compression_tests.ak | 124 --------- zkp/lib/tests/plonk_affine_tests.ak | 192 ------------- zkp/lib/tests/plonk_tests.ak | 320 +++++++++++++++++++++- zkp/lib/tests/point_tests.ak | 239 ++++++++++++++++ 4 files changed, 557 insertions(+), 318 deletions(-) delete mode 100644 zkp/lib/tests/affine_compression_tests.ak delete mode 100644 zkp/lib/tests/plonk_affine_tests.ak create mode 100644 zkp/lib/tests/point_tests.ak diff --git a/zkp/lib/tests/affine_compression_tests.ak b/zkp/lib/tests/affine_compression_tests.ak deleted file mode 100644 index 157bf82..0000000 --- a/zkp/lib/tests/affine_compression_tests.ak +++ /dev/null @@ -1,124 +0,0 @@ -use aiken/primitive/bytearray -use common/blst_affine.{ - G1Infinity, G1Point, G2Infinity, G2Point, g1_affine_compress, - g2_affine_compress, -} -use tests/test_utils.{int_to_field_bytes} - -const g1_a = - G1Point { - x: int_to_field_bytes( - 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, - ), - y: int_to_field_bytes( - 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, - ), - } - -const g1_b = - G1Point { - x: int_to_field_bytes( - 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, - ), - y: int_to_field_bytes( - 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, - ), - } - -const g2_a = - G2Point { - x: ( - int_to_field_bytes( - 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, - ), - int_to_field_bytes( - 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, - ), - ), - y: ( - int_to_field_bytes( - 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, - ), - int_to_field_bytes( - 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, - ), - ), - } - -// Test G1 compression format -test test_g1_compression_format() { - let point = g1_a - let compressed = g1_affine_compress(point) - // Verify compressed format: should be 48 bytes with compression bit set - let compressed_length = bytearray.length(compressed) - let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) - let has_compression_bit = first_byte >= 128 - // Check if bit 7 is set (0x80) - compressed_length == 48 && has_compression_bit -} - -test test_g1_infinity_compression() { - let compressed = g1_affine_compress(G1Infinity) - let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) - let length = bytearray.length(compressed) - // Should have compression bit (0x80) + infinity bit (0x40) = 0xc0 = 192 - let is_infinity_encoding = first_byte == 192 - let correct_length = length == 48 - is_infinity_encoding && correct_length -} - -test test_g1_different_points_different_compression() { - let compressed1 = g1_affine_compress(g1_a) - let compressed2 = g1_affine_compress(g1_b) - // Different points should compress to different values - compressed1 != compressed2 -} - -test test_g2_compression_format() { - expect G2Point { x: x2, y: y2 } = g2_a - let compressed = g2_affine_compress(G2Point(x2, y2)) - let compressed_length = bytearray.length(compressed) - let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) - let has_compression_bit = first_byte >= 128 - // G2 compressed should be 96 bytes with compression bit set - compressed_length == 96 && has_compression_bit -} - -test test_g2_infinity_compression() { - let compressed = g2_affine_compress(G2Infinity) - let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) - let length = bytearray.length(compressed) - // Should have compression + infinity bits - let is_infinity_encoding = first_byte == 192 - // 0xc0 - let correct_length = length == 96 - is_infinity_encoding && correct_length -} - -test test_g2_infinity_debug() { - let compressed = g2_affine_compress(G2Infinity) - let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) - // Check just the first byte - first_byte == 192 -} - -test test_g2_infinity_length() { - let compressed = g2_affine_compress(G2Infinity) - let length = bytearray.length(compressed) - // Check only the length - length == 96 -} - -// Test that y-coordinate ordering bit is being set correctly -test test_y_ordering_bit() { - expect G1Point { y, .. } = g1_a - let compressed = g1_affine_compress(g1_a) - let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) - // Extract y-ordering bit (bit 5, value 32) - let y_bit_set = first_byte % 64 >= 32 - // Check if bit 5 is set - // This should match our y_is_larger logic from the compression - let first_y_byte = bytearray.take(y, 1) - let expected_y_larger = bytearray.to_int_big_endian(first_y_byte) >= 128 - y_bit_set == expected_y_larger -} diff --git a/zkp/lib/tests/plonk_affine_tests.ak b/zkp/lib/tests/plonk_affine_tests.ak deleted file mode 100644 index 6d858c1..0000000 --- a/zkp/lib/tests/plonk_affine_tests.ak +++ /dev/null @@ -1,192 +0,0 @@ -use common/blst_affine.{G1Infinity, G1Point, G2Point} -use plonk/proofs.{plonk_prepare_proof} -use plonk/raw_affine.{ - AffineProof, AffineVerificationKey, prepare_affine_proof, prepare_affine_vkey, -} -use plonk/verifier.{verify_plonk} -use tests/test_utils.{int_to_field_bytes} - -pub const test_plonk_proof_affine: AffineProof = - AffineProof { - commitment_a: G1Point { - x: int_to_field_bytes( - 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, - ), - y: int_to_field_bytes( - 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, - ), - }, - commitment_b: G1Point { - x: int_to_field_bytes( - 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, - ), - y: int_to_field_bytes( - 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, - ), - }, - commitment_c: G1Point { - x: int_to_field_bytes( - 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, - ), - y: int_to_field_bytes( - 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, - ), - }, - commitment_z: G1Point { - x: int_to_field_bytes( - 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, - ), - y: int_to_field_bytes( - 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, - ), - }, - t_low: G1Point { - x: int_to_field_bytes( - 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, - ), - y: int_to_field_bytes( - 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, - ), - }, - t_mid: G1Point { - x: int_to_field_bytes( - 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, - ), - y: int_to_field_bytes( - 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, - ), - }, - t_high: G1Point { - x: int_to_field_bytes( - 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, - ), - y: int_to_field_bytes( - 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, - ), - }, - w_omega: G1Point { - x: int_to_field_bytes( - 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, - ), - y: int_to_field_bytes( - 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, - ), - }, - w_omega_zeta: G1Point { - x: int_to_field_bytes( - 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, - ), - y: int_to_field_bytes( - 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, - ), - }, - a_eval: 18376945509548979154234285967502303667654384864417109878730063780807771386804, - b_eval: 48091350738882108076678452440283491448151427314124311219043220475171874763276, - c_eval: 19995498867920222404318895039685100281891200599706204591987499224278246956716, - s_sig1_p: 22639172691520838707669313728412965432032750722138107727665998164239763135904, - s_sig2_p: 38226063540390035205748121923613940584145755981537302966237851066336751425207, - z_omega: 46342921279167233860927215620995843068292504644614777821809165775517811410811, - } - -pub const test_plonk_vkey_affine: AffineVerificationKey = - AffineVerificationKey { - n_public: 2, - power: 8, - k1: 2, - k2: 3, - q_m: G1Point { - x: int_to_field_bytes( - 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, - ), - y: int_to_field_bytes( - 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, - ), - }, - q_l: G1Point { - x: int_to_field_bytes( - 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, - ), - y: int_to_field_bytes( - 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, - ), - }, - q_r: G1Point { - x: int_to_field_bytes( - 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, - ), - y: int_to_field_bytes( - 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, - ), - }, - q_o: G1Point { - x: int_to_field_bytes( - 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, - ), - y: int_to_field_bytes( - 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, - ), - }, - q_c: G1Infinity, - s_sig1: G1Point { - x: int_to_field_bytes( - 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, - ), - y: int_to_field_bytes( - 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, - ), - }, - s_sig2: G1Point { - x: int_to_field_bytes( - 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, - ), - y: int_to_field_bytes( - 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, - ), - }, - s_sig3: G1Point { - x: int_to_field_bytes( - 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, - ), - y: int_to_field_bytes( - 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, - ), - }, - x2: G2Point { - x: ( - int_to_field_bytes( - 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, - ), - int_to_field_bytes( - 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, - ), - ), - y: ( - int_to_field_bytes( - 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, - ), - int_to_field_bytes( - 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, - ), - ), - }, - w: 21071158244812412064791010377580296085971058123779034548857891862303448703672, - } - -pub const test_plonk_pub_inputs: List = - [ - 8301577178634781303874616783681599046073052196886694608055856488206182952326, - 5, - ] - -pub const test_prepared_preinputs = prepare_affine_vkey(test_plonk_vkey_affine) -pub const test_proof = prepare_affine_proof(test_plonk_proof_affine) -pub const test_prepared_proof = - plonk_prepare_proof(test_prepared_preinputs, test_plonk_pub_inputs, test_proof) - -test test_plonk_verification_with_affine_points() { - verify_plonk( - preinputs: test_prepared_preinputs, - pub_inputs: test_plonk_pub_inputs, - proof: test_prepared_proof, - ) -} diff --git a/zkp/lib/tests/plonk_tests.ak b/zkp/lib/tests/plonk_tests.ak index b14a165..3ff023e 100644 --- a/zkp/lib/tests/plonk_tests.ak +++ b/zkp/lib/tests/plonk_tests.ak @@ -1,5 +1,19 @@ -use common/common.{G2Point, Point} +use aiken/collection/list +use aiken/crypto/bls12_381/scalar +use common/blst_affine.{G1Infinity, G1Point, G2Point, g1_affine_compress, g2_affine_compress} +use common/common.{G2Point as RawG2Point, Point} +use plonk/preinputs.{PlonkPreInputs, plonk_prepare_preinputs} +use plonk/proofs.{plonk_prepare_proof} use plonk/raw.{PlonkVerificationKey, RawPlonkProof} +use plonk/raw_affine.{ + AffineProof, AffineVerificationKey, prepare_affine_proof, prepare_affine_vkey, +} +use plonk/verifier.{verify_plonk} +use tests/test_utils.{int_to_field_bytes} + +// =========================================================================== +// Raw (projective) test vectors +// =========================================================================== pub const test_plonk_proof: RawPlonkProof = RawPlonkProof { @@ -98,7 +112,7 @@ pub const test_plonk_vkey: PlonkVerificationKey = y: 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, z: 1, }, - x2: G2Point { + x2: RawG2Point { x: ( 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, @@ -111,3 +125,305 @@ pub const test_plonk_vkey: PlonkVerificationKey = }, w: 21071158244812412064791010377580296085971058123779034548857891862303448703672, } + +// =========================================================================== +// Affine test vectors +// =========================================================================== + +pub const test_plonk_proof_affine: AffineProof = + AffineProof { + commitment_a: G1Point { + x: int_to_field_bytes( + 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, + ), + y: int_to_field_bytes( + 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, + ), + }, + commitment_b: G1Point { + x: int_to_field_bytes( + 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, + ), + y: int_to_field_bytes( + 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + ), + }, + commitment_c: G1Point { + x: int_to_field_bytes( + 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, + ), + y: int_to_field_bytes( + 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + ), + }, + commitment_z: G1Point { + x: int_to_field_bytes( + 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, + ), + y: int_to_field_bytes( + 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + ), + }, + t_low: G1Point { + x: int_to_field_bytes( + 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, + ), + y: int_to_field_bytes( + 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + ), + }, + t_mid: G1Point { + x: int_to_field_bytes( + 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, + ), + y: int_to_field_bytes( + 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, + ), + }, + t_high: G1Point { + x: int_to_field_bytes( + 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, + ), + y: int_to_field_bytes( + 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, + ), + }, + w_omega: G1Point { + x: int_to_field_bytes( + 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, + ), + y: int_to_field_bytes( + 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, + ), + }, + w_omega_zeta: G1Point { + x: int_to_field_bytes( + 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, + ), + y: int_to_field_bytes( + 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, + ), + }, + a_eval: 18376945509548979154234285967502303667654384864417109878730063780807771386804, + b_eval: 48091350738882108076678452440283491448151427314124311219043220475171874763276, + c_eval: 19995498867920222404318895039685100281891200599706204591987499224278246956716, + s_sig1_p: 22639172691520838707669313728412965432032750722138107727665998164239763135904, + s_sig2_p: 38226063540390035205748121923613940584145755981537302966237851066336751425207, + z_omega: 46342921279167233860927215620995843068292504644614777821809165775517811410811, + } + +pub const test_plonk_vkey_affine: AffineVerificationKey = + AffineVerificationKey { + n_public: 2, + power: 8, + k1: 2, + k2: 3, + q_m: G1Point { + x: int_to_field_bytes( + 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, + ), + y: int_to_field_bytes( + 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, + ), + }, + q_l: G1Point { + x: int_to_field_bytes( + 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, + ), + y: int_to_field_bytes( + 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, + ), + }, + q_r: G1Point { + x: int_to_field_bytes( + 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, + ), + y: int_to_field_bytes( + 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, + ), + }, + q_o: G1Point { + x: int_to_field_bytes( + 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, + ), + y: int_to_field_bytes( + 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, + ), + }, + q_c: G1Infinity, + s_sig1: G1Point { + x: int_to_field_bytes( + 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, + ), + y: int_to_field_bytes( + 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, + ), + }, + s_sig2: G1Point { + x: int_to_field_bytes( + 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, + ), + y: int_to_field_bytes( + 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, + ), + }, + s_sig3: G1Point { + x: int_to_field_bytes( + 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, + ), + y: int_to_field_bytes( + 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, + ), + }, + x2: G2Point { + x: ( + int_to_field_bytes( + 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, + ), + int_to_field_bytes( + 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, + ), + ), + y: ( + int_to_field_bytes( + 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, + ), + int_to_field_bytes( + 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, + ), + ), + }, + w: 21071158244812412064791010377580296085971058123779034548857891862303448703672, + } + +pub const test_plonk_pub_inputs: List = + [ + 8301577178634781303874616783681599046073052196886694608055856488206182952326, + 5, + ] + +// =========================================================================== +// Vkey compression tests +// =========================================================================== + +test test_vkey_g1_compression() { + expect _compressed_q_m = g1_affine_compress(test_plonk_vkey_affine.q_m) + expect _compressed_q_l = g1_affine_compress(test_plonk_vkey_affine.q_l) + expect _compressed_q_r = g1_affine_compress(test_plonk_vkey_affine.q_r) + expect _compressed_q_o = g1_affine_compress(test_plonk_vkey_affine.q_o) + expect _compressed_q_c = g1_affine_compress(test_plonk_vkey_affine.q_c) + expect _compressed_s_sig1 = g1_affine_compress(test_plonk_vkey_affine.s_sig1) + expect _compressed_s_sig2 = g1_affine_compress(test_plonk_vkey_affine.s_sig2) + expect _compressed_s_sig3 = g1_affine_compress(test_plonk_vkey_affine.s_sig3) + True +} + +test test_vkey_g2_compression() { + expect _compressed_x2 = g2_affine_compress(test_plonk_vkey_affine.x2) + True +} + +// =========================================================================== +// Preinputs preparation tests +// =========================================================================== + +test test_preinputs_creation() { + expect _preinputs = + PlonkPreInputs { + power: test_plonk_vkey_affine.power, + k1: scalar.from_int(test_plonk_vkey_affine.k1), + k2: scalar.from_int(test_plonk_vkey_affine.k2), + q_m: g1_affine_compress(test_plonk_vkey_affine.q_m), + q_l: g1_affine_compress(test_plonk_vkey_affine.q_l), + q_r: g1_affine_compress(test_plonk_vkey_affine.q_r), + q_o: g1_affine_compress(test_plonk_vkey_affine.q_o), + q_c: g1_affine_compress(test_plonk_vkey_affine.q_c), + s_sig1: g1_affine_compress(test_plonk_vkey_affine.s_sig1), + s_sig2: g1_affine_compress(test_plonk_vkey_affine.s_sig2), + s_sig3: g1_affine_compress(test_plonk_vkey_affine.s_sig3), + x2: g2_affine_compress(test_plonk_vkey_affine.x2), + } + True +} + +test test_plonk_prepare_preinputs() { + let preinputs = + PlonkPreInputs { + power: test_plonk_vkey_affine.power, + k1: scalar.from_int(test_plonk_vkey_affine.k1), + k2: scalar.from_int(test_plonk_vkey_affine.k2), + q_m: g1_affine_compress(test_plonk_vkey_affine.q_m), + q_l: g1_affine_compress(test_plonk_vkey_affine.q_l), + q_r: g1_affine_compress(test_plonk_vkey_affine.q_r), + q_o: g1_affine_compress(test_plonk_vkey_affine.q_o), + q_c: g1_affine_compress(test_plonk_vkey_affine.q_c), + s_sig1: g1_affine_compress(test_plonk_vkey_affine.s_sig1), + s_sig2: g1_affine_compress(test_plonk_vkey_affine.s_sig2), + s_sig3: g1_affine_compress(test_plonk_vkey_affine.s_sig3), + x2: g2_affine_compress(test_plonk_vkey_affine.x2), + } + expect _prepared = + plonk_prepare_preinputs( + inputs: preinputs, + generator: scalar.from_int(test_plonk_vkey_affine.w), + n_public: test_plonk_vkey_affine.n_public, + ) + True +} + +// =========================================================================== +// Vkey and proof preparation via convenience functions +// =========================================================================== + +test test_prepare_vkey_function() { + expect _prepared = prepare_affine_vkey(test_plonk_vkey_affine) + True +} + +test test_prepare_affine_proof_function() { + expect _prepared = prepare_affine_proof(test_plonk_proof_affine) + True +} + +// =========================================================================== +// Scalar / generator value tests +// =========================================================================== + +test test_generator_value() { + let w = test_plonk_vkey_affine.w + expect _generator = scalar.from_int(w) + True +} + +test test_power_calculation() { + let power = test_plonk_vkey_affine.power + let two = scalar.from_int(2) + let result = scalar.scale(two, power) + let n = scalar.to_int(result) + n == 256 +} + +test test_generator_list_creation() { + let w = test_plonk_vkey_affine.w + let n_public = test_plonk_vkey_affine.n_public + let generator = scalar.from_int(w) + expect _generators = + list.map(list.range(0, n_public), fn(i) { scalar.scale(generator, i) }) + True +} + +// =========================================================================== +// Full PLONK verification (expensive - may exceed test budget) +// =========================================================================== + +test test_plonk_verification_with_affine_points() { + let prepared_preinputs = prepare_affine_vkey(test_plonk_vkey_affine) + let proof = prepare_affine_proof(test_plonk_proof_affine) + let prepared_proof = + plonk_prepare_proof(prepared_preinputs, test_plonk_pub_inputs, proof) + verify_plonk( + preinputs: prepared_preinputs, + pub_inputs: test_plonk_pub_inputs, + proof: prepared_proof, + ) +} diff --git a/zkp/lib/tests/point_tests.ak b/zkp/lib/tests/point_tests.ak new file mode 100644 index 0000000..eac1a72 --- /dev/null +++ b/zkp/lib/tests/point_tests.ak @@ -0,0 +1,239 @@ +use aiken/crypto/bls12_381/g1 +use aiken/primitive/bytearray +use common/blst_affine.{ + G1Affine, G1Infinity, G1Point, G2Infinity, G2Point, g1_affine_compress, + g2_affine_compress, +} +use tests/plonk_tests.{ + test_plonk_proof_affine, test_plonk_vkey_affine, +} +use tests/test_utils.{int_to_field_bytes} + +// --------------------------------------------------------------------------- +// Test point constants +// --------------------------------------------------------------------------- + +const g1_a = + G1Point { + x: int_to_field_bytes( + 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, + ), + y: int_to_field_bytes( + 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + ), + } + +const g1_b = + G1Point { + x: int_to_field_bytes( + 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, + ), + y: int_to_field_bytes( + 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + ), + } + +pub const simple_g1_point: G1Affine = + G1Point { + x: int_to_field_bytes( + 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507, + ), + y: int_to_field_bytes( + 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569, + ), + } + +// --------------------------------------------------------------------------- +// G1 compression tests +// --------------------------------------------------------------------------- + +test test_g1_compression_format() { + let compressed = g1_affine_compress(g1_a) + let compressed_length = bytearray.length(compressed) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let has_compression_bit = first_byte >= 128 + compressed_length == 48 && has_compression_bit +} + +test test_g1_infinity_compression() { + let compressed = g1_affine_compress(G1Infinity) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let length = bytearray.length(compressed) + let is_infinity_encoding = first_byte == 192 + let correct_length = length == 48 + is_infinity_encoding && correct_length +} + +test test_g1_different_points_different_compression() { + let compressed1 = g1_affine_compress(g1_a) + let compressed2 = g1_affine_compress(g1_b) + compressed1 != compressed2 +} + +test test_y_ordering_bit() { + expect G1Point { y, .. } = g1_a + let compressed = g1_affine_compress(g1_a) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let y_bit_set = first_byte % 64 >= 32 + let y_int = bytearray.to_int_big_endian(y) + let expected_y_larger = + y_int > 2001204777610833696708894912867952078278441409969503942666029068062015825245418932221343814564507832018947136279893 + y_bit_set == expected_y_larger +} + +// --------------------------------------------------------------------------- +// G2 compression tests +// --------------------------------------------------------------------------- + +test test_g2_compression_format() { + let point = + G2Point { + x: ( + int_to_field_bytes( + 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, + ), + int_to_field_bytes( + 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + ), + ), + y: ( + int_to_field_bytes( + 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, + ), + int_to_field_bytes( + 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + ), + ), + } + let compressed = g2_affine_compress(point) + let compressed_length = bytearray.length(compressed) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let has_compression_bit = first_byte >= 128 + compressed_length == 96 && has_compression_bit +} + +test test_g2_infinity_compression() { + let compressed = g2_affine_compress(G2Infinity) + let first_byte = bytearray.to_int_big_endian(bytearray.take(compressed, 1)) + let length = bytearray.length(compressed) + let is_infinity_encoding = first_byte == 192 + let correct_length = length == 96 + is_infinity_encoding && correct_length +} + +// --------------------------------------------------------------------------- +// Infinity decompression tests +// --------------------------------------------------------------------------- + +test test_known_infinity_format() { + // Standard BLS12-381 infinity: 0xc0 followed by 47 zero bytes (48 bytes total = 96 hex chars) + let known_infinity = #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + expect _decompressed = g1.decompress(known_infinity) + let our_infinity = g1_affine_compress(test_plonk_vkey_affine.q_c) + known_infinity == our_infinity +} + +test test_infinity_roundtrip() { + let compressed_infinity = g1_affine_compress(test_plonk_vkey_affine.q_c) + expect _decompressed = g1.decompress(compressed_infinity) + True +} + +// --------------------------------------------------------------------------- +// Individual point decompression (vkey points) +// --------------------------------------------------------------------------- + +test test_vkey_point_decompressions() { + let compressed_q_m = g1_affine_compress(test_plonk_vkey_affine.q_m) + expect _d1 = g1.decompress(compressed_q_m) + + let compressed_q_l = g1_affine_compress(test_plonk_vkey_affine.q_l) + expect _d2 = g1.decompress(compressed_q_l) + + let compressed_q_r = g1_affine_compress(test_plonk_vkey_affine.q_r) + expect _d3 = g1.decompress(compressed_q_r) + + let compressed_q_o = g1_affine_compress(test_plonk_vkey_affine.q_o) + expect _d4 = g1.decompress(compressed_q_o) + + let compressed_q_c = g1_affine_compress(test_plonk_vkey_affine.q_c) + expect _d5 = g1.decompress(compressed_q_c) + + True +} + +// --------------------------------------------------------------------------- +// Point structure validation (proof points) +// --------------------------------------------------------------------------- + +test test_proof_point_structures() { + when test_plonk_proof_affine.commitment_a is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.commitment_b is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.commitment_c is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.commitment_z is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.t_low is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.t_mid is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.t_high is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.w_omega is { + G1Point { x: _, y: _ } -> + when test_plonk_proof_affine.w_omega_zeta is { + G1Point { x: _, y: _ } -> True + _ -> False + } + _ -> False + } + _ -> False + } + _ -> False + } + _ -> False + } + _ -> False + } + _ -> False + } + _ -> False + } + _ -> False + } +} + +// --------------------------------------------------------------------------- +// Point structure validation (vkey points) +// --------------------------------------------------------------------------- + +test test_vkey_point_structures() { + let is_g1 = fn(p) { + when p is { + G1Point { x: _, y: _ } -> True + _ -> False + } + } + and { + is_g1(test_plonk_vkey_affine.q_m), + is_g1(test_plonk_vkey_affine.q_l), + is_g1(test_plonk_vkey_affine.q_r), + is_g1(test_plonk_vkey_affine.q_o), + is_g1(test_plonk_vkey_affine.s_sig1), + is_g1(test_plonk_vkey_affine.s_sig2), + is_g1(test_plonk_vkey_affine.s_sig3), + when test_plonk_vkey_affine.x2 is { + G2Point { x: _, y: _ } -> True + _ -> False + }, + } +} + +test test_simple_point_validation() { + when simple_g1_point is { + G1Point { x, y: _y } -> x == x + _ -> False + } +} From 5578663eefc06bfec9951a991dcd57f6e83eda6d Mon Sep 17 00:00:00 2001 From: samdelaney Date: Tue, 24 Feb 2026 14:38:37 -0800 Subject: [PATCH 20/25] readme status update --- README.md | 83 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 7721d58..696aa14 100644 --- a/README.md +++ b/README.md @@ -4,54 +4,71 @@ A standards-compliant Zero-Knowledge Proof (ZKP) library for Cardano smart contr ## Project Overview -The Aiken ZKP Standards Library takes a pragmatic approach to implementing zero-knowledge proof systems on Cardano. We distinguish between committed features (ready for production) and aspirational features (planned for future development). +The Aiken ZKP Standards Library takes a pragmatic approach to implementing zero-knowledge proof systems on Cardano. We distinguish between committed features (current targets) and aspirational features (planned for future development). -## Features +This implementation is designed for Plutus v3, leveraging its built-in BLS12-381 curve functions to ensure efficient and secure verification of ZKPs. It emphasizes clarity and maintainability, making it suitable for educational/demonstrative purposes without sacrificing security. -### Features Currently Under Development +## Targeted Features -#### Groth16 -- Generic Groth16 proof verifier system -- Example circuits and R1CS constraint sets -- Comprehensive test suite +### Groth16 -#### Plonk -- Generic Plonk proof verifier system -- circom and SnarkJS integration -- Complete verification examples +Status: Complete -#### Bullet Proofs -- Generic Bullet Proof verifier system -- Example circuits and implementations -- Extensive testing suite +The Groth16 implementation is largely based on Modulo-P's [ak-381](https://github.com/Modulo-P/ak-381), with some ideas taken from [zarassh's implementation](https://github.com/tarassh/zkSNARK-under-the-hood/blob/main/groth16.py) and an emphasis on explicitness and clarity. This should make this implementation ideal for learning, or for further modification. -### Aspirational Features +Currently, the Groth16 module includes: -- **Marlin**: Preprocessing zkSNARKs with Universal and Updatable SRS -- **Plonky2**: Advanced recursive proof composition +- [x] Generic Groth16 proof verifier system +- [x] Onchain verification tests -## Implementation +Wishlist: -### On-Chain -- Leverages PlutusV3's BLS12-381 curve builtin functions -- Focuses on efficient verification -- Maintains strong security guarantees +- [ ] Integration examples with circom and SnarkJS +- [ ] Integration into a merkelized validator -### Off-Chain -- Examples using circom -- Integration with SnarkJS -- User-customizable proof generation +### Plonk -## Getting Started +Status: Optimizing -### Prerequisites -- Aiken development environment -- Familiarity with ZKP systems -- Understanding of Cardano smart contracts +The Plonk implementation is loosely based on perturbing's [plutus-plonk](https://github.com/perturbing/plutus-plonk-example), with several optimizations and restructuring done around point compression to improve performance and clarity. + +The Plonk module currently includes: + +- [x] Generic Plonk proof verifier system +- [x] Onchain verification tests + +Wishlist: + +- [ ] Optimize further to fit within Plutus resource limits +- [ ] Integration examples with circom and SnarkJS +- [ ] Integration into a merkelized validator + +### Bulletproofs + +Status: Early Development + +The Bulletproofs implementation is in the early stages, with a focus on building out the necessary field arithmetic and point operations required for Bulletproofs. + +### Helper Functions + +- Affine point operations - conversion to/from native types +- Field arithmetic utilities + +## Aspirational ZKP Systems + +- **Marlin**: Preprocessing zkSNARKs with Universal and Updatable SRS +- **Plonky2**: Advanced recursive proof composition ## Contributing We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) for: + - Development guidelines - Submission process -- Testing requirements \ No newline at end of file +- Testing requirements + +### Prerequisites + +- Aiken development environment +- Familiarity with ZKP systems +- Understanding of Cardano smart contracts From ba045ea4dd78bf1427d602b33f665717267e3ea9 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 2 Mar 2026 22:55:45 -0800 Subject: [PATCH 21/25] g1_on_curve --- zkp/lib/common/blst_affine.ak | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/zkp/lib/common/blst_affine.ak b/zkp/lib/common/blst_affine.ak index 0c1128f..ee87cb4 100644 --- a/zkp/lib/common/blst_affine.ak +++ b/zkp/lib/common/blst_affine.ak @@ -98,3 +98,17 @@ pub fn g2_affine_compress(point: G2Affine) -> ByteArray { } } } + +pub fn g1_on_curve(point: G1Affine) -> Bool { + when point is { + G1Infinity -> True + G1Point { x, y } -> { + // Check if the point satisfies the BLS12-381 G1 curve equation: y^2 = x^3 + 4 + let x_int = bytearray.to_int_big_endian(x) + let y_int = bytearray.to_int_big_endian(y) + let lhs = (y_int * y_int) % bls12_381_base_prime + let rhs = (x_int * x_int * x_int + 4) % bls12_381_base_prime + lhs == rhs + } + } +} \ No newline at end of file From d7d6a7e913a813efcfde73f6927e1333b108d6f3 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Mon, 2 Mar 2026 22:56:16 -0800 Subject: [PATCH 22/25] changes per review --- zkp/lib/plonk/preinputs.ak | 28 ++++++++++----- zkp/lib/plonk/proofs.ak | 18 +++++----- zkp/lib/tests/point_tests.ak | 69 ++++++++++-------------------------- 3 files changed, 46 insertions(+), 69 deletions(-) diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 63f4a31..065d160 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -41,20 +41,30 @@ pub fn plonk_prepare_preinputs( generator: State, n_public: Int, ) -> PreparedPlonkPreInputs { + expect q_m: G1Element = g1.decompress(inputs.q_m) + expect q_l: G1Element = g1.decompress(inputs.q_l) + expect q_r: G1Element = g1.decompress(inputs.q_r) + expect q_o: G1Element = g1.decompress(inputs.q_o) + expect q_c: G1Element = g1.decompress(inputs.q_c) + expect s_sig1: G1Element = g1.decompress(inputs.s_sig1) + expect s_sig2: G1Element = g1.decompress(inputs.s_sig2) + expect s_sig3: G1Element = g1.decompress(inputs.s_sig3) + expect x2: G2Element = g2.decompress(inputs.x2) + PreparedPlonkPreInputs { n: scalar.to_int(scale(scalar.from_int(2), inputs.power)), generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), power: inputs.power, k1: inputs.k1, k2: inputs.k2, - q_m: g1.decompress(inputs.q_m), - q_l: g1.decompress(inputs.q_l), - q_r: g1.decompress(inputs.q_r), - q_o: g1.decompress(inputs.q_o), - q_c: g1.decompress(inputs.q_c), - s_sig1: g1.decompress(inputs.s_sig1), - s_sig2: g1.decompress(inputs.s_sig2), - s_sig3: g1.decompress(inputs.s_sig3), - x2: g2.decompress(inputs.x2), + q_m, + q_l, + q_r, + q_o, + q_c, + s_sig1, + s_sig2, + s_sig3, + x2, } } diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak index c005cf9..3c7efbd 100644 --- a/zkp/lib/plonk/proofs.ak +++ b/zkp/lib/plonk/proofs.ak @@ -87,15 +87,15 @@ pub fn plonk_prepare_proof( ) // uncompress the proof commitments - let commitment_a: G1Element = g1.decompress(proof.commitment_a) - let commitment_b: G1Element = g1.decompress(proof.commitment_b) - let commitment_c: G1Element = g1.decompress(proof.commitment_c) - let commitment_z: G1Element = g1.decompress(proof.commitment_z) - let t_low: G1Element = g1.decompress(proof.t_low) - let t_mid: G1Element = g1.decompress(proof.t_mid) - let t_high: G1Element = g1.decompress(proof.t_high) - let w_omega: G1Element = g1.decompress(proof.w_omega) - let w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) + expect commitment_a: G1Element = g1.decompress(proof.commitment_a) + expect commitment_b: G1Element = g1.decompress(proof.commitment_b) + expect commitment_c: G1Element = g1.decompress(proof.commitment_c) + expect commitment_z: G1Element = g1.decompress(proof.commitment_z) + expect t_low: G1Element = g1.decompress(proof.t_low) + expect t_mid: G1Element = g1.decompress(proof.t_mid) + expect t_high: G1Element = g1.decompress(proof.t_high) + expect w_omega: G1Element = g1.decompress(proof.w_omega) + expect w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) // convert polynomial evaluations let a_eval: State = scalar.from_int(proof.a_eval) diff --git a/zkp/lib/tests/point_tests.ak b/zkp/lib/tests/point_tests.ak index eac1a72..53b711d 100644 --- a/zkp/lib/tests/point_tests.ak +++ b/zkp/lib/tests/point_tests.ak @@ -166,64 +166,31 @@ test test_vkey_point_decompressions() { // Point structure validation (proof points) // --------------------------------------------------------------------------- -test test_proof_point_structures() { - when test_plonk_proof_affine.commitment_a is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.commitment_b is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.commitment_c is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.commitment_z is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.t_low is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.t_mid is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.t_high is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.w_omega is { - G1Point { x: _, y: _ } -> - when test_plonk_proof_affine.w_omega_zeta is { - G1Point { x: _, y: _ } -> True - _ -> False - } - _ -> False - } - _ -> False - } - _ -> False - } - _ -> False - } - _ -> False - } - _ -> False - } - _ -> False - } - _ -> False + and { + g1_on_curve(test_plonk_proof_affine.commitment_a), + g1_on_curve(test_plonk_proof_affine.commitment_b), + g1_on_curve(test_plonk_proof_affine.commitment_c), + g1_on_curve(test_plonk_proof_affine.commitment_z), + g1_on_curve(test_plonk_proof_affine.t_low), + g1_on_curve(test_plonk_proof_affine.t_mid), + g1_on_curve(test_plonk_proof_affine.t_high), + g1_on_curve(test_plonk_proof_affine.w_omega), + g1_on_curve(test_plonk_proof_affine.w_omega_zeta), } -} // --------------------------------------------------------------------------- // Point structure validation (vkey points) // --------------------------------------------------------------------------- test test_vkey_point_structures() { - let is_g1 = fn(p) { - when p is { - G1Point { x: _, y: _ } -> True - _ -> False - } - } and { - is_g1(test_plonk_vkey_affine.q_m), - is_g1(test_plonk_vkey_affine.q_l), - is_g1(test_plonk_vkey_affine.q_r), - is_g1(test_plonk_vkey_affine.q_o), - is_g1(test_plonk_vkey_affine.s_sig1), - is_g1(test_plonk_vkey_affine.s_sig2), - is_g1(test_plonk_vkey_affine.s_sig3), + g1_on_curve(test_plonk_vkey_affine.q_m), + g1_on_curve(test_plonk_vkey_affine.q_l), + g1_on_curve(test_plonk_vkey_affine.q_r), + g1_on_curve(test_plonk_vkey_affine.q_o), + g1_on_curve(test_plonk_vkey_affine.s_sig1), + g1_on_curve(test_plonk_vkey_affine.s_sig2), + g1_on_curve(test_plonk_vkey_affine.s_sig3), when test_plonk_vkey_affine.x2 is { G2Point { x: _, y: _ } -> True _ -> False @@ -233,7 +200,7 @@ test test_vkey_point_structures() { test test_simple_point_validation() { when simple_g1_point is { - G1Point { x, y: _y } -> x == x + G1Point { _x, y: _y } -> g1_on_curve(simple_g1_point) _ -> False } } From cdfbae7a1b7e4eb0fdc34c1ff385c8eb150deb75 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Thu, 16 Jul 2026 18:10:13 -0700 Subject: [PATCH 23/25] bug fixes: - use keccak256 instead of blake2b224 - 0-index generators --- zkp/lib/common/blst_affine.ak | 6 +- zkp/lib/common/common.ak | 13 +- zkp/lib/plonk/preinputs.ak | 20 +-- zkp/lib/plonk/proofs.ak | 83 +++++++------ zkp/lib/plonk/verifier.ak | 77 +++++------- zkp/lib/tests/plonk_tests.ak | 225 ++++++++++++++++++---------------- zkp/lib/tests/point_tests.ak | 6 +- 7 files changed, 227 insertions(+), 203 deletions(-) diff --git a/zkp/lib/common/blst_affine.ak b/zkp/lib/common/blst_affine.ak index ee87cb4..4916746 100644 --- a/zkp/lib/common/blst_affine.ak +++ b/zkp/lib/common/blst_affine.ak @@ -1,8 +1,10 @@ use aiken/primitive/bytearray +/// BLS12-381 base field prime p +const bls12_381_base_prime: Int = + 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 + /// (p-1)/2 for BLS12-381 field modulus, used to determine y-coordinate sort bit. -/// p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 -/// half_p = (p-1)/2 const bls12_381_half_prime: Int = 2001204777610833696708894912867952078278441409969503942666029068062015825245418932221343814564507832018947136279893 diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index 73870f6..d17734a 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -160,16 +160,27 @@ fn mod_pow_loop(b, e, result) { // Blake2b-224 utilities pub fn concat_and_blake2b_224(inputs: List) -> ByteArray { - let concatenated = list.foldl(inputs, #"", bytearray.concat) + let concatenated = + list.foldl(inputs, #"", fn(x, acc) { bytearray.concat(acc, x) }) blake2b_224(concatenated) } +pub fn concat_and_keccak_256(inputs: List) -> ByteArray { + let concatenated = + list.foldl(inputs, #"", fn(x, acc) { bytearray.concat(acc, x) }) + crypto.keccak_256(concatenated) +} + // Scalar utilities pub fn scalar_rt_bs(bs: ByteArray) -> ByteArray { scalar.to_bytes(scalar.from_bytes(bs)) } +pub fn scalar_rt_bs_le(bs: ByteArray) -> ByteArray { + scalar.to_bytes_little_endian(scalar.from_bytes_little_endian(bs)) +} + pub fn scalar_rt_int(i: Int) -> Int { scalar.to_int(scalar.from_int(i)) } diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 065d160..0b6386f 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -21,6 +21,7 @@ pub type PlonkPreInputs { pub type PreparedPlonkPreInputs { n: Int, + generator: State, generators: List>, power: Int, k1: State, @@ -41,18 +42,19 @@ pub fn plonk_prepare_preinputs( generator: State, n_public: Int, ) -> PreparedPlonkPreInputs { - expect q_m: G1Element = g1.decompress(inputs.q_m) - expect q_l: G1Element = g1.decompress(inputs.q_l) - expect q_r: G1Element = g1.decompress(inputs.q_r) - expect q_o: G1Element = g1.decompress(inputs.q_o) - expect q_c: G1Element = g1.decompress(inputs.q_c) - expect s_sig1: G1Element = g1.decompress(inputs.s_sig1) - expect s_sig2: G1Element = g1.decompress(inputs.s_sig2) - expect s_sig3: G1Element = g1.decompress(inputs.s_sig3) - expect x2: G2Element = g2.decompress(inputs.x2) + let q_m = g1.decompress(inputs.q_m) + let q_l = g1.decompress(inputs.q_l) + let q_r = g1.decompress(inputs.q_r) + let q_o = g1.decompress(inputs.q_o) + let q_c = g1.decompress(inputs.q_c) + let s_sig1 = g1.decompress(inputs.s_sig1) + let s_sig2 = g1.decompress(inputs.s_sig2) + let s_sig3 = g1.decompress(inputs.s_sig3) + let x2 = g2.decompress(inputs.x2) PreparedPlonkPreInputs { n: scalar.to_int(scale(scalar.from_int(2), inputs.power)), + generator: generator, generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), power: inputs.power, k1: inputs.k1, diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak index 3c7efbd..d22034a 100644 --- a/zkp/lib/plonk/proofs.ak +++ b/zkp/lib/plonk/proofs.ak @@ -1,10 +1,9 @@ -use aiken/builtin.{blake2b_224} +use aiken/builtin.{keccak_256} use aiken/collection/list use aiken/crypto/bitwise.{State} -use aiken/crypto/bls12_381/scalar.{Scalar} +use aiken/crypto/bls12_381/scalar.{Scalar, field_prime} use aiken/crypto/bls12_381/g1 -use aiken/primitive/bytearray -use common/common.{concat_and_blake2b_224, scalar_rt_bs} +use common/common.{concat_and_keccak_256} use plonk/preinputs.{PreparedPlonkPreInputs} pub type PlonkProof { @@ -49,53 +48,59 @@ pub fn plonk_prepare_proof( pub_inputs: List, proof: PlonkProof, ) -> PreparedPlonkProof { + let pub_input_bytes = + list.map(pub_inputs, fn(x) { scalar.to_bytes(scalar.from_int(x)) }) let beta_inputs = - [ - g1.compress(preinputs.q_m), - g1.compress(preinputs.q_l), - g1.compress(preinputs.q_r), - g1.compress(preinputs.q_o), - g1.compress(preinputs.q_c), - g1.compress(preinputs.s_sig1), - g1.compress(preinputs.s_sig2), - g1.compress(preinputs.s_sig3), - list.foldl( - pub_inputs, - #"", - fn(a, b) { bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) }, + list.concat( + [ + g1.compress(preinputs.q_m), + g1.compress(preinputs.q_l), + g1.compress(preinputs.q_r), + g1.compress(preinputs.q_o), + g1.compress(preinputs.q_c), + g1.compress(preinputs.s_sig1), + g1.compress(preinputs.s_sig2), + g1.compress(preinputs.s_sig3), + ], + list.concat( + pub_input_bytes, + [proof.commitment_a, proof.commitment_b, proof.commitment_c], ), - proof.commitment_a, - proof.commitment_b, - proof.commitment_c, - ] - let beta = scalar_rt_bs(concat_and_blake2b_224(beta_inputs)) - let gamma = scalar_rt_bs(blake2b_224(beta)) - let alpha = - scalar_rt_bs(concat_and_blake2b_224([beta, gamma, proof.commitment_z])) - let zeta = + ) + let beta: State = scalar.from_bytes(concat_and_keccak_256(beta_inputs)) + let gamma: State = + scalar.from_bytes(keccak_256(scalar.to_bytes(beta))) + let alpha: State = scalar.from_bytes( - concat_and_blake2b_224([alpha, proof.t_low, proof.t_mid, proof.t_high]), + concat_and_keccak_256( + [scalar.to_bytes(beta), scalar.to_bytes(gamma), proof.commitment_z], + ), + ) + let zeta: State = + scalar.from_bytes( + concat_and_keccak_256( + [scalar.to_bytes(alpha), proof.t_low, proof.t_mid, proof.t_high], + ), ) let lagrange_inverses = list.map( preinputs.generators, fn(x) { - scalar.neg( - scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, x)), - ) + let denom = scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, x)) + scalar.scale(denom, field_prime - 2) }, ) // uncompress the proof commitments - expect commitment_a: G1Element = g1.decompress(proof.commitment_a) - expect commitment_b: G1Element = g1.decompress(proof.commitment_b) - expect commitment_c: G1Element = g1.decompress(proof.commitment_c) - expect commitment_z: G1Element = g1.decompress(proof.commitment_z) - expect t_low: G1Element = g1.decompress(proof.t_low) - expect t_mid: G1Element = g1.decompress(proof.t_mid) - expect t_high: G1Element = g1.decompress(proof.t_high) - expect w_omega: G1Element = g1.decompress(proof.w_omega) - expect w_omega_zeta: G1Element = g1.decompress(proof.w_omega_zeta) + let commitment_a = g1.decompress(proof.commitment_a) + let commitment_b = g1.decompress(proof.commitment_b) + let commitment_c = g1.decompress(proof.commitment_c) + let commitment_z = g1.decompress(proof.commitment_z) + let t_low = g1.decompress(proof.t_low) + let t_mid = g1.decompress(proof.t_mid) + let t_high = g1.decompress(proof.t_high) + let w_omega = g1.decompress(proof.w_omega) + let w_omega_zeta = g1.decompress(proof.w_omega_zeta) // convert polynomial evaluations let a_eval: State = scalar.from_int(proof.a_eval) diff --git a/zkp/lib/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak index c233fca..d8d605e 100644 --- a/zkp/lib/plonk/verifier.ak +++ b/zkp/lib/plonk/verifier.ak @@ -1,12 +1,11 @@ -use aiken/builtin.{blake2b_224} +use aiken/builtin.{keccak_256} use aiken/collection/list use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/g1 use aiken/crypto/bls12_381/g2 use aiken/crypto/bls12_381/pairing.{final_exponentiation, miller_loop} -use aiken/crypto/bls12_381/scalar.{Scalar, field_prime, scale2} -use aiken/primitive/bytearray -use common/common.{concat_and_blake2b_224, g1_sum, scalar_sum} +use aiken/crypto/bls12_381/scalar.{Scalar, scale2} +use common/common.{concat_and_keccak_256, g1_sum, scalar_sum} use plonk/preinputs.{PreparedPlonkPreInputs} use plonk/proofs.{PreparedPlonkProof} @@ -40,60 +39,45 @@ pub fn verify_plonk( let w_all: List> = list.map(pub_inputs, fn(x) { scalar.neg(scalar.from_int(x)) }) - // extract generator elements from trusted setup - expect Some(gen_tail) = list.tail(preinputs.generators) - expect Some(gen_2nd) = list.head(gen_tail) + // w (root of unity), used directly in the pairing check + let gen_w = preinputs.generator // define scalar constants let scalar1: State = scalar.from_int(1) - // I guess we don't need to decompress the generators? The aiken documentation is not clear on this. - // let g1_generator = g1.decompress(g1.generator) - // let g2_generator = g2.decompress(g2.generator) - // - // compute Fiat-Shamir challenges + // compute Fiat-Shamir challenges (BE encoding, keccak-256, all pub inputs) + // matching snarkjs's keccak256-compressed transcript variant + let pub_input_bytes = + list.map(pub_inputs, fn(x) { scalar.to_bytes(scalar.from_int(x)) }) let beta: State = scalar.from_bytes( - concat_and_blake2b_224( - [ - q_m, - q_l, - q_r, - q_o, - q_c, - s_sig1, - s_sig2, - s_sig3, - list.foldl( - pub_inputs, - #"", - fn(a, b) { - bytearray.concat(b, bytearray.from_int_big_endian(a, 32)) - }, + concat_and_keccak_256( + list.concat( + [q_m, q_l, q_r, q_o, q_c, s_sig1, s_sig2, s_sig3], + list.concat( + pub_input_bytes, + [bs_commitment_a, bs_commitment_b, bs_commitment_c], ), - bs_commitment_a, - bs_commitment_b, - bs_commitment_c, - ], + ), ), ) let gamma: State = - scalar.from_bytes(blake2b_224(scalar.to_bytes(beta))) + scalar.from_bytes(keccak_256(scalar.to_bytes(beta))) let alpha: State = scalar.from_bytes( - concat_and_blake2b_224( + concat_and_keccak_256( [scalar.to_bytes(beta), scalar.to_bytes(gamma), bs_commitment_z], ), ) let zeta: State = scalar.from_bytes( - concat_and_blake2b_224( + concat_and_keccak_256( [scalar.to_bytes(alpha), bs_t_low, bs_t_mid, bs_t_high], ), ) let v: State = scalar.from_bytes( - concat_and_blake2b_224( + concat_and_keccak_256( [ scalar.to_bytes(zeta), scalar.to_bytes(proof.a_eval), @@ -106,12 +90,10 @@ pub fn verify_plonk( ), ) let u: State = - scalar.from_bytes( - concat_and_blake2b_224([bs_w_omega, bs_w_omega_zeta]), - ) + scalar.from_bytes(concat_and_keccak_256([bs_w_omega, bs_w_omega_zeta])) // compute powers of zeta for polynomial evaluation - let pow2_zeta_p: State = scale2(zeta, field_prime) + let pow2_zeta_p: State = scale2(zeta, preinputs.power) let pow2_zeta_p_minus_1: State = scalar.sub(pow2_zeta_p, scalar1) // calculate lagrange polynomials @@ -252,7 +234,7 @@ pub fn verify_plonk( g1.add( g1.add( g1.scale(proof.w_omega, zeta), - g1.scale(proof.w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_2nd))), + g1.scale(proof.w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_w))), ), g1.sub(batched_polynomial_commitments, group_encoded_batch_eval), ), @@ -263,17 +245,18 @@ pub fn verify_plonk( // verify lagrange polynomial inverses let lagrange_check = - scalar_sum( + list.all( list.map2( proof.lagrange_inverses, preinputs.generators, - fn(x, y) { + fn(inv, gen) { scalar.mul( - x, - scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, y)), - ) + inv, + scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, gen)), + ) == scalar.from_int(1) }, ), - ) == scalar.from_int(1) + fn(b) { b }, + ) final_verification && lagrange_check } diff --git a/zkp/lib/tests/plonk_tests.ak b/zkp/lib/tests/plonk_tests.ak index 3ff023e..03925c2 100644 --- a/zkp/lib/tests/plonk_tests.ak +++ b/zkp/lib/tests/plonk_tests.ak @@ -18,112 +18,108 @@ use tests/test_utils.{int_to_field_bytes} pub const test_plonk_proof: RawPlonkProof = RawPlonkProof { commitment_a: Point { - x: 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, - y: 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, + x: 2955082493890020621983548864242801681331382761589593836396462786237809490379730083621148456292328311837539547233078, + y: 2990917470048022633751403325877110490023214940780163879068748589295894184468420861593276240916860222556041630309490, z: 1, }, commitment_b: Point { - x: 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, - y: 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + x: 2139748953192924893718111139796467598787187717809064847657431290279584919816359328113211416216965946326904372181144, + y: 2259668502813448495833083885609627801405772920489944855694509142813690380362130929482939621563275288429386208477174, z: 1, }, commitment_c: Point { - x: 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, - y: 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + x: 2029868808682428131517521841196317763678238092678415346121426692356142038272541088877792972412724621664112264945402, + y: 983551628294237524163209598922680914320460584515486665440996990611591240136708425683923303325991832470735477012116, z: 1, }, commitment_z: Point { - x: 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, - y: 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + x: 2387801164586378454252042857525161272590791672625817623997854477584013441773831593184571862621830523992226453996145, + y: 2963460115634323994397623882034777328875861522711306860138637408553554162058666092728227754125238789945570425688766, z: 1, }, t_low: Point { - x: 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, - y: 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + x: 1894516259155874795478636775854000079842047119288244714503215181482526148945291020416724440229372193886732121411353, + y: 1031154329693902984660329178103732470848052600799342617372338054649599821052286659433310234216256987572488522064742, z: 1, }, t_mid: Point { - x: 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, - y: 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, + x: 396230736967861803635980419570754115302632616415474674207045272682865255719918284458879898212237757916289534540503, + y: 1286345817905833523305277948742871641051845921916399591001898962704587418794625017612994249491313890015329626321188, z: 1, }, t_high: Point { - x: 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, - y: 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, + x: 1605020952126297833723226836157259589899522431634090862836060595671759262433465086466801152520962321838375613406480, + y: 3380611695801979784487178272729979547287652149847817235299237981219620495852861730756702710944277941379315901919628, z: 1, }, w_omega: Point { - x: 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, - y: 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, + x: 3450797490829451101684300912559686520039512327488446737240041689856044522958259926705205991152261371334594680942229, + y: 3325733188905070684197604249400574204476897614422083280941305902591531412024682158968329032713733872654995261106522, z: 1, }, w_omega_zeta: Point { - x: 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, - y: 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, + x: 863669987569139054364812933508800738931137091074775255594224713818925528334922843056187052587160290967836521340909, + y: 2166514895834521672481059065176477660352283408684189579849124753231689040068150141242374151666482477587206334178810, z: 1, }, - a_eval: 18376945509548979154234285967502303667654384864417109878730063780807771386804, - b_eval: 48091350738882108076678452440283491448151427314124311219043220475171874763276, - c_eval: 19995498867920222404318895039685100281891200599706204591987499224278246956716, - s_sig1_p: 22639172691520838707669313728412965432032750722138107727665998164239763135904, - s_sig2_p: 38226063540390035205748121923613940584145755981537302966237851066336751425207, - z_omega: 46342921279167233860927215620995843068292504644614777821809165775517811410811, + a_eval: 22757212845473628986664301092572773725843860106781649491082133699785307937133, + b_eval: 51429051849495352387445623433681097795689954214627149917980353176226473881347, + c_eval: 48242326795347271969267233696976762494320046178030976356298515157950481259823, + s_sig1_p: 49194979834930093396344655415079764564343526505778221100851639750638575027756, + s_sig2_p: 12011587759165679750727880280659232729752930325587463363828179196808481389053, + z_omega: 24019962433584515698965513494582469296076614442597410659646743422950745162859, } pub const test_plonk_vkey: PlonkVerificationKey = PlonkVerificationKey { n_public: 2, - power: 8, + power: 3, k1: 2, k2: 3, q_m: Point { - x: 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, - y: 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, + x: 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, + y: 3276847779626248817921041660741117745502051773945482686666030885472398470355715064844179289766796069907281222412724, z: 1, }, q_l: Point { - x: 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, - y: 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, - z: 1, - }, - q_r: Point { - x: 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, - y: 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, + x: 2832459832163495150479122726264129004255627621445400446445904161463167979768458296565736026250455466004404325092996, + y: 3741891246739296148991860192122594744687151654172617277458830187550386521367405061644711803513753648977431388490950, z: 1, }, + q_r: Point { x: 0, y: 1, z: 0 }, q_o: Point { - x: 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, - y: 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, + x: 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, + y: 725561775595418575496748164994786411054831045993525198666027250651633180135122799598508339362219594130613050147063, z: 1, }, - q_c: Point { x: 0, y: 1, z: 1 }, + q_c: Point { x: 0, y: 1, z: 0 }, s_sig1: Point { - x: 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, - y: 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, + x: 3944485635698459932433634706365550699364147680658608621022581948882602066423214201823994363817057003852352113973102, + y: 2481473700589819128241850529786326259739363860413421083419242322439772753684327842018177309699278701645719502063011, z: 1, }, s_sig2: Point { - x: 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, - y: 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, + x: 608913174169519000812264133236120028473675090457027054092233721874136995363377165138135110723165351281407038604929, + y: 2540453045645467686101782045248642180331599264159056690287844382784021393502357721825604716334062206625833942598550, z: 1, }, s_sig3: Point { - x: 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, - y: 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, + x: 324514252262195321244614754390467750793932334185439650123651923691851924066772665377485916928100616166627086332426, + y: 3113468956497733006315552301280484390953024074059319036050522016086390656265116534130248224200617657987091997981033, z: 1, }, x2: RawG2Point { x: ( - 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, - 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, + 315392587371006580320359080746390447102226920810775075601256338851306698095121762912694048386654833732339420515166, + 2161957325100184324059278577028157171100154313724439672191511832751025188121569798837418082248673407995264313041542, ), y: ( - 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, - 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, + 3808454252163556366145098778553759832772829525954193157529438608174590910603583474173900502559366413190158202701670, + 2556911765464293729917692748672416866651619396879891492332729234344507429587256369481474563225773001582828153945765, ), z: (1, 0), }, - w: 21071158244812412064791010377580296085971058123779034548857891862303448703672, + w: 28761180743467419819834788392525162889723178799021384024940474588120723734663, } // =========================================================================== @@ -134,173 +130,163 @@ pub const test_plonk_proof_affine: AffineProof = AffineProof { commitment_a: G1Point { x: int_to_field_bytes( - 399714812033820296216726717315692873642054968503075620037377214529856825716972894609352051705385873489893693842262, + 2955082493890020621983548864242801681331382761589593836396462786237809490379730083621148456292328311837539547233078, ), y: int_to_field_bytes( - 1782459767099423050585438078786855361136177224655928733773175176189013880612942735283494339385374420323073001112641, + 2990917470048022633751403325877110490023214940780163879068748589295894184468420861593276240916860222556041630309490, ), }, commitment_b: G1Point { x: int_to_field_bytes( - 1449497163185511255960735085284916465289671960584516882188976107972557459282929317206787632956592412629479052789269, + 2139748953192924893718111139796467598787187717809064847657431290279584919816359328113211416216965946326904372181144, ), y: int_to_field_bytes( - 3989435130608907884226398003251903810202294754002824591270717519803551766794598638405495657602973072105705998377235, + 2259668502813448495833083885609627801405772920489944855694509142813690380362130929482939621563275288429386208477174, ), }, commitment_c: G1Point { x: int_to_field_bytes( - 1736517976604336165319190912612272873938577839112916200234711436320455450585513057542289433496238251851408450867401, + 2029868808682428131517521841196317763678238092678415346121426692356142038272541088877792972412724621664112264945402, ), y: int_to_field_bytes( - 3933887163973139524131102938351008807575633708420235139733365238460765495095075881949112452329274308243204895789594, + 983551628294237524163209598922680914320460584515486665440996990611591240136708425683923303325991832470735477012116, ), }, commitment_z: G1Point { x: int_to_field_bytes( - 3704008370612030042639225780688100652501841279134321941704739881999208925475933291394297428500078646993316240973153, + 2387801164586378454252042857525161272590791672625817623997854477584013441773831593184571862621830523992226453996145, ), y: int_to_field_bytes( - 1810906233092108589178121796443607027092699677349013567194850167065200892154306562227413644705236351395811153581123, + 2963460115634323994397623882034777328875861522711306860138637408553554162058666092728227754125238789945570425688766, ), }, t_low: G1Point { x: int_to_field_bytes( - 1037602692507020953234459923280188933700635788749088578899524073006036329531065870292398532987175917561104371064333, + 1894516259155874795478636775854000079842047119288244714503215181482526148945291020416724440229372193886732121411353, ), y: int_to_field_bytes( - 539240156603698219404689729686562881306656606356852620033422782463096424705762574130046909186283630196554411802829, + 1031154329693902984660329178103732470848052600799342617372338054649599821052286659433310234216256987572488522064742, ), }, t_mid: G1Point { x: int_to_field_bytes( - 1692226746353335986575911773980405779260696262670254538831501191412195488685646640023799739269172886920159637695906, + 396230736967861803635980419570754115302632616415474674207045272682865255719918284458879898212237757916289534540503, ), y: int_to_field_bytes( - 1354336783191205878441510794012597580400027354490519841628062508125869118021464283240372252090033582923590283991560, + 1286345817905833523305277948742871641051845921916399591001898962704587418794625017612994249491313890015329626321188, ), }, t_high: G1Point { x: int_to_field_bytes( - 2268540311742292945504195970201055741326008213532161188285649354525499790295646583995939443476735249350880379085715, + 1605020952126297833723226836157259589899522431634090862836060595671759262433465086466801152520962321838375613406480, ), y: int_to_field_bytes( - 2569084549830440133536848041445191336451184233926465968821579928477223230877417703378165316647986190894815787263497, + 3380611695801979784487178272729979547287652149847817235299237981219620495852861730756702710944277941379315901919628, ), }, w_omega: G1Point { x: int_to_field_bytes( - 2256253188572816915510198423038750971978474393904429398295341708201382325046863124272051278358788247020323704142906, + 3450797490829451101684300912559686520039512327488446737240041689856044522958259926705205991152261371334594680942229, ), y: int_to_field_bytes( - 1979459898762844357184675048149583009814453205147993939465109586513217609705015904500222239602869407830277465911162, + 3325733188905070684197604249400574204476897614422083280941305902591531412024682158968329032713733872654995261106522, ), }, w_omega_zeta: G1Point { x: int_to_field_bytes( - 1398955141452817068638087562646700919444151484378980054571127020338377232477111068147866740784586884059458451124441, + 863669987569139054364812933508800738931137091074775255594224713818925528334922843056187052587160290967836521340909, ), y: int_to_field_bytes( - 2554317196176981949094860199287847940263931647744622493096717980568660295734791352507060120436023633165152199586641, + 2166514895834521672481059065176477660352283408684189579849124753231689040068150141242374151666482477587206334178810, ), }, - a_eval: 18376945509548979154234285967502303667654384864417109878730063780807771386804, - b_eval: 48091350738882108076678452440283491448151427314124311219043220475171874763276, - c_eval: 19995498867920222404318895039685100281891200599706204591987499224278246956716, - s_sig1_p: 22639172691520838707669313728412965432032750722138107727665998164239763135904, - s_sig2_p: 38226063540390035205748121923613940584145755981537302966237851066336751425207, - z_omega: 46342921279167233860927215620995843068292504644614777821809165775517811410811, + a_eval: 22757212845473628986664301092572773725843860106781649491082133699785307937133, + b_eval: 51429051849495352387445623433681097795689954214627149917980353176226473881347, + c_eval: 48242326795347271969267233696976762494320046178030976356298515157950481259823, + s_sig1_p: 49194979834930093396344655415079764564343526505778221100851639750638575027756, + s_sig2_p: 12011587759165679750727880280659232729752930325587463363828179196808481389053, + z_omega: 24019962433584515698965513494582469296076614442597410659646743422950745162859, } pub const test_plonk_vkey_affine: AffineVerificationKey = AffineVerificationKey { n_public: 2, - power: 8, + power: 3, k1: 2, k2: 3, q_m: G1Point { x: int_to_field_bytes( - 1485308189295961604185653399552265233568688514611223863279357861144388046899811084513518492552601431517165725165971, + 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, ), y: int_to_field_bytes( - 992538694578205251326096775943097925418467615058551631052111575895093418458300804064507757521625296625310599349723, + 3276847779626248817921041660741117745502051773945482686666030885472398470355715064844179289766796069907281222412724, ), }, q_l: G1Point { x: int_to_field_bytes( - 3808249754942397241330511301413196166003955782919018624626607966829702145577693560566042872185504406456536302983955, - ), - y: int_to_field_bytes( - 334985387256252305585406619525977031800805357668732100070799773304748206390389242856982647098157021442761014670945, - ), - }, - q_r: G1Point { - x: int_to_field_bytes( - 2897619634270883322450943457785188839105440105072710492480584851023729526708312634232963293320637346514551986805742, + 2832459832163495150479122726264129004255627621445400446445904161463167979768458296565736026250455466004404325092996, ), y: int_to_field_bytes( - 2855811981400578407057601033222167884051983841605446189038998606715383174965314273487201622917441463707586898309766, + 3741891246739296148991860192122594744687151654172617277458830187550386521367405061644711803513753648977431388490950, ), }, + q_r: G1Infinity, q_o: G1Point { x: int_to_field_bytes( - 814347128357583438958593756305639661199308802394740821018406167183473863302824931173904983106363373506812005720315, + 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, ), y: int_to_field_bytes( - 392517182941226059056427317111755221589156956623626216275829771223496202759401433988361922591974167029794586002532, + 725561775595418575496748164994786411054831045993525198666027250651633180135122799598508339362219594130613050147063, ), }, q_c: G1Infinity, s_sig1: G1Point { x: int_to_field_bytes( - 2197513614034856916370853438892501477152149934478303536645946153585183006435953093362820541577979483624308764288210, + 3944485635698459932433634706365550699364147680658608621022581948882602066423214201823994363817057003852352113973102, ), y: int_to_field_bytes( - 1004278254600332665808982339738638959174376451179755277367583932516007236071095154078965876620089463443221925197058, + 2481473700589819128241850529786326259739363860413421083419242322439772753684327842018177309699278701645719502063011, ), }, s_sig2: G1Point { x: int_to_field_bytes( - 2792320613499513162602939592043861109833994127350754796708006671824164354135183747858386254514235591122593379509572, + 608913174169519000812264133236120028473675090457027054092233721874136995363377165138135110723165351281407038604929, ), y: int_to_field_bytes( - 2333135428922304094120412484339749882594628537636730498336489319953557937241107284786470185251869116762991194721566, + 2540453045645467686101782045248642180331599264159056690287844382784021393502357721825604716334062206625833942598550, ), }, s_sig3: G1Point { x: int_to_field_bytes( - 1103919397130297959968277202338946722162096428414458862374865645981333532113010491895072499744737768601377012184171, + 324514252262195321244614754390467750793932334185439650123651923691851924066772665377485916928100616166627086332426, ), y: int_to_field_bytes( - 3820303361117471251597865673064664883576772502320326788336405540507729560912383753673093898281484117811905391873387, + 3113468956497733006315552301280484390953024074059319036050522016086390656265116534130248224200617657987091997981033, ), }, x2: G2Point { x: ( int_to_field_bytes( - 1384881107644691760284367063980460470837722062168972112603745064579088112821615947718233022262480072532339601220537, + 315392587371006580320359080746390447102226920810775075601256338851306698095121762912694048386654833732339420515166, ), int_to_field_bytes( - 2842651620008812854042218367288004845494747676361852248182110358230622686959604652894688487279824885403245017487211, + 2161957325100184324059278577028157171100154313724439672191511832751025188121569798837418082248673407995264313041542, ), ), y: ( int_to_field_bytes( - 2917417878144034957746750880342563779569305982143085633270575416134448866459149989199472589551242219517452091607367, + 3808454252163556366145098778553759832772829525954193157529438608174590910603583474173900502559366413190158202701670, ), int_to_field_bytes( - 2608346410842449397230047391131978849528470203228975825458456002305990109512591388208118490243142715582203913123829, + 2556911765464293729917692748672416866651619396879891492332729234344507429587256369481474563225773001582828153945765, ), ), }, - w: 21071158244812412064791010377580296085971058123779034548857891862303448703672, + w: 28761180743467419819834788392525162889723178799021384024940474588120723734663, } pub const test_plonk_pub_inputs: List = - [ - 8301577178634781303874616783681599046073052196886694608055856488206182952326, - 5, - ] + [63, 7] // =========================================================================== // Vkey compression tests @@ -400,7 +386,7 @@ test test_power_calculation() { let two = scalar.from_int(2) let result = scalar.scale(two, power) let n = scalar.to_int(result) - n == 256 + n == 8 } test test_generator_list_creation() { @@ -427,3 +413,36 @@ test test_plonk_verification_with_affine_points() { proof: prepared_proof, ) } + +// Sanity check for the test above: a proof tampered with (wrong a_eval) +// must be rejected, so we know verify_plonk isn't vacuously returning True. +test test_plonk_verification_rejects_tampered_proof() fail { + let tampered_proof_affine = + AffineProof { + ..test_plonk_proof_affine, + a_eval: test_plonk_proof_affine.a_eval + 1, + } + let prepared_preinputs = prepare_affine_vkey(test_plonk_vkey_affine) + let proof = prepare_affine_proof(tampered_proof_affine) + let prepared_proof = + plonk_prepare_proof(prepared_preinputs, test_plonk_pub_inputs, proof) + verify_plonk( + preinputs: prepared_preinputs, + pub_inputs: test_plonk_pub_inputs, + proof: prepared_proof, + ) +} + +// Sanity check with wrong public inputs instead of a tampered proof element. +test test_plonk_verification_rejects_wrong_pub_inputs() fail { + let wrong_pub_inputs = [64, 7] + let prepared_preinputs = prepare_affine_vkey(test_plonk_vkey_affine) + let proof = prepare_affine_proof(test_plonk_proof_affine) + let prepared_proof = + plonk_prepare_proof(prepared_preinputs, wrong_pub_inputs, proof) + verify_plonk( + preinputs: prepared_preinputs, + pub_inputs: wrong_pub_inputs, + proof: prepared_proof, + ) +} diff --git a/zkp/lib/tests/point_tests.ak b/zkp/lib/tests/point_tests.ak index 53b711d..f5d06b1 100644 --- a/zkp/lib/tests/point_tests.ak +++ b/zkp/lib/tests/point_tests.ak @@ -2,7 +2,7 @@ use aiken/crypto/bls12_381/g1 use aiken/primitive/bytearray use common/blst_affine.{ G1Affine, G1Infinity, G1Point, G2Infinity, G2Point, g1_affine_compress, - g2_affine_compress, + g1_on_curve, g2_affine_compress, } use tests/plonk_tests.{ test_plonk_proof_affine, test_plonk_vkey_affine, @@ -166,6 +166,7 @@ test test_vkey_point_decompressions() { // Point structure validation (proof points) // --------------------------------------------------------------------------- +test test_proof_point_structures() { and { g1_on_curve(test_plonk_proof_affine.commitment_a), g1_on_curve(test_plonk_proof_affine.commitment_b), @@ -177,6 +178,7 @@ test test_vkey_point_decompressions() { g1_on_curve(test_plonk_proof_affine.w_omega), g1_on_curve(test_plonk_proof_affine.w_omega_zeta), } +} // --------------------------------------------------------------------------- // Point structure validation (vkey points) @@ -200,7 +202,7 @@ test test_vkey_point_structures() { test test_simple_point_validation() { when simple_g1_point is { - G1Point { _x, y: _y } -> g1_on_curve(simple_g1_point) + G1Point { .. } -> g1_on_curve(simple_g1_point) _ -> False } } From 78556556a2e6e1d221f684371a079fe9bf3590c2 Mon Sep 17 00:00:00 2001 From: samdelaney Date: Thu, 16 Jul 2026 23:11:26 -0700 Subject: [PATCH 24/25] fix bullet for stdlib 3 --- zkp/lib/bullet/bullet.ak | 93 ++++++++++++++++++----------------- zkp/lib/common/blst_affine.ak | 9 +--- zkp/lib/common/common.ak | 8 ++- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/zkp/lib/bullet/bullet.ak b/zkp/lib/bullet/bullet.ak index 494dc3d..23b5ff4 100644 --- a/zkp/lib/bullet/bullet.ak +++ b/zkp/lib/bullet/bullet.ak @@ -1,7 +1,8 @@ use aiken/collection/list use aiken/crypto +use aiken/crypto/bitwise.{State} use aiken/crypto/bls12_381/g1 -use aiken/crypto/bls12_381/scalar +use aiken/crypto/bls12_381/scalar.{Scalar} use aiken/primitive/bytearray /// Prepended to every generator derivation and transcript hash so this @@ -30,19 +31,19 @@ pub type BulletproofProof { s: G1Element, t1: G1Element, t2: G1Element, - tau_x: scalar.Scalar, - mu: scalar.Scalar, - l_vec: List, - r_vec: List, + tau_x: State, + mu: State, + l_vec: List>, + r_vec: List>, } pub type ProofBlinding { - alpha: scalar.Scalar, - rho: scalar.Scalar, - tau1: scalar.Scalar, - tau2: scalar.Scalar, - s_l: List, - s_r: List, + alpha: State, + rho: State, + tau1: State, + tau2: State, + s_l: List>, + s_r: List>, } /// Derive a deterministic, nothing-up-my-sleeve set of generators for an @@ -86,9 +87,9 @@ pub fn setup(n: Int) -> BulletproofVerificationKey { pub fn commit_value( vk: BulletproofVerificationKey, value: Int, - gamma: scalar.Scalar, + gamma: State, ) -> G1Element { - expect Some(value_scalar) = scalar.new(value) + let value_scalar = scalar.from_int(value) g1.add(g1.scale(vk.g, value_scalar), g1.scale(vk.h, gamma)) } @@ -98,7 +99,7 @@ pub fn commit_value( pub fn generate_proof( vk: BulletproofVerificationKey, value: Int, - gamma: scalar.Scalar, + gamma: State, blinding: ProofBlinding, ) -> BulletproofProof { if value < 0 || value >= pow2(vk.n) { @@ -113,7 +114,7 @@ pub fn generate_proof( pub fn generate_proof_deterministic( vk: BulletproofVerificationKey, value: Int, - gamma: scalar.Scalar, + gamma: State, seed: ByteArray, ) -> BulletproofProof { generate_proof(vk, value, gamma, derive_blinding(seed, vk.n)) @@ -122,7 +123,7 @@ pub fn generate_proof_deterministic( fn generate_proof_unchecked( vk: BulletproofVerificationKey, value: Int, - gamma: scalar.Scalar, + gamma: State, blinding: ProofBlinding, ) -> BulletproofProof { if list.length(blinding.s_l) != vk.n || list.length(blinding.s_r) != vk.n { @@ -130,7 +131,7 @@ fn generate_proof_unchecked( } else { let bits = bits_of(value, vk.n) let a_l = list.map(bits, scalar_from_bit) - let a_r = list.map(a_l, fn(ai) { scalar.sub(ai, scalar.one) }) + let a_r = list.map(a_l, fn(ai) { scalar.sub(ai, scalar.from_int(1)) }) let v_commit = commit_value(vk, value, gamma) @@ -153,7 +154,7 @@ fn generate_proof_unchecked( let y = hash_to_scalar(bytearray.concat(state1, "y")) let z = hash_to_scalar(bytearray.concat(state1, "z")) - let two = scalar.add(scalar.one, scalar.one) + let two = scalar.add(scalar.from_int(1), scalar.from_int(1)) let z2 = scalar.mul(z, z) let y_pow = powers_of(y, vk.n) let two_pow = powers_of(two, vk.n) @@ -213,8 +214,8 @@ fn generate_proof_unchecked( /// Named helper (not an inline lambda) — a bare bit (0/1) is always a valid /// field element, so this cannot fail in practice. -fn scalar_from_bit(b: Int) -> scalar.Scalar { - expect Some(s) = scalar.new(b) +fn scalar_from_bit(b: Int) -> State { + let s = scalar.from_int(b) s } @@ -273,7 +274,7 @@ pub fn verify( when scalar.recip(y) is { None -> False Some(y_inv) -> { - let two = scalar.add(scalar.one, scalar.one) + let two = scalar.add(scalar.from_int(1), scalar.from_int(1)) let y_pow = powers_of(y, vk.n) let two_pow = powers_of(two, vk.n) let sum_y = scalar_sum(y_pow) @@ -379,37 +380,39 @@ fn transcript_absorb2( /// interpreted as a big-endian integer lands outside `field_prime` (~45% of /// 2^256) about 55% of the time, so a single-shot hash-to-scalar traps /// unacceptably often; retry with an incrementing counter instead. -fn hash_to_scalar(seed: ByteArray) -> scalar.Scalar { +fn hash_to_scalar(seed: ByteArray) -> State { hash_to_scalar_loop(seed, 0) } -fn hash_to_scalar_loop(seed: ByteArray, counter: Int) -> scalar.Scalar { +fn hash_to_scalar_loop(seed: ByteArray, counter: Int) -> State { let digest = crypto.blake2b_256( bytearray.concat(seed, bytearray.from_int_big_endian(counter, 4)), ) - when scalar.from_bytearray_big_endian(digest) is { - Some(s) -> s - None -> hash_to_scalar_loop(seed, counter + 1) + let n = bytearray.to_int_big_endian(digest) + if n < scalar.field_prime { + scalar.from_int(n) + } else { + hash_to_scalar_loop(seed, counter + 1) } } /// `scalar.scale(base, i)` is exponentiation (`base^i`), not i*base — it /// produces [1, base, base^2, ...] as required by the Bulletproofs protocol. -fn powers_of(base: scalar.Scalar, count: Int) -> List { +fn powers_of(base: State, count: Int) -> List> { list.map(list.range(0, count - 1), fn(i) { scalar.scale(base, i) }) } -fn scalar_sum(vec: List) -> scalar.Scalar { - list.foldl(vec, scalar.zero, scalar.add) +fn scalar_sum(vec: List>) -> State { + list.foldl(vec, scalar.from_int(0), scalar.add) } fn scalar_inner_product( - l: List, - r: List, -) -> scalar.Scalar { + l: List>, + r: List>, +) -> State { when (l, r) is { - ([], []) -> scalar.zero + ([], []) -> scalar.from_int(0) ([x, ..xs], [y, ..ys]) -> scalar.add(scalar.mul(x, y), scalar_inner_product(xs, ys)) (_, _) -> fail @"vector length mismatch" @@ -417,7 +420,7 @@ fn scalar_inner_product( } fn vector_commit( - scalars: List, + scalars: List>, points: List, ) -> G1Element { when (scalars, points) is { @@ -455,7 +458,7 @@ test setup_smoke() { test range_proof_valid() { let vk = setup(8) - expect Some(gamma) = scalar.new(12345) + let gamma = scalar.from_int(12345) let value = 200 let proof = generate_proof_deterministic(vk, value, gamma, "seed-1") let v_commit = commit_value(vk, value, gamma) @@ -464,7 +467,7 @@ test range_proof_valid() { test range_proof_valid_zero() { let vk = setup(8) - expect Some(gamma) = scalar.new(777) + let gamma = scalar.from_int(777) let value = 0 let proof = generate_proof_deterministic(vk, value, gamma, "seed-2") let v_commit = commit_value(vk, value, gamma) @@ -473,7 +476,7 @@ test range_proof_valid_zero() { test range_proof_valid_max() { let vk = setup(8) - expect Some(gamma) = scalar.new(9999) + let gamma = scalar.from_int(9999) let value = 255 let proof = generate_proof_deterministic(vk, value, gamma, "seed-3") let v_commit = commit_value(vk, value, gamma) @@ -482,7 +485,7 @@ test range_proof_valid_max() { test range_proof_fails_wrong_commitment() { let vk = setup(8) - expect Some(gamma) = scalar.new(12345) + let gamma = scalar.from_int(12345) let value = 200 let proof = generate_proof_deterministic(vk, value, gamma, "seed-4") let wrong_commit = commit_value(vk, value + 1, gamma) @@ -491,30 +494,30 @@ test range_proof_fails_wrong_commitment() { test range_proof_fails_tampered_tau_x() { let vk = setup(8) - expect Some(gamma) = scalar.new(12345) + let gamma = scalar.from_int(12345) let value = 200 let proof = generate_proof_deterministic(vk, value, gamma, "seed-5") let v_commit = commit_value(vk, value, gamma) let tampered = - BulletproofProof { ..proof, tau_x: scalar.add(proof.tau_x, scalar.one) } + BulletproofProof { ..proof, tau_x: scalar.add(proof.tau_x, scalar.from_int(1)) } !verify(vk, v_commit, tampered) } test range_proof_fails_tampered_l_vec() { let vk = setup(8) - expect Some(gamma) = scalar.new(12345) + let gamma = scalar.from_int(12345) let value = 200 let proof = generate_proof_deterministic(vk, value, gamma, "seed-6") let v_commit = commit_value(vk, value, gamma) expect [first, ..rest] = proof.l_vec let tampered = - BulletproofProof { ..proof, l_vec: [scalar.add(first, scalar.one), ..rest] } + BulletproofProof { ..proof, l_vec: [scalar.add(first, scalar.from_int(1)), ..rest] } !verify(vk, v_commit, tampered) } test range_proof_fails_wrong_vector_length() { let vk = setup(8) - expect Some(gamma) = scalar.new(12345) + let gamma = scalar.from_int(12345) let value = 200 let proof = generate_proof_deterministic(vk, value, gamma, "seed-7") let v_commit = commit_value(vk, value, gamma) @@ -525,7 +528,7 @@ test range_proof_fails_wrong_vector_length() { test generate_proof_rejects_out_of_range() fail { let vk = setup(8) - expect Some(gamma) = scalar.new(1) + let gamma = scalar.from_int(1) let blinding = derive_blinding("seed-8", vk.n) let proof = generate_proof(vk, 256, gamma, blinding) list.length(proof.l_vec) == vk.n @@ -533,7 +536,7 @@ test generate_proof_rejects_out_of_range() fail { test range_proof_fails_out_of_range_bypassing_guard() { let vk = setup(8) - expect Some(gamma) = scalar.new(555) + let gamma = scalar.from_int(555) let value = 256 let blinding = derive_blinding("seed-9", vk.n) let proof = generate_proof_unchecked(vk, value, gamma, blinding) diff --git a/zkp/lib/common/blst_affine.ak b/zkp/lib/common/blst_affine.ak index 4916746..8aa0149 100644 --- a/zkp/lib/common/blst_affine.ak +++ b/zkp/lib/common/blst_affine.ak @@ -1,12 +1,5 @@ use aiken/primitive/bytearray - -/// BLS12-381 base field prime p -const bls12_381_base_prime: Int = - 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 - -/// (p-1)/2 for BLS12-381 field modulus, used to determine y-coordinate sort bit. -const bls12_381_half_prime: Int = - 2001204777610833696708894912867952078278441409969503942666029068062015825245418932221343814564507832018947136279893 +use common/common.{bls12_381_base_prime, bls12_381_half_prime} /// Affine representation of a point on the BLS12-381 G1 curve pub type G1Affine { diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index d17734a..271daf5 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -6,10 +6,14 @@ use aiken/crypto/bls12_381/g1 use aiken/crypto/bls12_381/scalar.{Scalar, field_prime} use aiken/primitive/bytearray -// Constants for BLS12-381 -pub const bls12_381_base_prime = +/// BLS12-381 base field prime p +pub const bls12_381_base_prime: Int = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 +/// (p-1)/2 for BLS12-381 field modulus, used to determine y-coordinate sort bit. +pub const bls12_381_half_prime: Int = + 2001204777610833696708894912867952078278441409969503942666029068062015825245418932221343814564507832018947136279893 + pub const bls12_381_h = 1 // Common types for ZKP systems From 2e0c1a9b684ce4ff6ca8e79221ebc7fa0deee1ee Mon Sep 17 00:00:00 2001 From: samdelaney Date: Thu, 16 Jul 2026 23:12:38 -0700 Subject: [PATCH 25/25] format fix --- zkp/lib/bullet/bullet.ak | 10 ++++++++-- zkp/lib/common/blst_affine.ak | 6 +++--- zkp/lib/plonk/preinputs.ak | 4 ++-- zkp/lib/plonk/proofs.ak | 15 +++++++++------ zkp/lib/plonk/verifier.ak | 5 ++++- zkp/lib/tests/plonk_tests.ak | 7 ++++--- zkp/lib/tests/point_tests.ak | 7 +++---- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/zkp/lib/bullet/bullet.ak b/zkp/lib/bullet/bullet.ak index 23b5ff4..a11e5f3 100644 --- a/zkp/lib/bullet/bullet.ak +++ b/zkp/lib/bullet/bullet.ak @@ -499,7 +499,10 @@ test range_proof_fails_tampered_tau_x() { let proof = generate_proof_deterministic(vk, value, gamma, "seed-5") let v_commit = commit_value(vk, value, gamma) let tampered = - BulletproofProof { ..proof, tau_x: scalar.add(proof.tau_x, scalar.from_int(1)) } + BulletproofProof { + ..proof, + tau_x: scalar.add(proof.tau_x, scalar.from_int(1)), + } !verify(vk, v_commit, tampered) } @@ -511,7 +514,10 @@ test range_proof_fails_tampered_l_vec() { let v_commit = commit_value(vk, value, gamma) expect [first, ..rest] = proof.l_vec let tampered = - BulletproofProof { ..proof, l_vec: [scalar.add(first, scalar.from_int(1)), ..rest] } + BulletproofProof { + ..proof, + l_vec: [scalar.add(first, scalar.from_int(1)), ..rest], + } !verify(vk, v_commit, tampered) } diff --git a/zkp/lib/common/blst_affine.ak b/zkp/lib/common/blst_affine.ak index 8aa0149..78cbb38 100644 --- a/zkp/lib/common/blst_affine.ak +++ b/zkp/lib/common/blst_affine.ak @@ -101,9 +101,9 @@ pub fn g1_on_curve(point: G1Affine) -> Bool { // Check if the point satisfies the BLS12-381 G1 curve equation: y^2 = x^3 + 4 let x_int = bytearray.to_int_big_endian(x) let y_int = bytearray.to_int_big_endian(y) - let lhs = (y_int * y_int) % bls12_381_base_prime - let rhs = (x_int * x_int * x_int + 4) % bls12_381_base_prime + let lhs = y_int * y_int % bls12_381_base_prime + let rhs = ( x_int * x_int * x_int + 4 ) % bls12_381_base_prime lhs == rhs } } -} \ No newline at end of file +} diff --git a/zkp/lib/plonk/preinputs.ak b/zkp/lib/plonk/preinputs.ak index 0b6386f..4992314 100644 --- a/zkp/lib/plonk/preinputs.ak +++ b/zkp/lib/plonk/preinputs.ak @@ -1,8 +1,8 @@ use aiken/collection/list use aiken/crypto/bitwise.{State} -use aiken/crypto/bls12_381/scalar.{Scalar, scale} use aiken/crypto/bls12_381/g1 use aiken/crypto/bls12_381/g2 +use aiken/crypto/bls12_381/scalar.{Scalar, scale} pub type PlonkPreInputs { power: Int, @@ -54,7 +54,7 @@ pub fn plonk_prepare_preinputs( PreparedPlonkPreInputs { n: scalar.to_int(scale(scalar.from_int(2), inputs.power)), - generator: generator, + generator, generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), power: inputs.power, k1: inputs.k1, diff --git a/zkp/lib/plonk/proofs.ak b/zkp/lib/plonk/proofs.ak index d22034a..37d9499 100644 --- a/zkp/lib/plonk/proofs.ak +++ b/zkp/lib/plonk/proofs.ak @@ -1,8 +1,8 @@ use aiken/builtin.{keccak_256} use aiken/collection/list use aiken/crypto/bitwise.{State} -use aiken/crypto/bls12_381/scalar.{Scalar, field_prime} use aiken/crypto/bls12_381/g1 +use aiken/crypto/bls12_381/scalar.{Scalar, field_prime} use common/common.{concat_and_keccak_256} use plonk/preinputs.{PreparedPlonkPreInputs} @@ -67,7 +67,8 @@ pub fn plonk_prepare_proof( [proof.commitment_a, proof.commitment_b, proof.commitment_c], ), ) - let beta: State = scalar.from_bytes(concat_and_keccak_256(beta_inputs)) + let beta: State = + scalar.from_bytes(concat_and_keccak_256(beta_inputs)) let gamma: State = scalar.from_bytes(keccak_256(scalar.to_bytes(beta))) let alpha: State = @@ -86,7 +87,8 @@ pub fn plonk_prepare_proof( list.map( preinputs.generators, fn(x) { - let denom = scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, x)) + let denom = + scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, x)) scalar.scale(denom, field_prime - 2) }, ) @@ -110,7 +112,7 @@ pub fn plonk_prepare_proof( let s_sig2_p: State = scalar.from_int(proof.s_sig2_p) let z_omega: State = scalar.from_int(proof.z_omega) - PreparedPlonkProof { + PreparedPlonkProof { commitment_a, commitment_b, commitment_c, @@ -126,5 +128,6 @@ pub fn plonk_prepare_proof( s_sig1_p, s_sig2_p, z_omega, - lagrange_inverses } -} \ No newline at end of file + lagrange_inverses, + } +} diff --git a/zkp/lib/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak index d8d605e..ebb975d 100644 --- a/zkp/lib/plonk/verifier.ak +++ b/zkp/lib/plonk/verifier.ak @@ -226,7 +226,10 @@ pub fn verify_plonk( // verify polynomial evaluations let miller_loop_1 = - miller_loop(g1.add(proof.w_omega, g1.scale(proof.w_omega_zeta, u)), preinputs.x2) + miller_loop( + g1.add(proof.w_omega, g1.scale(proof.w_omega_zeta, u)), + preinputs.x2, + ) // verify constraint satisfaction let miller_loop_2 = diff --git a/zkp/lib/tests/plonk_tests.ak b/zkp/lib/tests/plonk_tests.ak index 03925c2..657ffc7 100644 --- a/zkp/lib/tests/plonk_tests.ak +++ b/zkp/lib/tests/plonk_tests.ak @@ -1,6 +1,8 @@ use aiken/collection/list use aiken/crypto/bls12_381/scalar -use common/blst_affine.{G1Infinity, G1Point, G2Point, g1_affine_compress, g2_affine_compress} +use common/blst_affine.{ + G1Infinity, G1Point, G2Point, g1_affine_compress, g2_affine_compress, +} use common/common.{G2Point as RawG2Point, Point} use plonk/preinputs.{PlonkPreInputs, plonk_prepare_preinputs} use plonk/proofs.{plonk_prepare_proof} @@ -285,8 +287,7 @@ pub const test_plonk_vkey_affine: AffineVerificationKey = w: 28761180743467419819834788392525162889723178799021384024940474588120723734663, } -pub const test_plonk_pub_inputs: List = - [63, 7] +pub const test_plonk_pub_inputs: List = [63, 7] // =========================================================================== // Vkey compression tests diff --git a/zkp/lib/tests/point_tests.ak b/zkp/lib/tests/point_tests.ak index f5d06b1..cd74ba1 100644 --- a/zkp/lib/tests/point_tests.ak +++ b/zkp/lib/tests/point_tests.ak @@ -4,9 +4,7 @@ use common/blst_affine.{ G1Affine, G1Infinity, G1Point, G2Infinity, G2Point, g1_affine_compress, g1_on_curve, g2_affine_compress, } -use tests/plonk_tests.{ - test_plonk_proof_affine, test_plonk_vkey_affine, -} +use tests/plonk_tests.{test_plonk_proof_affine, test_plonk_vkey_affine} use tests/test_utils.{int_to_field_bytes} // --------------------------------------------------------------------------- @@ -127,7 +125,8 @@ test test_g2_infinity_compression() { test test_known_infinity_format() { // Standard BLS12-381 infinity: 0xc0 followed by 47 zero bytes (48 bytes total = 96 hex chars) - let known_infinity = #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + let known_infinity = + #"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" expect _decompressed = g1.decompress(known_infinity) let our_infinity = g1_affine_compress(test_plonk_vkey_affine.q_c) known_infinity == our_infinity