Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
83 changes: 50 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- Testing requirements

### Prerequisites

- Aiken development environment
- Familiarity with ZKP systems
- Understanding of Cardano smart contracts
4 changes: 2 additions & 2 deletions zkp/aiken.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
4 changes: 2 additions & 2 deletions zkp/aiken.toml
Original file line number Diff line number Diff line change
@@ -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'"
Expand All @@ -12,7 +12,7 @@ platform = "github"

[[dependencies]]
name = "aiken-lang/stdlib"
version = "v2.1.0"
version = "3.0.0"
source = "github"

[config]
44 changes: 19 additions & 25 deletions zkp/lib/bullet/bullet.ak
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 {
Expand All @@ -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"
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)]
}
}

Expand All @@ -377,8 +372,7 @@ pub fn hadamard_product(
) -> List<common.Field> {
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"
}
}
114 changes: 114 additions & 0 deletions zkp/lib/common/blst_affine.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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
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)
}
Comment thread
SamDelaney marked this conversation as resolved.
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)
}
Comment thread
SamDelaney marked this conversation as resolved.
// 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
}
}
}
Loading
Loading