mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-06-29 10:29:32 +00:00
initialize tests
This commit is contained in:
parent
4577f2cbcd
commit
2620c42ab4
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -7454,6 +7454,19 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppe_test_data_gen"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"authenticated_transfer_core",
|
||||
"borsh",
|
||||
"clap",
|
||||
"lee",
|
||||
"lee_core",
|
||||
"risc0-zkvm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@ -9851,6 +9864,7 @@ dependencies = [
|
||||
name = "test_program_methods"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"risc0-build",
|
||||
]
|
||||
|
||||
@ -9859,6 +9873,7 @@ name = "test_programs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"authenticated_transfer_core",
|
||||
"bytemuck",
|
||||
"clock_core",
|
||||
"faucet_core",
|
||||
"lee_core",
|
||||
|
||||
@ -48,6 +48,7 @@ members = [
|
||||
"tools/cycle_bench",
|
||||
"tools/crypto_primitives_bench",
|
||||
"tools/integration_bench",
|
||||
"tools/ppe_test_data_gen",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
50
bench_ppe_aggregation.sh
Executable file
50
bench_ppe_aggregation.sh
Executable file
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs the PPE aggregation test across a range of fixture counts and prints a results table.
|
||||
#
|
||||
# Usage:
|
||||
# ./bench_ppe_aggregation.sh
|
||||
#
|
||||
# Environment:
|
||||
# PPE_FIXTURES - path to the fixture file (default: ppe_fixtures.bin)
|
||||
# COUNTS - space-separated list of counts to test (default: powers of 2, 1..256)
|
||||
#
|
||||
# Example:
|
||||
# PPE_FIXTURES=/path/to/ppe_fixtures.bin COUNTS="4 8 16" ./bench_ppe_aggregation.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
FIXTURES="$(realpath "${PPE_FIXTURES:-ppe_fixtures.bin}")"
|
||||
COUNTS="${COUNTS:-1 2 4 6 8 10 12 14 16}"
|
||||
|
||||
if [ ! -f "$FIXTURES" ]; then
|
||||
echo "ERROR: fixture file '$FIXTURES' not found."
|
||||
echo "Generate it first:"
|
||||
echo " RISC0_DEV_MODE=1 cargo run --release -p ppe_test_data_gen -- --output $FIXTURES"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "\n%-6s %14s %20s\n" "n" "proving_ms" "proof_size_bytes"
|
||||
printf "%-6s %14s %20s\n" "------" "--------------" "--------------------"
|
||||
|
||||
for count in $COUNTS; do
|
||||
line=$(
|
||||
PPE_FIXTURES="$FIXTURES" \
|
||||
PPE_FIXTURES_COUNT="$count" \
|
||||
cargo test -p lee aggregate_ppe_proofs_from_fixtures -- --nocapture 2>&1 \
|
||||
| grep -v "^test_programs:" \
|
||||
| grep "\[lee::analytics\] ppe_aggregation" || true
|
||||
)
|
||||
|
||||
if [ -z "$line" ]; then
|
||||
printf "%-6s %14s %20s\n" "$count" "skipped" "-"
|
||||
continue
|
||||
fi
|
||||
|
||||
n=$(echo "$line" | grep -o 'n=[0-9]*' | cut -d= -f2)
|
||||
proving_ms=$(echo "$line" | grep -o 'proving_ms=[0-9]*' | cut -d= -f2)
|
||||
proof_size=$(echo "$line" | grep -o 'proof_size_bytes=[0-9]*'| cut -d= -f2)
|
||||
|
||||
printf "%-6s %14s %20s\n" "$n" "$proving_ms" "$proof_size"
|
||||
done
|
||||
|
||||
printf "\n"
|
||||
@ -838,6 +838,234 @@ mod tests {
|
||||
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
|
||||
}
|
||||
|
||||
/// Prove N PPE transactions and aggregate all their proofs into one succinct receipt.
|
||||
///
|
||||
/// Uses the `ppe_aggregation` guest (in `test_program_methods`). The host
|
||||
/// loads each PPE receipt as an assumption and passes the journals to the
|
||||
/// guest, which calls `env::verify` for each. The resulting receipt proves
|
||||
/// "all N PPE executions were valid" in one succinct proof.
|
||||
///
|
||||
/// This test uses N=3 to demonstrate the generalised aggregation.
|
||||
#[test]
|
||||
fn aggregate_n_ppe_proofs() {
|
||||
use lee_core::account::Nonce;
|
||||
use risc0_zkvm::{ExecutorEnv, InnerReceipt, ProverOpts, Receipt, default_prover};
|
||||
use test_program_methods::{PPE_AGGREGATION_ELF, PPE_AGGREGATION_ID};
|
||||
|
||||
let program = Program::authenticated_transfer_program();
|
||||
|
||||
// ── Proof 0: public sender → private recipient ────────────────────────────
|
||||
let keys_0 = test_private_account_keys_1();
|
||||
let ssk_0 = SharedSecretKey::encapsulate_deterministic(&keys_0.vpk(), &[0_u8; 32], 0).0;
|
||||
let (output_0, proof_0) = execute_and_prove(
|
||||
vec![
|
||||
AccountWithMetadata::new(
|
||||
Account { program_owner: program.id(), balance: 100, ..Account::default() },
|
||||
true,
|
||||
AccountId::new([0x01; 32]),
|
||||
),
|
||||
AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::for_regular_private_account(&keys_0.npk(), 0),
|
||||
),
|
||||
],
|
||||
Program::serialize_instruction(
|
||||
authenticated_transfer_core::Instruction::Transfer { amount: 10 },
|
||||
).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
npk: keys_0.npk(),
|
||||
ssk: ssk_0,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
&program.clone().into(),
|
||||
).expect("proof 0 should succeed");
|
||||
|
||||
// ── Proof 1: public sender → private recipient ────────────────────────────
|
||||
let keys_1 = test_private_account_keys_2();
|
||||
let ssk_1 = SharedSecretKey::encapsulate_deterministic(&keys_1.vpk(), &[0_u8; 32], 0).0;
|
||||
let (output_1, proof_1) = execute_and_prove(
|
||||
vec![
|
||||
AccountWithMetadata::new(
|
||||
Account { program_owner: program.id(), balance: 200, ..Account::default() },
|
||||
true,
|
||||
AccountId::new([0x02; 32]),
|
||||
),
|
||||
AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::for_regular_private_account(&keys_1.npk(), 0),
|
||||
),
|
||||
],
|
||||
Program::serialize_instruction(
|
||||
authenticated_transfer_core::Instruction::Transfer { amount: 20 },
|
||||
).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
npk: keys_1.npk(),
|
||||
ssk: ssk_1,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
&program.clone().into(),
|
||||
).expect("proof 1 should succeed");
|
||||
|
||||
// ── Proof 2: fully private transfer ──────────────────────────────────────
|
||||
let sender_keys_2 = test_private_account_keys_1();
|
||||
let recipient_keys_2 = test_private_account_keys_2();
|
||||
let sender_2_id = AccountId::for_regular_private_account(&sender_keys_2.npk(), 0);
|
||||
let sender_2_account =
|
||||
Account { program_owner: program.id(), balance: 50, nonce: Nonce(1), ..Account::default() };
|
||||
let sender_2_commitment = Commitment::new(&sender_2_id, &sender_2_account);
|
||||
let mut cs = CommitmentSet::with_capacity(1);
|
||||
cs.extend(std::slice::from_ref(&sender_2_commitment));
|
||||
// sender is output index 0, recipient is output index 1
|
||||
let ssk_2_sender =
|
||||
SharedSecretKey::encapsulate_deterministic(&sender_keys_2.vpk(), &[0_u8; 32], 0).0;
|
||||
let ssk_2_recipient =
|
||||
SharedSecretKey::encapsulate_deterministic(&recipient_keys_2.vpk(), &[0_u8; 32], 1).0;
|
||||
let (output_2, proof_2) = execute_and_prove(
|
||||
vec![
|
||||
AccountWithMetadata::new(sender_2_account, true, sender_2_id),
|
||||
AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::for_regular_private_account(&recipient_keys_2.npk(), 1),
|
||||
),
|
||||
],
|
||||
Program::serialize_instruction(
|
||||
authenticated_transfer_core::Instruction::Transfer { amount: 30 },
|
||||
).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
ssk: ssk_2_sender,
|
||||
nsk: sender_keys_2.nsk,
|
||||
membership_proof: cs.get_proof_for(&sender_2_commitment).unwrap(),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
npk: recipient_keys_2.npk(),
|
||||
ssk: ssk_2_recipient,
|
||||
identifier: 1,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
).expect("proof 2 should succeed");
|
||||
|
||||
// ── Aggregate all three ───────────────────────────────────────────────────
|
||||
let proofs: Vec<(PrivacyPreservingCircuitOutput, Proof)> =
|
||||
vec![(output_0, proof_0), (output_1, proof_1), (output_2, proof_2)];
|
||||
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
env_builder.write(&PRIVACY_PRESERVING_CIRCUIT_ID).unwrap();
|
||||
env_builder.write(&(proofs.len() as u32)).unwrap();
|
||||
|
||||
// Write journals first, then add assumptions — ordering matters for the guest.
|
||||
let journals: Vec<Vec<u8>> = proofs.iter().map(|(o, _)| o.to_bytes()).collect();
|
||||
for journal in &journals {
|
||||
env_builder.write(journal).unwrap();
|
||||
}
|
||||
for ((_, proof), journal) in proofs.iter().zip(&journals) {
|
||||
let inner: InnerReceipt = borsh::from_slice(&proof.0).unwrap();
|
||||
env_builder.add_assumption(Receipt::new(inner, journal.clone()));
|
||||
}
|
||||
|
||||
let env = env_builder.build().unwrap();
|
||||
let prove_info = default_prover()
|
||||
.prove_with_opts(env, PPE_AGGREGATION_ELF, &ProverOpts::succinct())
|
||||
.expect("aggregation proving should succeed");
|
||||
|
||||
prove_info
|
||||
.receipt
|
||||
.verify(PPE_AGGREGATION_ID)
|
||||
.expect("aggregated proof must verify");
|
||||
|
||||
let recovered: Vec<PrivacyPreservingCircuitOutput> =
|
||||
prove_info.receipt.journal.decode().unwrap();
|
||||
assert_eq!(recovered.len(), proofs.len());
|
||||
for (i, (expected_output, _)) in proofs.iter().enumerate() {
|
||||
assert_eq!(&recovered[i], expected_output, "output {i} mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
/// Aggregate pre-generated PPE proofs loaded from disk.
|
||||
///
|
||||
/// This test isolates the aggregation circuit from individual transaction proving:
|
||||
/// it loads fixtures produced by `ppe_test_data_gen`, reconstructs each receipt,
|
||||
/// and runs only the `PPE_AGGREGATION_ELF` circuit — no `execute_and_prove` call.
|
||||
///
|
||||
/// Skips gracefully when the fixture file is absent so CI is not broken.
|
||||
/// To run: generate fixtures first, then point the test at them:
|
||||
///
|
||||
/// ```sh
|
||||
/// RISC0_DEV_MODE=1 cargo run --release -p ppe_test_data_gen -- --output ppe_fixtures.bin
|
||||
/// RISC0_DEV_MODE=1 cargo test -p lee aggregate_ppe_proofs_from_fixtures
|
||||
/// ```
|
||||
///
|
||||
/// Override the path via the `PPE_FIXTURES` env var (default: `ppe_fixtures.bin`).
|
||||
#[test]
|
||||
fn aggregate_ppe_proofs_from_fixtures() {
|
||||
use risc0_zkvm::{ExecutorEnv, InnerReceipt, ProverOpts, Receipt, default_prover};
|
||||
use test_program_methods::{PPE_AGGREGATION_ELF, PPE_AGGREGATION_ID, PpeFixture};
|
||||
|
||||
let path =
|
||||
std::env::var("PPE_FIXTURES").unwrap_or_else(|_| "ppe_fixtures.bin".to_owned());
|
||||
let mut fixtures = PpeFixture::load_bundle(&path);
|
||||
|
||||
if fixtures.is_empty() {
|
||||
return; // file absent — load_bundle already printed a skip notice
|
||||
}
|
||||
|
||||
if let Ok(count_str) = std::env::var("PPE_FIXTURES_COUNT") {
|
||||
let count: usize = count_str.parse().expect("PPE_FIXTURES_COUNT must be a number");
|
||||
fixtures.truncate(count);
|
||||
}
|
||||
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
env_builder.write(&PRIVACY_PRESERVING_CIRCUIT_ID).unwrap();
|
||||
env_builder
|
||||
.write(&u32::try_from(fixtures.len()).expect("fixture count fits in u32"))
|
||||
.unwrap();
|
||||
|
||||
// Journals must be written before assumptions (guest reads them in order).
|
||||
for f in &fixtures {
|
||||
env_builder.write(&f.output_bytes).unwrap();
|
||||
}
|
||||
for f in &fixtures {
|
||||
let inner: InnerReceipt = borsh::from_slice(&f.proof_bytes)
|
||||
.expect("fixture proof_bytes is not a valid InnerReceipt");
|
||||
env_builder.add_assumption(Receipt::new(inner, f.output_bytes.clone()));
|
||||
}
|
||||
|
||||
let env = env_builder.build().unwrap();
|
||||
let t0 = std::time::Instant::now();
|
||||
let prove_info = default_prover()
|
||||
.prove_with_opts(env, PPE_AGGREGATION_ELF, &ProverOpts::succinct())
|
||||
.expect("aggregation proving should succeed");
|
||||
let proving_ms = t0.elapsed().as_millis();
|
||||
|
||||
let proof_size = borsh::to_vec(&prove_info.receipt.inner).unwrap().len();
|
||||
eprintln!(
|
||||
"[lee::analytics] ppe_aggregation n={} proving_ms={} proof_size_bytes={}",
|
||||
fixtures.len(),
|
||||
proving_ms,
|
||||
proof_size,
|
||||
);
|
||||
|
||||
prove_info
|
||||
.receipt
|
||||
.verify(PPE_AGGREGATION_ID)
|
||||
.expect("aggregated proof must verify");
|
||||
|
||||
let recovered: Vec<PrivacyPreservingCircuitOutput> =
|
||||
prove_info.receipt.journal.decode().unwrap();
|
||||
assert_eq!(recovered.len(), fixtures.len(), "recovered output count mismatch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_pda_update_identifier_mismatch_fails() {
|
||||
let program = Program::pda_spend_proxy();
|
||||
|
||||
@ -7,6 +7,9 @@ license = { workspace = true }
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
borsh.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build.workspace = true
|
||||
|
||||
|
||||
@ -14,4 +14,5 @@ clock_core.workspace = true
|
||||
faucet_core.workspace = true
|
||||
|
||||
risc0-zkvm.workspace = true
|
||||
bytemuck.workspace = true
|
||||
serde = { workspace = true, default-features = false }
|
||||
|
||||
39
test_program_methods/guest/src/bin/ppe_aggregation.rs
Normal file
39
test_program_methods/guest/src/bin/ppe_aggregation.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use lee_core::PrivacyPreservingCircuitOutput;
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
/// Aggregation circuit for N privacy-preserving execution proofs.
|
||||
///
|
||||
/// The host writes:
|
||||
/// 1. The PPE circuit image ID (`[u32; 8]`)
|
||||
/// 2. The count N (`u32`)
|
||||
/// 3. N journal byte-buffers (each produced by `PrivacyPreservingCircuitOutput::to_bytes()`)
|
||||
///
|
||||
/// It also loads each PPE receipt as an assumption before running this guest.
|
||||
/// `env::verify` checks each assumption cryptographically; if any proof is
|
||||
/// invalid the guest panics and no aggregation receipt is produced.
|
||||
///
|
||||
/// Journal: `Vec<PrivacyPreservingCircuitOutput>` — the verifier recovers all
|
||||
/// circuit outputs from the single aggregated proof.
|
||||
fn main() {
|
||||
// The host passes the PPE circuit image ID so the guest stays independent
|
||||
// of the host-only `lee` crate.
|
||||
let ppe_image_id: [u32; 8] = env::read();
|
||||
let count: u32 = env::read();
|
||||
|
||||
let mut outputs = Vec::with_capacity(count as usize);
|
||||
|
||||
for _ in 0..count {
|
||||
let journal: Vec<u8> = env::read();
|
||||
|
||||
env::verify(ppe_image_id, &journal)
|
||||
.expect("PPE_aggregation: a PPE proof failed verification");
|
||||
|
||||
let word_slice: &[u32] = bytemuck::cast_slice(&journal);
|
||||
let output: PrivacyPreservingCircuitOutput =
|
||||
risc0_zkvm::serde::from_slice(word_slice)
|
||||
.expect("PPE_aggregation: failed to deserialise circuit output");
|
||||
outputs.push(output);
|
||||
}
|
||||
|
||||
env::commit(&outputs);
|
||||
}
|
||||
38
test_program_methods/src/fixtures.rs
Normal file
38
test_program_methods/src/fixtures.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
|
||||
/// A single pre-generated PPE proof fixture.
|
||||
///
|
||||
/// Produced by `ppe_test_data_gen` and consumed by the aggregation test so that
|
||||
/// individual transaction proof generation is fully decoupled from the aggregation step.
|
||||
///
|
||||
/// Load a bundle with [`PpeFixture::load_bundle`].
|
||||
#[derive(BorshSerialize, BorshDeserialize)]
|
||||
pub struct PpeFixture {
|
||||
/// Human-readable label identifying the scenario.
|
||||
pub label: String,
|
||||
/// `PrivacyPreservingCircuitOutput` encoded via `to_bytes()` (risc0 serde / u32 word slice).
|
||||
/// This is the journal that was committed by the PPE circuit.
|
||||
pub output_bytes: Vec<u8>,
|
||||
/// Borsh-encoded `InnerReceipt` (from `Proof::into_inner()`).
|
||||
pub proof_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PpeFixture {
|
||||
/// Loads a Borsh-encoded `Vec<PpeFixture>` from `path`.
|
||||
///
|
||||
/// Returns an empty `Vec` (and prints a skip notice) when the file does not exist,
|
||||
/// so that test suites skip gracefully when fixtures have not been generated yet.
|
||||
/// Any other I/O error or deserialisation failure panics with a diagnostic message.
|
||||
pub fn load_bundle(path: &str) -> Vec<Self> {
|
||||
if !std::path::Path::new(path).exists() {
|
||||
eprintln!(
|
||||
"[test_program_methods] PPE fixture file '{path}' not found — skipping. \
|
||||
Run `RISC0_DEV_MODE=1 cargo run --release -p ppe_test_data_gen` to generate it."
|
||||
);
|
||||
return Vec::new();
|
||||
}
|
||||
let bytes = std::fs::read(path)
|
||||
.unwrap_or_else(|e| panic!("failed to read PPE fixture file '{path}': {e}"));
|
||||
borsh::from_slice(&bytes).expect("PPE fixture bundle failed Borsh deserialisation")
|
||||
}
|
||||
}
|
||||
@ -1 +1,4 @@
|
||||
pub mod fixtures;
|
||||
pub use fixtures::PpeFixture;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
|
||||
25
tools/ppe_test_data_gen/Cargo.toml
Normal file
25
tools/ppe_test_data_gen/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "ppe_test_data_gen"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# Enabling `prove` links in the RISC0 prover backend. Without it the binary
|
||||
# compiles but `execute_and_prove` will panic at runtime.
|
||||
# Set RISC0_DEV_MODE=1 for fast mock proofs during development.
|
||||
[features]
|
||||
default = ["prove"]
|
||||
prove = ["lee/prove", "risc0-zkvm/prove"]
|
||||
|
||||
[dependencies]
|
||||
lee = { workspace = true }
|
||||
lee_core = { workspace = true, features = ["test_utils"] }
|
||||
authenticated_transfer_core.workspace = true
|
||||
risc0-zkvm.workspace = true
|
||||
borsh.workspace = true
|
||||
anyhow.workspace = true
|
||||
clap = { workspace = true }
|
||||
193
tools/ppe_test_data_gen/src/main.rs
Normal file
193
tools/ppe_test_data_gen/src/main.rs
Normal file
@ -0,0 +1,193 @@
|
||||
//! Generates LEZ privacy-preserving execution (PPE) proof fixtures for aggregation testing.
|
||||
//!
|
||||
//! Each fixture bundles a `PrivacyPreservingCircuitOutput` (serialised with risc0 serde via
|
||||
//! `to_bytes()`) and the raw `InnerReceipt` bytes (Borsh-encoded, from `Proof::into_inner()`).
|
||||
//! The whole bundle is a Borsh-encoded `Vec<PpeFixture>`.
|
||||
//!
|
||||
//! Keys are derived deterministically from the proof index so the fixture file is
|
||||
//! reproducible.
|
||||
//! # Usage
|
||||
//!
|
||||
//! ```sh
|
||||
//! # Fast mock proofs — good for iteration:
|
||||
//! RISC0_DEV_MODE=1 cargo run --release -p ppe_test_data_gen -- --output ppe_fixtures.bin
|
||||
//!
|
||||
//! # Real STARK proofs (slow, production-quality):
|
||||
//! cargo run --release -p ppe_test_data_gen -- --output ppe_fixtures.bin
|
||||
//! ```
|
||||
//!
|
||||
//! # Loading fixtures in aggregation code
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let bytes = std::fs::read("ppe_fixtures.bin").unwrap();
|
||||
//! let fixtures: Vec<PpeFixture> = borsh::from_slice(&bytes).unwrap();
|
||||
//!
|
||||
//! for f in &fixtures {
|
||||
//! // Decode the circuit output:
|
||||
//! let words: &[u32] = bytemuck::cast_slice(&f.output_bytes);
|
||||
//! let output: PrivacyPreservingCircuitOutput =
|
||||
//! risc0_zkvm::serde::from_slice(words).unwrap();
|
||||
//!
|
||||
//! // Reconstruct the Receipt for use as an aggregation assumption:
|
||||
//! let inner: risc0_zkvm::InnerReceipt = borsh::from_slice(&f.proof_bytes).unwrap();
|
||||
//! let receipt = risc0_zkvm::Receipt::new(inner, output.to_bytes());
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::as_conversions,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::print_stderr,
|
||||
reason = "CLI tool — intentional index-to-byte casts, counter arithmetic, and diagnostic output"
|
||||
)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use authenticated_transfer_core::Instruction;
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use clap::Parser;
|
||||
use lee::{
|
||||
execute_and_prove, privacy_preserving_transaction::circuit::ProgramWithDependencies,
|
||||
program::Program,
|
||||
};
|
||||
use lee_core::{
|
||||
InputAccountIdentity, NullifierPublicKey, SharedSecretKey,
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
encryption::ViewingPublicKey,
|
||||
};
|
||||
|
||||
/// Mirror of `test_program_methods::PpeFixture`. Borsh field order must stay in sync.
|
||||
#[derive(BorshSerialize, BorshDeserialize)]
|
||||
struct PpeFixture {
|
||||
label: String,
|
||||
output_bytes: Vec<u8>,
|
||||
proof_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
name = "ppe_test_data_gen",
|
||||
about = "Generate PPE proof fixtures for aggregation testing"
|
||||
)]
|
||||
struct Cli {
|
||||
/// Output file path for the Borsh-serialised fixture bundle.
|
||||
#[arg(long, default_value = "ppe_fixtures.bin")]
|
||||
output: PathBuf,
|
||||
|
||||
/// Number of independent PPE proofs to generate.
|
||||
#[arg(long, default_value_t = 16)]
|
||||
count: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let program = Program::authenticated_transfer_program();
|
||||
let mut fixtures: Vec<PpeFixture> = Vec::with_capacity(cli.count);
|
||||
|
||||
for i in 0..cli.count {
|
||||
let lo = (i & 0xFF) as u8;
|
||||
let hi = ((i >> 8) & 0xFF) as u8;
|
||||
|
||||
// Non-zero bases ensure no key is accidentally all-zero.
|
||||
let mut nsk = [41_u8; 32];
|
||||
nsk[0] = lo;
|
||||
nsk[1] = hi;
|
||||
|
||||
// ViewingPublicKey requires two independent 32-byte seed halves (d, z).
|
||||
let mut d = [42_u8; 32];
|
||||
d[0] = lo;
|
||||
d[1] = hi;
|
||||
let mut z = [43_u8; 32];
|
||||
z[0] = lo;
|
||||
z[1] = hi;
|
||||
|
||||
// The message hash used for deterministic encapsulation; vary it per proof index.
|
||||
let mut msg = [44_u8; 32];
|
||||
msg[0] = lo;
|
||||
msg[1] = hi;
|
||||
|
||||
let amount: u128 = 100;
|
||||
let label = format!("public_to_private_{i}");
|
||||
|
||||
let vpk = ViewingPublicKey::from_seed(&d, &z);
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
// `encapsulate_deterministic` requires `lee_core` with `test_utils` feature.
|
||||
// The recipient output is at index 0 (the only private output in this scenario).
|
||||
let (ssk, _epk) = SharedSecretKey::encapsulate_deterministic(&vpk, &msg, 0);
|
||||
|
||||
let mut sender_seed = [45_u8; 32];
|
||||
sender_seed[0] = lo;
|
||||
sender_seed[1] = hi;
|
||||
|
||||
let sender = AccountWithMetadata::new(
|
||||
Account {
|
||||
program_owner: program.id(),
|
||||
balance: amount + 10,
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
AccountId::new(sender_seed),
|
||||
);
|
||||
let recipient = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::for_regular_private_account(&npk, 0),
|
||||
);
|
||||
|
||||
let instruction =
|
||||
Program::serialize_instruction(Instruction::Transfer { amount })
|
||||
.context("serialise instruction")?;
|
||||
|
||||
eprintln!(
|
||||
"[ppe_test_data_gen] ({}/{}) proving '{label}' ...",
|
||||
i + 1,
|
||||
cli.count,
|
||||
);
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender, recipient],
|
||||
instruction,
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
npk,
|
||||
ssk,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
&ProgramWithDependencies::from(program.clone()),
|
||||
)
|
||||
.with_context(|| format!("execute_and_prove for '{label}'"))?;
|
||||
|
||||
let proof_bytes = proof.into_inner();
|
||||
let output_bytes = output.to_bytes();
|
||||
|
||||
eprintln!(
|
||||
"[ppe_test_data_gen] proof={} B output={} B commitments={} ciphertexts={}",
|
||||
proof_bytes.len(),
|
||||
output_bytes.len(),
|
||||
output.new_commitments.len(),
|
||||
output.ciphertexts.len(),
|
||||
);
|
||||
|
||||
fixtures.push(PpeFixture {
|
||||
label,
|
||||
output_bytes,
|
||||
proof_bytes,
|
||||
});
|
||||
}
|
||||
|
||||
let bundle = borsh::to_vec(&fixtures).context("serialise fixture bundle")?;
|
||||
std::fs::write(&cli.output, &bundle).context("write output file")?;
|
||||
|
||||
eprintln!(
|
||||
"[ppe_test_data_gen] wrote {} fixtures ({} bytes total) -> {}",
|
||||
fixtures.len(),
|
||||
bundle.len(),
|
||||
cli.output.display(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user