From c20f1660cad47492d7ff3f415c402336005a3c23 Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:11:22 +0100 Subject: [PATCH 1/9] experiment(conoir): add coNoir MPC double-dip spike MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proof-of-life for the apertrue x coNoir primitive: a minimal nullifier membership circuit (Poseidon2 + field equality) run through the full 3-party REP3 MPC pipeline. Both no-collision (public bit 0) and collision (public bit 1) cases generate and verify; only the bit is revealed, the candidate and set stay secret-shared. Reference artifacts only (circuit + run script + README) — the co-noir binary/CRS are reproducible from the co-snarks repo and not committed. Not for main; lineage for the upcoming IMT-non-membership version. Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/README.md | 37 ++++++++++++++++++ .../conoir-spike/nullifier_check/Nargo.toml | 7 ++++ .../conoir-spike/nullifier_check/Prover.toml | 3 ++ .../conoir-spike/nullifier_check/src/main.nr | 31 +++++++++++++++ .../conoir-spike/run_nullifier_check.sh | 39 +++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 experiments/conoir-spike/README.md create mode 100644 experiments/conoir-spike/nullifier_check/Nargo.toml create mode 100644 experiments/conoir-spike/nullifier_check/Prover.toml create mode 100644 experiments/conoir-spike/nullifier_check/src/main.nr create mode 100755 experiments/conoir-spike/run_nullifier_check.sh diff --git a/experiments/conoir-spike/README.md b/experiments/conoir-spike/README.md new file mode 100644 index 0000000..5769314 --- /dev/null +++ b/experiments/conoir-spike/README.md @@ -0,0 +1,37 @@ +# coNoir spike — authenticated-nullifier double-dip check under MPC + +Proof-of-life for the **apertrue × coNoir** primitive: prove whether an authenticated +nullifier already exists in a consortium's private set, revealing **only** a yes/no bit, +with the candidate and the set kept secret-shared across the MPC parties. + +## Result (2026-06-23) +Ran the full 3-party REP3 MPC pipeline (split-input → witness → proving-key → vk → proof → verify) +on `nullifier_check`: + +| Case | Public output | Verified | +|------|---------------|----------| +| candidate not in set | `0` (false) | ✅ | +| candidate in set (double-dip) | `1` (true) | ✅ | + +Whole pipeline ~1s for this tiny circuit (proving ~77ms, verify ~2ms). Only the bit was revealed. + +## Circuit +`nullifier_check/src/main.nr` — derives `n = Poseidon2([content, scope])`, scans a private +set for membership, returns the collision bit. Poseidon2 + field equality only → inside coNoir's +supported MPC lane (no RSA/ECDSA, which stay in apertrue's local C2PA proof). + +## Versions +- co-noir **0.7.0** (built from `github.com/TaceoLabs/co-snarks`). +- nargo **1.0.0-beta.20** (coNoir's target; apertrue is on beta.18 — small gap). +- bn254 CRS from the co-snarks repo. + +## Reproduce +`run_nullifier_check.sh` is preserved **as a reference**. Its relative paths assume it sits in +`co-noir/co-noir/examples/` of a co-snarks clone (it points at `../../../target/release/co-noir` +and `../../co-noir-common/src/crs/*`). To re-run: clone co-snarks, `cargo build --release --bin +co-noir`, drop `nullifier_check/` into `examples/test_vectors/`, place this script in `examples/`, +and run it. Compile the circuit with nargo beta.20 first (`nargo execute`). + +## Next step +Replace the toy 4-element linear scan with **Colofon's IMT non-membership** circuit run under MPC — +that's the first real-scale version and tells us the performance story. diff --git a/experiments/conoir-spike/nullifier_check/Nargo.toml b/experiments/conoir-spike/nullifier_check/Nargo.toml new file mode 100644 index 0000000..d3d6cf4 --- /dev/null +++ b/experiments/conoir-spike/nullifier_check/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nullifier_check" +type = "bin" +compiler_version = ">=1.0.0" +authors = ["Apertrue"] + +[dependencies] diff --git a/experiments/conoir-spike/nullifier_check/Prover.toml b/experiments/conoir-spike/nullifier_check/Prover.toml new file mode 100644 index 0000000..40221a6 --- /dev/null +++ b/experiments/conoir-spike/nullifier_check/Prover.toml @@ -0,0 +1,3 @@ +candidate_content = "1" +scope = "2" +existing = ["10", "0x299bfccd7daf3c917e51291383929049ec0eaed800af245056cbf135f7dea636", "12", "13"] diff --git a/experiments/conoir-spike/nullifier_check/src/main.nr b/experiments/conoir-spike/nullifier_check/src/main.nr new file mode 100644 index 0000000..252f6a0 --- /dev/null +++ b/experiments/conoir-spike/nullifier_check/src/main.nr @@ -0,0 +1,31 @@ +// Minimal cross-party double-dip check (coNoir spike). +// +// Mirrors the apertrue x coNoir primitive at its smallest: +// - each party's authentic content yields a nullifier n = Poseidon2([content, scope]) +// - the consortium holds a private set of existing nullifiers (secret-shared in MPC) +// - we reveal ONLY whether n is already present (a double-dip) +// +// Uses Poseidon2 + field equality only -> inside coNoir's supported lane. + +global SET_SIZE: u32 = 4; + +fn main( + // private: this party's authenticated content fingerprint (e.g. apertrue content_hash) + candidate_content: Field, + // context binding (consortium / epoch); kept private here, can be made public later + scope: Field, + // private: the consortium's existing nullifiers (secret-shared across the MPC parties) + existing: [Field; SET_SIZE], +) -> pub bool { + // derive the nullifier with the Poseidon2 blackbox (permutation form, supported in MPC) + let nullifier: Field = std::hash::poseidon2_permutation::<4>([candidate_content, scope, 0, 0])[0]; + + // membership scan: collision == true means the item is already used somewhere + let mut collision: bool = false; + for i in 0..SET_SIZE { + collision = collision | (existing[i] == nullifier); + } + + // the ONLY thing revealed: the double-dip bit + collision +} diff --git a/experiments/conoir-spike/run_nullifier_check.sh b/experiments/conoir-spike/run_nullifier_check.sh new file mode 100755 index 0000000..ef1feb7 --- /dev/null +++ b/experiments/conoir-spike/run_nullifier_check.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# coNoir spike: run the nullifier_check circuit through the full 3-party MPC pipeline. +# Mirrors run_all_steps_poseidon.sh, adapted for test_vectors/nullifier_check. +set -e +cd "$(dirname "$(realpath "$0")")" + +CO=../../../target/release/co-noir # built binary (faster than cargo run) +TV=test_vectors/nullifier_check +CRS1=../../co-noir-common/src/crs/bn254_g1.dat +CRS2=../../co-noir-common/src/crs/bn254_g2.dat + +echo "### 1. split-input (REP3 secret-share the Prover.toml inputs) ###" +"$CO" split-input --circuit "$TV/target/nullifier_check.json" --input "$TV/Prover.toml" --protocol REP3 --out-dir "$TV" + +echo "### 2. generate-witness in MPC (3 parties) ###" +"$CO" generate-witness --input "$TV/Prover.toml.0.shared" --circuit "$TV/target/nullifier_check.json" --protocol REP3 --config configs/party1.toml --out "$TV/witness.0.shared" & +"$CO" generate-witness --input "$TV/Prover.toml.1.shared" --circuit "$TV/target/nullifier_check.json" --protocol REP3 --config configs/party2.toml --out "$TV/witness.1.shared" & +"$CO" generate-witness --input "$TV/Prover.toml.2.shared" --circuit "$TV/target/nullifier_check.json" --protocol REP3 --config configs/party3.toml --out "$TV/witness.2.shared" +wait $(jobs -p) + +echo "### 3. build-proving-key in MPC (3 parties) ###" +"$CO" build-proving-key --witness "$TV/witness.0.shared" --circuit "$TV/target/nullifier_check.json" --protocol REP3 --config configs/party1.toml --out "$TV/pk.0.shared" --crs "$CRS1" & +"$CO" build-proving-key --witness "$TV/witness.1.shared" --circuit "$TV/target/nullifier_check.json" --protocol REP3 --config configs/party2.toml --out "$TV/pk.1.shared" --crs "$CRS1" & +"$CO" build-proving-key --witness "$TV/witness.2.shared" --circuit "$TV/target/nullifier_check.json" --protocol REP3 --config configs/party3.toml --out "$TV/pk.2.shared" --crs "$CRS1" +wait $(jobs -p) + +echo "### 4. create verification key ###" +"$CO" create-vk --circuit "$TV/target/nullifier_check.json" --crs "$CRS1" --hasher keccak --vk "$TV/vk" + +echo "### 5. generate-proof in MPC (3 parties) ###" +"$CO" generate-proof --proving-key "$TV/pk.0.shared" --protocol REP3 --hasher keccak --config configs/party1.toml --crs "$CRS1" --out "$TV/proof.0.proof" --vk "$TV/vk" --public-input "$TV/public_input" & +"$CO" generate-proof --proving-key "$TV/pk.1.shared" --protocol REP3 --hasher keccak --config configs/party2.toml --crs "$CRS1" --out "$TV/proof.1.proof" --vk "$TV/vk" & +"$CO" generate-proof --proving-key "$TV/pk.2.shared" --protocol REP3 --hasher keccak --config configs/party3.toml --crs "$CRS1" --out "$TV/proof.2.proof" --vk "$TV/vk" +wait $(jobs -p) + +echo "### 6. verify the proof + show public output ###" +"$CO" verify --proof "$TV/proof.0.proof" --public-input "$TV/public_input" --vk "$TV/vk" --hasher keccak --crs "$CRS2" +echo "--- public_input (the revealed collision bit) ---" +cat "$TV/public_input" 2>/dev/null || echo "(no public_input file)" From e7ef52d458dad4a11362fccd7fa30432fcc4fa8d Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:23:42 +0100 Subject: [PATCH 2/9] experiment(conoir): add IMT non-membership primitive spike imt_nm_mini isolates the two bit-decomposition ops Colofon's check_non_membership needs (Field::lt range check + to_le_bits Merkle path) plus Poseidon2. Ran under 3-party REP3 MPC -> proof verified, so coNoir's co-brillig/co-acvm handle them and the full Colofon IMT will port. Depth-8 single check ~4s (build-proving-key ~2.0s dominant). README notes the beta.20 migrations needed for the real port (u1 removed -> bool; to_le_bits returns [bool; N]). Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/README.md | 18 +++++++- .../conoir-spike/imt_nm_mini/Nargo.toml | 7 ++++ .../conoir-spike/imt_nm_mini/Prover.toml | 6 +++ .../conoir-spike/imt_nm_mini/src/main.nr | 41 +++++++++++++++++++ experiments/conoir-spike/run_imt_nm_mini.sh | 39 ++++++++++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 experiments/conoir-spike/imt_nm_mini/Nargo.toml create mode 100644 experiments/conoir-spike/imt_nm_mini/Prover.toml create mode 100644 experiments/conoir-spike/imt_nm_mini/src/main.nr create mode 100755 experiments/conoir-spike/run_imt_nm_mini.sh diff --git a/experiments/conoir-spike/README.md b/experiments/conoir-spike/README.md index 5769314..74e31d4 100644 --- a/experiments/conoir-spike/README.md +++ b/experiments/conoir-spike/README.md @@ -32,6 +32,20 @@ and `../../co-noir-common/src/crs/*`). To re-run: clone co-snarks, `cargo build co-noir`, drop `nullifier_check/` into `examples/test_vectors/`, place this script in `examples/`, and run it. Compile the circuit with nargo beta.20 first (`nargo execute`). +## IMT non-membership primitive (2026-06-23) +`imt_nm_mini` isolates the two operations Colofon's `check_non_membership` needs that fall in +coNoir's risk zone (bit-decomposition based): `Field::lt` (the low-leaf range check) and +`to_le_bits` (Merkle-path left/right selection), plus Poseidon2. Depth-8, single check, +221 ACIR opcodes / 3845 gates. + +Ran under 3-party REP3 MPC -> **proof verified**. So coNoir's co-brillig/co-acvm handle those ops; +the full Colofon IMT non-membership will port. Perf: build-proving-key ~2.0s (dominant, scales with +circuit size), generate-proof ~0.6s, verify ~3ms, ~4s total. + +beta.20 migration notes for the real port: `u1` is removed (use `bool`); `to_le_bits` returns +`[bool; N]` (Colofon's `root.nr` still uses the old `[u1; N]`). + ## Next step -Replace the toy 4-element linear scan with **Colofon's IMT non-membership** circuit run under MPC — -that's the first real-scale version and tells us the performance story. +Wire up the **real `colofon_imt` lib** (traits/generics + CveLeafPreimage + a real tree witness) at +depth-32 / up to 50 checks to get the true-scale MPC proving time. Still all-local nodes so far +(no network latency in these numbers). diff --git a/experiments/conoir-spike/imt_nm_mini/Nargo.toml b/experiments/conoir-spike/imt_nm_mini/Nargo.toml new file mode 100644 index 0000000..6031eb5 --- /dev/null +++ b/experiments/conoir-spike/imt_nm_mini/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "imt_nm_mini" +type = "bin" +compiler_version = ">=1.0.0" +authors = ["Apertrue"] + +[dependencies] diff --git a/experiments/conoir-spike/imt_nm_mini/Prover.toml b/experiments/conoir-spike/imt_nm_mini/Prover.toml new file mode 100644 index 0000000..cf0b51b --- /dev/null +++ b/experiments/conoir-spike/imt_nm_mini/Prover.toml @@ -0,0 +1,6 @@ +# Non-membership case: low_key < key < next_key (10 < 20 < 30) -> in_range holds. +key = "20" +low_key = "10" +next_key = "30" +low_leaf_index = "5" +sibling_path = ["111", "222", "333", "444", "555", "666", "777", "888"] diff --git a/experiments/conoir-spike/imt_nm_mini/src/main.nr b/experiments/conoir-spike/imt_nm_mini/src/main.nr new file mode 100644 index 0000000..9cbd31b --- /dev/null +++ b/experiments/conoir-spike/imt_nm_mini/src/main.nr @@ -0,0 +1,41 @@ +// Minimal IMT non-membership primitive test for coNoir MPC. +// +// Isolates the two operations Colofon's check_non_membership needs that fall +// in coNoir's risk zone (bit-decomposition based): +// 1. Field::lt -> the low-leaf range check (low_key < key < next_key) +// 2. to_le_bits -> left/right selection while hashing the Merkle path +// Plus Poseidon2 (already proven supported). If this runs under MPC and +// verifies, the full Colofon IMT non-membership circuit will too. + +global DEPTH: u32 = 8; + +fn merkle_hash(l: Field, r: Field) -> Field { + std::hash::poseidon2_permutation::<4>([l, r, 0, 0])[0] +} + +fn main( + key: Field, // candidate (query) key, the non-membership target + low_key: Field, // low leaf's key + next_key: Field, // low leaf's next key + low_leaf_index: Field, // low leaf's index in the tree + sibling_path: [Field; DEPTH], // non-membership Merkle opening for the low leaf +) -> pub Field { // returns the computed IMT root (public) + // RISK OP 1: comparisons. Non-membership requires the key to sit strictly + // inside the low leaf's gap. lt() lowers to bit decomposition. + let in_range = low_key.lt(key) & key.lt(next_key); + assert(in_range, "key not strictly within low-leaf gap"); + + // RISK OP 2: to_le_bits drives left/right selection up the tree; Poseidon2 hashes. + let low_leaf = merkle_hash(low_key, next_key); + let mut node = low_leaf; + let bits: [bool; DEPTH] = low_leaf_index.to_le_bits(); + for i in 0..DEPTH { + let (a, b) = if bits[i] { + (sibling_path[i], node) + } else { + (node, sibling_path[i]) + }; + node = merkle_hash(a, b); + } + node +} diff --git a/experiments/conoir-spike/run_imt_nm_mini.sh b/experiments/conoir-spike/run_imt_nm_mini.sh new file mode 100755 index 0000000..8204f54 --- /dev/null +++ b/experiments/conoir-spike/run_imt_nm_mini.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# coNoir spike: run the imt_nm_mini circuit through the full 3-party MPC pipeline. +# Mirrors run_all_steps_poseidon.sh, adapted for test_vectors/imt_nm_mini. +set -e +cd "$(dirname "$(realpath "$0")")" + +CO=../../../target/release/co-noir # built binary (faster than cargo run) +TV=test_vectors/imt_nm_mini +CRS1=../../co-noir-common/src/crs/bn254_g1.dat +CRS2=../../co-noir-common/src/crs/bn254_g2.dat + +echo "### 1. split-input (REP3 secret-share the Prover.toml inputs) ###" +"$CO" split-input --circuit "$TV/target/imt_nm_mini.json" --input "$TV/Prover.toml" --protocol REP3 --out-dir "$TV" + +echo "### 2. generate-witness in MPC (3 parties) ###" +"$CO" generate-witness --input "$TV/Prover.toml.0.shared" --circuit "$TV/target/imt_nm_mini.json" --protocol REP3 --config configs/party1.toml --out "$TV/witness.0.shared" & +"$CO" generate-witness --input "$TV/Prover.toml.1.shared" --circuit "$TV/target/imt_nm_mini.json" --protocol REP3 --config configs/party2.toml --out "$TV/witness.1.shared" & +"$CO" generate-witness --input "$TV/Prover.toml.2.shared" --circuit "$TV/target/imt_nm_mini.json" --protocol REP3 --config configs/party3.toml --out "$TV/witness.2.shared" +wait $(jobs -p) + +echo "### 3. build-proving-key in MPC (3 parties) ###" +"$CO" build-proving-key --witness "$TV/witness.0.shared" --circuit "$TV/target/imt_nm_mini.json" --protocol REP3 --config configs/party1.toml --out "$TV/pk.0.shared" --crs "$CRS1" & +"$CO" build-proving-key --witness "$TV/witness.1.shared" --circuit "$TV/target/imt_nm_mini.json" --protocol REP3 --config configs/party2.toml --out "$TV/pk.1.shared" --crs "$CRS1" & +"$CO" build-proving-key --witness "$TV/witness.2.shared" --circuit "$TV/target/imt_nm_mini.json" --protocol REP3 --config configs/party3.toml --out "$TV/pk.2.shared" --crs "$CRS1" +wait $(jobs -p) + +echo "### 4. create verification key ###" +"$CO" create-vk --circuit "$TV/target/imt_nm_mini.json" --crs "$CRS1" --hasher keccak --vk "$TV/vk" + +echo "### 5. generate-proof in MPC (3 parties) ###" +"$CO" generate-proof --proving-key "$TV/pk.0.shared" --protocol REP3 --hasher keccak --config configs/party1.toml --crs "$CRS1" --out "$TV/proof.0.proof" --vk "$TV/vk" --public-input "$TV/public_input" & +"$CO" generate-proof --proving-key "$TV/pk.1.shared" --protocol REP3 --hasher keccak --config configs/party2.toml --crs "$CRS1" --out "$TV/proof.1.proof" --vk "$TV/vk" & +"$CO" generate-proof --proving-key "$TV/pk.2.shared" --protocol REP3 --hasher keccak --config configs/party3.toml --crs "$CRS1" --out "$TV/proof.2.proof" --vk "$TV/vk" +wait $(jobs -p) + +echo "### 6. verify the proof + show public output ###" +"$CO" verify --proof "$TV/proof.0.proof" --public-input "$TV/public_input" --vk "$TV/vk" --hasher keccak --crs "$CRS2" +echo "--- public_input (the revealed collision bit) ---" +cat "$TV/public_input" 2>/dev/null || echo "(no public_input file)" From d440018e73232a1c60f43b058a3878f3d28f96ba Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:38:55 +0100 Subject: [PATCH 3/9] experiment(conoir): add depth-32 scale benchmark + perf numbers imt_nm_scale mirrors Colofon's per-component non-membership shape (leaf-preimage hash + low-leaf lt + 32-level Merkle path), batched over N checks; bench_imt_scale.sh sweeps N and times the MPC pipeline. Full Colofon scale (N=50, depth-32, 153,843 gates) under 3-party REP3 MPC: ~160s total (proving-key 107s dominant, proof 30s, witness 23s), verify constant 29ms. ~3k gates/check. Nodes co-located (no network latency). Feasibility + perf envelope established. Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/README.md | 22 ++++++- experiments/conoir-spike/bench_imt_scale.sh | 63 +++++++++++++++++++ .../conoir-spike/imt_nm_scale/Nargo.toml | 7 +++ .../conoir-spike/imt_nm_scale/src/main.nr | 49 +++++++++++++++ 4 files changed, 138 insertions(+), 3 deletions(-) create mode 100755 experiments/conoir-spike/bench_imt_scale.sh create mode 100644 experiments/conoir-spike/imt_nm_scale/Nargo.toml create mode 100644 experiments/conoir-spike/imt_nm_scale/src/main.nr diff --git a/experiments/conoir-spike/README.md b/experiments/conoir-spike/README.md index 74e31d4..362fb81 100644 --- a/experiments/conoir-spike/README.md +++ b/experiments/conoir-spike/README.md @@ -45,7 +45,23 @@ circuit size), generate-proof ~0.6s, verify ~3ms, ~4s total. beta.20 migration notes for the real port: `u1` is removed (use `bool`); `to_le_bits` returns `[bool; N]` (Colofon's `root.nr` still uses the old `[u1; N]`). +## Depth-32 scale perf (2026-06-23) +`imt_nm_scale` mirrors Colofon's per-component non-membership shape (leaf-preimage hash + low-leaf +`lt` + 32-level Merkle path), batched over N checks. `bench_imt_scale.sh N` patches `CHECKS`, +generates the witness, compiles, counts gates, and runs the timed 3-party MPC pipeline. + +| N (checks) | gates | witness | proving-key | proof | verify | total | +|------------|-------|---------|-------------|-------|--------|-------| +| 1 | 5,798 | 0.6s | 2.3s | 1.2s | 29ms | ~4.1s | +| 50 (full) | 153,843 | 23s | 107s | 30s | 29ms | ~160s (~2.7 min) | + +~3k gates per added depth-32 check; proving-key dominant + slightly super-linear; **verify constant +29ms regardless of scale.** Full-scale double-dip ≈ 2.7 min to prove, instant to verify — fine for a +batch/async fraud check. Caveat: all 3 MPC nodes co-located, so NO network latency in these numbers. + ## Next step -Wire up the **real `colofon_imt` lib** (traits/generics + CveLeafPreimage + a real tree witness) at -depth-32 / up to 50 checks to get the true-scale MPC proving time. Still all-local nodes so far -(no network latency in these numbers). +The apertrue <-> coNoir **composition / binding**: ensure the nullifier the coNoir circuit checks is +provably the apertrue-C2PA-authenticated one (the "authenticated" in authenticated-MPC). Leading +approach: enforce authenticity at registry-ADD time (off-MPC single-party apertrue proof check), +so the MPC non-membership only checks an already-authentic registry. Also open: real networked +3-machine latency, and wiring the literal `colofon_imt` lib. diff --git a/experiments/conoir-spike/bench_imt_scale.sh b/experiments/conoir-spike/bench_imt_scale.sh new file mode 100755 index 0000000..d328833 --- /dev/null +++ b/experiments/conoir-spike/bench_imt_scale.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Benchmark imt_nm_scale at a given number of checks N. +# Usage: bench_imt_scale.sh N +set -e +cd "$(dirname "$(realpath "$0")")" +N="${1:-1}" +NARGO=~/.nargo/bin/nargo +CO=../../../target/release/co-noir +TV=test_vectors/imt_nm_scale +CRS1=../../co-noir-common/src/crs/bn254_g1.dat +CRS2=../../co-noir-common/src/crs/bn254_g2.dat +now(){ python3 -c 'import time;print(int(time.time()*1000))'; } + +echo "########## N = $N ##########" + +# 1. patch CHECKS in the circuit +sed -i '' "s/^global CHECKS: u32 = .*/global CHECKS: u32 = $N;/" "$TV/src/main.nr" + +# 2. generate Prover.toml (N checks, depth 32; low_key < key < next_key) +node -e ' +const N=+process.argv[1], D=32; +const arr=(f)=>"["+Array.from({length:N},(_,c)=>`"${f(c)}"`).join(", ")+"]"; +let s=""; +s+="key = "+arr(c=>c*1000+20)+"\n"; +s+="low_key = "+arr(c=>c*1000+10)+"\n"; +s+="next_key = "+arr(c=>c*1000+30)+"\n"; +s+="next_index = "+arr(c=>c)+"\n"; +s+="low_leaf_index = "+arr(c=>c)+"\n"; +const paths=Array.from({length:N},(_,c)=>"["+Array.from({length:D},(_,i)=>`"${c*100+i+1}"`).join(", ")+"]"); +s+="sibling_path = ["+paths.join(", ")+"]\n"; +process.stdout.write(s); +' "$N" > "$TV/Prover.toml" + +# 3. compile + gate count +( cd "$TV" && "$NARGO" execute >/dev/null 2>&1 ) +GATES=$(bb gates -b "$TV/target/imt_nm_scale.json" 2>/dev/null | grep -oE '"circuit_size": *[0-9]+' | grep -oE '[0-9]+' | head -1) +echo "gates(circuit_size) = ${GATES:-unknown}" + +# 4. timed MPC pipeline +t0=$(now) +"$CO" split-input --circuit "$TV/target/imt_nm_scale.json" --input "$TV/Prover.toml" --protocol REP3 --out-dir "$TV" >/dev/null 2>&1 +for p in 0 1 2; do + "$CO" generate-witness --input "$TV/Prover.toml.$p.shared" --circuit "$TV/target/imt_nm_scale.json" --protocol REP3 --config "configs/party$((p+1)).toml" --out "$TV/w.$p.shared" >/dev/null 2>&1 & +done; wait +tw=$(now) +for p in 0 1 2; do + "$CO" build-proving-key --witness "$TV/w.$p.shared" --circuit "$TV/target/imt_nm_scale.json" --protocol REP3 --config "configs/party$((p+1)).toml" --out "$TV/pk.$p.shared" --crs "$CRS1" >/dev/null 2>&1 & +done; wait +tpk=$(now) +"$CO" create-vk --circuit "$TV/target/imt_nm_scale.json" --crs "$CRS1" --hasher keccak --vk "$TV/vk" >/dev/null 2>&1 +for p in 0 1 2; do + extra=""; [ "$p" = "0" ] && extra="--public-input $TV/public_input" + "$CO" generate-proof --proving-key "$TV/pk.$p.shared" --protocol REP3 --hasher keccak --config "configs/party$((p+1)).toml" --crs "$CRS1" --out "$TV/proof.$p.proof" --vk "$TV/vk" $extra >/dev/null 2>&1 & +done; wait +tpf=$(now) +"$CO" verify --proof "$TV/proof.0.proof" --public-input "$TV/public_input" --vk "$TV/vk" --hasher keccak --crs "$CRS2" >/dev/null 2>&1 && echo "VERIFIED ok" +tv=$(now) + +echo "witness: $((tw-t0)) ms" +echo "proving-key: $((tpk-tw)) ms" +echo "proof: $((tpf-tpk)) ms (incl vk)" +echo "verify: $((tv-tpf)) ms" +echo "TOTAL: $((tv-t0)) ms" diff --git a/experiments/conoir-spike/imt_nm_scale/Nargo.toml b/experiments/conoir-spike/imt_nm_scale/Nargo.toml new file mode 100644 index 0000000..6708e35 --- /dev/null +++ b/experiments/conoir-spike/imt_nm_scale/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "imt_nm_scale" +type = "bin" +compiler_version = ">=1.0.0" +authors = ["Apertrue"] + +[dependencies] diff --git a/experiments/conoir-spike/imt_nm_scale/src/main.nr b/experiments/conoir-spike/imt_nm_scale/src/main.nr new file mode 100644 index 0000000..3d9562d --- /dev/null +++ b/experiments/conoir-spike/imt_nm_scale/src/main.nr @@ -0,0 +1,49 @@ +// Depth-32 IMT non-membership at Colofon scale, for the coNoir MPC perf number. +// +// Mirrors the dominant cost of Colofon's sbom_non_membership per component: +// - leaf-preimage hash (value, next_value, next_index) -> Poseidon2 +// - low-leaf range check (low < key < next) -> Field::lt +// - 32-level Merkle path to the CVE-tree root -> to_le_bits + Poseidon2 +// batched over CHECKS components (Colofon MAX_SBOM_COMPONENTS = 50). +// +// CHECKS is patched by the run harness to sweep N=1 and N=50. + +global DEPTH: u32 = 32; +global CHECKS: u32 = 50; + +fn merkle_hash(l: Field, r: Field) -> Field { + std::hash::poseidon2_permutation::<4>([l, r, 0, 0])[0] +} + +fn main( + key: [Field; CHECKS], // query keys (non-membership targets) + low_key: [Field; CHECKS], // low leaf key + next_key: [Field; CHECKS], // low leaf next key + next_index: [Field; CHECKS], // low leaf next index (part of preimage) + low_leaf_index: [Field; CHECKS], // low leaf position in the tree + sibling_path: [[Field; DEPTH]; CHECKS], // per-check non-membership opening +) -> pub Field { // accumulated roots (keeps work live) + let mut acc: Field = 0; + for c in 0..CHECKS { + // low-leaf range check + let in_range = low_key[c].lt(key[c]) & key[c].lt(next_key[c]); + assert(in_range, "key not within low-leaf gap"); + + // leaf preimage hash: Poseidon2(Poseidon2(low_key, next_key), next_index) + let low_leaf = merkle_hash(merkle_hash(low_key[c], next_key[c]), next_index[c]); + + // 32-level Merkle path to the root + let mut node = low_leaf; + let bits: [bool; DEPTH] = low_leaf_index[c].to_le_bits(); + for i in 0..DEPTH { + let (a, b) = if bits[i] { + (sibling_path[c][i], node) + } else { + (node, sibling_path[c][i]) + }; + node = merkle_hash(a, b); + } + acc = acc + node; + } + acc +} From dcd9cdf36dcb0f4c1418e15810afdc6c1a6e2775 Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:32:24 +0100 Subject: [PATCH 4/9] experiment(conoir): add binding / authenticated-MPC seam spike bound_nm demonstrates the apertrue <-> coNoir binding: a public hiding commitment Commit(nullifier, blind) is the C2PA authenticity anchor; the party secret-shares (nullifier, blind); a lightweight in-MPC opening check binds them; non-membership runs on the secret-shared nullifier; only the collision bit is revealed. No recursive verification in MPC. Verified under 3-party MPC: no-collision -> 0, collision -> 1, and binding-failure (substituted nullifier) -> rejected ("commitment opening failed"). README adds the threat-model notes (membership-oracle defense via authenticated queries; coNoir is semi-honest only). Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/README.md | 36 +++++++++++++++--- experiments/conoir-spike/bound_nm/Nargo.toml | 7 ++++ experiments/conoir-spike/bound_nm/Prover.toml | 7 ++++ experiments/conoir-spike/bound_nm/src/main.nr | 37 +++++++++++++++++++ 4 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 experiments/conoir-spike/bound_nm/Nargo.toml create mode 100644 experiments/conoir-spike/bound_nm/Prover.toml create mode 100644 experiments/conoir-spike/bound_nm/src/main.nr diff --git a/experiments/conoir-spike/README.md b/experiments/conoir-spike/README.md index 362fb81..c9bf3bf 100644 --- a/experiments/conoir-spike/README.md +++ b/experiments/conoir-spike/README.md @@ -59,9 +59,33 @@ generates the witness, compiles, counts gates, and runs the timed 3-party MPC pi 29ms regardless of scale.** Full-scale double-dip ≈ 2.7 min to prove, instant to verify — fine for a batch/async fraud check. Caveat: all 3 MPC nodes co-located, so NO network latency in these numbers. -## Next step -The apertrue <-> coNoir **composition / binding**: ensure the nullifier the coNoir circuit checks is -provably the apertrue-C2PA-authenticated one (the "authenticated" in authenticated-MPC). Leading -approach: enforce authenticity at registry-ADD time (off-MPC single-party apertrue proof check), -so the MPC non-membership only checks an already-authentic registry. Also open: real networked -3-machine latency, and wiring the literal `colofon_imt` lib. +## Binding / authenticated-MPC seam (2026-06-23) +`bound_nm` demonstrates the apertrue <-> coNoir binding. A public `commitment = Commit(nullifier, +blind)` is the authenticity anchor (in the real system apertrue's C2PA proof attests it off-MPC; +it is hiding so it leaks nothing). The party secret-shares `(nullifier, blind)`; a lightweight +in-MPC opening check binds them to the commitment; non-membership runs on the deterministic +secret-shared nullifier; only the collision bit is revealed. No recursive proof verification in MPC. + +Ran under 3-party MPC: +- no-collision -> bit 0 (verified) +- collision (set contains the nullifier) -> bit 1 (verified) +- binding failure (substitute a nullifier that does NOT open the commitment) -> rejected, + "Assertion failed: commitment opening failed" + +The binding-failure case is the security property: a party cannot substitute an arbitrary nullifier; +it must open the authenticated commitment. + +### Threat-model notes (from design review) +- MPC protects the registry AT REST (1 REP3 node can't read shares). The real attack is the + membership ORACLE (guess a low-entropy ID, query, read the bit) -> require a C2PA-authenticated + query too (can only test items you authentically hold). +- coNoir is **semi-honest** only (`mpc-core/src/lib.rs`): secure vs passive nodes, NOT vs actively + deviating ones. For competing institutions, either run nodes under reputable/independent/audited + operators (governance) or wait for malicious-secure REP3 (not yet implemented). +- Privacy rests on: honest-but-curious operators + no 2-of-3 collusion. + +## Remaining build items +- Networked 3-machine latency (needs real infra; all numbers here are co-located, no WAN latency). +- Wire the literal `colofon_imt` lib (faithfulness; underlying ops + perf already proven). +- When a lighthouse is committed: graft the blinded-commitment output into production apertrue + proof_a/proof_b (NOT done here -- a breaking change to the live proof format, premature pre-lighthouse). diff --git a/experiments/conoir-spike/bound_nm/Nargo.toml b/experiments/conoir-spike/bound_nm/Nargo.toml new file mode 100644 index 0000000..fe48de0 --- /dev/null +++ b/experiments/conoir-spike/bound_nm/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bound_nm" +type = "bin" +compiler_version = ">=1.0.0" +authors = ["Apertrue"] + +[dependencies] diff --git a/experiments/conoir-spike/bound_nm/Prover.toml b/experiments/conoir-spike/bound_nm/Prover.toml new file mode 100644 index 0000000..9abcf50 --- /dev/null +++ b/experiments/conoir-spike/bound_nm/Prover.toml @@ -0,0 +1,7 @@ +# Bound double-dip, no-collision case. nullifier 42 opens the commitment. +# Collision: add "42" to existing. Binding-failure: change nullifier to a value +# that does not open the commitment -> "commitment opening failed". +nullifier = "42" +blind = "12345" +commitment = "0x03f0661cae8bd60726876aa42536ef1d790c866dc3312872471385c22d1d44ff" +existing = ["10", "11", "12", "13"] diff --git a/experiments/conoir-spike/bound_nm/src/main.nr b/experiments/conoir-spike/bound_nm/src/main.nr new file mode 100644 index 0000000..5f18ad5 --- /dev/null +++ b/experiments/conoir-spike/bound_nm/src/main.nr @@ -0,0 +1,37 @@ +// Bound double-dip check (coNoir spike) -- the apertrue <-> coNoir seam. +// +// Demonstrates the binding that makes this "authenticated MPC": +// - commitment = Commit(nullifier, blind) is a PUBLIC authenticity anchor. +// In the real system apertrue's C2PA proof attests this commitment off-MPC +// (verifying it reveals only the commitment, which is hiding -> leaks nothing). +// - The party secret-shares (nullifier, blind) into the MPC. +// - A lightweight in-MPC OPENING check binds the secret-shared values to the +// public commitment -- so the nullifier being checked is provably the +// authenticated one, without any node ever seeing it. +// - Non-membership (double-dip) runs on the deterministic secret-shared +// nullifier; only the collision bit is revealed. +// +// No recursive proof verification in MPC -- just a Poseidon2 opening check. + +global SET_SIZE: u32 = 4; + +fn commit(value: Field, blind: Field) -> Field { + std::hash::poseidon2_permutation::<4>([value, blind, 0, 0])[0] +} + +fn main( + nullifier: Field, // private, secret-shared + blind: Field, // private, secret-shared + commitment: pub Field, // public authenticity anchor (hiding) + existing: [Field; SET_SIZE], // private, secret-shared registry +) -> pub bool { + // BINDING: the secret-shared (nullifier, blind) must open the public anchor. + assert(commit(nullifier, blind) == commitment, "commitment opening failed"); + + // DOUBLE-DIP: is this authenticated nullifier already present? + let mut collision: bool = false; + for i in 0..SET_SIZE { + collision = collision | (existing[i] == nullifier); + } + collision +} From 49d654cf81b693cf29bb3b63d31ead5fdb696a7f Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:35:59 +0100 Subject: [PATCH 5/9] experiment(conoir): literal colofon_imt lib runs under coNoir MPC imt_real calls the REAL colofon_imt lib (check_non_membership + CveLeafPreimage), compiled under nargo beta.20 and run under 3-party coNoir MPC: non_existence proven, verified, ~3.5s at depth-8. Confirms faithfulness, not just a reimplementation. Two migrations needed against beta.20: root.nr u1 -> bool, and bump the poseidon dep v0.1.1 -> v0.3.0 (v0.1.1 fails: comptime RATE + hash arity). README documents these. Networked-latency item left open (needs real infra). Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/README.md | 16 ++++++++++++-- experiments/conoir-spike/imt_real/Nargo.toml | 8 +++++++ experiments/conoir-spike/imt_real/Prover.toml | 7 ++++++ experiments/conoir-spike/imt_real/src/main.nr | 22 +++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 experiments/conoir-spike/imt_real/Nargo.toml create mode 100644 experiments/conoir-spike/imt_real/Prover.toml create mode 100644 experiments/conoir-spike/imt_real/src/main.nr diff --git a/experiments/conoir-spike/README.md b/experiments/conoir-spike/README.md index c9bf3bf..5dd35fe 100644 --- a/experiments/conoir-spike/README.md +++ b/experiments/conoir-spike/README.md @@ -84,8 +84,20 @@ it must open the authenticated commitment. operators (governance) or wait for malicious-secure REP3 (not yet implemented). - Privacy rests on: honest-but-curious operators + no 2-of-3 collusion. +## Literal colofon_imt port (2026-06-23) +`imt_real` calls the REAL `colofon_imt` lib (`check_non_membership` + `CveLeafPreimage`), not a +reimplementation. It compiles under nargo beta.20 and runs under 3-party coNoir MPC: `non_existence` +proven true, verified, ~3.5s at depth-8. Confirms faithfulness end to end. + +The port needs exactly two migrations to the lib (against beta.20): +1. `root.nr`: `[u1; N]` -> `[bool; N]` (and `if indices[i] == 1` -> `if indices[i]`) -- `u1` removed. +2. `Nargo.toml`: bump the `poseidon` dep `v0.1.1` -> `v0.3.0` (v0.1.1 fails under beta.20: + "Comptime global RATE used in non-comptime code" + a Poseidon2::hash arity error). +`imt_real` here depends on a beta.20-migrated copy of `colofon_imt` (lib copy not committed; the two +migrations above are the whole delta). + ## Remaining build items -- Networked 3-machine latency (needs real infra; all numbers here are co-located, no WAN latency). -- Wire the literal `colofon_imt` lib (faithfulness; underlying ops + perf already proven). +- Networked 3-machine latency: NOT done -- needs real infra (3 machines / WAN). All numbers here are + co-located, so no network-round latency is reflected. This is an infra task, not a code task. - When a lighthouse is committed: graft the blinded-commitment output into production apertrue proof_a/proof_b (NOT done here -- a breaking change to the live proof format, premature pre-lighthouse). diff --git a/experiments/conoir-spike/imt_real/Nargo.toml b/experiments/conoir-spike/imt_real/Nargo.toml new file mode 100644 index 0000000..3df94fb --- /dev/null +++ b/experiments/conoir-spike/imt_real/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "imt_real" +type = "bin" +compiler_version = ">=1.0.0" +authors = ["Apertrue"] + +[dependencies] +colofon_imt = { path = "../colofon_imt_lib" } diff --git a/experiments/conoir-spike/imt_real/Prover.toml b/experiments/conoir-spike/imt_real/Prover.toml new file mode 100644 index 0000000..6bb1da1 --- /dev/null +++ b/experiments/conoir-spike/imt_real/Prover.toml @@ -0,0 +1,7 @@ +key="20" +low_key="10" +next_key="30" +next_index="0" +low_leaf_index="0" +sibling_path=["1","2","3","4","5","6","7","8"] +tree_root="0x28618b6eec45a079d66c8e06817c3274f12a40e18551f92f607fa825787150c0" diff --git a/experiments/conoir-spike/imt_real/src/main.nr b/experiments/conoir-spike/imt_real/src/main.nr new file mode 100644 index 0000000..4d66e4a --- /dev/null +++ b/experiments/conoir-spike/imt_real/src/main.nr @@ -0,0 +1,22 @@ +// Faithful port test: the REAL colofon_imt lib (check_non_membership + +// CveLeafPreimage) compiled under nargo beta.20 and run under coNoir MPC. + +use colofon_imt::cve_leaf_preimage::CveLeafPreimage; +use colofon_imt::membership::{check_non_membership, MembershipWitness}; + +global TREE_HEIGHT: u32 = 8; + +fn main( + key: Field, // non-membership target + low_key: Field, // low leaf key + next_key: Field, // low leaf next key + next_index: Field, // low leaf next index + low_leaf_index: Field, // low leaf position + sibling_path: [Field; TREE_HEIGHT], // membership opening for the low leaf + tree_root: pub Field, // public IMT root +) -> pub bool { + let low_leaf = CveLeafPreimage::new(low_key, next_key, next_index); + let witness = MembershipWitness { leaf_index: low_leaf_index, sibling_path }; + let (non_existence, _valid, _exists) = check_non_membership(key, low_leaf, witness, tree_root); + non_existence +} From 896f49b92a67815117d75f0e8df3ac50bb65a971 Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:20:15 +0100 Subject: [PATCH 6/9] experiment(conoir): add Option-A binding wrapper + lock architecture binding_wrapper is the client-side, off-MPC step: recursively verifies an apertrue proof (real bb_proof_verification lib, beta.18) and emits C = Commit(public_values[2], blind) -- a commitment to the VERIFIED nullifier, not a free witness. Gate count 773,025: smaller than image_aggregator (~1.5M, 2x verify) which apertrue already proves client-side in production, and within initSRSChonk(2^21) capacity. So the recursion overhead is in-budget, not a new cost category. README records the locked architecture: containerised downstream sidecar, composition (Option A) not duplication (B) nor production mutation (C now). N stays unblinded; proof_a/proof_b untouched. Remaining risk is product (lighthouse + design partner + node governance), not engineering. Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/README.md | 32 ++++++++++++++++--- .../conoir-spike/binding_wrapper/.gitignore | 1 + .../conoir-spike/binding_wrapper/Nargo.toml | 10 ++++++ .../conoir-spike/binding_wrapper/src/main.nr | 30 +++++++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 experiments/conoir-spike/binding_wrapper/.gitignore create mode 100644 experiments/conoir-spike/binding_wrapper/Nargo.toml create mode 100644 experiments/conoir-spike/binding_wrapper/src/main.nr diff --git a/experiments/conoir-spike/README.md b/experiments/conoir-spike/README.md index 5dd35fe..6033abf 100644 --- a/experiments/conoir-spike/README.md +++ b/experiments/conoir-spike/README.md @@ -96,8 +96,30 @@ The port needs exactly two migrations to the lib (against beta.20): `imt_real` here depends on a beta.20-migrated copy of `colofon_imt` (lib copy not committed; the two migrations above are the whole delta). -## Remaining build items -- Networked 3-machine latency: NOT done -- needs real infra (3 machines / WAN). All numbers here are - co-located, so no network-round latency is reflected. This is an infra task, not a code task. -- When a lighthouse is committed: graft the blinded-commitment output into production apertrue - proof_a/proof_b (NOT done here -- a breaking change to the live proof format, premature pre-lighthouse). +## Binding wrapper -- Option A (2026-06-23) +`binding_wrapper` is the CLIENT-SIDE, off-MPC step that produces the commitment without touching +production. It recursively verifies an apertrue proof (one branch of the aggregator, real +`bb_proof_verification` lib, beta.18 toolchain) and emits `C = Poseidon2(public_values[2], blind)` -- +a commitment to the VERIFIED nullifier (index 2), not a free witness. + +Gate count: **773,025** (one recursive verify + commit). For context, apertrue's `image_aggregator` +does 2x verify (~1.5M gates) client-side in PRODUCTION today, and proving capacity is +`initSRSChonk(2^21)` ~2M. So the wrapper is smaller than what apertrue already proves client-side -- +recursion overhead is in-budget, not a new cost category. Compilation + gate count confirmed; full +end-to-end prove not run (needs a real apertrue inner proof; apertrue already runs equivalent +recursion, so it will work). + +### Locked architecture (CTO decision) +coNoir double-dip is a **containerised downstream sidecar** that consumes apertrue's existing proof + +deterministic nullifier N. It derives `C` via `binding_wrapper` (composition, NOT duplicating the +C2PA circuit), secret-shares N into the MPC, and runs the bound non-membership. N is never blinded +(load-bearing for dedup/SD/verification/on-chain). Production proof_a/proof_b are untouched. +Rejected: duplicating proof_a (Option B, anti-pattern). Deferred: folding C into proof_a (Option C, +post-lighthouse only, behind a flag -- it is a breaking format change rippling to proof_b/aggregators/ +all VKs/on-chain verifier/backend parser/client). + +## Remaining (non-code / deferred) +- Networked 3-machine latency: needs real infra (3 machines / WAN). All numbers here are co-located. +- Production graft of C into proof_a: deferred to post-lighthouse (Option C above). +- The gating risk is now PRODUCT, not engineering: lighthouse use case + first design partner + + node-operator governance (which makes coNoir's semi-honest model acceptable). diff --git a/experiments/conoir-spike/binding_wrapper/.gitignore b/experiments/conoir-spike/binding_wrapper/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/experiments/conoir-spike/binding_wrapper/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/experiments/conoir-spike/binding_wrapper/Nargo.toml b/experiments/conoir-spike/binding_wrapper/Nargo.toml new file mode 100644 index 0000000..b7dc80b --- /dev/null +++ b/experiments/conoir-spike/binding_wrapper/Nargo.toml @@ -0,0 +1,10 @@ +[package] +name = "binding_wrapper" +type = "bin" +authors = ["Apertrue"] +compiler_version = ">=1.0.0" +description = "Option A: recursively verify an apertrue proof, emit C = Commit(nullifier, blind). Client-side, off-MPC. Production proof_a/proof_b untouched." + +[dependencies] +bb_proof_verification = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.0.0-devnet.2-patch.3", directory = "barretenberg/noir/bb_proof_verification" } +poseidon = { tag = "v0.1.1", git = "https://github.com/noir-lang/poseidon" } diff --git a/experiments/conoir-spike/binding_wrapper/src/main.nr b/experiments/conoir-spike/binding_wrapper/src/main.nr new file mode 100644 index 0000000..da2e36c --- /dev/null +++ b/experiments/conoir-spike/binding_wrapper/src/main.nr @@ -0,0 +1,30 @@ +// Option A -- the coNoir binding wrapper (CLIENT-SIDE, off-MPC). +// +// Recursively verifies an apertrue proof (mirroring one branch of the +// aggregators), then emits a blinded commitment to the VERIFIED nullifier. +// Production proof_a/proof_b are untouched; this layers on top of their output. +// +// - verify_honk_proof binds `public_values` to the authentic apertrue proof. +// - the nullifier is public_values[2] (a verified output, NOT a free witness). +// - output C = Poseidon2(nullifier, blind) is the public authenticity anchor +// that the MPC opening check later binds the secret-shared nullifier to. + +use dep::bb_proof_verification::{UltraHonkZKProof, UltraHonkVerificationKey, verify_honk_proof}; +use poseidon::poseidon2::Poseidon2; + +fn main( + proof: UltraHonkZKProof, // private: the apertrue proof (stand-in) + vk: UltraHonkVerificationKey, // private + public_values: [Field; 12], // private: inner proof's public outputs + vk_hash: Field, // private + blind: Field, // private: fresh random blind +) -> pub Field { // public: C = Commit(nullifier, blind) + // 1. Recursively verify the apertrue proof; binds public_values to it. + verify_honk_proof(vk, proof, public_values, vk_hash); + + // 2. The nullifier is the VERIFIED public output at index 2. + let nullifier = public_values[2]; + + // 3. Emit the hiding commitment to the authentic nullifier. + Poseidon2::hash([nullifier, blind], 2) +} From adfe46725b9024898f9986a77db58aba52d9b947 Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:55:27 +0100 Subject: [PATCH 7/9] docs(conoir): add vertical-agnostic product one-pager Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/ONE-PAGER.md | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 experiments/conoir-spike/ONE-PAGER.md diff --git a/experiments/conoir-spike/ONE-PAGER.md b/experiments/conoir-spike/ONE-PAGER.md new file mode 100644 index 0000000..d316ec1 --- /dev/null +++ b/experiments/conoir-spike/ONE-PAGER.md @@ -0,0 +1,34 @@ +# Apertrue — Authenticated Private Computation +### *Let distrusting organizations check each other's data for fraud — without sharing it, and without anyone being able to lie about it.* + +> Working title / positioning. Vertical-agnostic. Claims are factual to the built prototype. + +## The problem +Organizations constantly need to check data **against each other** to catch duplication and fraud — the same invoice financed by two lenders, the same claim paid by two insurers, the same asset pledged twice. But they **can't share the underlying data**: it's competitively sensitive, contractually restricted, and often regulated personal data. + +So today they face a false choice: **don't check** (and eat the fraud), or **pool the data** (legally and commercially impossible). And even the privacy-preserving approaches that exist have a deeper flaw — they can prove you *computed correctly*, but not that your *inputs were real*. + +## The breakthrough: authenticity + privacy, together +Apertrue combines two things that have never been combined: + +- **Cryptographic privacy** — multi-party computation lets organizations jointly compute over secret-shared data. No party ever sees another's inputs. +- **Cryptographic authenticity** — apertrue's content-provenance layer (built on the C2PA standard + zero-knowledge proofs) guarantees each input is **genuine, unaltered, and hardware-attested** before it enters the computation. + +The result is **verifiable private computation over provably-authentic data**: no one has to reveal their inputs, *and* no one can fabricate them. That combination — solving MPC's "garbage-in" problem with hardware-grade authenticity — is the defensible core. The ingredients are public; **the working combination is not.** + +## What it does, plainly +Multiple organizations can answer *"has this item already been used by anyone in our network?"* — and get a trustworthy **yes/no** — while revealing **nothing**: not their data, not their volumes, not even the item being checked. Fabricated entries are rejected by the authenticity layer; the privacy is cryptographic, not "trust us." + +## Proof — this is built, not a whitepaper +We have a working, benchmarked end-to-end prototype: +- **Runs under real 3-party secure computation** — proofs generate and verify. +- **Full-registry scale**: proving is a **batch operation measured in minutes**; verification is **instant (milliseconds)** and constant regardless of scale. +- **The authenticity binding holds**: a party that tries to substitute a fabricated input is **provably rejected**. +- Built on **production-grade ZK** — the same recursive-proof machinery apertrue already runs client-side. + +## Why it's defensible +- **Network effect** — the registry becomes more valuable as participants join, and the cryptography makes it the *only* way they can participate without exposing data. The network *is* the moat. +- **Standards position** — apertrue's authenticity layer sits on the open C2PA / CAWG standards apertrue is helping shape, giving a credibility and distribution advantage. + +## Where we are / the ask +Working prototype, feasibility and performance validated, architecture locked. **Seeking a first design partner** to run a pilot over real (privately-held) data — and **non-dilutive funding** to harden the system for production. From f92dd6de202a26aca5e27ebd51095516472271b5 Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:53:42 +0100 Subject: [PATCH 8/9] feat(quorum): in-circuit Proof A + FatturaPA adapter + architecture design Captures the Quorum authenticated-private-computation spike, with this session's additions: - ARCHITECTURE.md: envelope-agnostic core + pluggable input adapters, two-signer model, open decisions, honest status + build sequence. - proof_a_receivable: in-circuit "Proof A" -- verifies the obligor/issuer ECDSA-P256 signature over canonical_id and the role-bound trust-list leaf, emits the role-stamped anchor. Proven end-to-end with negative tests (bad sig, unauthorised role); run_proof_a.sh. - adapters/: formal adapter interface (ADAPTER.md) + FatturaPA reference adapter (fattura_adapter.py) proving the full pipeline on the real invoice (real FatturaPA -> canonical_id -> Proof A -> bound_receivables); run_fattura_pipeline.sh. - _mkroot: Poseidon2 Merkle-root helper. Also commits the prior untracked spike: commit/bound receivable + aggregate engines and the p1_provenance FatturaPA->C2PA demo. Stand-in obligor key and in-circuit X.509-chain verification are documented follow-ups (real signer = partner crux). Private keys and build artifacts are gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- experiments/conoir-spike/quorum/.gitignore | 9 + .../conoir-spike/quorum/ARCHITECTURE.md | 216 ++++++++++++++++++ experiments/conoir-spike/quorum/README.md | 73 ++++++ .../conoir-spike/quorum/_mkroot/Nargo.toml | 8 + .../conoir-spike/quorum/_mkroot/Prover.toml | 5 + .../conoir-spike/quorum/_mkroot/src/main.nr | 34 +++ .../conoir-spike/quorum/adapters/ADAPTER.md | 108 +++++++++ .../quorum/adapters/fattura_adapter.py | 195 ++++++++++++++++ .../adapters/fattura_adapter_object.json | 198 ++++++++++++++++ .../quorum/adapters/run_fattura_pipeline.sh | 125 ++++++++++ .../conoir-spike/quorum/aggregate-README.md | 41 ++++ .../quorum/bound_aggregate/Nargo.toml | 9 + .../quorum/bound_aggregate/Prover.toml | 17 ++ .../quorum/bound_aggregate/src/main.nr | 56 +++++ .../quorum/bound_receivables/Nargo.toml | 9 + .../quorum/bound_receivables/Prover.toml | 9 + .../quorum/bound_receivables/Prover_sdi.toml | 14 ++ .../quorum/bound_receivables/src/main.nr | 69 ++++++ .../quorum/commit_aggregate/Nargo.toml | 9 + .../quorum/commit_aggregate/Prover.toml | 7 + .../quorum/commit_aggregate/src/main.nr | 24 ++ .../quorum/commit_receivable/Nargo.toml | 9 + .../quorum/commit_receivable/Prover.toml | 6 + .../quorum/commit_receivable/Prover_a.toml | 6 + .../quorum/commit_receivable/Prover_sdi.toml | 5 + .../quorum/commit_receivable/src/main.nr | 37 +++ .../p1_provenance/IT01234567890_FPR01.xml | 110 +++++++++ .../quorum/p1_provenance/README.md | 87 +++++++ .../p1_provenance/admission/admission.sh | 88 +++++++ .../p1_provenance/admission/certs/_chk.cid | 1 + .../admission/certs/obligor_leaf.csr | 8 + .../admission/certs/obligor_leaf.ext | 2 + .../admission/certs/obligor_leaf.pem | 12 + .../admission/certs/obligor_leaf.pub | 4 + .../admission/certs/obligor_root.pem | 12 + .../admission/certs/obligor_root.srl | 1 + .../admission/certs/obligor_true.cid | 1 + .../admission/certs/obligor_true.sig | Bin 0 -> 70 bytes .../admission/certs/sdi_infl.cid | 1 + .../admission/certs/sdi_infl.sig | Bin 0 -> 70 bytes .../admission/certs/sdi_leaf.csr | 8 + .../admission/certs/sdi_leaf.ext | 2 + .../admission/certs/sdi_leaf.pem | 12 + .../admission/certs/sdi_leaf.pub | 4 + .../admission/certs/sdi_root.pem | 12 + .../admission/certs/sdi_root.srl | 1 + .../admission/certs/sdi_true.cid | 1 + .../admission/certs/sdi_true.sig | Bin 0 -> 71 bytes .../admission/certs/seller_leaf.csr | 8 + .../admission/certs/seller_leaf.ext | 2 + .../admission/certs/seller_leaf.pem | 12 + .../admission/certs/seller_leaf.pub | 4 + .../admission/certs/seller_root.pem | 12 + .../admission/certs/seller_root.srl | 1 + .../admission/certs/seller_true.cid | 1 + .../admission/certs/seller_true.sig | 1 + .../admission/certs/trust_store.pem | 24 ++ .../quorum/p1_provenance/c2pie_schema.json | 7 + .../quorum/p1_provenance/invoice.pdf | Bin 0 -> 18357 bytes .../quorum/p1_provenance/invoice_c2pa.pdf | Bin 0 -> 22343 bytes .../quorum/p1_provenance/invoice_quorum.pdf | Bin 0 -> 33975 bytes .../quorum/p1_provenance/invoice_with_xml.pdf | Bin 0 -> 22904 bytes .../manifest_receivable.c2patool.json | 23 ++ .../quorum/p1_provenance/p1_next.py | 112 +++++++++ .../quorum/proof_a_receivable/Nargo.toml | 9 + .../quorum/proof_a_receivable/Prover.toml | 13 ++ .../quorum/proof_a_receivable/run_proof_a.sh | 175 ++++++++++++++ .../quorum/proof_a_receivable/src/main.nr | 98 ++++++++ .../quorum/run_bound_aggregate.sh | 29 +++ .../quorum/run_bound_receivables.sh | 48 ++++ .../quorum/run_quorum_scenarios.sh | 39 ++++ 71 files changed, 2283 insertions(+) create mode 100644 experiments/conoir-spike/quorum/.gitignore create mode 100644 experiments/conoir-spike/quorum/ARCHITECTURE.md create mode 100644 experiments/conoir-spike/quorum/README.md create mode 100644 experiments/conoir-spike/quorum/_mkroot/Nargo.toml create mode 100644 experiments/conoir-spike/quorum/_mkroot/Prover.toml create mode 100644 experiments/conoir-spike/quorum/_mkroot/src/main.nr create mode 100644 experiments/conoir-spike/quorum/adapters/ADAPTER.md create mode 100755 experiments/conoir-spike/quorum/adapters/fattura_adapter.py create mode 100644 experiments/conoir-spike/quorum/adapters/fattura_adapter_object.json create mode 100755 experiments/conoir-spike/quorum/adapters/run_fattura_pipeline.sh create mode 100644 experiments/conoir-spike/quorum/aggregate-README.md create mode 100644 experiments/conoir-spike/quorum/bound_aggregate/Nargo.toml create mode 100644 experiments/conoir-spike/quorum/bound_aggregate/Prover.toml create mode 100644 experiments/conoir-spike/quorum/bound_aggregate/src/main.nr create mode 100644 experiments/conoir-spike/quorum/bound_receivables/Nargo.toml create mode 100644 experiments/conoir-spike/quorum/bound_receivables/Prover.toml create mode 100644 experiments/conoir-spike/quorum/bound_receivables/Prover_sdi.toml create mode 100644 experiments/conoir-spike/quorum/bound_receivables/src/main.nr create mode 100644 experiments/conoir-spike/quorum/commit_aggregate/Nargo.toml create mode 100644 experiments/conoir-spike/quorum/commit_aggregate/Prover.toml create mode 100644 experiments/conoir-spike/quorum/commit_aggregate/src/main.nr create mode 100644 experiments/conoir-spike/quorum/commit_receivable/Nargo.toml create mode 100644 experiments/conoir-spike/quorum/commit_receivable/Prover.toml create mode 100644 experiments/conoir-spike/quorum/commit_receivable/Prover_a.toml create mode 100644 experiments/conoir-spike/quorum/commit_receivable/Prover_sdi.toml create mode 100644 experiments/conoir-spike/quorum/commit_receivable/src/main.nr create mode 100644 experiments/conoir-spike/quorum/p1_provenance/IT01234567890_FPR01.xml create mode 100644 experiments/conoir-spike/quorum/p1_provenance/README.md create mode 100755 experiments/conoir-spike/quorum/p1_provenance/admission/admission.sh create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/_chk.cid create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.csr create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.ext create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pem create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pub create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.pem create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.srl create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_true.cid create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_true.sig create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_infl.cid create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_infl.sig create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.csr create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.ext create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pem create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pub create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.pem create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.srl create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_true.cid create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_true.sig create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_leaf.csr create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_leaf.ext create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_leaf.pem create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_leaf.pub create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_root.pem create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_root.srl create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_true.cid create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/seller_true.sig create mode 100644 experiments/conoir-spike/quorum/p1_provenance/admission/certs/trust_store.pem create mode 100644 experiments/conoir-spike/quorum/p1_provenance/c2pie_schema.json create mode 100644 experiments/conoir-spike/quorum/p1_provenance/invoice.pdf create mode 100644 experiments/conoir-spike/quorum/p1_provenance/invoice_c2pa.pdf create mode 100644 experiments/conoir-spike/quorum/p1_provenance/invoice_quorum.pdf create mode 100644 experiments/conoir-spike/quorum/p1_provenance/invoice_with_xml.pdf create mode 100644 experiments/conoir-spike/quorum/p1_provenance/manifest_receivable.c2patool.json create mode 100644 experiments/conoir-spike/quorum/p1_provenance/p1_next.py create mode 100644 experiments/conoir-spike/quorum/proof_a_receivable/Nargo.toml create mode 100644 experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml create mode 100755 experiments/conoir-spike/quorum/proof_a_receivable/run_proof_a.sh create mode 100644 experiments/conoir-spike/quorum/proof_a_receivable/src/main.nr create mode 100755 experiments/conoir-spike/quorum/run_bound_aggregate.sh create mode 100755 experiments/conoir-spike/quorum/run_bound_receivables.sh create mode 100755 experiments/conoir-spike/quorum/run_quorum_scenarios.sh diff --git a/experiments/conoir-spike/quorum/.gitignore b/experiments/conoir-spike/quorum/.gitignore new file mode 100644 index 0000000..c69cf43 --- /dev/null +++ b/experiments/conoir-spike/quorum/.gitignore @@ -0,0 +1,9 @@ +# Noir build artifacts +**/target/ + +# Python caches +**/__pycache__/ + +# Stand-in keys + regenerable crypto material — never commit private keys. +# (regenerated by adapters/fattura_adapter.py / run_fattura_pipeline.sh) +adapters/keys/ diff --git a/experiments/conoir-spike/quorum/ARCHITECTURE.md b/experiments/conoir-spike/quorum/ARCHITECTURE.md new file mode 100644 index 0000000..341e63e --- /dev/null +++ b/experiments/conoir-spike/quorum/ARCHITECTURE.md @@ -0,0 +1,216 @@ +# Apertrue Quorum — Architecture Design + +Consolidated from the authenticity analysis (C2PA vs PAdES/e-invoice/clearance, +canonicalisation, the two-signer model). This is the target production +architecture; it is deliberately **envelope-agnostic** with **pluggable input +adapters**, so we never fork the system per industry. + +--- + +## 0. The one-line shape + +``` +source document → [INPUT ADAPTER] → {canonical fields, signatures, certs} + → [CANONICALISATION] → canonical_id, anchor + → [PROOF A, per party, in-circuit] → role-stamped anchor (+ proof) + → [ENGINE, joint via coNoir] → one bit (+ portable proof) +``` + +The **core** (canonicalisation → Proof A → engine → coNoir) is shared across +every vertical. Only the **input adapter** changes per document format. + +--- + +## 1. Invariants (do not break these) + +1. **The core is envelope-agnostic.** It consumes `{canonical fields, signatures, + trust anchors}` — never a specific document format. (Confirmed: the circuits + already operate on canonical fields, never on C2PA.) +2. **Two guarantees, married:** authenticity (a non-gameable signature) **and** + private joint computation (coNoir). Neither alone is enough. +3. **The trust is the *signer*, not the envelope.** C2PA/PAdES/e-invoice are + transport; the trust comes from *who signed* the canonical fields. +4. **Per-vertical authenticity:** ride native rails where they exist + (structured), use C2PA where they don't (unstructured/media). Never force a + format onto producers. +5. **No operator ever holds the union.** Each party keeps its own book; only + roots/commitments are public; only one bit leaves. + +--- + +## 2. The core (shared) — mostly built + +- `canonical_id = Poseidon2([canonical fields])` +- `anchor = Poseidon2([canonical_id, salt, signer_role])` +- **Three engines**, pick per use case: + - **Membership** (duplication / double-X) — `bound_receivables`, IMT non-membership. + - **Aggregate** (sum vs a signed limit) — `bound_aggregate`. + - **Relational** (distance / time / order) — `bound_geotime`. +- **Status:** built and proven under real 3-party REP3 MPC. + +--- + +## 3. Proof A — the authenticity binding **(PRIORITY BUILD — the soundness fix)** + +**Today (stand-in):** the obligor signature is verified *off-circuit* (openssl in +admission); the circuit only consumes the role-stamped `anchor` and checks +`signer_role ∈ accepted_roles`. So the trustless proof does **not** itself attest +that a real obligor signed — a fabricated anchor claiming `role = obligor` would +pass the MPC. + +**Target (sound):** Proof A runs **in-circuit**, per party, and: +1. verifies the **obligor's** ES256 signature **over `canonical_id`**, +2. verifies the **issuer/clearance** signature, +3. checks **both certs ∈ trust-list Merkle root**, +4. binds to the **role-stamped anchor**. + +So the one-bit answer provably rests on a genuine, obligor-signed document. + +**Reuse:** apertrue's `proof_a_ecdsa_p256` already does in-circuit ES256 +verification + Merkle membership + nullifier binding. This is integration, not +new crypto. + +**Two-signer model:** +- **issuer / clearance** signs → the invoice is *genuine* (catches fabricated documents). +- **obligor** signs → the debt is *owed* (catches fabricated debts; the non-gameable signer). +- Roles are bound into the anchor. + +--- + +## 4. Input adapters (off-circuit, per vertical) — build at pilot + +**Adapter contract (the interface every adapter satisfies):** + +``` +adapter(source_document) -> { + canonical_fields, // the fields the canonicaliser hashes + issuer: { signature, cert_chain }, // signer #1 (genuine) + obligor: { signature, cert_chain }, // signer #2 (owed) — over canonical_id +} +``` + +Because every adapter emits this same shape, **Proof A and the engines never +change per vertical.** + +- **Native-rail adapter** (structured: FatturaPA / UBL / CII / EDI): parse the + e-invoice → canonical fields; ride the **clearance / AdES (XAdES/PAdES)** + signature as *issuer*; source the **obligor** confirmation from a buyer + acceptance / **approved-payables** confirmation. Lean on EN 16931 field + definitions + existing parsers (e.g. Mustangproject) — map a few syntaxes to + the canonical schema, not one per country. +- **C2PA adapter** (unstructured docs + media): read the signed **custom + assertion** carrying canonical fields + obligor signature; verify the COSE + manifest signature; bind via `c2pa.hash.data`. This is where C2PA is + load-bearing (no native rail to ride). + +**Do not pre-build adapters.** Build the one the first pilot's documents need. + +--- + +## 5. Canonicalisation — the make-or-break + +- A **deterministic spec**: exactly how each field normalises + (VAT, `amount → cents`, `date → YYYYMMDD`, currency, ...) → `canonical_id`. + Two parties must derive byte-identical ids from the same source. +- **Key move:** the **non-gameable signer emits *and* signs the canonical form** + (`canonical_id`). Then verifying parties don't re-canonicalise — they verify the + signature over the signed `canonical_id`. This solves determinism **and** + binding at once. (The run-through already does this: the obligor signs + `canonical_id`.) +- Where the signer only signs the native document, the adapter recomputes + `canonical_id` from it under the shared spec and binds by matching hashes. +- Per-vertical field set; ride EN 16931 fields where available. + +--- + +## 6. The signer — the partner-dependent crux (GTM, not crypto) + +- **issuer/clearance:** a tax platform (SdI), an e-invoice clearance, or a + verified platform. +- **obligor:** the customer who owes — sourced from a **buyer acceptance** or, + strongest and already in production, an **approved-payables / supply-chain- + finance confirmation**, where the anchor buyer already confirms invoices. +- **Today:** a self-made CA stand-in. A real non-gameable signer is the + **critical path** — and likely lives in **approved-payables finance**, where + *both* signers (cleared invoice + buyer confirmation) already exist. + +--- + +## 7. Deployment & trust model + +- coNoir runs on a **committee of independent, non-colluding operators** (REP3 / + proof delegation, e.g. TACEO:Proof). No single operator — and not Apertrue — + ever holds the union; each only sees a secret share. +- **Registry-of-roots:** each party publishes a committed root; the cross-party + check is **non-membership against published roots** (indexed Merkle tree). +- **Freshness:** two financings in the same window before roots refresh can both + read "not financed" — stated as an explicit **settlement-window** bound. +- **Liveness:** the always-on committee stands in for offline parties. + +--- + +## 8. Honest status + +| Component | State | +|---|---| +| Core engines (membership / aggregate / relational) | **built**, MPC-proven (3-party) | +| Canonicalisation (FatturaPA fields) | **real** for the demo | +| C2PA packaging (c2pie) | **real** (media-first tooling; PDF support nascent) | +| **Proof A — in-circuit signature binding** | **BUILT + e2e-proven** (`proof_a_receivable`; `run_proof_a.sh`) | +| **Obligor signature verified in-circuit** | **BUILT + e2e-proven** (ECDSA-P256 over `canonical_id`, role bound in trust-list leaf) | +| Adapter interface + FatturaPA reference adapter | **BUILT + e2e-proven** on the real invoice (`adapters/`); other verticals pilot-driven | +| Real non-gameable signer | **STAND-IN** (self-made CA; partner-dependent) | +| Committee / roots deployment | **DESIGN** (TACEO:Proof available) | + +--- + +## 9. Build sequence + +1. **In-circuit Proof A** — verify obligor + issuer signatures over + `canonical_id`, signer ∈ trust root, role bound in leaf, emit role-stamped + anchor. ✓ **DONE** — `proof_a_receivable` proves end-to-end, negative tests + pass (bad sig, unauthorised role), anchor hands off to `bound_receivables` + (`run_proof_a.sh`). Note: ECDSA needs low-s normalisation for Noir's verifier. +2. **Adapter interface** + the **first pilot's** adapter only (native-rail *or* + C2PA, per the pilot vertical). ✓ **DONE (reference)** — interface formalised + (`adapters/ADAPTER.md`) + FatturaPA adapter (`adapters/fattura_adapter.py`) + proves the full pipeline on the real invoice (`run_fattura_pipeline.sh`). + Other verticals' adapters remain pilot-driven. +3. **Canonicalisation spec** for that vertical, signer-emits-canonical-form. +4. Wire to the chosen **engine** + the **committee/roots** deployment for the pilot. +5. **Do not** pre-build other adapters/engines/verticals. + +> The engine and pattern are proven; the priority is the in-circuit signature +> binding, and the bottleneck remains a real partner + a real non-gameable signer. + +--- + +## 10. Open decisions (resolve before this is "production-correct") + +Load-bearing and deliberately unresolved; some are pilot/scale-dependent. + +1. **Proof A composition — local vs joint, and the recursion.** Signature + verification belongs in a cheap **per-party local Proof A** (single-prover), + recursively bound into the joint engine — **not** inside the joint MPC + (in-MPC P-256 is prohibitive). Decide the binding: recursive in-circuit + verification of each party's Proof A, vs. separately-checked proofs the + relying party also validates. Drives both soundness and cost. + **→ RESOLVED:** per-party *local* Proof A → emits the role-stamped `anchor` + as a public output → fed to `bound_receivables` as `candidate_anchor` (the + `binding_wrapper` "verify, then emit a public anchor" pattern). Signature + verification stays out of the MPC. Recursion-into-one-proof is an optional + later step. Built: `quorum/proof_a_receivable` (compiles, beta.20). +2. **Canonicalisation determinism across independently-received copies.** The + "signer signs `canonical_id`" move requires the *same* signed `canonical_id` + to reach every party who finances the invoice. Specify how it travels with + the document so two lenders derive byte-identical ids. +3. **Decentralised registry-of-roots is research-adjacent.** Private + non-membership against a published root *without the other party live* is + key-transparency / accumulator territory. Near-term = the **committee model**; + the roots model is a research track, not yet production. +4. **Freshness / simultaneity** is a bounded settlement-window limit, not solved. + Choose: near-real-time roots, a mandatory pre-advance live check, or an + openly-stated window. +5. **Toolchain reconciliation.** The coNoir spike uses nargo beta.20; apertrue's + `proof_a` is beta.18. Reconcile versions before reusing `proof_a` in the + Quorum flow. diff --git a/experiments/conoir-spike/quorum/README.md b/experiments/conoir-spike/quorum/README.md new file mode 100644 index 0000000..4b3c64c --- /dev/null +++ b/experiments/conoir-spike/quorum/README.md @@ -0,0 +1,73 @@ +# Apertrue Quorum — P0/P1: double-financing check, two signer roles, under MPC + +Runnable proof-of-life for the receivables product (`apertrue-quorum-plan.md`). Answers the +First Brands question — *has this exact receivable already been financed anywhere in the +network?* — under 3-party REP3 MPC, revealing **only one bit + which guarantee the anchor +carries**. Demonstrates the **two-signer thesis**: the signer is configurable per market, +and *which fraud you catch depends on who signs*. + +## The First Brands correction (why two signers, not one) +First Brands was **not only** double-pledging of real invoices — it was **also** fabricated +and inflated invoices (invented sales; amounts inflated up to ~10×). So: +- **Clearance (SdI) alone** would have caught the double-pledge slice and **missed the + fabrication slice** — a large part of the actual fraud. +- The **obligor (debtor)** signature catches fabrication/inflation, because the debtor will + not sign a lie. +- **Together** they are the honest answer to "what would have caught First Brands." + Clearance is *not* sufficient on its own; don't let any single-signer framing imply it is. + +## Two signer roles → two different guarantees +| Role | Signer | Guarantee the proof carries | Catches | +|---|---|---|---| +| 1 | SdI clearance (Agenzia delle Entrate) | "uniquely identified, cleared, **not** financed elsewhere" — **NOT** proof the debt is real | double-pledging | +| 2 | Obligor / debtor confirmation | "the debt is **genuinely owed**" (stronger) | double-pledging **and** fabrication/inflation | + +The role is **bound into the anchor** (`anchor = Poseidon2([canonical_id, salt, role])`), so +an SdI anchor and an obligor anchor for the same receivable are distinct and cannot be +swapped, and the role is never silently upgraded. + +## Layers +- `commit_receivable/` — Layer 1: `canonical_id = Poseidon2([uuid,debtor,amount,date])`, + role-bound `anchor`. +- `p1_provenance/admission/admission.sh` — Layer 2 **acceptable-anchor allow-list** + (off-MPC). Real openssl cert chains; admits a submission only if the inner signature + verifies **over the canonical_id** (so the signer vouches for exactly debtor∥amount∥id) + **and** the signer chains to a trusted root; the root fixes the role. `p1_provenance/` + also has a real C2PA-signed invoice PDF (via c2pie). +- `bound_receivables/` — Layer 3 (MPC): binds secret-shared fields to the role-bound anchor, + enforces the role is in the allow-list, scans the network's financed fingerprints, reveals + `(already_financed, role)`. + +## Admission decisions (Layer 2, real cert chains) +``` +obligor: true receivable -> ADMITTED role=2 the debt is GENUINELY OWED +SdI: true receivable -> ADMITTED role=1 cleared (NOT proof of debt) +SdI: INFLATED (seller cleared) -> ADMITTED role=1 cleared (NOT proof of debt) <- the gap +obligor: INFLATED (fabrication) -> REJECTED no obligor signature over the lie <- fabrication caught +seller self-signed -> REJECTED signer not in allow-list <- fraudster can't self-vouch +``` + +## MPC results (Layer 3, full 3-party REP3, proof verified) +| # | Scenario | Output | Verified | +|---|----------|--------|----------| +| 1 | obligor, true, not financed | `already_financed=0`, role 2 — **owed** | ✅ | +| 2 | obligor, true, double-financed | `already_financed=1`, role 2 — **owed** | ✅ | +| 3 | SdI, **inflated** (seller cleared), not financed | `already_financed=0`, role 1 — **cleared, NOT owed** (the gap) | ✅ | +| 4 | seller self-signed (role 3) | — | ❌ rejected by MPC allow-list (defense in depth) | + +Where each fraud is caught: **fabrication/inflation → at admission** (no obligor signature +over the lie). **Seller-as-signer → at admission *and* in-MPC allow-list** (defense in +depth). **Double-pledging → in-MPC membership** (bit = 1). The MPC binding alone proves +"these fields open this anchor"; it does **not** prove the signer was acceptable — that is +the admission/allow-list job, which is why both layers exist. + +## Reproduce +``` +cd p1_provenance/admission && ./admission.sh # Layer 2 allow-list decisions +cd ../.. && ./run_quorum_scenarios.sh # Layer 3 MPC, all four scenarios +``` + +## Still stand-in (needs a design partner / deeper C2PA build) +- Clearance + obligor certs are self-made CAs — real SdI / debtor certs need a partner. +- Asset is a PDF rendering (c2pie); raw-XML C2PA needs the data-hash sidecar API. +- Receivable fields carried as CreativeWork; custom assertion = c2pie programmatic API (P1.next). diff --git a/experiments/conoir-spike/quorum/_mkroot/Nargo.toml b/experiments/conoir-spike/quorum/_mkroot/Nargo.toml new file mode 100644 index 0000000..c0e2bac --- /dev/null +++ b/experiments/conoir-spike/quorum/_mkroot/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "_mkroot" +type = "bin" +authors = ["Apertrue"] +compiler_version = ">=1.0.0" + +[dependencies] +poseidon = { tag = "v0.3.0", git = "https://github.com/noir-lang/poseidon" } diff --git a/experiments/conoir-spike/quorum/_mkroot/Prover.toml b/experiments/conoir-spike/quorum/_mkroot/Prover.toml new file mode 100644 index 0000000..e60908e --- /dev/null +++ b/experiments/conoir-spike/quorum/_mkroot/Prover.toml @@ -0,0 +1,5 @@ +signer_pubkey_x = [184, 196, 60, 29, 156, 89, 169, 92, 112, 56, 23, 82, 52, 132, 157, 216, 26, 175, 28, 175, 154, 107, 100, 91, 224, 231, 205, 20, 85, 95, 222, 100] +signer_pubkey_y = [252, 213, 234, 45, 227, 108, 134, 93, 214, 236, 225, 34, 82, 254, 169, 210, 219, 200, 134, 59, 103, 33, 15, 47, 118, 153, 65, 29, 251, 84, 126, 248] +signer_role = "2" +merkle_path = ["0","0","0","0","0","0","0","0"] +merkle_indices = ["0","0","0","0","0","0","0","0"] diff --git a/experiments/conoir-spike/quorum/_mkroot/src/main.nr b/experiments/conoir-spike/quorum/_mkroot/src/main.nr new file mode 100644 index 0000000..e290895 --- /dev/null +++ b/experiments/conoir-spike/quorum/_mkroot/src/main.nr @@ -0,0 +1,34 @@ +use poseidon::poseidon2::Poseidon2; +global MERKLE_DEPTH: u32 = 8; + +fn be_bytes_to_field(b: [u8; 32]) -> Field { + let mut acc: Field = 0; + for i in 0..32 { + acc = acc * 256 + b[i] as Field; + } + acc +} + +fn compute_merkle_root(leaf: Field, path: [Field; MERKLE_DEPTH], indices: [Field; MERKLE_DEPTH]) -> Field { + let mut current = leaf; + for i in 0..MERKLE_DEPTH { + let is_left = indices[i] == 0; + let left = if is_left { current } else { path[i] }; + let right = if is_left { path[i] } else { current }; + current = Poseidon2::hash([left, right], 2); + } + current +} + +fn main( + signer_pubkey_x: [u8; 32], + signer_pubkey_y: [u8; 32], + signer_role: Field, + merkle_path: [Field; MERKLE_DEPTH], + merkle_indices: [Field; MERKLE_DEPTH], +) -> pub Field { + let x_f = be_bytes_to_field(signer_pubkey_x); + let y_f = be_bytes_to_field(signer_pubkey_y); + let leaf = Poseidon2::hash([x_f, y_f, signer_role], 3); + compute_merkle_root(leaf, merkle_path, merkle_indices) +} diff --git a/experiments/conoir-spike/quorum/adapters/ADAPTER.md b/experiments/conoir-spike/quorum/adapters/ADAPTER.md new file mode 100644 index 0000000..e904df5 --- /dev/null +++ b/experiments/conoir-spike/quorum/adapters/ADAPTER.md @@ -0,0 +1,108 @@ +# Quorum Adapter Interface + +A Quorum **adapter** turns a real-world receivable document (an Italian FatturaPA +e-invoice, an Indian IRP invoice, a UBL/PEPPOL bill, …) into one canonical object that +the in-circuit **Proof A** (`proof_a_receivable`) consumes. Proof A then proves, in +zero-knowledge, that an *authorised, role-bound* signer put an ECDSA P-256 signature on +this receivable's `canonical_id`, and emits the role-stamped authenticity **anchor** that +the joint `bound_receivables` MPC circuit binds to. + +The adapter is the only document-format-specific code in the pipeline. Everything +downstream (`proof_a_receivable`, `commit_receivable`, `bound_receivables`) speaks only the +canonical object below, so a new document type = a new adapter, nothing else. + +## 1. The interface contract (the object every adapter MUST emit) + +```jsonc +{ + "canonical_fields": { + "invoice_uuid": , // globally-unique receivable id (issuer-scoped) + "debtor_id": , // who owes the money + "amount": , // minor units (cents) + "issue_date": // YYYYMMDD + }, + "canonical_id": "0x..", // Poseidon2([uuid, debtor, amount, date], 4) + "canonical_id_bytes": [; 32], // big-endian encoding of canonical_id (the SIGNED message) + "obligor": { + "pubkey_x": [; 32], // signer P-256 public key X (big-endian) + "pubkey_y": [; 32], // signer P-256 public key Y (big-endian) + "signature": [; 64], // r||s, LOW-S normalised + "signer_role": 2 // 1 = SdI/clearance, 2 = obligor/debtor confirmation + } +} +``` + +These map 1:1 onto Proof A's witness (`proof_a_receivable/src/main.nr`): +`signer_pubkey_x/y`, `signature`, `canonical_id_bytes`, the four canonical fields, plus +`signer_role` (public). The only inputs the adapter does **not** supply are the +trust-list witness (`merkle_path`, `merkle_indices`, `trust_list_root`) and the privacy +`salt` — those belong to the trust-list operator and the prover, not the document. + +### Field semantics +- `canonical_id = Poseidon2([invoice_uuid, debtor_id, amount, issue_date], 4)` — the value + the obligor vouches for and the value the network's double-financing check keys on. +- `anchor = Poseidon2([canonical_id, salt, signer_role], 3)` — Proof A's **public output**. + Role-bound, so a clearance anchor and an obligor anchor for the same receivable are + distinct values and cannot be swapped. + +## 2. Signing spec (the Quorum standard) + +**Raw ECDSA P-256 over the 32-byte big-endian `canonical_id`, used directly as the +digest. No SHA-256 wrapper.** `canonical_id` is already a Poseidon2 commitment, so a second +hash adds nothing; Proof A calls `std::ecdsa_secp256r1::verify_signature(x, y, sig, +canonical_id_bytes)` with the 32-byte message as the digest. Signatures MUST be +**low-S normalised** (`s = min(s, n-s)`) — Noir's verifier rejects high-S. The 64-byte +`signature` is `r||s`, each zero-left-padded to 32 bytes. + +Practical note: `openssl pkeyutl -sign` (not `openssl dgst -sha256 -sign`) treats its input +bytes as the digest, which is exactly the raw-ECDSA form Proof A expects. The legacy +`admission.sh` produced its `*.sig` with `openssl dgst -sha256`, so those signatures do +**not** verify under this spec — the adapter (re-)signs `canonical_id` raw. + +## 3. How the FatturaPA adapter satisfies the contract + +`fattura_adapter.py ` (driven end-to-end by `run_fattura_pipeline.sh`): + +1. **Canonicalisation** (namespace-agnostic XML walk, identical to `p1_provenance`): + - `invoice_uuid = CedentePrestatore/…/IdFiscaleIVA/IdCodice (01234567890) * 1e6 + DatiGeneraliDocumento/Numero (123)` → `1234567890000123` + - `debtor_id = CessionarioCommittente/…/CodiceFiscale (09876543210)` with leading zeros dropped → `9876543210` + - `amount = DatiRiepilogo/ImponibileImporto (5.00 EUR) * 100` → `500` + - `issue_date = DatiGeneraliDocumento/Data (2014-12-18)` → `20141218` +2. **canonical_id** — computed by running the `commit_receivable` circuit (proof_a's + Poseidon2 scheme) so the adapter and the circuits cannot drift. For this invoice: + `0x06cc60c66ce6389ea53783b55dc5ff1b480e9a43ecf4c942262f5d73d7a87280`, which equals the + committed `admission/certs/obligor_true.cid` — confirming the real document reproduces + the expected fingerprint. +3. **obligor signature** — raw-ECDSA-signs `canonical_id` (see §2), extracts `x`/`y` from + the uncompressed public-key point, normalises the DER signature to low-S `r||s`. +4. Emits the JSON object above (`fattura_adapter_object.json`). + +`run_fattura_pipeline.sh` then builds the depth-8 trust root from the obligor `(key, role)` +leaf via `_mkroot`, runs **Proof A** (in-circuit ECDSA verify + trust-list membership → +anchor), checks the proven anchor equals the `commit_receivable` anchor for the same +`(salt=42, role=2)`, and hands the anchor to `bound_receivables` (clean → `false`, +double-financed → `true`). + +## 4. Honest caveats (follow-ups for a design partner) + +1. **The obligor key is a STAND-IN.** The real obligor private key (an Agenzia delle + Entrate / SdI *ricevuta*, or an Indian IRP IRN authority) is not in the repo — only + `admission/certs/obligor_leaf.pem/.pub` exist, without the private key. The adapter + therefore mints a stand-in P-256 key+cert (`adapters/keys/obligor_standin.key`) and + signs with it. This proves the *mechanism* end-to-end; the non-gameable signer is the + partner crux. The security claim ("the debtor will not sign a lie, so fabrication/ + inflation cannot be admitted as role 2") only holds once the signer is a genuine + authority. +2. **Proof A trusts the signer's LEAF key directly** via the trust list (a Poseidon2 + `(x, y, role)` leaf → root membership). It does **not** verify an X.509 certificate + chain to a CA root in-circuit. Full production parity (cf. apertrue's `proof_a`, which + performs in-circuit chain verification) would verify the obligor leaf cert chains to a + trusted Quorum root inside the circuit, rather than trusting an enrolled leaf key. Noted + follow-up. + +## 5. Files + +- `adapters/fattura_adapter.py` — the FatturaPA adapter (XML → interface object). +- `adapters/run_fattura_pipeline.sh` — full end-to-end runner (idempotent). +- `adapters/fattura_adapter_object.json` — the emitted interface object for this invoice. +- `adapters/keys/obligor_standin.*` — minted stand-in obligor key+cert (gitignore-worthy). diff --git a/experiments/conoir-spike/quorum/adapters/fattura_adapter.py b/experiments/conoir-spike/quorum/adapters/fattura_adapter.py new file mode 100755 index 0000000..5404422 --- /dev/null +++ b/experiments/conoir-spike/quorum/adapters/fattura_adapter.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +""" +fattura_adapter.py -- Apertrue Quorum FatturaPA adapter. + +Turns a REAL Italian e-invoice (FatturaPA XML) into the Quorum +ADAPTER-INTERFACE object that `proof_a_receivable` consumes: + + { canonical_fields: {invoice_uuid, debtor_id, amount, issue_date}, + canonical_id, canonical_id_bytes, + obligor: { pubkey_x:[32], pubkey_y:[32], signature:[64] r||s low-s, signer_role:2 } } + +Canonicalisation (identical to p1_provenance, see README/admission.sh): + invoice_uuid = supplierVAT(CedentePrestatore IdCodice) * 1e6 + Numero + debtor_id = buyer CodiceFiscale (CessionarioCommittente), leading zeros dropped + amount = ImponibileImporto in cents (5.00 EUR -> 500) + issue_date = DatiGeneraliDocumento/Data -> YYYYMMDD integer + +canonical_id is the Poseidon2 commitment of those four fields, computed by the +`commit_receivable` Noir circuit (proof_a's scheme). + +Signing spec (Quorum standard): RAW ECDSA P-256 over the 32-byte big-endian +canonical_id used DIRECTLY as the digest -- NO sha256 wrapper. This matches +`proof_a_receivable`'s in-circuit `verify_signature(...)`. + +Obligor key: the real obligor PRIVATE key is not present (only obligor_leaf.pem/.pub +exist in admission/certs). So this adapter MINTS a stand-in obligor P-256 key+cert +(reused idempotently) and raw-signs canonical_id with it. The output flags this. +The real, non-gameable obligor signer is the partner crux (SdI ricevuta / IRP IRN). +""" +import argparse, binascii, json, os, subprocess, sys, xml.etree.ElementTree as ET + +HERE = os.path.dirname(os.path.abspath(__file__)) +QUORUM = os.path.dirname(HERE) +NARGO = os.environ.get("NARGO", os.path.expanduser("~/.nargo/bin/nargo")) +KEYDIR = os.path.join(HERE, "keys") +N_P256 = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + +def _local(tag): + return tag.split('}', 1)[-1] # strip XML namespace + +def _find(root, *path): + """Walk by LOCAL element names (namespace-agnostic), return first text match.""" + nodes = [root] + for name in path: + nxt = [] + for n in nodes: + for c in n: + if _local(c.tag) == name: + nxt.append(c) + nodes = nxt + if not nodes: + return None + return nodes[0].text.strip() if nodes[0].text else None + +def parse_canonical_fields(xml_path): + root = ET.parse(xml_path).getroot() + hdr = next(c for c in root if _local(c.tag) == "FatturaElettronicaHeader") + body = next(c for c in root if _local(c.tag) == "FatturaElettronicaBody") + + supplier_vat = _find(hdr, "CedentePrestatore", "DatiAnagrafici", "IdFiscaleIVA", "IdCodice") + debtor_cf = _find(hdr, "CessionarioCommittente", "DatiAnagrafici", "CodiceFiscale") + numero = _find(body, "DatiGenerali", "DatiGeneraliDocumento", "Numero") + data = _find(body, "DatiGenerali", "DatiGeneraliDocumento", "Data") + imponibile = _find(body, "DatiBeniServizi", "DatiRiepilogo", "ImponibileImporto") + divisa = _find(body, "DatiGenerali", "DatiGeneraliDocumento", "Divisa") + + invoice_uuid = int(supplier_vat) * 1_000_000 + int(numero) + debtor_id = int(debtor_cf) # int() drops leading zeros + amount = round(float(imponibile) * 100) # EUR -> cents + issue_date = int(data.replace("-", "")) # 2014-12-18 -> 20141218 + + return { + "invoice_uuid": invoice_uuid, + "debtor_id": debtor_id, + "amount": amount, + "issue_date": issue_date, + "_meta": {"supplier_vat": supplier_vat, "debtor_cf": debtor_cf, + "numero": numero, "issue_date_raw": data, "currency": divisa, + "imponibile_eur": imponibile}, + } + +def compute_canonical_id(fields, salt, signer_role): + """Run commit_receivable to get [canonical_id, anchor]. canonical_id is field 0.""" + toml = (f'invoice_uuid = "{fields["invoice_uuid"]}"\n' + f'debtor_id = "{fields["debtor_id"]}"\n' + f'amount = "{fields["amount"]}"\n' + f'issue_date = "{fields["issue_date"]}"\n' + f'salt = "{salt}"\n' + f'signer_role = "{signer_role}"\n') + cr = os.path.join(QUORUM, "commit_receivable") + open(os.path.join(cr, "Prover.toml"), "w").write(toml) + out = subprocess.run([NARGO, "execute", "--program-dir", cr], + capture_output=True, text=True).stdout + line = next(l for l in out.splitlines() if "Circuit output" in l) + inner = line.split("Circuit output:", 1)[1].split("[", 1)[1].rsplit("]", 1)[0] + cid, anchor = [t.strip() for t in inner.split(",")] + return cid, anchor + +def mint_standin_obligor(): + """Idempotently mint a stand-in obligor P-256 key + self-signed cert.""" + key = os.path.join(KEYDIR, "obligor_standin.key") + pem = os.path.join(KEYDIR, "obligor_standin.pem") + os.makedirs(KEYDIR, exist_ok=True) + if not os.path.exists(key): + subprocess.run(["openssl", "ecparam", "-name", "prime256v1", "-genkey", + "-noout", "-out", key], check=True, capture_output=True) + subprocess.run(["openssl", "req", "-new", "-x509", "-key", key, "-out", pem, + "-days", "3650", + "-subj", "/CN=obligor_standin/O=Apertrue Quorum STANDIN"], + check=True, capture_output=True) + return key + +def raw_sign_and_extract(key_path, cid_hex, workdir): + """Raw-ECDSA sign the 32-byte canonical_id; return (x[32], y[32], sig r||s low-s [64]).""" + cid_bin = os.path.join(workdir, "cid.bin") + sig_der = os.path.join(workdir, "sig.der") + pub_der = os.path.join(workdir, "pub.der") + open(cid_bin, "wb").write(binascii.unhexlify(cid_hex)) + # pkeyutl -sign treats the EC input bytes AS the digest -> raw ECDSA, no sha256 wrapper + subprocess.run(["openssl", "pkeyutl", "-sign", "-inkey", key_path, + "-in", cid_bin, "-out", sig_der], check=True, capture_output=True) + subprocess.run(["openssl", "ec", "-in", key_path, "-pubout", + "-conv_form", "uncompressed", "-outform", "DER", "-out", pub_der], + check=True, capture_output=True) + + pub = open(pub_der, "rb").read() + point = pub[-65:] + assert point[0] == 4, "public key is not an uncompressed point" + x = list(point[1:33]); y = list(point[33:65]) + + der = open(sig_der, "rb").read() + assert der[0] == 0x30 + def read_int(b, i): + assert b[i] == 0x02 + ln = b[i + 1] + return b[i + 2:i + 2 + ln], i + 2 + ln + r, i = read_int(der, 2) + s, _ = read_int(der, i) + sv = int.from_bytes(s, "big") + if sv > N_P256 // 2: # Noir verify_signature requires LOW-S (canonical) + sv = N_P256 - sv + s = sv.to_bytes(32, "big") + r = r.lstrip(b"\x00").rjust(32, b"\x00") + sig = list(r) + list(s) + return x, y, sig + +def main(): + ap = argparse.ArgumentParser(description="FatturaPA -> Quorum adapter-interface object") + ap.add_argument("xml", nargs="?", + default=os.path.join(QUORUM, "p1_provenance", "IT01234567890_FPR01.xml")) + ap.add_argument("--salt", default="42") + ap.add_argument("--signer-role", default="2") + ap.add_argument("--out", default=os.path.join(HERE, "fattura_adapter_object.json")) + args = ap.parse_args() + + fields = parse_canonical_fields(args.xml) + canonical_id, anchor = compute_canonical_id(fields, args.salt, args.signer_role) + cid_hex = canonical_id[2:].rjust(64, "0") + cid_bytes = list(binascii.unhexlify(cid_hex)) + + key_path = mint_standin_obligor() + workdir = os.path.join(HERE, "keys") + x, y, sig = raw_sign_and_extract(key_path, cid_hex, workdir) + + obj = { + "scheme": "FatturaPA", + "source_document": os.path.basename(args.xml), + "canonical_fields": { + "invoice_uuid": fields["invoice_uuid"], + "debtor_id": fields["debtor_id"], + "amount": fields["amount"], + "issue_date": fields["issue_date"], + }, + "canonical_id": canonical_id, + "canonical_id_bytes": cid_bytes, + "obligor": { + "pubkey_x": x, + "pubkey_y": y, + "signature": sig, # 64 bytes r||s, low-s normalised + "signer_role": int(args.signer_role), + }, + "_role_bound_anchor_at_salt": {"salt": int(args.salt), "anchor": anchor}, + "_notes": { + "signing_spec": "raw ECDSA P-256 over be32(canonical_id) used directly as digest (no sha256)", + "obligor_key": "STAND-IN: minted P-256 key (adapters/keys/obligor_standin.key); " + "real obligor key is the partner crux (SdI ricevuta / IRP IRN), not present in repo", + "field_derivation": fields["_meta"], + }, + } + open(args.out, "w").write(json.dumps(obj, indent=2)) + print(json.dumps(obj, indent=2)) + print(f"\n# wrote {args.out}", file=sys.stderr) + +if __name__ == "__main__": + main() diff --git a/experiments/conoir-spike/quorum/adapters/fattura_adapter_object.json b/experiments/conoir-spike/quorum/adapters/fattura_adapter_object.json new file mode 100644 index 0000000..f639364 --- /dev/null +++ b/experiments/conoir-spike/quorum/adapters/fattura_adapter_object.json @@ -0,0 +1,198 @@ +{ + "scheme": "FatturaPA", + "source_document": "IT01234567890_FPR01.xml", + "canonical_fields": { + "invoice_uuid": 1234567890000123, + "debtor_id": 9876543210, + "amount": 500, + "issue_date": 20141218 + }, + "canonical_id": "0x06cc60c66ce6389ea53783b55dc5ff1b480e9a43ecf4c942262f5d73d7a87280", + "canonical_id_bytes": [ + 6, + 204, + 96, + 198, + 108, + 230, + 56, + 158, + 165, + 55, + 131, + 181, + 93, + 197, + 255, + 27, + 72, + 14, + 154, + 67, + 236, + 244, + 201, + 66, + 38, + 47, + 93, + 115, + 215, + 168, + 114, + 128 + ], + "obligor": { + "pubkey_x": [ + 184, + 196, + 60, + 29, + 156, + 89, + 169, + 92, + 112, + 56, + 23, + 82, + 52, + 132, + 157, + 216, + 26, + 175, + 28, + 175, + 154, + 107, + 100, + 91, + 224, + 231, + 205, + 20, + 85, + 95, + 222, + 100 + ], + "pubkey_y": [ + 252, + 213, + 234, + 45, + 227, + 108, + 134, + 93, + 214, + 236, + 225, + 34, + 82, + 254, + 169, + 210, + 219, + 200, + 134, + 59, + 103, + 33, + 15, + 47, + 118, + 153, + 65, + 29, + 251, + 84, + 126, + 248 + ], + "signature": [ + 7, + 37, + 93, + 31, + 67, + 230, + 53, + 149, + 219, + 170, + 249, + 240, + 195, + 233, + 219, + 71, + 90, + 67, + 107, + 87, + 62, + 241, + 161, + 56, + 110, + 203, + 223, + 223, + 220, + 59, + 5, + 231, + 22, + 206, + 41, + 160, + 113, + 75, + 198, + 70, + 214, + 51, + 9, + 208, + 151, + 254, + 19, + 12, + 109, + 140, + 73, + 102, + 198, + 240, + 222, + 13, + 199, + 200, + 40, + 223, + 82, + 158, + 214, + 251 + ], + "signer_role": 2 + }, + "_role_bound_anchor_at_salt": { + "salt": 42, + "anchor": "0x108137c71e47833c5655288aea2160955230af60e82c0d0fd4f9d8297c912d0b" + }, + "_notes": { + "signing_spec": "raw ECDSA P-256 over be32(canonical_id) used directly as digest (no sha256)", + "obligor_key": "STAND-IN: minted P-256 key (adapters/keys/obligor_standin.key); real obligor key is the partner crux (SdI ricevuta / IRP IRN), not present in repo", + "field_derivation": { + "supplier_vat": "01234567890", + "debtor_cf": "09876543210", + "numero": "123", + "issue_date_raw": "2014-12-18", + "currency": "EUR", + "imponibile_eur": "5.00" + } + } +} \ No newline at end of file diff --git a/experiments/conoir-spike/quorum/adapters/run_fattura_pipeline.sh b/experiments/conoir-spike/quorum/adapters/run_fattura_pipeline.sh new file mode 100755 index 0000000..d9c7c04 --- /dev/null +++ b/experiments/conoir-spike/quorum/adapters/run_fattura_pipeline.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# run_fattura_pipeline.sh -- FULL Quorum pipeline on the REAL FatturaPA invoice. +# +# real FatturaPA XML +# -> fattura_adapter.py (adapter-interface object: fields, canonical_id, obligor sig) +# -> _mkroot (Poseidon2 depth-8 trust-list root for the obligor (key,role)) +# -> proof_a_receivable (in-circuit: ECDSA verify + trust-list membership -> anchor) +# -> commit_receivable (expected anchor for the same fields/salt/role) == proven anchor? +# -> bound_receivables (double-financing one-bit answer: clean=false, double=true) +# +# Idempotent: re-running reuses the stand-in obligor key and recomputes everything. +# Requires: nargo (1.0.0-beta.20) at ~/.nargo/bin/nargo, openssl, python3. +set -euo pipefail + +NARGO="${NARGO:-$HOME/.nargo/bin/nargo}" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # .../quorum/adapters +QUORUM="$(cd "$HERE/.." && pwd)" +XML="${1:-$QUORUM/p1_provenance/IT01234567890_FPR01.xml}" +SALT="${SALT:-42}" +SIGNER_ROLE="${SIGNER_ROLE:-2}" +OBJ="$HERE/fattura_adapter_object.json" + +red() { printf '\033[31m%s\033[0m\n' "$*"; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +hr() { printf '\n==== %s ====\n' "$*"; } + +# --------------------------------------------------------------------------- +hr "STEP 1: adapter -- FatturaPA XML -> adapter-interface object" +python3 "$HERE/fattura_adapter.py" "$XML" --salt "$SALT" --signer-role "$SIGNER_ROLE" --out "$OBJ" >/dev/null +# pull values out of the JSON into shell +read -r INVOICE_UUID DEBTOR_ID AMOUNT ISSUE_DATE CANONICAL_ID < <(python3 -c " +import json;o=json.load(open('$OBJ'));f=o['canonical_fields'] +print(f['invoice_uuid'],f['debtor_id'],f['amount'],f['issue_date'],o['canonical_id'])") +X=$(python3 -c "import json;print(json.load(open('$OBJ'))['obligor']['pubkey_x'])") +Y=$(python3 -c "import json;print(json.load(open('$OBJ'))['obligor']['pubkey_y'])") +SIG=$(python3 -c "import json;print(json.load(open('$OBJ'))['obligor']['signature'])") +CIDB=$(python3 -c "import json;print(json.load(open('$OBJ'))['canonical_id_bytes'])") +echo "invoice_uuid = $INVOICE_UUID" +echo "debtor_id = $DEBTOR_ID" +echo "amount = $AMOUNT" +echo "issue_date = $ISSUE_DATE" +echo "canonical_id = $CANONICAL_ID" + +# confirm canonical_id matches the committed obligor_true.cid +EXPECT_CID="0x$(xxd -p "$QUORUM/p1_provenance/admission/certs/obligor_true.cid" | tr -d '\n')" +if [ "$CANONICAL_ID" = "$EXPECT_CID" ]; then + green "PASS: canonical_id == obligor_true.cid ($EXPECT_CID)" +else + red "FAIL: canonical_id $CANONICAL_ID != obligor_true.cid $EXPECT_CID"; exit 1 +fi + +# --------------------------------------------------------------------------- +hr "STEP 2: commit_receivable -> expected role-bound anchor (same salt, role)" +cat > "$QUORUM/commit_receivable/Prover.toml" <&1 | grep "Circuit output") +EXPECTED_ANCHOR=$(echo "$CR_OUT" | sed -E 's/.*\[(0x[0-9a-f]+), (0x[0-9a-f]+)\].*/\2/') +echo "expected anchor = $EXPECTED_ANCHOR" + +# --------------------------------------------------------------------------- +hr "STEP 3: _mkroot -> trust_list_root for obligor (key, role) leaf" +cat > "$QUORUM/_mkroot/Prover.toml" <&1 | grep "Circuit output" | sed -E 's/.*(0x[0-9a-f]+).*/\1/') +echo "trust_list_root = $ROOT" + +# --------------------------------------------------------------------------- +hr "STEP 4: proof_a_receivable -- in-circuit ECDSA verify + membership -> anchor" +cat > "$QUORUM/proof_a_receivable/Prover.toml" <&1 | grep "Circuit output" | sed -E 's/.*(0x[0-9a-f]+).*/\1/') +echo "proven anchor = $PROVEN_ANCHOR" +if [ "$PROVEN_ANCHOR" = "$EXPECTED_ANCHOR" ]; then + green "PASS: proof_a anchor == commit_receivable anchor" +else + red "FAIL: anchor mismatch (proven $PROVEN_ANCHOR vs expected $EXPECTED_ANCHOR)"; exit 1 +fi + +# --------------------------------------------------------------------------- +hr "STEP 5: bound_receivables -- double-financing one-bit answer" +br_run () { # $1 = financed array contents, $2 = label, $3 = expected bool + cat > "$QUORUM/bound_receivables/Prover.toml" <&1 | grep "Circuit output") + echo " $2 -> $OUT" + echo "$OUT" | grep -q "($3," && green " PASS: already_financed=$3 (anchor opened cleanly)" \ + || { red " FAIL: expected already_financed=$3"; exit 1; } +} +br_run '"0x01","0x02","0x03","0x04","0x05","0x06"' "clean (cid NOT in financed book)" "false" +br_run "\"0x01\",\"0x02\",\"$CANONICAL_ID\",\"0x04\",\"0x05\",\"0x06\"" "double (cid IS in financed book)" "true" + +hr "ALL CHECKS PASSED -- real FatturaPA invoice proven end-to-end" diff --git a/experiments/conoir-spike/quorum/aggregate-README.md b/experiments/conoir-spike/quorum/aggregate-README.md new file mode 100644 index 0000000..80cb615 --- /dev/null +++ b/experiments/conoir-spike/quorum/aggregate-README.md @@ -0,0 +1,41 @@ +# Apertrue Quorum — aggregate engine: marine over-insurance, under MPC (2026-06) + +Runnable proof-of-life for the second fraud shape in `apertrue-quorum-plan.md`: not "has this +been seen before?" (membership) but "do separate, legitimate pieces sum past a limit?". The +case is a hull insured by several insurers past its agreed value, the point at which a ship is +worth more sunk than afloat. Answered under 3-party REP3 MPC, revealing **only one bit**. + +## Circuits +- `commit_aggregate/` — Layer 1 (off-MPC). Role-stamped anchors `Poseidon2([value, salt, + role])` for each insurer line (role 3) and the valuer-signed agreed value (role 4). +- `bound_aggregate/` — Layer 3 (MPC). Binds each secret-shared line to its insurer anchor and + the secret-shared agreed value to the valuer anchor, checks both roles against the + allow-list, sums the lines, and reveals only whether the sum exceeds the agreed value. + +## Authenticity (two signers, adapted) +- Each **line** is signed by the **insurer** that wrote it (role 3): honest, free, it vouches + for its own exposure. +- The **agreed value** is signed by a **valuer** (role 4), never the owner, who could inflate + it. An inflated value does not open the valuer's anchor and is rejected. + +## Result (full 3-party MPC, proof verified) +| Case | Setup | Output | Verified | +|------|-------|--------|----------| +| over-insured | lines 20M + 25M + 10M = 55M, value 50M | `over_insured = 1` | ✅ | +| within the limit | same lines, value 60M | `over_insured = 0` | ✅ | +| fabricated line | a line inflated to 30M, breaks its insurer anchor | — | ❌ rejected | +| wrong signer role | a line carrying the valuer role instead of an insurer's | — | ❌ rejected by the allow-list | + +No insurer's line and no agreed value is revealed; only the bit crosses between the parties. +The fabricated and wrong-role cases fail at verification, exactly as in the membership engine. + +## Scope +Same as the rest of the spike: the engine, the binding and the allow-list are real and run. +The certificates (insurer, valuer) are stand-in; the lines and value are sample figures. + +## Reproduce +``` +cd commit_aggregate && ~/.nargo/bin/nargo execute # prints the four anchors +cd .. && ./run_bound_aggregate.sh # over-insured case, full MPC +./run_bound_aggregate.sh /path/to/variant_Prover.toml # other cases +``` diff --git a/experiments/conoir-spike/quorum/bound_aggregate/Nargo.toml b/experiments/conoir-spike/quorum/bound_aggregate/Nargo.toml new file mode 100644 index 0000000..4cfdcce --- /dev/null +++ b/experiments/conoir-spike/quorum/bound_aggregate/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bound_aggregate" +type = "bin" +authors = ["Apertrue"] +compiler_version = ">=1.0.0" +description = "Apertrue Quorum aggregate engine: sum secret-shared insurer lines against a valuer-signed agreed value under MPC. Reveals only the over-insured bit." + +[dependencies] +poseidon = { tag = "v0.3.0", git = "https://github.com/noir-lang/poseidon" } diff --git a/experiments/conoir-spike/quorum/bound_aggregate/Prover.toml b/experiments/conoir-spike/quorum/bound_aggregate/Prover.toml new file mode 100644 index 0000000..146fc99 --- /dev/null +++ b/experiments/conoir-spike/quorum/bound_aggregate/Prover.toml @@ -0,0 +1,17 @@ +# PUBLIC anchors (from commit_aggregate): three insurer lines + the valuer-signed value +line_anchor = [ + "0x162ae25928e5b1c2372c7b47384f715830e9e5612c6e5c343edb434457f3a412", + "0x15a0acd5bdada247adb810cc90b356d1be9f9a9fc00e57341821401f0871407c", + "0x23cff3f18aaf298b6d50f1559a48e48bcdb15aa018098cf50847bdaab7474a91", +] +value_anchor = "0x2a5aa4535b0dfa2c0922bf221b329388d5e02b68dc190893ae02ae0190da0389" +accepted_line_role = "3" +accepted_value_role = "4" + +# PRIVATE (secret-shared): the lines and the agreed value +line = ["20000000", "25000000", "10000000"] # sum = 55,000,000 +line_salt = ["0xa1a1a1a1a1a1a1a1", "0xa2a2a2a2a2a2a2a2", "0xa3a3a3a3a3a3a3a3"] +line_role = ["3", "3", "3"] +agreed_value = "50000000" # 55M > 50M -> over-insured +value_salt = "0xb1b1b1b1b1b1b1b1" +value_role = "4" diff --git a/experiments/conoir-spike/quorum/bound_aggregate/src/main.nr b/experiments/conoir-spike/quorum/bound_aggregate/src/main.nr new file mode 100644 index 0000000..ee69c92 --- /dev/null +++ b/experiments/conoir-spike/quorum/bound_aggregate/src/main.nr @@ -0,0 +1,56 @@ +// bound_aggregate -- Apertrue Quorum aggregate engine, under MPC (marine over-insurance). +// +// "Do the separate, legitimate lines of cover on this hull sum past its agreed value?" +// -> reveal ONE bit, nothing else. No insurer sees another's line; the agreed value and +// every line stay secret-shared; only over-insured / not crosses between them. +// +// Authenticity (the two-signer idea, adapted): +// - each line is signed by the INSURER that wrote it (role 3): honest, free authenticity, +// it vouches for its own exposure. +// - the agreed value is signed by a VALUER (role 4), never the owner, who could inflate it. +// Both are bound to public, role-stamped anchors; a line signed with the wrong role, or an +// inflated value that does not open the valuer's anchor, is rejected. +// +// Same skeleton as the membership engine: bind authenticated inputs, run a predicate +// (here a sum against a threshold), reveal one bit. Simpler maths, no Merkle tree. + +use poseidon::poseidon2::Poseidon2; + +global N_LINES: u32 = 3; + +fn anchor(value: Field, salt: Field, signer_role: Field) -> Field { + Poseidon2::hash([value, salt, signer_role], 3) +} + +fn main( + // ---- PUBLIC ---- + line_anchor: pub [Field; N_LINES], // each insurer's signed line anchor + value_anchor: pub Field, // the valuer-signed agreed-value anchor + accepted_line_role: pub Field, // allow-list: role an insurer line must carry (3) + accepted_value_role: pub Field, // allow-list: role the valuer must carry (4) + // ---- PRIVATE (secret-shared) ---- + line: [Field; N_LINES], // each insurer's cover amount + line_salt: [Field; N_LINES], + line_role: [Field; N_LINES], + agreed_value: Field, // the valuer-attested hull value (never revealed) + value_salt: Field, + value_role: Field, +) -> pub bool { + // bind each line to its insurer anchor, check the role, and total it up + let mut total: Field = 0; + for i in 0..N_LINES { + assert( + anchor(line[i], line_salt[i], line_role[i]) == line_anchor[i], + "line anchor opening failed", + ); + assert(line_role[i] == accepted_line_role, "line signer role not accepted"); + total = total + line[i]; + } + + // bind the agreed value to the valuer's anchor and check the role + assert(anchor(agreed_value, value_salt, value_role) == value_anchor, "value anchor opening failed"); + assert(value_role == accepted_value_role, "value signer role not accepted"); + + // over-insured if the lines sum past the agreed value + (total as u64) > (agreed_value as u64) +} diff --git a/experiments/conoir-spike/quorum/bound_receivables/Nargo.toml b/experiments/conoir-spike/quorum/bound_receivables/Nargo.toml new file mode 100644 index 0000000..8f00d50 --- /dev/null +++ b/experiments/conoir-spike/quorum/bound_receivables/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bound_receivables" +type = "bin" +authors = ["Apertrue"] +compiler_version = ">=1.0.0" +description = "Apertrue Quorum: authenticated double-financing check across lenders' books under MPC. Reveals only the already-financed bit." + +[dependencies] +poseidon = { tag = "v0.3.0", git = "https://github.com/noir-lang/poseidon" } diff --git a/experiments/conoir-spike/quorum/bound_receivables/Prover.toml b/experiments/conoir-spike/quorum/bound_receivables/Prover.toml new file mode 100644 index 0000000..71ce1c7 --- /dev/null +++ b/experiments/conoir-spike/quorum/bound_receivables/Prover.toml @@ -0,0 +1,9 @@ +candidate_anchor = "0x108137c71e47833c5655288aea2160955230af60e82c0d0fd4f9d8297c912d0b" +signer_role = "2" +accepted_roles = ["1", "2"] +invoice_uuid = "1234567890000123" +debtor_id = "9876543210" +amount = "500" +issue_date = "20141218" +salt = "42" +financed = ["0x01","0x02","0x06cc60c66ce6389ea53783b55dc5ff1b480e9a43ecf4c942262f5d73d7a87280","0x04","0x05","0x06"] diff --git a/experiments/conoir-spike/quorum/bound_receivables/Prover_sdi.toml b/experiments/conoir-spike/quorum/bound_receivables/Prover_sdi.toml new file mode 100644 index 0000000..d7c4663 --- /dev/null +++ b/experiments/conoir-spike/quorum/bound_receivables/Prover_sdi.toml @@ -0,0 +1,14 @@ +candidate_anchor = "0x015dac177e4952c70e1161e5478ab76817250fa1d521e258e505cfea9c1648a3" +invoice_uuid = "1234567890000123" +debtor_id = "9876543210" +amount = "500" +issue_date = "20141218" +salt = "0x7777777777777777" +financed = [ + "0x01a1111111111111111111111111111111111111111111111111111111111111", + "0x02b2222222222222222222222222222222222222222222222222222222222222", + "0x03c3333333333333333333333333333333333333333333333333333333333333", + "0x04d4444444444444444444444444444444444444444444444444444444444444", + "0x05e5555555555555555555555555555555555555555555555555555555555555", + "0x06f6666666666666666666666666666666666666666666666666666666666666", +] diff --git a/experiments/conoir-spike/quorum/bound_receivables/src/main.nr b/experiments/conoir-spike/quorum/bound_receivables/src/main.nr new file mode 100644 index 0000000..cfcbc0e --- /dev/null +++ b/experiments/conoir-spike/quorum/bound_receivables/src/main.nr @@ -0,0 +1,69 @@ +// bound_receivables -- Apertrue Quorum double-financing check, under MPC, with the +// acceptable-anchor allow-list and per-signer guarantee. +// +// "Before a lender advances cash, does this EXACT receivable already appear in any lender's +// book across the network?" -> reveal ONE bit, plus WHICH guarantee the anchor carries. +// +// Two distinct signer roles defend different frauds (the spine of the thesis): +// role 1 SdI clearance -> proves "uniquely identified, cleared, not financed elsewhere". +// Catches DOUBLE-PLEDGING of real invoices. Does NOT prove the +// debt is real -- a seller can clear a fabricated/inflated invoice. +// role 2 obligor/debtor -> proves "the debt is genuinely owed". Also catches FABRICATION +// and INFLATION, because the debtor will not sign a lie. +// (First Brands was BOTH double-pledging AND fabricated/inflated invoices. Clearance alone +// catches the former; the obligor catches the latter. Together = the honest answer.) +// +// Bindings enforced here: +// 1. the secret-shared fields open the public, ROLE-BOUND anchor (authenticated query); +// 2. the signer_role is in the acceptable-anchor ALLOW-LIST (a seller's own signature, +// role 3, is rejected here -- the fraudster cannot be the trusted signer). +// The inner signature verification + role assignment happen OFF-MPC in admission (Layer 2), +// against the trust list; this circuit binds to its role-stamped output. + +use poseidon::poseidon2::Poseidon2; + +global BOOK_UNION: u32 = 6; // already-financed fingerprints across the network +global ALLOW_LIST: u32 = 2; // size of the acceptable-anchor allow-list + +fn canonical_id(uuid: Field, debtor: Field, amount: Field, date: Field) -> Field { + Poseidon2::hash([uuid, debtor, amount, date], 4) +} + +fn anchor(cid: Field, salt: Field, signer_role: Field) -> Field { + Poseidon2::hash([cid, salt, signer_role], 3) +} + +fn main( + // ---- PUBLIC ---- + candidate_anchor: pub Field, // role-bound authenticity anchor (from admission) + signer_role: pub Field, // 1 = SdI clearance, 2 = obligor + accepted_roles: pub [Field; ALLOW_LIST], // the acceptable-anchor allow-list + // ---- PRIVATE (secret-shared) ---- + invoice_uuid: Field, + debtor_id: Field, + amount: Field, + issue_date: Field, + salt: Field, + financed: [Field; BOOK_UNION], // network's already-financed fingerprints +) -> pub (bool, Field) { + let cid = canonical_id(invoice_uuid, debtor_id, amount, issue_date); + + // 1. AUTHENTICATED QUERY: fields must open the role-bound anchor. + assert(anchor(cid, salt, signer_role) == candidate_anchor, "candidate anchor opening failed"); + + // 2. ALLOW-LIST: the signer role must be acceptable (rejects e.g. a seller self-signature). + let mut role_ok: bool = false; + for i in 0..ALLOW_LIST { + role_ok = role_ok | (accepted_roles[i] == signer_role); + } + assert(role_ok, "signer role not in acceptable-anchor allow-list"); + + // 3. DOUBLE-DIP: is this exact receivable already financed anywhere in the network? + let mut already_financed: bool = false; + for i in 0..BOOK_UNION { + already_financed = already_financed | (financed[i] == cid); + } + + // reveal the bit + the role (which fixes the guarantee the proof carries) + (already_financed, signer_role) +} diff --git a/experiments/conoir-spike/quorum/commit_aggregate/Nargo.toml b/experiments/conoir-spike/quorum/commit_aggregate/Nargo.toml new file mode 100644 index 0000000..8773348 --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_aggregate/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "commit_aggregate" +type = "bin" +authors = ["Apertrue"] +compiler_version = ">=1.0.0" +description = "Quorum aggregate engine: produce role-stamped anchors for insurer lines and the valuer-signed agreed value, off-MPC." + +[dependencies] +poseidon = { tag = "v0.3.0", git = "https://github.com/noir-lang/poseidon" } diff --git a/experiments/conoir-spike/quorum/commit_aggregate/Prover.toml b/experiments/conoir-spike/quorum/commit_aggregate/Prover.toml new file mode 100644 index 0000000..72dfb86 --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_aggregate/Prover.toml @@ -0,0 +1,7 @@ +# marine over-insurance: three insurer lines of cover + the valuer's agreed hull value (USD) +# line A = $20,000,000 line B = $25,000,000 line C = $10,000,000 (sum = $55,000,000) +# agreed value = $50,000,000 -> sum exceeds value -> over-insured +# roles: 3 = insurer line, 4 = valuer +values = ["20000000", "25000000", "10000000", "50000000"] +salts = ["0xa1a1a1a1a1a1a1a1", "0xa2a2a2a2a2a2a2a2", "0xa3a3a3a3a3a3a3a3", "0xb1b1b1b1b1b1b1b1"] +roles = ["3", "3", "3", "4"] diff --git a/experiments/conoir-spike/quorum/commit_aggregate/src/main.nr b/experiments/conoir-spike/quorum/commit_aggregate/src/main.nr new file mode 100644 index 0000000..1b3db16 --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_aggregate/src/main.nr @@ -0,0 +1,24 @@ +// commit_aggregate -- off-MPC anchor generator for the aggregate engine (marine over-insurance). +// +// Each authentic quantity (an insurer's line of cover, or the valuer's agreed value) is +// committed with a role-stamped anchor, exactly as in the membership engine: +// anchor = Poseidon2([value, salt, signer_role], 3) +// Roles here: 3 = insurer line (the insurer signs its own exposure, free authenticity), +// 4 = valuer (the agreed value is signed by a valuer, never the owner). +// +// Run with `nargo execute` to obtain the four public anchors (3 lines + 1 agreed value). + +use poseidon::poseidon2::Poseidon2; + +fn anchor(value: Field, salt: Field, signer_role: Field) -> Field { + Poseidon2::hash([value, salt, signer_role], 3) +} + +fn main(values: [Field; 4], salts: [Field; 4], roles: [Field; 4]) -> pub [Field; 4] { + [ + anchor(values[0], salts[0], roles[0]), + anchor(values[1], salts[1], roles[1]), + anchor(values[2], salts[2], roles[2]), + anchor(values[3], salts[3], roles[3]), + ] +} diff --git a/experiments/conoir-spike/quorum/commit_receivable/Nargo.toml b/experiments/conoir-spike/quorum/commit_receivable/Nargo.toml new file mode 100644 index 0000000..a705baa --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_receivable/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "commit_receivable" +type = "bin" +authors = ["Apertrue"] +compiler_version = ">=1.0.0" +description = "Quorum Layer 1: produce (canonical_id, authenticity anchor) for a receivable, off-MPC." + +[dependencies] +poseidon = { tag = "v0.3.0", git = "https://github.com/noir-lang/poseidon" } diff --git a/experiments/conoir-spike/quorum/commit_receivable/Prover.toml b/experiments/conoir-spike/quorum/commit_receivable/Prover.toml new file mode 100644 index 0000000..4ced4a5 --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_receivable/Prover.toml @@ -0,0 +1,6 @@ +invoice_uuid = "1234567890000123" +debtor_id = "9876543210" +amount = "500" +issue_date = "20141218" +salt = "42" +signer_role = "2" diff --git a/experiments/conoir-spike/quorum/commit_receivable/Prover_a.toml b/experiments/conoir-spike/quorum/commit_receivable/Prover_a.toml new file mode 100644 index 0000000..4ced4a5 --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_receivable/Prover_a.toml @@ -0,0 +1,6 @@ +invoice_uuid = "1234567890000123" +debtor_id = "9876543210" +amount = "500" +issue_date = "20141218" +salt = "42" +signer_role = "2" diff --git a/experiments/conoir-spike/quorum/commit_receivable/Prover_sdi.toml b/experiments/conoir-spike/quorum/commit_receivable/Prover_sdi.toml new file mode 100644 index 0000000..76306d9 --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_receivable/Prover_sdi.toml @@ -0,0 +1,5 @@ +invoice_uuid = "1234567890000123" +debtor_id = "9876543210" +amount = "500" +issue_date = "20141218" +salt = "0x7777777777777777" diff --git a/experiments/conoir-spike/quorum/commit_receivable/src/main.nr b/experiments/conoir-spike/quorum/commit_receivable/src/main.nr new file mode 100644 index 0000000..2f56cef --- /dev/null +++ b/experiments/conoir-spike/quorum/commit_receivable/src/main.nr @@ -0,0 +1,37 @@ +// commit_receivable -- off-MPC anchor generator for Apertrue Quorum (receivables). +// +// Mirrors Layer 1: a non-fraudster signs the receivable value over the CANONICAL FIELDS, +// C2PA carries it, and the local proof emits a PUBLIC, role-bound authenticity anchor: +// +// canonical_id = Poseidon2([invoice_uuid, debtor_id, amount, issue_date], 4) +// anchor = Poseidon2([canonical_id, salt, signer_role], 3) +// +// signer_role binds WHO attested into the anchor itself, so an SdI-clearance anchor and an +// obligor (debtor-confirmation) anchor for the same receivable are DISTINCT values and +// cannot be swapped. The role determines the GUARANTEE the proof carries: +// role 1 (SdI clearance) -> "uniquely-identified, cleared, not financed elsewhere" +// role 2 (obligor/debtor) -> "the debt is genuinely owed" (the stronger claim) +// +// Run headless with `nargo execute` to obtain (canonical_id, anchor). + +use poseidon::poseidon2::Poseidon2; + +fn canonical_id(uuid: Field, debtor: Field, amount: Field, date: Field) -> Field { + Poseidon2::hash([uuid, debtor, amount, date], 4) +} + +fn anchor(cid: Field, salt: Field, signer_role: Field) -> Field { + Poseidon2::hash([cid, salt, signer_role], 3) +} + +fn main( + invoice_uuid: Field, + debtor_id: Field, + amount: Field, + issue_date: Field, + salt: Field, + signer_role: Field, // 1 = SdI clearance, 2 = obligor/debtor confirmation +) -> pub [Field; 2] { + let cid = canonical_id(invoice_uuid, debtor_id, amount, issue_date); + [cid, anchor(cid, salt, signer_role)] +} diff --git a/experiments/conoir-spike/quorum/p1_provenance/IT01234567890_FPR01.xml b/experiments/conoir-spike/quorum/p1_provenance/IT01234567890_FPR01.xml new file mode 100644 index 0000000..c82c1bc --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/IT01234567890_FPR01.xml @@ -0,0 +1,110 @@ + + + + + + IT + 01234567890 + + 00001 + FPR12 + ABC1234 + + + + + + IT + 01234567890 + + + SOCIETA' ALPHA SRL + + RF19 + + + VIALE ROMA 543 + 07100 + SASSARI + SS + IT + + + + + 09876543210 + + DITTA BETA + + + + VIA TORINO 38-B + 00145 + ROMA + RM + IT + + + + + + + TD01 + EUR + 2014-12-18 + 123 + LA FATTURA FA RIFERIMENTO AD UNA OPERAZIONE AAAA BBBBBBBBBBBBBBBBBB CCC DDDDDDDDDDDDDDD E FFFFFFFFFFFFFFFFFFFF GGGGGGGGGG HHHHHHH II LLLLLLLLLLLLLLLLL MMM NNNNN OO PPPPPPPPPPP QQQQ RRRR SSSSSSSSSSSSSS + SEGUE DESCRIZIONE CAUSALE NEL CASO IN CUI NON SIANO STATI SUFFICIENTI 200 CARATTERI AAAAAAAAAAA BBBBBBBBBBBBBBBBB + + + 1 + 66685 + 1 + + + 1 + 123 + 2012-09-01 + 5 + 123abc + 456def + + + + + IT + 24681012141 + + + Trasporto spa + + + 2012-10-22T16:46:12.000+02:00 + + + + + 1 + DESCRIZIONE DELLA FORNITURA + 5.00 + 1.00 + 5.00 + 22.00 + + + 22.00 + 5.00 + 1.10 + I + + + + TP01 + + MP01 + 2015-01-30 + 6.10 + + + + diff --git a/experiments/conoir-spike/quorum/p1_provenance/README.md b/experiments/conoir-spike/quorum/p1_provenance/README.md new file mode 100644 index 0000000..033b329 --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/README.md @@ -0,0 +1,87 @@ +# Quorum P1 — real provenance adapter (FatturaPA → C2PA → anchor → MPC) + +End-to-end with a **real Italian e-invoice**: parse it, render to PDF, bind a real C2PA +manifest, derive the canonical fingerprint + authenticity anchor, and feed that anchor into +the `bound_receivables` MPC proof. Closes the loop from a real document to the one-bit +double-financing answer. + +## The real chain (what actually ran) +1. **Real FatturaPA invoice** — `IT01234567890_FPR01.xml` (SOCIETA' ALPHA SRL → DITTA BETA, + no. 123, 2014-12-18, EUR; public sample, simevo/fattura-elettronica-json). +2. **Canonicalisation** (deterministic, the make-or-break field): + - `invoice_uuid = supplierVAT(01234567890) * 1e6 + numero(123) = 1234567890000123` + - `debtor_id = buyer CF 09876543210 → 9876543210` + - `amount = 5.00 EUR → 500` (cents) · `issue_date = 2014-12-18 → 20141218` +3. **Anchor** (`commit_receivable`, proof_a's scheme): + - `canonical_id = Poseidon2([uuid, debtor, amount, date]) = 0x06cc60c6…87280` + - `anchor = Poseidon2([canonical_id, salt]) = 0x015dac17…48a3` +4. **Real C2PA-signed PDF** — `invoice_c2pa.pdf`, produced by **c2pie** (PS256, RSA cert + chain). Contains `c2pa.claim`, `c2pa.hash.data` (hard binding to the PDF bytes), + `c2pa.signature` (COSE), the CreativeWork assertion, in an embedded `manifest.c2pa` + (`/AFRelationship /C2PA_Manifest`). Verified embedded via JUMBF markers. +5. **MPC** — anchor fed to `bound_receivables`, full 3-party REP3: + - clean → `already_financed = 0` (verified) · double-financed → `1` (verified). + +## What is REAL vs STAND-IN +| Element | State | +|---|---| +| FatturaPA invoice + fields + canonicalisation | **real** | +| canonical_id / anchor (proof_a Poseidon2 scheme) | **real** | +| C2PA manifest on a PDF, hard data-hash binding, COSE sig, cert chain | **real** (via c2pie) | +| MPC double-financing proof from this anchor | **real** (verified) | +| Clearance signer cert (Agenzia delle Entrate / SdI) | **stand-in** self-made CA — real cert needs a partner; validation_state untrusted until the CA is a known anchor | +| Asset format | PDF **rendering** of the invoice (+ could embed the XML as attachment); raw-XML C2PA needs the data-hash sidecar API (see tooling note) | + +## Tooling notes +- Homebrew **c2patool 0.26.29 is media-only** — it maps `.xml`→SVG and reports PDF/generic + "type is unsupported" (PDF/data-hash are non-default c2pa-rs features). So it cannot bind + to a raw e-invoice XML or PDF. +- **c2pie** (TourmalineCore) fills the gap: Python, embeds C2PA into **PDF** (and JPG), + PS256 + cert chains, with a hard `c2pa.hash.data` binding. No raw-XML / sidecar. +- For raw-**XML** C2PA (data-hash sidecar) the spec supports it; needs a c2pa-rs build with + the data-hash feature, or the CAWG identity-assertion path. Tracked for P1.next. + +## Reproduce +``` +# render + sign a C2PA PDF (needs: c2pie in a venv, openssl) +python3 -m venv venv && . venv/bin/activate && pip install c2pie +cupsfilter invoice.txt > invoice.pdf +# generate an RSA CA->leaf chain (emailProtection EKU), then: +python3 -c "from c2pie.signing import sign_file; \ + sign_file('invoice.pdf','invoice_c2pa.pdf','rsa_leaf.key','rsa_chain.pem','c2pie_schema.json')" +# derive anchor + run MPC +cd ../commit_receivable && ~/.nargo/bin/nargo execute --prover-name Prover_sdi +cd ../ && ./run_bound_receivables.sh bound_receivables/Prover_sdi.toml +``` + +## P1.next (done) — `p1_next.py` → `invoice_quorum.pdf` +Two of the three items are now real (the third needs a partner cert): + +- ✅ **Custom C2PA assertion** `org.apertrue.quorum.receivable` (via c2pie's programmatic + `interface` API, not just CreativeWork) carrying the canonical fields, the role-bound + anchor, the signer role/guarantee, and the **inner obligor ES256 signature over the + canonical_id**. Referenced twice in the file (assertion box + claim url) → bound by the + claim signature. +- ✅ **Authoritative XML travels with the C2PA.** Embedded two ways: + (a) inside the PDF (under the `c2pa.hash.data` hard binding — the XML bytes are physically + present and hashed), and (b) — the robust path — inside the signed manifest itself as + `org.apertrue.quorum.source_document` (base64 + sha256). Extracted back out of the + manifest it **round-trips bit-perfectly** (4315 bytes == source). + Note: c2pie's PDF incremental update clobbers the PDF *attachment* names tree (only + `manifest.c2pa` remains navigable), which is why the manifest-embedded copy (b) is the + load-bearing one. +- ⬜ **Real clearance/obligor cert** (genuine SdI *ricevuta* / India IRP IRN): still + stand-in; needs a design partner. The inner-signature **verification** path exists + (admission, openssl) and a proof_a-style in-circuit verify is the remaining step. + +Note: anchors here are now **role-bound** (`Poseidon2([canonical_id, salt, role])`); the +obligor anchor is `0x276218a3…`, superseding the earlier pre-role `0x015dac17…`. + +## Reproduce P1.next +``` +. venv/bin/activate # c2pie + pypdf +cd admission && ./admission.sh # regenerates certs + obligor signature +# regenerate an RSA CA->leaf chain (rsa_leaf.key + rsa_chain.pem), then: +python3 ../p1_next.py rsa_leaf.key rsa_chain.pem +``` +(Private keys are not committed — the scripts regenerate them.) diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/admission.sh b/experiments/conoir-spike/quorum/p1_provenance/admission/admission.sh new file mode 100755 index 0000000..2feac2a --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/admission.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Apertrue Quorum -- Layer 2 ADMISSION (off-MPC acceptable-anchor allow-list). +# +# Decides whether a receivable's authenticity signature is acceptable, and if so assigns a +# SIGNER ROLE and emits the role-bound anchor that the MPC circuit binds to. +# +# trust list (allow-list of roots): SdI root -> role 1 (clearance) +# Obligor root -> role 2 (debtor confirmation) +# NOT trusted: Seller root -> rejected (the fraudster cannot self-vouch) +# +# A submission is admitted only if: (a) the inner signature verifies over the CANONICAL_ID +# (so the signer vouched for exactly debtor|amount|id, the value the membership check keys +# on), AND (b) the signer's cert chains to a trusted root. The role comes from which root. +# +# The fabrication catch lives HERE: the obligor signs only the TRUE canonical_id; there is +# no obligor signature over an inflated/fictitious canonical_id, so an inflated invoice can +# never be admitted as role 2. (Clearance, role 1, will admit it -- it only proves "cleared".) +set -e +DIR="$(cd "$(dirname "$0")" && pwd)" +CERTS="$DIR/certs"; mkdir -p "$CERTS" +COMMIT="$HOME/apertrue/circuits/experiments/conoir-spike/quorum/commit_receivable" +NARGO="$HOME/.nargo/bin/nargo" + +CID_TRUE=06cc60c66ce6389ea53783b55dc5ff1b480e9a43ecf4c942262f5d73d7a87280 # amount 500 +CID_INFL=015e98c898078299bf9ed776c7855548a0207ec782c4b245d222fbdcd358bb03 # amount 5000 (inflated) + +mkroot() { # name + openssl ecparam -name prime256v1 -genkey -noout -out "$CERTS/$1.key" 2>/dev/null + openssl req -new -x509 -key "$CERTS/$1.key" -out "$CERTS/$1.pem" -days 3650 \ + -subj "/CN=$1/O=Apertrue Quorum STANDIN" \ + -addext "basicConstraints=critical,CA:TRUE" 2>/dev/null +} +mkleaf() { # name rootname + openssl ecparam -name prime256v1 -genkey -noout -out "$CERTS/$1.key" 2>/dev/null + openssl req -new -key "$CERTS/$1.key" -out "$CERTS/$1.csr" -subj "/CN=$1/O=Apertrue Quorum STANDIN" 2>/dev/null + printf "keyUsage=critical,digitalSignature\nextendedKeyUsage=emailProtection\n" > "$CERTS/$1.ext" + openssl x509 -req -in "$CERTS/$1.csr" -CA "$CERTS/$2.pem" -CAkey "$CERTS/$2.key" -CAcreateserial \ + -out "$CERTS/$1.pem" -days 3650 -extfile "$CERTS/$1.ext" 2>/dev/null + openssl x509 -in "$CERTS/$1.pem" -pubkey -noout > "$CERTS/$1.pub" 2>/dev/null +} +sign_cid() { # leafname cidhex outname -- signer vouches for the canonical_id + printf "$2" | xxd -r -p > "$CERTS/$3.cid" + openssl dgst -sha256 -sign "$CERTS/$1.key" -out "$CERTS/$3.sig" "$CERTS/$3.cid" 2>/dev/null +} + +echo "### generate trust roots + signer leaves (one-time) ###" +mkroot sdi_root; mkroot obligor_root; mkroot seller_root +mkleaf sdi_leaf sdi_root +mkleaf obligor_leaf obligor_root +mkleaf seller_leaf seller_root +cat "$CERTS/sdi_root.pem" "$CERTS/obligor_root.pem" > "$CERTS/trust_store.pem" # allow-list + +echo "### produce signatures (who vouches for what) ###" +sign_cid sdi_leaf $CID_TRUE sdi_true # SdI clears the true invoice +sign_cid sdi_leaf $CID_INFL sdi_infl # SdI also clears the INFLATED invoice (it only clears) +sign_cid obligor_leaf $CID_TRUE obligor_true # debtor confirms ONLY the true debt +sign_cid seller_leaf $CID_TRUE seller_true # seller self-attests (not acceptable) +# NOTE: there is deliberately NO obligor signature over CID_INFL -- the debtor won't sign a lie. + +SDI_SUBJ=$(openssl x509 -in "$CERTS/sdi_root.pem" -noout -subject 2>/dev/null | sed 's/^subject=//') +OBL_SUBJ=$(openssl x509 -in "$CERTS/obligor_root.pem" -noout -subject 2>/dev/null | sed 's/^subject=//') + +admit() { # label leafname cidhex tag + local label="$1" leaf="$2" cid="$3" tag="$4" + printf "$cid" | xxd -r -p > "$CERTS/_chk.cid" + # (a) inner signature over the canonical_id? + if ! openssl dgst -sha256 -verify "$CERTS/$leaf.pub" -signature "$CERTS/$tag.sig" "$CERTS/_chk.cid" >/dev/null 2>&1; then + printf "%-34s -> REJECTED (no valid signature over this canonical_id)\n" "$label"; return; fi + # (b) does the signer chain to a trusted root (allow-list)? + if ! openssl verify -CAfile "$CERTS/trust_store.pem" "$CERTS/$leaf.pem" >/dev/null 2>&1; then + printf "%-34s -> REJECTED (signer not in acceptable-anchor allow-list)\n" "$label"; return; fi + # role from issuer + local iss role guar + iss=$(openssl x509 -in "$CERTS/$leaf.pem" -noout -issuer 2>/dev/null | sed 's/^issuer=//') + if [ "$iss" = "$SDI_SUBJ" ]; then role=1; guar='cleared + uniquely identified (NOT proof of debt)'; + elif [ "$iss" = "$OBL_SUBJ" ]; then role=2; guar='the debt is GENUINELY OWED'; + else printf "%-34s -> REJECTED (unknown issuer)\n" "$label"; return; fi + # emit role-bound anchor + printf "%-34s -> ADMITTED role=%s guarantee: %s\n" "$label" "$role" "$guar" +} + +echo +echo "### ADMISSION DECISIONS ###" +admit "obligor: true receivable" obligor_leaf $CID_TRUE obligor_true +admit "SdI: true receivable" sdi_leaf $CID_TRUE sdi_true +admit "SdI: INFLATED (seller cleared)" sdi_leaf $CID_INFL sdi_infl +admit "obligor: INFLATED (fabrication)" obligor_leaf $CID_INFL sdi_infl # no obligor sig exists -> rejected +admit "seller self-signed" seller_leaf $CID_TRUE seller_true diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/_chk.cid b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/_chk.cid new file mode 100644 index 0000000..c2cf81e --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/_chk.cid @@ -0,0 +1 @@ +`l87]HCB&/]sרr \ No newline at end of file diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.csr b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.csr new file mode 100644 index 0000000..d4c462e --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIHzMIGbAgEAMDkxFTATBgNVBAMMDG9ibGlnb3JfbGVhZjEgMB4GA1UECgwXQXBl +cnRydWUgUXVvcnVtIFNUQU5ESU4wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARj +DLk/Hg5vuz0+rAwQKZmsr6DOMG40MyJst4UhJrbXcV3HF/9qg2ixpaTp/hUki6kC +UOQzDsmVxUmTRewSZ9BKoAAwCgYIKoZIzj0EAwIDRwAwRAIgFiLb3riRirsURKJk +/sHr21SzYE1EwLgA7CkEy7AdJ20CIF5DhwcEMe+mTodcYSijm/BUiPBCdk/+tE1q +PqFy9skz +-----END CERTIFICATE REQUEST----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.ext b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.ext new file mode 100644 index 0000000..7e63ec8 --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.ext @@ -0,0 +1,2 @@ +keyUsage=critical,digitalSignature +extendedKeyUsage=emailProtection diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pem b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pem new file mode 100644 index 0000000..4bcae4a --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB2jCCAYGgAwIBAgIUHdw6reRhOMG+k99eAgAEPuIDSnkwCgYIKoZIzj0EAwIw +OTEVMBMGA1UEAwwMb2JsaWdvcl9yb290MSAwHgYDVQQKDBdBcGVydHJ1ZSBRdW9y +dW0gU1RBTkRJTjAeFw0yNjA2MjYxMjQ4MzVaFw0zNjA2MjMxMjQ4MzVaMDkxFTAT +BgNVBAMMDG9ibGlnb3JfbGVhZjEgMB4GA1UECgwXQXBlcnRydWUgUXVvcnVtIFNU +QU5ESU4wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARjDLk/Hg5vuz0+rAwQKZms +r6DOMG40MyJst4UhJrbXcV3HF/9qg2ixpaTp/hUki6kCUOQzDsmVxUmTRewSZ9BK +o2cwZTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwQwHQYDVR0O +BBYEFPOqHt9D0Y2Qwt2DIw4wgZ3qDaTBMB8GA1UdIwQYMBaAFII8PIvRZxlKJNqH +h4KkBkr1cRfSMAoGCCqGSM49BAMCA0cAMEQCIB46ITJPe3VyK3NHIJYHQ4XT6/mR +/y0owHp0fAJB7IUxAiBu8ONTfzaEFFBEOpHIqN335owvlOZopR3zqa6qBFOipQ== +-----END CERTIFICATE----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pub b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pub new file mode 100644 index 0000000..8cd97af --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_leaf.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYwy5Px4Ob7s9PqwMECmZrK+gzjBu +NDMibLeFISa213Fdxxf/aoNosaWk6f4VJIupAlDkMw7JlcVJk0XsEmfQSg== +-----END PUBLIC KEY----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.pem b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.pem new file mode 100644 index 0000000..ae4373e --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxzCCAW2gAwIBAgIUWMjz/lFVcuZvGGuD8lN4wHzWXAcwCgYIKoZIzj0EAwIw +OTEVMBMGA1UEAwwMb2JsaWdvcl9yb290MSAwHgYDVQQKDBdBcGVydHJ1ZSBRdW9y +dW0gU1RBTkRJTjAeFw0yNjA2MjYxMjQ4MzVaFw0zNjA2MjMxMjQ4MzVaMDkxFTAT +BgNVBAMMDG9ibGlnb3Jfcm9vdDEgMB4GA1UECgwXQXBlcnRydWUgUXVvcnVtIFNU +QU5ESU4wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqfNSS+oszgwodij9T3A6X +ujK62/Wrzhy/ct/vojGoOs7lsPUcdFY3F/iy6iQwnyIpwllnSwn7a699v1b1UDL4 +o1MwUTAdBgNVHQ4EFgQUgjw8i9FnGUok2oeHgqQGSvVxF9IwHwYDVR0jBBgwFoAU +gjw8i9FnGUok2oeHgqQGSvVxF9IwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQD +AgNIADBFAiAJCYaGfG6qHoeZfT6EsDNH7elJH05xKQpttP2lXYqlVgIhALHb26jq +72af9ilhPSp+ZE6RskbXweGPFTFSB+mP+Mb5 +-----END CERTIFICATE----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.srl b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.srl new file mode 100644 index 0000000..0f8d957 --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_root.srl @@ -0,0 +1 @@ +1DDC3AADE46138C1BE93DF5E0200043EE2034A79 diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_true.cid b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_true.cid new file mode 100644 index 0000000..c2cf81e --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_true.cid @@ -0,0 +1 @@ +`l87]HCB&/]sרr \ No newline at end of file diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_true.sig b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/obligor_true.sig new file mode 100644 index 0000000000000000000000000000000000000000..f3c20eac0602dc99941a3a07b67f17b1fb86aee5 GIT binary patch literal 70 zcmV-M0J;A#L;@fN^Z@li&}jvqHI2nER>slW2-V}%(9MEZwN*DrKfYlCAaK@!GiiwM cp5~HJpwXH%he_rfGW7=ckTw0asaq{=x{C24djJ3c literal 0 HcmV?d00001 diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_infl.cid b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_infl.cid new file mode 100644 index 0000000..d56f628 --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_infl.cid @@ -0,0 +1 @@ +^ȘvDžUH ~ǂIJE"X \ No newline at end of file diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_infl.sig b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_infl.sig new file mode 100644 index 0000000000000000000000000000000000000000..b321eb7475098e8f5b7d6bc3243cc9cb69dd5caf GIT binary patch literal 70 zcmV-M0J;A#L;@fmr`tH_CTf**J@>0m06n@8@7y{@YB0Q*R~#P`H~?V+ASd}m+~u^O cTtU!l3$E8R7oVW~LlNN&g&azBUB-LvwCb)N1^@s6 literal 0 HcmV?d00001 diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.csr b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.csr new file mode 100644 index 0000000..7a8c409 --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIHxMIGXAgEAMDUxETAPBgNVBAMMCHNkaV9sZWFmMSAwHgYDVQQKDBdBcGVydHJ1 +ZSBRdW9ydW0gU1RBTkRJTjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBY0uER0 +qq89DaUEVKdqKCm87RRj6YkT1u8wyY8oEMm9GDbLdNBWHxbeJciNX8DG9iIFePfu +yl8CJ/OIsIe2t2+gADAKBggqhkjOPQQDAgNJADBGAiEA7VWIZGZHE+WkjJp6OyP4 +bBc7DZKzWzeVhzTL2zTDzi8CIQCI6by3W+2v9alfvh7xwP0FdEu60kFIP7b0CwcO +HxQ01Q== +-----END CERTIFICATE REQUEST----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.ext b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.ext new file mode 100644 index 0000000..7e63ec8 --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.ext @@ -0,0 +1,2 @@ +keyUsage=critical,digitalSignature +extendedKeyUsage=emailProtection diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pem b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pem new file mode 100644 index 0000000..d86507e --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB0jCCAXmgAwIBAgIUQKNhLaYdWrRk/s1O/q0b9qaysVQwCgYIKoZIzj0EAwIw +NTERMA8GA1UEAwwIc2RpX3Jvb3QxIDAeBgNVBAoMF0FwZXJ0cnVlIFF1b3J1bSBT +VEFORElOMB4XDTI2MDYyNjEyNDgzNVoXDTM2MDYyMzEyNDgzNVowNTERMA8GA1UE +AwwIc2RpX2xlYWYxIDAeBgNVBAoMF0FwZXJ0cnVlIFF1b3J1bSBTVEFORElOMFkw +EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFjS4RHSqrz0NpQRUp2ooKbztFGPpiRPW +7zDJjygQyb0YNst00FYfFt4lyI1fwMb2IgV49+7KXwIn84iwh7a3b6NnMGUwDgYD +VR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMEMB0GA1UdDgQWBBTqwTY2 +uGDbxLELHaVxrWSbWax4ijAfBgNVHSMEGDAWgBTahpYT+n+kRGxOc1oVcMEGAe+Q +FDAKBggqhkjOPQQDAgNHADBEAiBSfwyXdUlMvnTzzU7oTeplIAUqKIWtPZTboIau +cEfQ4wIgcDRdOjf/1wkScqiv2NsqLgHgkI4fh479R/m+bhJH/TE= +-----END CERTIFICATE----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pub b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pub new file mode 100644 index 0000000..61218fe --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_leaf.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFjS4RHSqrz0NpQRUp2ooKbztFGPp +iRPW7zDJjygQyb0YNst00FYfFt4lyI1fwMb2IgV49+7KXwIn84iwh7a3bw== +-----END PUBLIC KEY----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.pem b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.pem new file mode 100644 index 0000000..03e6a42 --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBwDCCAWWgAwIBAgIUfSsdGSwwq0KnsBKeBPaH3gSYv40wCgYIKoZIzj0EAwIw +NTERMA8GA1UEAwwIc2RpX3Jvb3QxIDAeBgNVBAoMF0FwZXJ0cnVlIFF1b3J1bSBT +VEFORElOMB4XDTI2MDYyNjEyNDgzNVoXDTM2MDYyMzEyNDgzNVowNTERMA8GA1UE +AwwIc2RpX3Jvb3QxIDAeBgNVBAoMF0FwZXJ0cnVlIFF1b3J1bSBTVEFORElOMFkw +EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPseog0XuTPtf1+xBI5yiZdBn2sxkkJAN +qyQ/RFpNSqUkE4E/vaIU3Czj+oVF4eC7JU4go0iIuEZonoQGk7F3EaNTMFEwHQYD +VR0OBBYEFNqGlhP6f6REbE5zWhVwwQYB75AUMB8GA1UdIwQYMBaAFNqGlhP6f6RE +bE5zWhVwwQYB75AUMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIh +ALYzjT8Us6Yos9B1smyT9thw841JJ0uKLh/KYZTOXBOdAiEAynGOtBM0LsbcZeG+ +na6kCKISUZmLc7JdcP1Em0lUxg4= +-----END CERTIFICATE----- diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.srl b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.srl new file mode 100644 index 0000000..27fffaf --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_root.srl @@ -0,0 +1 @@ +40A3612DA61D5AB464FECD4EFEAD1BF6A6B2B154 diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_true.cid b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_true.cid new file mode 100644 index 0000000..c2cf81e --- /dev/null +++ b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_true.cid @@ -0,0 +1 @@ +`l87]HCB&/]sרr \ No newline at end of file diff --git a/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_true.sig b/experiments/conoir-spike/quorum/p1_provenance/admission/certs/sdi_true.sig new file mode 100644 index 0000000000000000000000000000000000000000..44e444a13715d34be6dcc8106246c5045f6d2e95 GIT binary patch literal 71 zcmV-N0J#4!MFJpR6GS03w11&c35m7JDvdoAM$<4iAm6zeW)I?}3Do-nApq?3iuvL? dj{{Cwr$%+CpW$K+27g!{r)?~U8 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 literal 0 HcmV?d00001 diff --git a/experiments/conoir-spike/quorum/p1_provenance/invoice_c2pa.pdf b/experiments/conoir-spike/quorum/p1_provenance/invoice_c2pa.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a5fce958fff773b3937a972cde9edbafe29af9d1 GIT binary patch literal 22343 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 bytes: + return self.description_box.serialize() + self.serialize_content_boxes() + +def main(): + invoice_pdf = os.path.join(HERE, "invoice.pdf") + xml_path = os.path.join(HERE, "IT01234567890_FPR01.xml") + key_path = sys.argv[1] # rsa_leaf.key + chain_path = sys.argv[2] # rsa_chain.pem + out_path = os.path.join(HERE, "invoice_quorum.pdf") + + # --- 1) embed the authoritative FatturaPA XML INSIDE the PDF --- + xml_bytes = open(xml_path, "rb").read() + r = PdfReader(invoice_pdf); w = PdfWriter(); w.append(r) + w.add_attachment("IT01234567890_FPR01.xml", xml_bytes) + tmp = os.path.join(HERE, "invoice_with_xml.pdf") + with open(tmp, "wb") as f: w.write(f) + raw = open(tmp, "rb").read() + print("embedded XML -> PDF: %d bytes (orig invoice %d, xml %d)" % (len(raw), os.path.getsize(invoice_pdf), len(xml_bytes))) + + # --- 2) custom receivable assertion carrying fields + role-bound anchor + inner signature --- + obligor_sig_b64 = base64.b64encode(open(os.path.join(ADM, "obligor_true.sig"), "rb").read()).decode() + receivable = { + "scheme": "FatturaPA", + "signer_role": SIGNER_ROLE, + "signer_role_name": "obligor/debtor confirmation", + "guarantee": "the debt is genuinely owed", + "non_fraudster_signer": "DITTA BETA (debtor) [STAND-IN CERT]", + "supplier_vat": "IT01234567890", + "debtor_cf": "09876543210", + "invoice_number": "123", + "issue_date": "2014-12-18", + "amount_cents": 500, + "currency": "EUR", + "canonical_id": CANONICAL_ID, + "anchor": ANCHOR, + "inner_signature_es256_over_canonical_id_b64": obligor_sig_b64, + "source_document": "IT01234567890_FPR01.xml (embedded in this PDF)", + } + receivable_assertion = CustomJsonAssertion("org.apertrue.quorum.receivable", receivable) + + # carry the AUTHORITATIVE source XML inside the signed manifest itself, so it travels with + # the C2PA (bound by the claim signature, extractable regardless of PDF attachment quirks). + source_doc = { + "format": "application/xml (FatturaPA)", + "filename": "IT01234567890_FPR01.xml", + "sha256": hashlib.sha256(xml_bytes).hexdigest(), + "bytes_b64": base64.b64encode(xml_bytes).decode(), + } + source_assertion = CustomJsonAssertion("org.apertrue.quorum.source_document", source_doc) + creative_work = c2pie_GenerateAssertion(C2PA_AssertionTypes.creative_work, { + "@context": "https://schema.org", "@type": "CreativeWork", + "author": [{"@type": "Organization", "name": "IT-SdI / debtor confirmation STAND-IN"}], + "copyrightYear": "2014", "copyrightHolder": "FatturaPA no.123", + }) + + # --- 3) hard binding over the WHOLE pdf (incl. embedded XML); manifest appended at EOF --- + cai_offset = len(raw) + hash_data = c2pie_GenerateHashDataAssertion(cai_offset=cai_offset, hashed_data=hashlib.sha256(raw).digest()) + + manifest = c2pie_GenerateManifest( + assertions=[receivable_assertion, source_assertion, creative_work, hash_data], + private_key=open(key_path, "rb").read(), + certificate_chain=open(chain_path, "rb").read(), + ) + signed = c2pie_EmplaceManifest(C2PA_ContentTypes.pdf, raw, cai_offset, manifest) + with open(out_path, "wb") as f: f.write(signed) + print("signed C2PA PDF -> %s (%d bytes)" % (out_path, len(signed))) + +if __name__ == "__main__": + main() diff --git a/experiments/conoir-spike/quorum/proof_a_receivable/Nargo.toml b/experiments/conoir-spike/quorum/proof_a_receivable/Nargo.toml new file mode 100644 index 0000000..942c493 --- /dev/null +++ b/experiments/conoir-spike/quorum/proof_a_receivable/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "proof_a_receivable" +type = "bin" +authors = ["Apertrue"] +compiler_version = ">=1.0.0" +description = "Quorum Proof A: in-circuit verification that a trusted, role-bound signer signed canonical_id; emits the role-stamped authenticity anchor." + +[dependencies] +poseidon = { tag = "v0.3.0", git = "https://github.com/noir-lang/poseidon" } diff --git a/experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml b/experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml new file mode 100644 index 0000000..fc381fd --- /dev/null +++ b/experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml @@ -0,0 +1,13 @@ +trust_list_root = "0x0ed8af316e7f4b18c08d40bee7ca210d382fa442858498d9ff317769de826c15" +signer_role = "2" +invoice_uuid = "1234567890000123" +debtor_id = "9876543210" +amount = "500" +issue_date = "20141218" +salt = "42" +signer_pubkey_x = [184, 196, 60, 29, 156, 89, 169, 92, 112, 56, 23, 82, 52, 132, 157, 216, 26, 175, 28, 175, 154, 107, 100, 91, 224, 231, 205, 20, 85, 95, 222, 100] +signer_pubkey_y = [252, 213, 234, 45, 227, 108, 134, 93, 214, 236, 225, 34, 82, 254, 169, 210, 219, 200, 134, 59, 103, 33, 15, 47, 118, 153, 65, 29, 251, 84, 126, 248] +signature = [7, 37, 93, 31, 67, 230, 53, 149, 219, 170, 249, 240, 195, 233, 219, 71, 90, 67, 107, 87, 62, 241, 161, 56, 110, 203, 223, 223, 220, 59, 5, 231, 22, 206, 41, 160, 113, 75, 198, 70, 214, 51, 9, 208, 151, 254, 19, 12, 109, 140, 73, 102, 198, 240, 222, 13, 199, 200, 40, 223, 82, 158, 214, 251] +canonical_id_bytes = [6, 204, 96, 198, 108, 230, 56, 158, 165, 55, 131, 181, 93, 197, 255, 27, 72, 14, 154, 67, 236, 244, 201, 66, 38, 47, 93, 115, 215, 168, 114, 128] +merkle_path = ["0","0","0","0","0","0","0","0"] +merkle_indices = ["0","0","0","0","0","0","0","0"] diff --git a/experiments/conoir-spike/quorum/proof_a_receivable/run_proof_a.sh b/experiments/conoir-spike/quorum/proof_a_receivable/run_proof_a.sh new file mode 100755 index 0000000..1d1e3bf --- /dev/null +++ b/experiments/conoir-spike/quorum/proof_a_receivable/run_proof_a.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# run_proof_a.sh -- end-to-end test harness for the `proof_a_receivable` Noir circuit. +# +# Proves Proof A END-TO-END with a real OpenSSL ECDSA P-256 signature, runs two +# negative tests, and demonstrates that Proof A's anchor output feeds `bound_receivables`. +# +# Idempotent: regenerates a fresh P-256 key and recomputes the trust-list root each run. +# (The anchor is independent of the key -- it depends only on canonical_id, salt, role -- +# so the "executed anchor == expected anchor" check is stable across runs.) +# +# Requires: nargo (1.0.0-beta.20) at ~/.nargo/bin/nargo, openssl, python3. +set -euo pipefail + +NARGO="${NARGO:-$HOME/.nargo/bin/nargo}" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # .../quorum/proof_a_receivable +QUORUM="$(cd "$HERE/.." && pwd)" +WD="$(mktemp -d)" +trap 'rm -rf "$WD"' EXIT + +# Fixed receivable + role used throughout. +INVOICE_UUID=1234567890000123 +DEBTOR_ID=9876543210 +AMOUNT=500 +ISSUE_DATE=20141218 +SALT=42 +SIGNER_ROLE=2 + +red() { printf '\033[31m%s\033[0m\n' "$*"; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +hr() { printf '\n==== %s ====\n' "$*"; } + +# --------------------------------------------------------------------------- +hr "STEP 1/2: commit_receivable -> canonical_id, expected anchor" +cat > "$QUORUM/commit_receivable/Prover.toml" <&1 | grep "Circuit output") +# Circuit output: [0x..canonical_id.., 0x..anchor..] +CANONICAL_ID=$(echo "$CR_OUT" | sed -E 's/.*\[(0x[0-9a-f]+), (0x[0-9a-f]+)\].*/\1/') +EXPECTED_ANCHOR=$(echo "$CR_OUT" | sed -E 's/.*\[(0x[0-9a-f]+), (0x[0-9a-f]+)\].*/\2/') +echo "canonical_id = $CANONICAL_ID" +echo "expected anchor = $EXPECTED_ANCHOR" + +# --------------------------------------------------------------------------- +hr "STEP 3/4: P-256 key, pubkey x/y, raw-ECDSA sign of canonical_id digest" +# canonical_id -> 32 big-endian bytes (the signed digest) +CIDHEX=${CANONICAL_ID#0x} +CIDHEX=$(printf '%064s' "$CIDHEX" | tr ' ' '0') +python3 -c "import binascii;open('$WD/cid.bin','wb').write(binascii.unhexlify('$CIDHEX'))" + +openssl ecparam -name prime256v1 -genkey -noout -out "$WD/key.pem" +# raw ECDSA over the 32-byte digest (pkeyutl -sign treats EC input as the digest) +openssl pkeyutl -sign -inkey "$WD/key.pem" -in "$WD/cid.bin" -out "$WD/sig.der" +openssl ec -in "$WD/key.pem" -pubout -conv_form uncompressed -outform DER -out "$WD/pub.der" 2>/dev/null + +python3 - "$WD" <<'PY' +import sys +WD=sys.argv[1] +pub=open(WD+'/pub.der','rb').read() +point=pub[-65:]; assert point[0]==4, "not uncompressed point" +x=point[1:33]; y=point[33:65] +der=open(WD+'/sig.der','rb').read(); assert der[0]==0x30 +def read_int(b,i): + assert b[i]==0x02; ln=b[i+1]; return b[i+2:i+2+ln], i+2+ln +r,i=read_int(der,2); s,_=read_int(der,i) +# Noir's ecdsa_secp256r1::verify_signature requires LOW-S (canonical) form; +# OpenSSL does not enforce it, so normalise s -> min(s, n-s). +N=0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 +sv=int.from_bytes(s,'big') +if sv > N//2: sv = N - sv +s=sv.to_bytes(32,'big') +r=r.lstrip(b'\x00').rjust(32,b'\x00'); s=s.rjust(32,b'\x00') +sig=r+s +arr=lambda bb:"["+", ".join(str(c) for c in bb)+"]" +open(WD+'/x.txt','w').write(arr(x)) +open(WD+'/y.txt','w').write(arr(y)) +open(WD+'/sig.txt','w').write(arr(sig)) +open(WD+'/cidbytes.txt','w').write(arr(open(WD+'/cid.bin','rb').read())) +PY +X=$(cat "$WD/x.txt"); Y=$(cat "$WD/y.txt"); SIG=$(cat "$WD/sig.txt"); CIDB=$(cat "$WD/cidbytes.txt") + +# --------------------------------------------------------------------------- +hr "STEP 5: _mkroot -> trust_list_root (siblings=0, indices=0, leftmost leaf)" +cat > "$QUORUM/_mkroot/Prover.toml" <&1 | grep "Circuit output" | sed -E 's/.*(0x[0-9a-f]+).*/\1/') +echo "trust_list_root = $ROOT" + +# --------------------------------------------------------------------------- +hr "STEP 6: proof_a_receivable Prover.toml + execute" +cat > "$HERE/Prover.toml" <&1 | grep "Circuit output" | sed -E 's/.*(0x[0-9a-f]+).*/\1/') +echo "proven anchor = $PROVEN_ANCHOR" +if [ "$PROVEN_ANCHOR" = "$EXPECTED_ANCHOR" ]; then + green "PASS: proven anchor == expected anchor" +else + red "FAIL: anchor mismatch"; exit 1 +fi + +cp "$HERE/Prover.toml" "$WD/good.toml" + +# --------------------------------------------------------------------------- +hr "STEP 7a: NEGATIVE -- flip one signature byte (expect ECDSA failure)" +SIGBAD=$(echo "$SIG" | sed -E 's/^\[([0-9]+),/["bad",/' ; true) +# bump first signature byte by 1 (mod 256) to keep it a valid u8 +FIRST=$(echo "$SIG" | sed -E 's/^\[([0-9]+),.*/\1/') +NEWFIRST=$(( (FIRST + 1) % 256 )) +sed -E "s/^signature = \[$FIRST,/signature = [$NEWFIRST,/" "$WD/good.toml" > "$HERE/Prover.toml" +if OUT=$("$NARGO" execute --program-dir "$HERE" 2>&1); then + red "FAIL: expected ECDSA assertion failure but execute succeeded"; exit 1 +else + echo "$OUT" | grep -iE "Assertion failed: ECDSA" && green "PASS: ECDSA signature assertion fired" +fi + +# --------------------------------------------------------------------------- +hr "STEP 7b: NEGATIVE -- signer_role=1 (leaf built for role 2; expect trust-list failure)" +sed -E 's/^signer_role = "2"/signer_role = "1"/' "$WD/good.toml" > "$HERE/Prover.toml" +if OUT=$("$NARGO" execute --program-dir "$HERE" 2>&1); then + red "FAIL: expected trust-list assertion failure but execute succeeded"; exit 1 +else + echo "$OUT" | grep -iE "Assertion failed: signer \(key, role\) not in trust list" \ + && green "PASS: trust-list (key,role) assertion fired" +fi + +# restore the good Prover.toml +cp "$WD/good.toml" "$HERE/Prover.toml" + +# --------------------------------------------------------------------------- +hr "STEP 8: HANDOFF -- proven anchor feeds bound_receivables" +br_run () { # $1 = financed array contents, $2 = label, $3 = expected bool + cat > "$QUORUM/bound_receivables/Prover.toml" <&1 | grep "Circuit output") + echo " $2 -> $OUT" + echo "$OUT" | grep -q "($3," && green " PASS: already_financed=$3 (anchor opened)" \ + || { red " FAIL: expected already_financed=$3"; exit 1; } +} +br_run '"0x01","0x02","0x03","0x04","0x05","0x06"' "clean (cid NOT financed)" "false" +br_run "\"0x01\",\"0x02\",\"$CANONICAL_ID\",\"0x04\",\"0x05\",\"0x06\"" "double (cid IS financed) " "true" + +hr "ALL CHECKS PASSED" diff --git a/experiments/conoir-spike/quorum/proof_a_receivable/src/main.nr b/experiments/conoir-spike/quorum/proof_a_receivable/src/main.nr new file mode 100644 index 0000000..1bd8145 --- /dev/null +++ b/experiments/conoir-spike/quorum/proof_a_receivable/src/main.nr @@ -0,0 +1,98 @@ +// proof_a_receivable -- Apertrue Quorum "Proof A" (per-party, single-prover, LOCAL). +// +// Replaces the off-circuit OpenSSL admission step with an in-circuit binding: +// it proves that an AUTHORISED, ROLE-BOUND signer put an ES256 signature on the +// receivable's canonical_id, and emits the role-stamped authenticity anchor as a +// PUBLIC output. That anchor becomes `candidate_anchor` for the joint +// `bound_receivables` MPC circuit -- so the one-bit answer provably rests on a +// real signer, not an asserted role. +// +// canonical_id = Poseidon2([invoice_uuid, debtor_id, amount, issue_date], 4) +// anchor = Poseidon2([canonical_id, salt, signer_role], 3) +// +// The trust list binds (signer_key, role): a leaf is Poseidon2([x, y, role]), so a +// signer can only claim the role its authorised entry encodes (SdI root -> role 1, +// obligor root -> role 2). This closes "claim role 2 without being the obligor". +// +// Signature: ECDSA P-256 over the 32-byte big-endian encoding of canonical_id used +// directly as the digest (raw ECDSA; canonical_id is already a Poseidon2 commitment, +// so no extra SHA-256 wrapper is needed). The signer produces a raw ECDSA signature +// over be32(canonical_id) (e.g. `openssl pkeyutl -sign` over the 32-byte value). + +use poseidon::poseidon2::Poseidon2; +use std::ecdsa_secp256r1::verify_signature; + +global MERKLE_DEPTH: u32 = 8; + +fn canonical_id(uuid: Field, debtor: Field, amount: Field, date: Field) -> Field { + Poseidon2::hash([uuid, debtor, amount, date], 4) +} + +fn anchor(cid: Field, salt: Field, signer_role: Field) -> Field { + Poseidon2::hash([cid, salt, signer_role], 3) +} + +// Interpret 32 big-endian bytes as a Field (reduces mod the BN254 prime, like proof_a). +fn be_bytes_to_field(b: [u8; 32]) -> Field { + let mut acc: Field = 0; + for i in 0..32 { + acc = acc * 256 + b[i] as Field; + } + acc +} + +// Depth-8 Poseidon2 Merkle root. indices[i] == 0 => current node is the LEFT child. +fn compute_merkle_root( + leaf: Field, + path: [Field; MERKLE_DEPTH], + indices: [Field; MERKLE_DEPTH], +) -> Field { + let mut current = leaf; + for i in 0..MERKLE_DEPTH { + let is_left = indices[i] == 0; + let left = if is_left { current } else { path[i] }; + let right = if is_left { path[i] } else { current }; + current = Poseidon2::hash([left, right], 2); + } + current +} + +fn main( + // --- public --- + trust_list_root: pub Field, // Quorum trust list (authorised signer (key,role) leaves) + signer_role: pub Field, // 1 = SdI clearance, 2 = obligor/debtor confirmation + // --- private: the receivable --- + invoice_uuid: Field, + debtor_id: Field, + amount: Field, + issue_date: Field, + salt: Field, + // --- private: the signer's ES256 signature over canonical_id --- + signer_pubkey_x: [u8; 32], + signer_pubkey_y: [u8; 32], + signature: [u8; 64], + canonical_id_bytes: [u8; 32], // big-endian encoding of canonical_id (the signed message) + // --- private: trust-list membership of (key, role) --- + merkle_path: [Field; MERKLE_DEPTH], + merkle_indices: [Field; MERKLE_DEPTH], +) -> pub Field { + // 1. recompute canonical_id from the receivable fields + let cid = canonical_id(invoice_uuid, debtor_id, amount, issue_date); + + // 2. the witnessed bytes must encode exactly that canonical_id (big-endian) + assert(be_bytes_to_field(canonical_id_bytes) == cid, "canonical_id bytes != canonical_id"); + + // 3. verify the signer's ECDSA P-256 signature over canonical_id (used as the digest) + let sig_ok = verify_signature(signer_pubkey_x, signer_pubkey_y, signature, canonical_id_bytes); + assert(sig_ok, "ECDSA P-256 signature over canonical_id failed"); + + // 4. the (signer_key, role) pair must be an authorised leaf -> binds role to signer + let x_f = be_bytes_to_field(signer_pubkey_x); + let y_f = be_bytes_to_field(signer_pubkey_y); + let leaf = Poseidon2::hash([x_f, y_f, signer_role], 3); + let root = compute_merkle_root(leaf, merkle_path, merkle_indices); + assert(root == trust_list_root, "signer (key, role) not in trust list"); + + // 5. emit the role-stamped anchor -> becomes candidate_anchor for bound_receivables + anchor(cid, salt, signer_role) +} diff --git a/experiments/conoir-spike/quorum/run_bound_aggregate.sh b/experiments/conoir-spike/quorum/run_bound_aggregate.sh new file mode 100755 index 0000000..ec73b3f --- /dev/null +++ b/experiments/conoir-spike/quorum/run_bound_aggregate.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Apertrue Quorum -- aggregate engine (marine over-insurance) through full 3-party REP3 MPC. +set -e +ROOT="$HOME/co-snarks-spike"; CO="$ROOT/target/release/co-noir" +EX="$ROOT/co-noir/co-noir/examples"; CFG="$EX/configs" +CRS1="$ROOT/co-noir/co-noir-common/src/crs/bn254_g1.dat" +CRS2="$ROOT/co-noir/co-noir-common/src/crs/bn254_g2.dat" +SRC="$HOME/apertrue/circuits/experiments/conoir-spike/quorum/bound_aggregate" +TV="$EX/test_vectors/bound_aggregate"; J="$TV/target/bound_aggregate.json" +INPUT="${1:-$SRC/Prover.toml}" + +rm -rf "$TV"; mkdir -p "$TV/src" +cp "$SRC/Nargo.toml" "$TV/"; cp "$SRC/src/main.nr" "$TV/src/"; cp "$INPUT" "$TV/Prover.toml" +( cd "$TV" && PATH="$HOME/.nargo/bin:$PATH" nargo compile ) + +cd "$TV" +"$CO" split-input --circuit "$J" --input "$TV/Prover.toml" --protocol REP3 --out-dir "$TV" +for i in 0 1 2; do "$CO" generate-witness --input "$TV/Prover.toml.$i.shared" --circuit "$J" --protocol REP3 --config "$CFG/party$((i+1)).toml" --out "$TV/witness.$i.shared" & done; wait +for i in 0 1 2; do "$CO" build-proving-key --witness "$TV/witness.$i.shared" --circuit "$J" --protocol REP3 --config "$CFG/party$((i+1)).toml" --out "$TV/pk.$i.shared" --crs "$CRS1" & done; wait +"$CO" create-vk --circuit "$J" --crs "$CRS1" --hasher keccak --vk "$TV/vk" +"$CO" generate-proof --proving-key "$TV/pk.0.shared" --protocol REP3 --hasher keccak --config "$CFG/party1.toml" --crs "$CRS1" --out "$TV/proof.0.proof" --vk "$TV/vk" --public-input "$TV/public_input" & +for i in 1 2; do "$CO" generate-proof --proving-key "$TV/pk.$i.shared" --protocol REP3 --hasher keccak --config "$CFG/party$((i+1)).toml" --crs "$CRS1" --out "$TV/proof.$i.proof" --vk "$TV/vk" & done; wait +"$CO" verify --proof "$TV/proof.0.proof" --public-input "$TV/public_input" --vk "$TV/vk" --hasher keccak --crs "$CRS2" +PUB="$TV/public_input" python3 - <<'PY' +import os +b=open(os.environ["PUB"],"rb").read(); n=len(b)//32 +out=int.from_bytes(b[(n-1)*32:n*32],"big") +print(" => over_insured =", out) +PY diff --git a/experiments/conoir-spike/quorum/run_bound_receivables.sh b/experiments/conoir-spike/quorum/run_bound_receivables.sh new file mode 100755 index 0000000..c722b4d --- /dev/null +++ b/experiments/conoir-spike/quorum/run_bound_receivables.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Apertrue Quorum: run bound_receivables through the full 3-party REP3 MPC pipeline. +# Answers "is this receivable already financed anywhere in the network?" revealing one bit. +# Usage: ./run_bound_receivables.sh [Prover.toml] (default: the clean case) +set -e +ROOT="$HOME/co-snarks-spike"; CO="$ROOT/target/release/co-noir" +EX="$ROOT/co-noir/co-noir/examples"; CFG="$EX/configs" +CRS1="$ROOT/co-noir/co-noir-common/src/crs/bn254_g1.dat" +CRS2="$ROOT/co-noir/co-noir-common/src/crs/bn254_g2.dat" +SRC="$HOME/apertrue/circuits/experiments/conoir-spike/quorum/bound_receivables" +TV="$EX/test_vectors/bound_receivables" +J="$TV/target/bound_receivables.json" +INPUT="${1:-$SRC/Prover.toml}" + +# stage circuit into the co-snarks clone + compile (beta.20) +rm -rf "$TV"; mkdir -p "$TV/src" +cp "$SRC/Nargo.toml" "$TV/"; cp "$SRC/src/main.nr" "$TV/src/"; cp "$INPUT" "$TV/Prover.toml" +( cd "$TV" && PATH="$HOME/.nargo/bin:$PATH" nargo compile ) + +cd "$TV" +echo "### 1. split-input ###" +"$CO" split-input --circuit "$J" --input "$TV/Prover.toml" --protocol REP3 --out-dir "$TV" +echo "### 2. generate-witness (3 parties) ###" +for i in 0 1 2; do "$CO" generate-witness --input "$TV/Prover.toml.$i.shared" --circuit "$J" --protocol REP3 --config "$CFG/party$((i+1)).toml" --out "$TV/witness.$i.shared" & done; wait +echo "### 3. build-proving-key (3 parties) ###" +for i in 0 1 2; do "$CO" build-proving-key --witness "$TV/witness.$i.shared" --circuit "$J" --protocol REP3 --config "$CFG/party$((i+1)).toml" --out "$TV/pk.$i.shared" --crs "$CRS1" & done; wait +echo "### 4. create-vk ###" +"$CO" create-vk --circuit "$J" --crs "$CRS1" --hasher keccak --vk "$TV/vk" +echo "### 5. generate-proof (3 parties) ###" +"$CO" generate-proof --proving-key "$TV/pk.0.shared" --protocol REP3 --hasher keccak --config "$CFG/party1.toml" --crs "$CRS1" --out "$TV/proof.0.proof" --vk "$TV/vk" --public-input "$TV/public_input" & +for i in 1 2; do "$CO" generate-proof --proving-key "$TV/pk.$i.shared" --protocol REP3 --hasher keccak --config "$CFG/party$((i+1)).toml" --crs "$CRS1" --out "$TV/proof.$i.proof" --vk "$TV/vk" & done; wait +echo "### 6. verify ###" +"$CO" verify --proof "$TV/proof.0.proof" --public-input "$TV/public_input" --vk "$TV/vk" --hasher keccak --crs "$CRS2" +echo "--- public output (anchor, role, allow-list, bit, guarantee) ---" +PUB="$TV/public_input" python3 - <<'PY' +import os +b=open(os.environ["PUB"],"rb").read() +n=len(b)//32 +vals=[int.from_bytes(b[i*32:(i+1)*32],"big") for i in range(n)] +# layout: [candidate_anchor, signer_role, accepted_roles[0..1], already_financed, signer_role_out] +def hexs(v): return str(v) if v<100000 else "0x"+("%064x"%v).lstrip("0") +labels=["candidate_anchor","IN signer_role","accepted_role[0]","accepted_role[1]","OUT already_financed","OUT signer_role"] +for i,v in enumerate(vals): + print(" %-22s = %s" % (labels[i] if i already_financed=%d | role=%d | guarantee: %s" % (fin, role, guar)) +PY diff --git a/experiments/conoir-spike/quorum/run_quorum_scenarios.sh b/experiments/conoir-spike/quorum/run_quorum_scenarios.sh new file mode 100755 index 0000000..219cac6 --- /dev/null +++ b/experiments/conoir-spike/quorum/run_quorum_scenarios.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Apertrue Quorum -- the two-signer thesis, end to end under MPC. +# Anchors come from Layer-2 admission (p1_provenance/admission/admission.sh). +Q="$HOME/apertrue/circuits/experiments/conoir-spike/quorum" +cd "$Q" + +OBL=0x276218a3095f1f2a85ee95ceeece1963fb83fbbc4da888208d85b2b3013822e5 # role 2, true (amount 500) +SDI_INFL=0x29b3212006ae841a7ad864218a471c73a7813f28140377223877cb26beef1180 # role 1, INFLATED (amount 5000) +SELLER=0x25bb81fa6fd90d294abd6719560ae8d74c88b559fee18245d9337ca23b54984e # role 3, true (not in allow-list) +CID_TRUE='"0x06cc60c66ce6389ea53783b55dc5ff1b480e9a43ecf4c942262f5d73d7a87280"' +# exactly BOOK_UNION=6 entries; CLEAN5 = first 5 (leave a slot for the double-finance hit) +CLEAN5='"0x01a1111111111111111111111111111111111111111111111111111111111111","0x02b2222222222222222222222222222222222222222222222222222222222222","0x03c3333333333333333333333333333333333333333333333333333333333333","0x04d4444444444444444444444444444444444444444444444444444444444444","0x05e5555555555555555555555555555555555555555555555555555555555555"' +CLEAN="$CLEAN5,\"0x06f6666666666666666666666666666666666666666666666666666666666666\"" + +prover() { # anchor role amount financed_csv -> /tmp/q.toml + cat > /tmp/q.toml <&1 | grep -iE "verified|verification failed|=> already_financed|allow-list|opening failed" || true; } + +echo "########## 1) OBLIGOR, true receivable, not financed (expect 0, owed) ##########" +prover "$OBL" 2 500 "$CLEAN"; run +echo "########## 2) OBLIGOR, true receivable, DOUBLE-FINANCED (expect 1, owed) ##########" +prover "$OBL" 2 500 "$CLEAN5,$CID_TRUE"; run +echo "########## 3) SdI, INFLATED invoice seller cleared, not financed (expect 0, cleared-NOT-owed = THE GAP) ##########" +prover "$SDI_INFL" 1 5000 "$CLEAN"; run +echo "########## 4) SELLER self-signed (role 3) -> MPC allow-list rejects (defense in depth) ##########" +prover "$SELLER" 3 500 "$CLEAN"; run +rm -f /tmp/q.toml \ No newline at end of file From 994a44ededb9d3ec46795031af3c1bbb40f10bbb Mon Sep 17 00:00:00 2001 From: Jamie Newton <33573418+newtsjamie@users.noreply.github.com> Date: Mon, 29 Jun 2026 23:44:15 +0100 Subject: [PATCH 9/9] feat(quorum): C2PA warehouse-receipt adapter (the C2PA-document leg) Parallel to the FatturaPA native-rail adapter, this proves the C2PA path where C2PA is the load-bearing carrier (an unstructured warehouse receipt with no clearance rail): - warehouse_adapter.py: wrap (c2pie embeds an org.apertrue.quorum.collateral custom assertion carrying canonical fields + operator pubkey + raw-ECDSA- over-canonical_id signature, hash-bound via c2pa.hash.data) and read (c2patool extracts the assertion; canonical_id re-derived from recovered fields, matches). - proves the SAME pipeline: C2PA doc -> Proof A (in-circuit ECDSA verify + role-bound trust-list membership) -> anchor == expected -> bound_receivables false->true. run_warehouse_pipeline.sh (idempotent). - ADAPTER.md: new C2PA-document adapter section; C2PA = carrier, trust = the in-circuit-verified signature. Now both adapter legs are demonstrated end-to-end: FatturaPA (native rail, no C2PA) and warehouse receipt (C2PA carrier). Stand-in keys gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../conoir-spike/quorum/_mkroot/Prover.toml | 4 +- .../conoir-spike/quorum/adapters/ADAPTER.md | 64 +++- .../quorum/adapters/run_warehouse_pipeline.sh | 160 ++++++++ .../quorum/adapters/warehouse_adapter.py | 347 ++++++++++++++++++ .../adapters/warehouse_adapter_object.json | 195 ++++++++++ .../quorum/adapters/warehouse_receipt.pdf | Bin 0 -> 19115 bytes .../quorum/adapters/warehouse_receipt.txt | 11 + .../adapters/warehouse_receipt_c2pa.pdf | Bin 0 -> 23398 bytes .../quorum/bound_receivables/Prover.toml | 12 +- .../quorum/commit_receivable/Prover.toml | 8 +- .../quorum/proof_a_receivable/Prover.toml | 18 +- 11 files changed, 796 insertions(+), 23 deletions(-) create mode 100755 experiments/conoir-spike/quorum/adapters/run_warehouse_pipeline.sh create mode 100644 experiments/conoir-spike/quorum/adapters/warehouse_adapter.py create mode 100644 experiments/conoir-spike/quorum/adapters/warehouse_adapter_object.json create mode 100644 experiments/conoir-spike/quorum/adapters/warehouse_receipt.pdf create mode 100644 experiments/conoir-spike/quorum/adapters/warehouse_receipt.txt create mode 100644 experiments/conoir-spike/quorum/adapters/warehouse_receipt_c2pa.pdf diff --git a/experiments/conoir-spike/quorum/_mkroot/Prover.toml b/experiments/conoir-spike/quorum/_mkroot/Prover.toml index e60908e..d00039f 100644 --- a/experiments/conoir-spike/quorum/_mkroot/Prover.toml +++ b/experiments/conoir-spike/quorum/_mkroot/Prover.toml @@ -1,5 +1,5 @@ -signer_pubkey_x = [184, 196, 60, 29, 156, 89, 169, 92, 112, 56, 23, 82, 52, 132, 157, 216, 26, 175, 28, 175, 154, 107, 100, 91, 224, 231, 205, 20, 85, 95, 222, 100] -signer_pubkey_y = [252, 213, 234, 45, 227, 108, 134, 93, 214, 236, 225, 34, 82, 254, 169, 210, 219, 200, 134, 59, 103, 33, 15, 47, 118, 153, 65, 29, 251, 84, 126, 248] +signer_pubkey_x = [118, 30, 224, 102, 155, 108, 46, 96, 35, 198, 166, 178, 25, 180, 27, 216, 240, 64, 196, 153, 145, 140, 214, 216, 98, 231, 196, 128, 219, 233, 217, 102] +signer_pubkey_y = [90, 109, 25, 193, 124, 211, 44, 180, 7, 38, 154, 58, 185, 254, 127, 175, 20, 153, 246, 236, 171, 71, 33, 56, 255, 210, 243, 76, 125, 122, 173, 32] signer_role = "2" merkle_path = ["0","0","0","0","0","0","0","0"] merkle_indices = ["0","0","0","0","0","0","0","0"] diff --git a/experiments/conoir-spike/quorum/adapters/ADAPTER.md b/experiments/conoir-spike/quorum/adapters/ADAPTER.md index e904df5..d293b10 100644 --- a/experiments/conoir-spike/quorum/adapters/ADAPTER.md +++ b/experiments/conoir-spike/quorum/adapters/ADAPTER.md @@ -83,6 +83,60 @@ anchor), checks the proven anchor equals the `commit_receivable` anchor for the `(salt=42, role=2)`, and hands the anchor to `bound_receivables` (clean → `false`, double-financed → `true`). +## 3b. C2PA-document adapter (warehouse receipt) — the *carrier* path + +The FatturaPA adapter reads canonical fields from a **native structured rail** (the +e-invoice XML). Many collateral documents have **no such rail** — a warehouse receipt, +a bill of lading, a deposit slip. For these, **C2PA is the load-bearing CARRIER**: the +signed canonical fields, the signer's public key, and the signer's raw-ECDSA signature +over `canonical_id` are embedded in a C2PA **custom assertion** and **hash-bound** to the +document, then read back out of the manifest. C2PA transports + binds; the *trust* is the +**signature**, verified in-circuit by Proof A — exactly as in the native-rail path. + +`warehouse_adapter.py` (driven end-to-end by `run_warehouse_pipeline.sh`), modes `wrap` / +`read` / `both`: + +1. **Canonical fields** (warehouse receipt, four fields, same structure as receivables; + names are adapter-local, mapped onto the generic 4-field circuit slots): + - `receipt_uuid = 7001234000042` (operator licence 7001234 · 1e6 + receipt no 42) → slot 1 + - `commodity_id = 74031100` (HS code, copper cathode grade A) → slot 2 + - `quantity = 25000` (250.00 metric tonnes, centi-tonnes) → slot 3 + - `deposit_date = 20240315` (2024-03-15) → slot 4 + - `canonical_id = 0x20adcccdd6e2e94b4a78a2cba482c287b37c3c840204f9412d5baa1cdb11a423` + (computed by `commit_receivable`, salt=42, signer_role=2). +2. **WRAP (C2PA is genuinely created).** Renders a warehouse-receipt PDF (`cupsfilter`), + then uses **c2pie** (the same tool as `p1_provenance/p1_next.py`) to embed a custom + assertion **`org.apertrue.quorum.collateral`** carrying the canonical fields, the + operator's `pubkey_x/y` (hex), and the **raw ECDSA P-256 signature over `canonical_id`** + (low-s, `r‖s` hex; §2 signing spec), plus a `c2pa.hash.data` hard binding over the whole + PDF. The manifest is signed with a stand-in RSA leaf→CA chain (PS256). Output: + `warehouse_receipt_c2pa.pdf`. +3. **READ (C2PA is genuinely parsed).** Parses the manifest back with **c2patool**, recovers + `{canonical_fields, pubkey_x, pubkey_y, signature, signer_role}`, **re-derives** + `canonical_id` from the *recovered* fields and asserts it equals the embedded value, and + emits `warehouse_adapter_object.json` (the **same interface shape** as fattura). +4. **Trust root + Proof A.** `_mkroot` builds the depth-8 Poseidon2 root from the operator + `(x, y, role=2)` leaf; `proof_a_receivable` does the **in-circuit ECDSA verify + trust-list + membership** over the C2PA-carried fields and emits the anchor; the proven anchor equals the + `commit_receivable` anchor `0x2f458e5a…7744`. +5. **Handoff.** The anchor feeds `bound_receivables` (accepted_roles `[1,2]`): `financed` + without the cid → `(false, 0x02)`, then with the cid → `(true, 0x02)` — anchor opens cleanly. + +The exact carrier commands (proving C2PA is exercised, not faked): +- **wrap**: `c2pie_GenerateManifest([CustomJsonAssertion("org.apertrue.quorum.collateral", …), + c2pie_GenerateHashDataAssertion(…)], rsa_leaf.key, rsa_chain.pem)` → + `c2pie_EmplaceManifest(C2PA_ContentTypes.pdf, …)` (in `warehouse_adapter.py wrap`). +- **read**: `c2patool warehouse_receipt_c2pa.pdf` → JSON → pull the + `org.apertrue.quorum.collateral` assertion (in `warehouse_adapter.py read`). + +### Caveats specific to this adapter +- The **warehouse-operator P-256 key is a STAND-IN** (`adapters/keys/operator_standin.key`); + the real operator/custodian key is the partner crux (same security note as §4.1). +- The **C2PA manifest signer is a stand-in RSA leaf→CA chain** (`adapters/keys/c2pa_leaf.*`, + PS256); c2pie is the carrier tool. A production manifest would chain to a real C2PA CA. +- Proof A trusts the operator **leaf key directly** via the trust list (Poseidon2 `(x,y,role)` + leaf → root), not an in-circuit X.509 chain — the same §4.2 follow-up applies. + ## 4. Honest caveats (follow-ups for a design partner) 1. **The obligor key is a STAND-IN.** The real obligor private key (an Agenzia delle @@ -102,7 +156,13 @@ double-financed → `true`). ## 5. Files -- `adapters/fattura_adapter.py` — the FatturaPA adapter (XML → interface object). +- `adapters/fattura_adapter.py` — the FatturaPA adapter (native XML → interface object). - `adapters/run_fattura_pipeline.sh` — full end-to-end runner (idempotent). - `adapters/fattura_adapter_object.json` — the emitted interface object for this invoice. -- `adapters/keys/obligor_standin.*` — minted stand-in obligor key+cert (gitignore-worthy). +- `adapters/keys/obligor_standin.*` — minted stand-in obligor key+cert (gitignored). +- `adapters/warehouse_adapter.py` — the C2PA warehouse-receipt adapter (C2PA assertion → interface object); modes wrap/read/both. Needs a python with `c2pie`+`pypdf`. +- `adapters/run_warehouse_pipeline.sh` — full end-to-end runner (idempotent); auto-detects a c2pie python (or set `C2PIE_PY`). +- `adapters/warehouse_adapter_object.json` — the emitted interface object (recovered from the C2PA manifest). +- `adapters/warehouse_receipt.pdf` / `warehouse_receipt_c2pa.pdf` — the rendered receipt and its C2PA-signed form. +- `adapters/keys/operator_standin.*` — minted stand-in warehouse-operator P-256 key+cert (gitignored). +- `adapters/keys/c2pa_leaf.* / c2pa_ca.*` — minted stand-in RSA chain that signs the C2PA manifest (gitignored). diff --git a/experiments/conoir-spike/quorum/adapters/run_warehouse_pipeline.sh b/experiments/conoir-spike/quorum/adapters/run_warehouse_pipeline.sh new file mode 100755 index 0000000..7d71794 --- /dev/null +++ b/experiments/conoir-spike/quorum/adapters/run_warehouse_pipeline.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# run_warehouse_pipeline.sh -- FULL Quorum pipeline on a C2PA WAREHOUSE RECEIPT. +# +# Parallel to run_fattura_pipeline.sh, but the canonical fields are NOT read from a +# native structured rail -- they are CARRIED BY C2PA. The adapter creates a warehouse +# receipt PDF, embeds the signed canonical fields + operator key + raw-ECDSA signature +# in a C2PA custom assertion (org.apertrue.quorum.collateral) hash-bound to the file, +# then READS them back out of the manifest. C2PA is the load-bearing carrier; the +# trust is the operator's signature, verified IN-CIRCUIT by Proof A. +# +# warehouse receipt PDF + C2PA manifest (c2pie wrap) +# -> warehouse_adapter.py read (parse C2PA -> adapter-interface object) +# -> commit_receivable (expected role-bound anchor for the same fields/salt/role) +# -> _mkroot (Poseidon2 depth-8 trust-list root for operator (key,role)) +# -> proof_a_receivable (in-circuit: ECDSA verify + trust-list membership -> anchor) +# -> commit anchor == proven anchor? +# -> bound_receivables (double-financing one-bit answer: clean=false, double=true) +# +# Idempotent: reuses the stand-in operator P-256 key + C2PA RSA chain, recomputes the rest. +# Requires: nargo (1.0.0-beta.20) at ~/.nargo/bin/nargo, openssl, c2patool, cupsfilter, +# and a python with `c2pie` + `pypdf` importable (set C2PIE_PY, else auto-detected). +set -euo pipefail + +NARGO="${NARGO:-$HOME/.nargo/bin/nargo}" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # .../quorum/adapters +QUORUM="$(cd "$HERE/.." && pwd)" +SALT="${SALT:-42}" +SIGNER_ROLE="${SIGNER_ROLE:-2}" +OBJ="$HERE/warehouse_adapter_object.json" +C2PA_PDF="$HERE/warehouse_receipt_c2pa.pdf" + +red() { printf '\033[31m%s\033[0m\n' "$*"; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +hr() { printf '\n==== %s ====\n' "$*"; } + +# ---- locate a python that can import c2pie (the C2PA carrier tool) ---- +find_c2pie_py() { + local cand + for cand in "${C2PIE_PY:-}" \ + "/private/tmp/claude-501/-Users-jamienewton/1c6e4848-1e0b-4678-af70-eb7bad88f949/scratchpad/c2pie_venv/bin/python3" \ + "$HERE/.c2pie_venv/bin/python3" \ + "$QUORUM/p1_provenance/venv/bin/python3" \ + "python3"; do + [ -z "$cand" ] && continue + if command -v "$cand" >/dev/null 2>&1 || [ -x "$cand" ]; then + if "$cand" -c "import c2pie, pypdf" >/dev/null 2>&1; then echo "$cand"; return 0; fi + fi + done + # last resort: build a local venv (needs network) + local v="$HERE/.c2pie_venv" + python3 -m venv "$v" >/dev/null 2>&1 || true + "$v/bin/pip" install -q c2pie pypdf >/dev/null 2>&1 || true + if "$v/bin/python3" -c "import c2pie, pypdf" >/dev/null 2>&1; then echo "$v/bin/python3"; return 0; fi + return 1 +} + +C2PIE_PY="$(find_c2pie_py)" || { red "FATAL: no python with c2pie+pypdf. Set C2PIE_PY=/path/to/venv/python3"; exit 1; } +echo "c2pie python: $C2PIE_PY" + +# --------------------------------------------------------------------------- +hr "STEP 1: WRAP -- build warehouse receipt PDF + embed signed fields in C2PA" +"$C2PIE_PY" "$HERE/warehouse_adapter.py" --mode wrap --salt "$SALT" --signer-role "$SIGNER_ROLE" +green "wrote C2PA-signed PDF: $C2PA_PDF" + +# prove C2PA is genuinely exercised: show the embedded assertion labels via c2patool +echo "-- c2patool assertion labels (read from the signed PDF) --" +c2patool "$C2PA_PDF" 2>&1 | "$C2PIE_PY" -c \ + "import sys,json;d=json.load(sys.stdin);m=d['manifests'][d['active_manifest']];print(' ',[a['label'] for a in m['assertions']])" + +# --------------------------------------------------------------------------- +hr "STEP 2: READ -- parse C2PA manifest back -> adapter-interface object" +"$C2PIE_PY" "$HERE/warehouse_adapter.py" --mode read --salt "$SALT" --signer-role "$SIGNER_ROLE" \ + --pdf "$C2PA_PDF" --out "$OBJ" >/dev/null +read -r RECEIPT_UUID COMMODITY_ID QUANTITY DEPOSIT_DATE CANONICAL_ID < <(python3 -c " +import json;o=json.load(open('$OBJ'));f=o['canonical_fields'] +print(f['receipt_uuid'],f['commodity_id'],f['quantity'],f['deposit_date'],o['canonical_id'])") +X=$(python3 -c "import json;print(json.load(open('$OBJ'))['obligor']['pubkey_x'])") +Y=$(python3 -c "import json;print(json.load(open('$OBJ'))['obligor']['pubkey_y'])") +SIG=$(python3 -c "import json;print(json.load(open('$OBJ'))['obligor']['signature'])") +CIDB=$(python3 -c "import json;print(json.load(open('$OBJ'))['canonical_id_bytes'])") +echo "receipt_uuid = $RECEIPT_UUID (recovered from C2PA assertion)" +echo "commodity_id = $COMMODITY_ID" +echo "quantity = $QUANTITY" +echo "deposit_date = $DEPOSIT_DATE" +echo "canonical_id = $CANONICAL_ID (re-derived from recovered fields, matches embedded)" + +# --------------------------------------------------------------------------- +hr "STEP 3: commit_receivable -> expected role-bound anchor (same salt, role)" +cat > "$QUORUM/commit_receivable/Prover.toml" <&1 | grep "Circuit output") +EXPECTED_ANCHOR=$(echo "$CR_OUT" | sed -E 's/.*\[(0x[0-9a-f]+), (0x[0-9a-f]+)\].*/\2/') +echo "expected anchor = $EXPECTED_ANCHOR" + +# --------------------------------------------------------------------------- +hr "STEP 4: _mkroot -> trust_list_root for operator (key, role) leaf" +cat > "$QUORUM/_mkroot/Prover.toml" <&1 | grep "Circuit output" | sed -E 's/.*(0x[0-9a-f]+).*/\1/') +echo "trust_list_root = $ROOT" + +# --------------------------------------------------------------------------- +hr "STEP 5: proof_a_receivable -- in-circuit ECDSA verify + membership -> anchor" +cat > "$QUORUM/proof_a_receivable/Prover.toml" <&1 | grep "Circuit output" | sed -E 's/.*(0x[0-9a-f]+).*/\1/') +echo "proven anchor = $PROVEN_ANCHOR" +if [ "$PROVEN_ANCHOR" = "$EXPECTED_ANCHOR" ]; then + green "PASS: proof_a anchor == commit_receivable anchor (in-circuit ECDSA over C2PA-carried fields)" +else + red "FAIL: anchor mismatch (proven $PROVEN_ANCHOR vs expected $EXPECTED_ANCHOR)"; exit 1 +fi + +# --------------------------------------------------------------------------- +hr "STEP 6: bound_receivables -- double-financing one-bit answer (accepted_roles incl. 2)" +br_run () { # $1 = financed array contents, $2 = label, $3 = expected bool + cat > "$QUORUM/bound_receivables/Prover.toml" <&1 | grep "Circuit output") + echo " $2 -> $OUT" + echo "$OUT" | grep -q "($3," && green " PASS: already_financed=$3 (anchor opened cleanly)" \ + || { red " FAIL: expected already_financed=$3"; exit 1; } +} +br_run '"0x01","0x02","0x03","0x04","0x05","0x06"' "clean (cid NOT in financed book)" "false" +br_run "\"0x01\",\"0x02\",\"$CANONICAL_ID\",\"0x04\",\"0x05\",\"0x06\"" "double (cid IS in financed book)" "true" + +hr "ALL CHECKS PASSED -- C2PA warehouse receipt proven end-to-end (C2PA carried the fields)" diff --git a/experiments/conoir-spike/quorum/adapters/warehouse_adapter.py b/experiments/conoir-spike/quorum/adapters/warehouse_adapter.py new file mode 100644 index 0000000..5933b1d --- /dev/null +++ b/experiments/conoir-spike/quorum/adapters/warehouse_adapter.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python3 +""" +warehouse_adapter.py -- Apertrue Quorum C2PA WAREHOUSE-RECEIPT adapter. + +Parallel to fattura_adapter.py, but for an UNSTRUCTURED collateral document (a +warehouse receipt) that has NO native clearance/structured rail to ride. So here +**C2PA is the load-bearing CARRIER**: the signed canonical fields + the operator's +key + the operator's raw-ECDSA signature over canonical_id are embedded in a C2PA +custom assertion and hash-bound to the document, then READ BACK out of the manifest. + +It emits the SAME Quorum adapter-interface object that `proof_a_receivable` consumes: + + { canonical_fields: {receipt_uuid, commodity_id, quantity, deposit_date}, + canonical_id, canonical_id_bytes, + obligor: { pubkey_x:[32], pubkey_y:[32], signature:[64] r||s low-s, signer_role:2 } } + +Field mapping onto the (generic, 4-field) commit_receivable / proof_a circuits: + receipt_uuid -> slot 1 (invoice_uuid) + commodity_id -> slot 2 (debtor_id) + quantity -> slot 3 (amount) + deposit_date -> slot 4 (issue_date) +The circuit hashes four Fields generically, so the *names* are adapter-local. + +Signing spec (Quorum standard, identical to fattura): RAW ECDSA P-256 over the +32-byte big-endian canonical_id used DIRECTLY as the digest (NO sha256 wrapper), +low-S normalised. signer_role = 2 (the non-gameable custodian/operator attestation; +reuses role 2 so bound_receivables' accepted_roles works unchanged). + +Modes: + wrap -- mint keys, sign canonical_id, build the warehouse-receipt PDF, embed a + C2PA `org.apertrue.quorum.collateral` custom assertion + c2pa.hash.data + hard binding (REQUIRES the c2pie venv python). + read -- parse the C2PA manifest back out (c2patool), recover {fields, pubkey, + signature, signer_role}, re-derive canonical_id from the RECOVERED fields + and assert it matches the embedded one, emit warehouse_adapter_object.json. + both -- wrap then read (default). + +STAND-IN caveats (see ADAPTER.md): the warehouse-operator P-256 key is a minted +stand-in (real operator key is the partner crux); the C2PA manifest is signed by a +minted RSA leaf->CA chain (PS256). c2pie is the carrier tool. Proof A trusts the +operator LEAF key directly via the trust list, not an in-circuit X.509 chain. +""" +import argparse, base64, binascii, json, os, subprocess, sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +QUORUM = os.path.dirname(HERE) +NARGO = os.environ.get("NARGO", os.path.expanduser("~/.nargo/bin/nargo")) +KEYDIR = os.path.join(HERE, "keys") +N_P256 = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + +# --- concrete warehouse-receipt canonical fields (Meridian Bonded Storage, receipt 42) --- +DEFAULT_FIELDS = { + "receipt_uuid": 7001234000042, # operator licence 7001234 * 1e6 + receipt no 42 + "commodity_id": 74031100, # HS code: copper cathode, grade A + "quantity": 25000, # 250.00 metric tonnes, in centi-tonnes + "deposit_date": 20240315, # 2024-03-15 +} +RECEIPT_PDF = os.path.join(HERE, "warehouse_receipt.pdf") +C2PA_PDF = os.path.join(HERE, "warehouse_receipt_c2pa.pdf") +COLLATERAL_LABEL = "org.apertrue.quorum.collateral" + + +# --------------------------------------------------------------------------- # +# circuit helpers (shared with fattura adapter) +# --------------------------------------------------------------------------- # +def compute_canonical_id(fields, salt, signer_role): + """Run commit_receivable to get [canonical_id, anchor]. canonical_id is field 0.""" + toml = (f'invoice_uuid = "{fields["receipt_uuid"]}"\n' + f'debtor_id = "{fields["commodity_id"]}"\n' + f'amount = "{fields["quantity"]}"\n' + f'issue_date = "{fields["deposit_date"]}"\n' + f'salt = "{salt}"\n' + f'signer_role = "{signer_role}"\n') + cr = os.path.join(QUORUM, "commit_receivable") + open(os.path.join(cr, "Prover.toml"), "w").write(toml) + out = subprocess.run([NARGO, "execute", "--program-dir", cr], + capture_output=True, text=True).stdout + line = next(l for l in out.splitlines() if "Circuit output" in l) + inner = line.split("Circuit output:", 1)[1].split("[", 1)[1].rsplit("]", 1)[0] + cid, anchor = [t.strip() for t in inner.split(",")] + return cid, anchor + + +def mint_standin_operator(): + """Idempotently mint a stand-in warehouse-operator P-256 key + self-signed cert.""" + key = os.path.join(KEYDIR, "operator_standin.key") + pem = os.path.join(KEYDIR, "operator_standin.pem") + os.makedirs(KEYDIR, exist_ok=True) + if not os.path.exists(key): + subprocess.run(["openssl", "ecparam", "-name", "prime256v1", "-genkey", + "-noout", "-out", key], check=True, capture_output=True) + subprocess.run(["openssl", "req", "-new", "-x509", "-key", key, "-out", pem, + "-days", "3650", + "-subj", "/CN=warehouse_operator_standin/O=Apertrue Quorum STANDIN"], + check=True, capture_output=True) + return key + + +def mint_c2pa_rsa_chain(): + """Idempotently mint an RSA CA->leaf chain (PS256, emailProtection EKU) for the + C2PA manifest signer. Returns (leaf_pkcs8_key_path, chain_pem_path).""" + os.makedirs(KEYDIR, exist_ok=True) + ca_key = os.path.join(KEYDIR, "c2pa_ca.key") + ca_pem = os.path.join(KEYDIR, "c2pa_ca.pem") + leaf_t = os.path.join(KEYDIR, "c2pa_leaf_trad.key") + leaf_k = os.path.join(KEYDIR, "c2pa_leaf.key") # PKCS#8 (what c2pie wants) + leaf_p = os.path.join(KEYDIR, "c2pa_leaf.pem") # = chain (leaf only, no root) + csr = os.path.join(KEYDIR, "c2pa_leaf.csr") + ext = os.path.join(KEYDIR, "c2pa_leaf.ext") + if not os.path.exists(leaf_k): + run = lambda *a: subprocess.run(a, check=True, capture_output=True) + run("openssl", "genrsa", "-out", ca_key, "3072") + run("openssl", "req", "-new", "-x509", "-key", ca_key, "-out", ca_pem, "-days", "3650", + "-subj", "/CN=apertrue_quorum_c2pa_ca/O=Apertrue Quorum STANDIN", + "-addext", "basicConstraints=critical,CA:TRUE") + run("openssl", "genrsa", "-out", leaf_t, "3072") + run("openssl", "pkcs8", "-topk8", "-nocrypt", "-in", leaf_t, "-out", leaf_k) + run("openssl", "req", "-new", "-key", leaf_k, "-out", csr, + "-subj", "/CN=apertrue_quorum_c2pa_leaf/O=Apertrue Quorum STANDIN") + open(ext, "w").write("keyUsage=critical,digitalSignature\nextendedKeyUsage=emailProtection\n") + run("openssl", "x509", "-req", "-in", csr, "-CA", ca_pem, "-CAkey", ca_key, + "-CAcreateserial", "-out", leaf_p, "-days", "3650", "-extfile", ext) + return leaf_k, leaf_p + + +def raw_sign_and_extract(key_path, cid_hex, workdir): + """Raw-ECDSA sign the 32-byte canonical_id; return (x[32], y[32], sig r||s low-s [64]).""" + cid_bin = os.path.join(workdir, "wh_cid.bin") + sig_der = os.path.join(workdir, "wh_sig.der") + pub_der = os.path.join(workdir, "wh_pub.der") + open(cid_bin, "wb").write(binascii.unhexlify(cid_hex)) + # pkeyutl -sign treats the EC input bytes AS the digest -> raw ECDSA, no sha256 wrapper + subprocess.run(["openssl", "pkeyutl", "-sign", "-inkey", key_path, + "-in", cid_bin, "-out", sig_der], check=True, capture_output=True) + subprocess.run(["openssl", "ec", "-in", key_path, "-pubout", + "-conv_form", "uncompressed", "-outform", "DER", "-out", pub_der], + check=True, capture_output=True) + pub = open(pub_der, "rb").read() + point = pub[-65:] + assert point[0] == 4, "public key is not an uncompressed point" + x = list(point[1:33]); y = list(point[33:65]) + der = open(sig_der, "rb").read() + assert der[0] == 0x30 + def read_int(b, i): + assert b[i] == 0x02 + ln = b[i + 1] + return b[i + 2:i + 2 + ln], i + 2 + ln + r, i = read_int(der, 2) + s, _ = read_int(der, i) + sv = int.from_bytes(s, "big") + if sv > N_P256 // 2: # Noir verify_signature requires LOW-S (canonical) + sv = N_P256 - sv + s = sv.to_bytes(32, "big") + r = r.lstrip(b"\x00").rjust(32, b"\x00") + sig = list(r) + list(s) + return x, y, sig + + +# --------------------------------------------------------------------------- # +# the warehouse-receipt document +# --------------------------------------------------------------------------- # +def build_receipt_pdf(fields, out_pdf): + """Render a simple warehouse-receipt text -> PDF via cupsfilter (idempotent).""" + qty = fields["quantity"] / 100.0 + d = str(fields["deposit_date"]) + txt = f"""WAREHOUSE RECEIPT (NON-NEGOTIABLE) + +Operator : Meridian Bonded Storage Ltd (licence 7001234) +Receipt UUID : {fields['receipt_uuid']} +Commodity : Copper Cathode, Grade A (HS {fields['commodity_id']}) +Quantity : {qty:.2f} metric tonnes +Deposit date : {d[0:4]}-{d[4:6]}-{d[6:8]} +Location : Bonded Warehouse 7, Rotterdam + +This receipt evidences collateral held on deposit and is the carrier for the +Apertrue Quorum custodian attestation embedded as a C2PA assertion in this file. +""" + txt_path = os.path.join(HERE, "warehouse_receipt.txt") + open(txt_path, "w").write(txt) + raw = subprocess.run(["/usr/sbin/cupsfilter", txt_path], + check=True, capture_output=True).stdout + open(out_pdf, "wb").write(raw) + return out_pdf + + +# --------------------------------------------------------------------------- # +# WRAP: embed the signed canonical fields into a C2PA custom assertion +# --------------------------------------------------------------------------- # +def wrap(fields, salt, signer_role): + import hashlib + from c2pie.interface import (c2pie_GenerateHashDataAssertion, + c2pie_GenerateManifest, c2pie_EmplaceManifest) + from c2pie.utils.assertion_schemas import json_to_bytes, C2PA_AssertionTypes + from c2pie.utils.content_types import C2PA_ContentTypes, jumbf_content_types + from c2pie.jumbf_boxes.super_box import SuperBox + from c2pie.jumbf_boxes.content_box import ContentBox + + class CustomJsonAssertion(SuperBox): + """C2PA assertion with an arbitrary label + JSON payload (c2pie ships only 3 enum types).""" + def __init__(self, label, schema): + self.type = C2PA_AssertionTypes.creative_work # any value != data_hash + self.schema = schema + cb = ContentBox(box_type=b"json".hex(), payload=json_to_bytes(schema)) + super().__init__(content_type=jumbf_content_types["json"], label=label, + content_boxes=[cb]) + def get_data_for_signing(self): + return self.description_box.serialize() + self.serialize_content_boxes() + + canonical_id, anchor = compute_canonical_id(fields, salt, signer_role) + cid_hex = canonical_id[2:].rjust(64, "0") + + op_key = mint_standin_operator() + x, y, sig = raw_sign_and_extract(op_key, cid_hex, KEYDIR) + + build_receipt_pdf(fields, RECEIPT_PDF) + raw = open(RECEIPT_PDF, "rb").read() + + # The collateral assertion IS the rail: it carries the signed canonical fields, + # the operator's pubkey (x,y) and the raw-ECDSA signature over canonical_id. + collateral = { + "scheme": "WarehouseReceipt", + "signer_role": signer_role, + "signer_role_name": "warehouse-operator / custodian attestation", + "guarantee": "the collateral is genuinely on deposit", + "non_gameable_signer": "Meridian Bonded Storage Ltd (operator) [STAND-IN CERT]", + "canonical_fields": { + "receipt_uuid": fields["receipt_uuid"], + "commodity_id": fields["commodity_id"], + "quantity": fields["quantity"], + "deposit_date": fields["deposit_date"], + }, + "canonical_id": canonical_id, + "anchor": anchor, + "salt": salt, + # the operator's P-256 attestation over canonical_id (the load-bearing TRUST) + "operator_pubkey_x_hex": "".join(f"{b:02x}" for b in x), + "operator_pubkey_y_hex": "".join(f"{b:02x}" for b in y), + "signature_rs_low_s_hex": "".join(f"{b:02x}" for b in sig), + "signing_spec": "raw ECDSA P-256 over be32(canonical_id) used directly as digest (no sha256), low-s", + "source_document": "warehouse_receipt.pdf (this file)", + } + collateral_assertion = CustomJsonAssertion(COLLATERAL_LABEL, collateral) + + # hard binding over the WHOLE pdf; manifest appended at EOF + cai_offset = len(raw) + hash_data = c2pie_GenerateHashDataAssertion(cai_offset=cai_offset, + hashed_data=hashlib.sha256(raw).digest()) + manifest = c2pie_GenerateManifest( + assertions=[collateral_assertion, hash_data], + private_key=open(mint_c2pa_rsa_chain()[0], "rb").read(), + certificate_chain=open(mint_c2pa_rsa_chain()[1], "rb").read(), + ) + signed = c2pie_EmplaceManifest(C2PA_ContentTypes.pdf, raw, cai_offset, manifest) + open(C2PA_PDF, "wb").write(signed) + print(f"# wrap: signed C2PA warehouse receipt -> {C2PA_PDF} ({len(signed)} bytes)", + file=sys.stderr) + return C2PA_PDF + + +# --------------------------------------------------------------------------- # +# READ: pull the assertion back out of the C2PA manifest (c2patool) +# --------------------------------------------------------------------------- # +def read_manifest(pdf_path): + """Parse the C2PA manifest via c2patool, return (collateral_assertion_dict, manifest_label).""" + out = subprocess.run(["c2patool", pdf_path], check=True, capture_output=True, text=True).stdout + rep = json.loads(out) + label = rep["active_manifest"] + man = rep["manifests"][label] + for a in man["assertions"]: + if a["label"] == COLLATERAL_LABEL: + return a["data"], label + raise SystemExit(f"FAIL: {COLLATERAL_LABEL} assertion not found in {pdf_path}") + + +def read_and_emit(pdf_path, salt, signer_role, out_path): + coll, manifest_label = read_manifest(pdf_path) + f = coll["canonical_fields"] + fields = { + "receipt_uuid": int(f["receipt_uuid"]), + "commodity_id": int(f["commodity_id"]), + "quantity": int(f["quantity"]), + "deposit_date": int(f["deposit_date"]), + } + # Re-derive canonical_id from the RECOVERED fields -> must match the embedded value. + cid_recomputed, anchor_recomputed = compute_canonical_id(fields, salt, signer_role) + if cid_recomputed != coll["canonical_id"]: + raise SystemExit(f"FAIL: recomputed canonical_id {cid_recomputed} != " + f"embedded {coll['canonical_id']}") + print(f"# read: canonical_id from C2PA matches recompute ({cid_recomputed})", file=sys.stderr) + + cid_hex = cid_recomputed[2:].rjust(64, "0") + cid_bytes = list(binascii.unhexlify(cid_hex)) + x = list(binascii.unhexlify(coll["operator_pubkey_x_hex"])) + y = list(binascii.unhexlify(coll["operator_pubkey_y_hex"])) + sig = list(binascii.unhexlify(coll["signature_rs_low_s_hex"])) + assert len(x) == 32 and len(y) == 32 and len(sig) == 64, "bad recovered key/sig lengths" + + obj = { + "scheme": "WarehouseReceipt", + "source_document": os.path.basename(pdf_path), + "carrier": "C2PA custom assertion (org.apertrue.quorum.collateral), hash-bound to the PDF", + "c2pa_manifest_label": manifest_label, + "canonical_fields": fields, + "canonical_id": cid_recomputed, + "canonical_id_bytes": cid_bytes, + "obligor": { + "pubkey_x": x, + "pubkey_y": y, + "signature": sig, # 64 bytes r||s, low-s normalised + "signer_role": int(signer_role), + }, + "_role_bound_anchor_at_salt": {"salt": int(salt), "anchor": anchor_recomputed}, + "_notes": { + "signing_spec": "raw ECDSA P-256 over be32(canonical_id) used directly as digest (no sha256)", + "carrier_note": "C2PA is the load-bearing CARRIER: the operator key+signature are " + "transported in a C2PA custom assertion and hash-bound to the file; " + "trust is the SIGNATURE, verified in-circuit by Proof A.", + "operator_key": "STAND-IN: minted P-256 key (adapters/keys/operator_standin.key); " + "real warehouse-operator key is the partner crux, not present in repo", + "c2pa_manifest_signer": "STAND-IN RSA leaf->CA chain (adapters/keys/c2pa_leaf.*), PS256", + "field_mapping": "receipt_uuid->slot1, commodity_id->slot2, quantity->slot3, deposit_date->slot4", + }, + } + open(out_path, "w").write(json.dumps(obj, indent=2)) + print(json.dumps(obj, indent=2)) + print(f"\n# wrote {out_path}", file=sys.stderr) + return obj + + +def main(): + ap = argparse.ArgumentParser(description="C2PA warehouse receipt -> Quorum adapter-interface object") + ap.add_argument("--mode", choices=["wrap", "read", "both"], default="both") + ap.add_argument("--salt", default="42") + ap.add_argument("--signer-role", default="2") + ap.add_argument("--pdf", default=C2PA_PDF, help="C2PA pdf to read (read mode)") + ap.add_argument("--out", default=os.path.join(HERE, "warehouse_adapter_object.json")) + args = ap.parse_args() + fields = dict(DEFAULT_FIELDS) + + if args.mode in ("wrap", "both"): + wrap(fields, args.salt, args.signer_role) + if args.mode in ("read", "both"): + read_and_emit(args.pdf, args.salt, args.signer_role, args.out) + + +if __name__ == "__main__": + main() diff --git a/experiments/conoir-spike/quorum/adapters/warehouse_adapter_object.json b/experiments/conoir-spike/quorum/adapters/warehouse_adapter_object.json new file mode 100644 index 0000000..e0e2ab3 --- /dev/null +++ b/experiments/conoir-spike/quorum/adapters/warehouse_adapter_object.json @@ -0,0 +1,195 @@ +{ + "scheme": "WarehouseReceipt", + "source_document": "warehouse_receipt_c2pa.pdf", + "carrier": "C2PA custom assertion (org.apertrue.quorum.collateral), hash-bound to the PDF", + "c2pa_manifest_label": "urn:uuid:78be8d074dc94d639d9b1e957f711ffe", + "canonical_fields": { + "receipt_uuid": 7001234000042, + "commodity_id": 74031100, + "quantity": 25000, + "deposit_date": 20240315 + }, + "canonical_id": "0x20adcccdd6e2e94b4a78a2cba482c287b37c3c840204f9412d5baa1cdb11a423", + "canonical_id_bytes": [ + 32, + 173, + 204, + 205, + 214, + 226, + 233, + 75, + 74, + 120, + 162, + 203, + 164, + 130, + 194, + 135, + 179, + 124, + 60, + 132, + 2, + 4, + 249, + 65, + 45, + 91, + 170, + 28, + 219, + 17, + 164, + 35 + ], + "obligor": { + "pubkey_x": [ + 118, + 30, + 224, + 102, + 155, + 108, + 46, + 96, + 35, + 198, + 166, + 178, + 25, + 180, + 27, + 216, + 240, + 64, + 196, + 153, + 145, + 140, + 214, + 216, + 98, + 231, + 196, + 128, + 219, + 233, + 217, + 102 + ], + "pubkey_y": [ + 90, + 109, + 25, + 193, + 124, + 211, + 44, + 180, + 7, + 38, + 154, + 58, + 185, + 254, + 127, + 175, + 20, + 153, + 246, + 236, + 171, + 71, + 33, + 56, + 255, + 210, + 243, + 76, + 125, + 122, + 173, + 32 + ], + "signature": [ + 61, + 17, + 95, + 117, + 81, + 75, + 66, + 152, + 32, + 140, + 81, + 150, + 199, + 115, + 60, + 143, + 170, + 225, + 36, + 183, + 118, + 223, + 194, + 4, + 226, + 161, + 77, + 154, + 35, + 179, + 239, + 76, + 114, + 64, + 251, + 219, + 186, + 150, + 60, + 34, + 51, + 101, + 172, + 8, + 103, + 207, + 149, + 84, + 50, + 142, + 0, + 255, + 216, + 243, + 180, + 9, + 119, + 63, + 175, + 221, + 207, + 177, + 200, + 156 + ], + "signer_role": 2 + }, + "_role_bound_anchor_at_salt": { + "salt": 42, + "anchor": "0x2f458e5aee3d011eebd93c81e310de32f13892134be3f27c7fccac4d1cef7744" + }, + "_notes": { + "signing_spec": "raw ECDSA P-256 over be32(canonical_id) used directly as digest (no sha256)", + "carrier_note": "C2PA is the load-bearing CARRIER: the operator key+signature are transported in a C2PA custom assertion and hash-bound to the file; trust is the SIGNATURE, verified in-circuit by Proof A.", + "operator_key": "STAND-IN: minted P-256 key (adapters/keys/operator_standin.key); real warehouse-operator key is the partner crux, not present in repo", + "c2pa_manifest_signer": "STAND-IN RSA leaf->CA chain (adapters/keys/c2pa_leaf.*), PS256", + "field_mapping": "receipt_uuid->slot1, commodity_id->slot2, quantity->slot3, deposit_date->slot4" + } +} \ No newline at end of file diff --git a/experiments/conoir-spike/quorum/adapters/warehouse_receipt.pdf b/experiments/conoir-spike/quorum/adapters/warehouse_receipt.pdf new file mode 100644 index 0000000000000000000000000000000000000000..58e436f4d698cb25a3445833b5f3134d999b6a94 GIT binary patch literal 19115 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 literal 0 HcmV?d00001 diff --git a/experiments/conoir-spike/quorum/bound_receivables/Prover.toml b/experiments/conoir-spike/quorum/bound_receivables/Prover.toml index 71ce1c7..d7c9fe7 100644 --- a/experiments/conoir-spike/quorum/bound_receivables/Prover.toml +++ b/experiments/conoir-spike/quorum/bound_receivables/Prover.toml @@ -1,9 +1,9 @@ -candidate_anchor = "0x108137c71e47833c5655288aea2160955230af60e82c0d0fd4f9d8297c912d0b" +candidate_anchor = "0x2f458e5aee3d011eebd93c81e310de32f13892134be3f27c7fccac4d1cef7744" signer_role = "2" accepted_roles = ["1", "2"] -invoice_uuid = "1234567890000123" -debtor_id = "9876543210" -amount = "500" -issue_date = "20141218" +invoice_uuid = "7001234000042" +debtor_id = "74031100" +amount = "25000" +issue_date = "20240315" salt = "42" -financed = ["0x01","0x02","0x06cc60c66ce6389ea53783b55dc5ff1b480e9a43ecf4c942262f5d73d7a87280","0x04","0x05","0x06"] +financed = ["0x01","0x02","0x20adcccdd6e2e94b4a78a2cba482c287b37c3c840204f9412d5baa1cdb11a423","0x04","0x05","0x06"] diff --git a/experiments/conoir-spike/quorum/commit_receivable/Prover.toml b/experiments/conoir-spike/quorum/commit_receivable/Prover.toml index 4ced4a5..cfe065b 100644 --- a/experiments/conoir-spike/quorum/commit_receivable/Prover.toml +++ b/experiments/conoir-spike/quorum/commit_receivable/Prover.toml @@ -1,6 +1,6 @@ -invoice_uuid = "1234567890000123" -debtor_id = "9876543210" -amount = "500" -issue_date = "20141218" +invoice_uuid = "7001234000042" +debtor_id = "74031100" +amount = "25000" +issue_date = "20240315" salt = "42" signer_role = "2" diff --git a/experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml b/experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml index fc381fd..af561e9 100644 --- a/experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml +++ b/experiments/conoir-spike/quorum/proof_a_receivable/Prover.toml @@ -1,13 +1,13 @@ -trust_list_root = "0x0ed8af316e7f4b18c08d40bee7ca210d382fa442858498d9ff317769de826c15" +trust_list_root = "0x13eb3c8373e7337dd54fcd50935dde6b5a2269b9b1d0de5b277174a3d7c1fed1" signer_role = "2" -invoice_uuid = "1234567890000123" -debtor_id = "9876543210" -amount = "500" -issue_date = "20141218" +invoice_uuid = "7001234000042" +debtor_id = "74031100" +amount = "25000" +issue_date = "20240315" salt = "42" -signer_pubkey_x = [184, 196, 60, 29, 156, 89, 169, 92, 112, 56, 23, 82, 52, 132, 157, 216, 26, 175, 28, 175, 154, 107, 100, 91, 224, 231, 205, 20, 85, 95, 222, 100] -signer_pubkey_y = [252, 213, 234, 45, 227, 108, 134, 93, 214, 236, 225, 34, 82, 254, 169, 210, 219, 200, 134, 59, 103, 33, 15, 47, 118, 153, 65, 29, 251, 84, 126, 248] -signature = [7, 37, 93, 31, 67, 230, 53, 149, 219, 170, 249, 240, 195, 233, 219, 71, 90, 67, 107, 87, 62, 241, 161, 56, 110, 203, 223, 223, 220, 59, 5, 231, 22, 206, 41, 160, 113, 75, 198, 70, 214, 51, 9, 208, 151, 254, 19, 12, 109, 140, 73, 102, 198, 240, 222, 13, 199, 200, 40, 223, 82, 158, 214, 251] -canonical_id_bytes = [6, 204, 96, 198, 108, 230, 56, 158, 165, 55, 131, 181, 93, 197, 255, 27, 72, 14, 154, 67, 236, 244, 201, 66, 38, 47, 93, 115, 215, 168, 114, 128] +signer_pubkey_x = [118, 30, 224, 102, 155, 108, 46, 96, 35, 198, 166, 178, 25, 180, 27, 216, 240, 64, 196, 153, 145, 140, 214, 216, 98, 231, 196, 128, 219, 233, 217, 102] +signer_pubkey_y = [90, 109, 25, 193, 124, 211, 44, 180, 7, 38, 154, 58, 185, 254, 127, 175, 20, 153, 246, 236, 171, 71, 33, 56, 255, 210, 243, 76, 125, 122, 173, 32] +signature = [61, 17, 95, 117, 81, 75, 66, 152, 32, 140, 81, 150, 199, 115, 60, 143, 170, 225, 36, 183, 118, 223, 194, 4, 226, 161, 77, 154, 35, 179, 239, 76, 114, 64, 251, 219, 186, 150, 60, 34, 51, 101, 172, 8, 103, 207, 149, 84, 50, 142, 0, 255, 216, 243, 180, 9, 119, 63, 175, 221, 207, 177, 200, 156] +canonical_id_bytes = [32, 173, 204, 205, 214, 226, 233, 75, 74, 120, 162, 203, 164, 130, 194, 135, 179, 124, 60, 132, 2, 4, 249, 65, 45, 91, 170, 28, 219, 17, 164, 35] merkle_path = ["0","0","0","0","0","0","0","0"] merkle_indices = ["0","0","0","0","0","0","0","0"]