Skip to content
Merged
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
44 changes: 44 additions & 0 deletions crates/neo-midnight-bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# neo-midnight-bridge

Experimental bridge: prove Neo FoldRun validity using Midnight's PLONK/KZG verifier stack.

## KZG params (BLS12-381 SRS)

For local tests/benchmarks, this crate can load `midnight-proofs` `ParamsKZG` files from:

`crates/neo-midnight-bridge/testdata/kzg_params/bls_midnight_2p{k}`

### Download pre-generated Midnight params

Midnight publishes pre-generated parameter files under:

`https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_midnight_2p{k}`

Example downloads:

```bash
BASE_URL="https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com"
OUT_DIR="crates/neo-midnight-bridge/testdata/kzg_params"

mkdir -p "$OUT_DIR"

# Download k=16,17,18
for k in 16 17 18; do
curl -L --fail -o "$OUT_DIR/bls_midnight_2p${k}" "$BASE_URL/bls_midnight_2p${k}"
done
```

Notes:
- Files can be large (e.g. `k=21` is ~400MB).
- Midnight-ledger contains SHA-256 hashes for each `bls_midnight_2p{k}` for integrity checking.

### Generate params from a Powers-of-Tau transcript

If you have a Midnight Powers-of-Tau transcript file (raw bytes: G1 powers followed by two G2
points), you can convert it into `ParamsKZG` files via:

```bash
cargo run -p neo-midnight-bridge --example params_from_powers_of_tau -- \
<powers_of_tau_path> crates/neo-midnight-bridge/testdata/kzg_params [k_max]
```

90 changes: 90 additions & 0 deletions crates/neo-midnight-bridge/examples/params_from_powers_of_tau.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! Convert a Midnight Powers-of-Tau transcript into `midnight-proofs` `ParamsKZG` files.
//!
//! This mirrors `external/midnight-ledger/transient-crypto/examples/translate-params.rs`,
//! but lives in this repo so we can generate params for outer compression proofs without
//! relying on Midnight ledger tooling.
//!
//! If you don't have a `powers_of_tau` transcript, you can alternatively download
//! pre-generated `bls_midnight_2p{k}` files and place them under:
//! `crates/neo-midnight-bridge/testdata/kzg_params/`.
//!
//! Usage:
//! cargo run -p neo-midnight-bridge --example params_from_powers_of_tau -- \
//! <powers_of_tau_path> <out_dir> [k_max]
//!
//! Output:
//! `<out_dir>/bls_midnight_2p{k}` for k in 0..=k_max (written in RawBytes format).

use midnight_curves::{serde::SerdeObject, Bls12, G1Affine, G2Affine};
use midnight_proofs::{poly::kzg::params::ParamsKZG, utils::SerdeFormat};
use std::path::PathBuf;

fn floor_log2(mut n: usize) -> u32 {
let mut log = 0u32;
while n > 1 {
n >>= 1;
log += 1;
}
log
}

fn main() {
let mut args = std::env::args().skip(1);
let pot_path = args
.next()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("powers_of_tau"));
let out_dir = args
.next()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."));
let k_max_arg = args
.next()
.map(|s| s.parse::<u32>().expect("k_max must be an integer"));

let bytes = std::fs::read(&pot_path).expect("read powers_of_tau");
let g1_size = G1Affine::uncompressed_size();
let g2_size = G2Affine::uncompressed_size();

assert!(bytes.len() >= 2 * g2_size, "powers_of_tau too short: need >= 2*G2");
let offset = bytes.len() - 2 * g2_size;
assert_eq!(offset % g1_size, 0, "powers_of_tau length not aligned to G1 size");

let g1_count = offset / g1_size;
let k_max_default = floor_log2(g1_count);
let k_max = k_max_arg.unwrap_or(k_max_default);
let needed = 1usize << k_max;
assert!(
g1_count >= needed,
"powers_of_tau has {g1_count} G1 points, but k_max={k_max} needs >= {needed}"
);

println!("Reading powers_of_tau from {pot_path:?}");
println!("G1 points: {g1_count} (max k by length: {k_max_default})");
println!("Generating ParamsKZG for k_max={k_max} into {out_dir:?}");

// Read G1 powers.
let mut g1s = Vec::with_capacity(g1_count);
for chunk in bytes[..offset].chunks(g1_size) {
let p = G1Affine::from_raw_bytes(chunk).expect("decode G1");
g1s.push(p.into());
}

// Read trailing G2 points (beta_g2, g2).
let g2_0 = G2Affine::from_raw_bytes(&bytes[offset..offset + g2_size]).expect("decode G2[0]");
let g2_1 = G2Affine::from_raw_bytes(&bytes[offset + g2_size..offset + 2 * g2_size]).expect("decode G2[1]");

std::fs::create_dir_all(&out_dir).expect("create out_dir");

// Build params at k_max, then downsize in-place and write all smaller k.
let mut params = ParamsKZG::<Bls12>::from_parts(k_max, g1s, None, g2_0.into(), g2_1.into());
for k in (0..=k_max).rev() {
println!("Writing k={k}");
params.downsize(k);
let out_path = out_dir.join(format!("bls_midnight_2p{k}"));
let mut f = std::fs::File::create(&out_path).expect("create output file");
params
.write_custom(&mut f, SerdeFormat::RawBytes)
.expect("write ParamsKZG");
}
}
Loading