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 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 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/bullet/bullet.ak b/zkp/lib/bullet/bullet.ak index 494dc3d..a11e5f3 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,36 @@ 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 +534,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 +542,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 new file mode 100644 index 0000000..78cbb38 --- /dev/null +++ b/zkp/lib/common/blst_affine.ak @@ -0,0 +1,109 @@ +use aiken/primitive/bytearray +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 { + /// 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 > (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 only: compression bit, no sort bit + bytearray.or_bytes(first_x_byte, #"80", False) + } + 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 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 { + // 0x80 | 0x20 = 0xa0: compression + sort bit + bytearray.or_bytes(first_x1_byte, #"a0", False) + } else { + // 0x80 only: compression bit, no sort bit + bytearray.or_bytes(first_x1_byte, #"80", False) + } + // Return compressed form: [c1_with_flags | c0] (96 bytes total) + bytearray.concat(bytearray.concat(compressed_first_byte, rest_x1), x0) + } + } +} + +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 + } + } +} diff --git a/zkp/lib/common/common.ak b/zkp/lib/common/common.ak index d3b2ea8..271daf5 100644 --- a/zkp/lib/common/common.ak +++ b/zkp/lib/common/common.ak @@ -1,14 +1,18 @@ +use aiken/builtin.{blake2b_224} 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, field_prime} use aiken/primitive/bytearray -// Constants for BLS12-381 -pub const bls12_381_prime = - 52435875175126190479447740508185965837690552500527637822603658699938581184513 +/// BLS12-381 base field prime p +pub const bls12_381_base_prime: Int = + 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 -// Additional curve constants -pub const bls12_381_r = - 52435875175126190479447740508185965837690552500527637822603658699938581184513 +/// (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 @@ -55,28 +59,32 @@ pub type VerificationError { } // Common utility functions for field operations +pub fn make_scalar(a: Int) -> Field { + 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 } } @@ -120,7 +128,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 { @@ -131,7 +139,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 { @@ -153,7 +161,51 @@ fn mod_pow_loop(b, e, result) { } } -// Point operations (expanding on existing ones) +// Blake2b-224 utilities + +pub fn concat_and_blake2b_224(inputs: List) -> ByteArray { + 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)) +} + +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_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 { @@ -188,7 +240,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) @@ -196,7 +248,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 { @@ -226,7 +278,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) @@ -248,7 +300,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)) @@ -258,7 +310,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" @@ -462,9 +514,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 @@ -479,7 +531,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 }, ) } @@ -489,7 +541,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 @@ -501,7 +553,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) @@ -615,7 +667,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) @@ -638,7 +690,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) } } @@ -787,7 +839,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)) @@ -816,7 +868,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) } } 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/plonk.ak b/zkp/lib/plonk/plonk.ak deleted file mode 100644 index 3a11a69..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 - expect Some(beta) = - scalar.from_bytearray_big_endian( - generate_transcript(bytearray.concat(ts2, #[1])), - ) - expect Some(gamma) = - scalar.from_bytearray_big_endian( - 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)) - - // Compute zeta challenge - let ts4 = - bytearray.concat( - ts3, - bytearray.concat(proof.t1, bytearray.concat(proof.t2, proof.t3)), - ) - expect Some(zeta) = scalar.from_bytearray_big_endian(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)) - expect Some(u) = - scalar.from_bytearray_big_endian( - 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 new file mode 100644 index 0000000..4992314 --- /dev/null +++ b/zkp/lib/plonk/preinputs.ak @@ -0,0 +1,72 @@ +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/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, +} + +pub type PreparedPlonkPreInputs { + n: Int, + generator: State, + generators: List>, + 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( + inputs: PlonkPreInputs, + generator: State, + n_public: Int, +) -> PreparedPlonkPreInputs { + 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, + generators: list.map(list.range(0, n_public), fn(n) { scale(generator, n) }), + power: inputs.power, + k1: inputs.k1, + k2: inputs.k2, + 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 new file mode 100644 index 0000000..37d9499 --- /dev/null +++ b/zkp/lib/plonk/proofs.ak @@ -0,0 +1,133 @@ +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/scalar.{Scalar, field_prime} +use common/common.{concat_and_keccak_256} +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: Int, + b_eval: Int, + c_eval: Int, + s_sig1_p: Int, + s_sig2_p: Int, + z_omega: Int, +} + +pub type PreparedPlonkProof { + 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>, +} + +pub fn plonk_prepare_proof( + preinputs: PreparedPlonkPreInputs, + 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 = + 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], + ), + ) + 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_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) { + let denom = + scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, x)) + scalar.scale(denom, field_prime - 2) + }, + ) + + // uncompress the proof commitments + 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) + 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, + } +} diff --git a/zkp/lib/plonk/raw.ak b/zkp/lib/plonk/raw.ak new file mode 100644 index 0000000..94bd47b --- /dev/null +++ b/zkp/lib/plonk/raw.ak @@ -0,0 +1,87 @@ +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, + 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_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, + } +} 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/plonk/verifier.ak b/zkp/lib/plonk/verifier.ak new file mode 100644 index 0000000..ebb975d --- /dev/null +++ b/zkp/lib/plonk/verifier.ak @@ -0,0 +1,265 @@ +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, scale2} +use common/common.{concat_and_keccak_256, g1_sum, scalar_sum} +use plonk/preinputs.{PreparedPlonkPreInputs} +use plonk/proofs.{PreparedPlonkProof} + +pub fn verify_plonk( + preinputs: PreparedPlonkPreInputs, + pub_inputs: List, + proof: PreparedPlonkProof, +) -> Bool { + // 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) + + // 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> = + list.map(pub_inputs, fn(x) { scalar.neg(scalar.from_int(x)) }) + + // 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) + + // 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_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], + ), + ), + ), + ) + let gamma: State = + scalar.from_bytes(keccak_256(scalar.to_bytes(beta))) + let alpha: State = + scalar.from_bytes( + concat_and_keccak_256( + [scalar.to_bytes(beta), scalar.to_bytes(gamma), bs_commitment_z], + ), + ) + let zeta: State = + scalar.from_bytes( + 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_keccak_256( + [ + scalar.to_bytes(zeta), + 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_keccak_256([bs_w_omega, bs_w_omega_zeta])) + + // compute powers of zeta for polynomial evaluation + 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 + let lagrange_polys: List> = + list.map2( + preinputs.generators, + 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) + + // compute public input polynomial evaluation at zeta + let pi_zeta: State = + scalar_sum( + 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, proof.z_omega) + let beta_zeta = scalar.mul(beta, zeta) + // compute shifted witness polynomials + 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 = + 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 constraint polynomial commitments + let constraint_commitments = + g1_sum( + [ + 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( + proof.commitment_z, + { + let product = + scalar.add(a_gamma, beta_zeta) + |> scalar.mul( + scalar.add(b_gamma, scalar.mul(beta_zeta, preinputs.k1)), + ) + |> scalar.mul( + scalar.add(c_gamma, scalar.mul(beta_zeta, preinputs.k2)), + ) + |> scalar.mul(alpha) + scalar.add( + scalar.add(product, scalar.mul(lagrange_poly_head, alpha_sq)), + u, + ) + }, + ), + g1.scale( + preinputs.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( + proof.t_low, + g1.add( + 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), + ), + ], + ) + + // combine batched polynomial commitments + let batched_polynomial_commitments = + g1.add( + constraint_commitments, + { + let sigma_witness_commits = + g1.add(preinputs.s_sig1, g1.scale(preinputs.s_sig2, v)) + |> g1.scale(v) + |> g1.add(proof.commitment_c, _) + |> g1.scale(v) + |> g1.add(proof.commitment_b, _) + |> g1.scale(v) + |> g1.add(proof.commitment_a, _) + g1.scale(sigma_witness_commits, v) + }, + ) + + // batch evaluations + let group_encoded_batch_eval = + g1.scale( + g1.generator, + { + let sigma_witness_evals = + scalar.add(proof.s_sig1_p, scalar.mul(v, proof.s_sig2_p)) + |> scalar.mul(v, _) + |> scalar.add(proof.c_eval, _) + |> scalar.mul(v, _) + |> scalar.add(proof.b_eval, _) + |> scalar.mul(v, _) + |> scalar.add(proof.a_eval, _) + scalar.add( + scalar.add(scalar.neg(r0), scalar.mul(v, sigma_witness_evals)), + scalar.mul(u, proof.z_omega), + ) + }, + ) + + // verify polynomial evaluations + let miller_loop_1 = + 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(proof.w_omega, zeta), + g1.scale(proof.w_omega_zeta, scalar.mul(u, scalar.mul(zeta, gen_w))), + ), + g1.sub(batched_polynomial_commitments, group_encoded_batch_eval), + ), + g2.generator, + ) + + let final_verification = final_exponentiation(miller_loop_1, miller_loop_2) + + // verify lagrange polynomial inverses + let lagrange_check = + list.all( + list.map2( + proof.lagrange_inverses, + preinputs.generators, + fn(inv, gen) { + scalar.mul( + inv, + scalar.mul(scalar.from_int(preinputs.n), scalar.sub(zeta, gen)), + ) == scalar.from_int(1) + }, + ), + fn(b) { b }, + ) + final_verification && lagrange_check +} diff --git a/zkp/lib/tests/groth_tests.ak b/zkp/lib/tests/groth_tests.ak new file mode 100644 index 0000000..0f46c0e --- /dev/null +++ b/zkp/lib/tests/groth_tests.ak @@ -0,0 +1,85 @@ +use groth/groth.{CompressedProof, CompressedVK, 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..657ffc7 --- /dev/null +++ b/zkp/lib/tests/plonk_tests.ak @@ -0,0 +1,449 @@ +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 { + commitment_a: Point { + x: 2955082493890020621983548864242801681331382761589593836396462786237809490379730083621148456292328311837539547233078, + y: 2990917470048022633751403325877110490023214940780163879068748589295894184468420861593276240916860222556041630309490, + z: 1, + }, + commitment_b: Point { + x: 2139748953192924893718111139796467598787187717809064847657431290279584919816359328113211416216965946326904372181144, + y: 2259668502813448495833083885609627801405772920489944855694509142813690380362130929482939621563275288429386208477174, + z: 1, + }, + commitment_c: Point { + x: 2029868808682428131517521841196317763678238092678415346121426692356142038272541088877792972412724621664112264945402, + y: 983551628294237524163209598922680914320460584515486665440996990611591240136708425683923303325991832470735477012116, + z: 1, + }, + commitment_z: Point { + x: 2387801164586378454252042857525161272590791672625817623997854477584013441773831593184571862621830523992226453996145, + y: 2963460115634323994397623882034777328875861522711306860138637408553554162058666092728227754125238789945570425688766, + z: 1, + }, + t_low: Point { + x: 1894516259155874795478636775854000079842047119288244714503215181482526148945291020416724440229372193886732121411353, + y: 1031154329693902984660329178103732470848052600799342617372338054649599821052286659433310234216256987572488522064742, + z: 1, + }, + t_mid: Point { + x: 396230736967861803635980419570754115302632616415474674207045272682865255719918284458879898212237757916289534540503, + y: 1286345817905833523305277948742871641051845921916399591001898962704587418794625017612994249491313890015329626321188, + z: 1, + }, + t_high: Point { + x: 1605020952126297833723226836157259589899522431634090862836060595671759262433465086466801152520962321838375613406480, + y: 3380611695801979784487178272729979547287652149847817235299237981219620495852861730756702710944277941379315901919628, + z: 1, + }, + w_omega: Point { + x: 3450797490829451101684300912559686520039512327488446737240041689856044522958259926705205991152261371334594680942229, + y: 3325733188905070684197604249400574204476897614422083280941305902591531412024682158968329032713733872654995261106522, + z: 1, + }, + w_omega_zeta: Point { + x: 863669987569139054364812933508800738931137091074775255594224713818925528334922843056187052587160290967836521340909, + y: 2166514895834521672481059065176477660352283408684189579849124753231689040068150141242374151666482477587206334178810, + z: 1, + }, + 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: 3, + k1: 2, + k2: 3, + q_m: Point { + x: 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, + y: 3276847779626248817921041660741117745502051773945482686666030885472398470355715064844179289766796069907281222412724, + z: 1, + }, + q_l: Point { + x: 2832459832163495150479122726264129004255627621445400446445904161463167979768458296565736026250455466004404325092996, + y: 3741891246739296148991860192122594744687151654172617277458830187550386521367405061644711803513753648977431388490950, + z: 1, + }, + q_r: Point { x: 0, y: 1, z: 0 }, + q_o: Point { + x: 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, + y: 725561775595418575496748164994786411054831045993525198666027250651633180135122799598508339362219594130613050147063, + z: 1, + }, + q_c: Point { x: 0, y: 1, z: 0 }, + s_sig1: Point { + x: 3944485635698459932433634706365550699364147680658608621022581948882602066423214201823994363817057003852352113973102, + y: 2481473700589819128241850529786326259739363860413421083419242322439772753684327842018177309699278701645719502063011, + z: 1, + }, + s_sig2: Point { + x: 608913174169519000812264133236120028473675090457027054092233721874136995363377165138135110723165351281407038604929, + y: 2540453045645467686101782045248642180331599264159056690287844382784021393502357721825604716334062206625833942598550, + z: 1, + }, + s_sig3: Point { + x: 324514252262195321244614754390467750793932334185439650123651923691851924066772665377485916928100616166627086332426, + y: 3113468956497733006315552301280484390953024074059319036050522016086390656265116534130248224200617657987091997981033, + z: 1, + }, + x2: RawG2Point { + x: ( + 315392587371006580320359080746390447102226920810775075601256338851306698095121762912694048386654833732339420515166, + 2161957325100184324059278577028157171100154313724439672191511832751025188121569798837418082248673407995264313041542, + ), + y: ( + 3808454252163556366145098778553759832772829525954193157529438608174590910603583474173900502559366413190158202701670, + 2556911765464293729917692748672416866651619396879891492332729234344507429587256369481474563225773001582828153945765, + ), + z: (1, 0), + }, + w: 28761180743467419819834788392525162889723178799021384024940474588120723734663, + } + +// =========================================================================== +// Affine test vectors +// =========================================================================== + +pub const test_plonk_proof_affine: AffineProof = + AffineProof { + commitment_a: G1Point { + x: int_to_field_bytes( + 2955082493890020621983548864242801681331382761589593836396462786237809490379730083621148456292328311837539547233078, + ), + y: int_to_field_bytes( + 2990917470048022633751403325877110490023214940780163879068748589295894184468420861593276240916860222556041630309490, + ), + }, + commitment_b: G1Point { + x: int_to_field_bytes( + 2139748953192924893718111139796467598787187717809064847657431290279584919816359328113211416216965946326904372181144, + ), + y: int_to_field_bytes( + 2259668502813448495833083885609627801405772920489944855694509142813690380362130929482939621563275288429386208477174, + ), + }, + commitment_c: G1Point { + x: int_to_field_bytes( + 2029868808682428131517521841196317763678238092678415346121426692356142038272541088877792972412724621664112264945402, + ), + y: int_to_field_bytes( + 983551628294237524163209598922680914320460584515486665440996990611591240136708425683923303325991832470735477012116, + ), + }, + commitment_z: G1Point { + x: int_to_field_bytes( + 2387801164586378454252042857525161272590791672625817623997854477584013441773831593184571862621830523992226453996145, + ), + y: int_to_field_bytes( + 2963460115634323994397623882034777328875861522711306860138637408553554162058666092728227754125238789945570425688766, + ), + }, + t_low: G1Point { + x: int_to_field_bytes( + 1894516259155874795478636775854000079842047119288244714503215181482526148945291020416724440229372193886732121411353, + ), + y: int_to_field_bytes( + 1031154329693902984660329178103732470848052600799342617372338054649599821052286659433310234216256987572488522064742, + ), + }, + t_mid: G1Point { + x: int_to_field_bytes( + 396230736967861803635980419570754115302632616415474674207045272682865255719918284458879898212237757916289534540503, + ), + y: int_to_field_bytes( + 1286345817905833523305277948742871641051845921916399591001898962704587418794625017612994249491313890015329626321188, + ), + }, + t_high: G1Point { + x: int_to_field_bytes( + 1605020952126297833723226836157259589899522431634090862836060595671759262433465086466801152520962321838375613406480, + ), + y: int_to_field_bytes( + 3380611695801979784487178272729979547287652149847817235299237981219620495852861730756702710944277941379315901919628, + ), + }, + w_omega: G1Point { + x: int_to_field_bytes( + 3450797490829451101684300912559686520039512327488446737240041689856044522958259926705205991152261371334594680942229, + ), + y: int_to_field_bytes( + 3325733188905070684197604249400574204476897614422083280941305902591531412024682158968329032713733872654995261106522, + ), + }, + w_omega_zeta: G1Point { + x: int_to_field_bytes( + 863669987569139054364812933508800738931137091074775255594224713818925528334922843056187052587160290967836521340909, + ), + y: int_to_field_bytes( + 2166514895834521672481059065176477660352283408684189579849124753231689040068150141242374151666482477587206334178810, + ), + }, + 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: 3, + k1: 2, + k2: 3, + q_m: G1Point { + x: int_to_field_bytes( + 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, + ), + y: int_to_field_bytes( + 3276847779626248817921041660741117745502051773945482686666030885472398470355715064844179289766796069907281222412724, + ), + }, + q_l: G1Point { + x: int_to_field_bytes( + 2832459832163495150479122726264129004255627621445400446445904161463167979768458296565736026250455466004404325092996, + ), + y: int_to_field_bytes( + 3741891246739296148991860192122594744687151654172617277458830187550386521367405061644711803513753648977431388490950, + ), + }, + q_r: G1Infinity, + q_o: G1Point { + x: int_to_field_bytes( + 390420818566493858367464181076148178859555478863002692514545834368269879447681841082068585551564235503308947232378, + ), + y: int_to_field_bytes( + 725561775595418575496748164994786411054831045993525198666027250651633180135122799598508339362219594130613050147063, + ), + }, + q_c: G1Infinity, + s_sig1: G1Point { + x: int_to_field_bytes( + 3944485635698459932433634706365550699364147680658608621022581948882602066423214201823994363817057003852352113973102, + ), + y: int_to_field_bytes( + 2481473700589819128241850529786326259739363860413421083419242322439772753684327842018177309699278701645719502063011, + ), + }, + s_sig2: G1Point { + x: int_to_field_bytes( + 608913174169519000812264133236120028473675090457027054092233721874136995363377165138135110723165351281407038604929, + ), + y: int_to_field_bytes( + 2540453045645467686101782045248642180331599264159056690287844382784021393502357721825604716334062206625833942598550, + ), + }, + s_sig3: G1Point { + x: int_to_field_bytes( + 324514252262195321244614754390467750793932334185439650123651923691851924066772665377485916928100616166627086332426, + ), + y: int_to_field_bytes( + 3113468956497733006315552301280484390953024074059319036050522016086390656265116534130248224200617657987091997981033, + ), + }, + x2: G2Point { + x: ( + int_to_field_bytes( + 315392587371006580320359080746390447102226920810775075601256338851306698095121762912694048386654833732339420515166, + ), + int_to_field_bytes( + 2161957325100184324059278577028157171100154313724439672191511832751025188121569798837418082248673407995264313041542, + ), + ), + y: ( + int_to_field_bytes( + 3808454252163556366145098778553759832772829525954193157529438608174590910603583474173900502559366413190158202701670, + ), + int_to_field_bytes( + 2556911765464293729917692748672416866651619396879891492332729234344507429587256369481474563225773001582828153945765, + ), + ), + }, + w: 28761180743467419819834788392525162889723178799021384024940474588120723734663, + } + +pub const test_plonk_pub_inputs: List = [63, 7] + +// =========================================================================== +// 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 == 8 +} + +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, + ) +} + +// 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 new file mode 100644 index 0000000..cd74ba1 --- /dev/null +++ b/zkp/lib/tests/point_tests.ak @@ -0,0 +1,207 @@ +use aiken/crypto/bls12_381/g1 +use aiken/primitive/bytearray +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/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() { + 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() { + and { + 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 + }, + } +} + +test test_simple_point_validation() { + when simple_g1_point is { + G1Point { .. } -> g1_on_curve(simple_g1_point) + _ -> False + } +} 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) +}