-
Notifications
You must be signed in to change notification settings - Fork 0
experiment(conoir): coNoir × apertrue double-dip feasibility + locked architecture #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c20f166
e7ef52d
d440018
dcd9cdf
49d654c
896f49b
adfe467
f92dd6d
994a44e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # 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`). | ||
|
|
||
| ## 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]`). | ||
|
|
||
| ## 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. | ||
|
|
||
| ## 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. | ||
|
|
||
| ## 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). | ||
|
|
||
| ## 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). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Comment on lines
+8
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Fix benchmark path constants to match this package layout. Line 9 targets 💡 Suggested patch-NARGO=~/.nargo/bin/nargo
-CO=../../../target/release/co-noir
-TV=test_vectors/imt_nm_scale
+NARGO=~/.nargo/bin/nargo
+CO=../../target/release/co-noir
+TV=imt_nm_scaleAlso applies to: 17-17, 35-35 🤖 Prompt for AI Agents |
||
| 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" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| target/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" } | ||
|
Comment on lines
+5
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Cross-check declared versions vs documented compatibility notes.
rg -n 'compiler_version|poseidon|beta\.18|beta\.20|v0\.1\.1|v0\.3\.0' \
experiments/conoir-spike/binding_wrapper/Nargo.toml \
experiments/conoir-spike/bound_nm/Nargo.toml \
experiments/conoir-spike/README.mdRepository: Apertrue/circuits Length of output: 1465 🏁 Script executed: cat -n experiments/conoir-spike/README.md | sed -n '20,100p'Repository: Apertrue/circuits Length of output: 5786 🏁 Script executed: cat -n experiments/conoir-spike/README.md | sed -n '99,120p'Repository: Apertrue/circuits Length of output: 1908 🏁 Script executed: cat experiments/conoir-spike/binding_wrapper/Nargo.tomlRepository: Apertrue/circuits Length of output: 680 🏁 Script executed: cat -n experiments/conoir-spike/README.md | sed -n '85,105p'Repository: Apertrue/circuits Length of output: 1681 Pin The manifest declares 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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]; | ||
|
|
||
|
Comment on lines
+23
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Compare wrapper vs upstream verifier pattern for VK hash binding.
rg -n 'verify_honk_proof|vk_hash|VK hash mismatch|assert\(vk_hash' \
experiments/conoir-spike/binding_wrapper/src/main.nr \
aztec_verifier/src/main.nrRepository: Apertrue/circuits Length of output: 2307 🏁 Script executed: # Read the full main.nr file from binding_wrapper to understand current implementation
cat -n experiments/conoir-spike/binding_wrapper/src/main.nrRepository: Apertrue/circuits Length of output: 1901 🏁 Script executed: # Check the aztec_verifier function signature to understand the allowlisted vk_hash pattern
sed -n '155,210p' aztec_verifier/src/main.nrRepository: Apertrue/circuits Length of output: 3377 Constrain The Compare this to Proposed hardening fn main(
proof: UltraHonkZKProof,
vk: UltraHonkVerificationKey,
public_values: [Field; 12],
vk_hash: Field,
+ expected_vk_hash: pub Field,
blind: Field,
) -> pub Field {
+ assert(vk_hash == expected_vk_hash, "VK hash mismatch");
verify_honk_proof(vk, proof, public_values, vk_hash);
let nullifier = public_values[2];
Poseidon2::hash([nullifier, blind], 2)
}🤖 Prompt for AI Agents |
||
| // 3. Emit the hiding commitment to the authentic nullifier. | ||
| Poseidon2::hash([nullifier, blind], 2) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| [package] | ||
| name = "bound_nm" | ||
| type = "bin" | ||
| compiler_version = ">=1.0.0" | ||
| authors = ["Apertrue"] | ||
|
|
||
| [dependencies] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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] | ||
| } | ||
|
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify the two commitment constructions used by the seam.
rg -n 'poseidon2_permutation|Poseidon2::hash|fn commit' \
experiments/conoir-spike/bound_nm/src/main.nr \
experiments/conoir-spike/binding_wrapper/src/main.nr \
tree_aggregator/src/main.nr
# Expected: one canonical construction everywhere, or explicit documented/tested equivalence.Repository: Apertrue/circuits Length of output: 1047 🏁 Script executed: # Check imports and understand what Poseidon2::hash and poseidon2_permutation are
cat -n experiments/conoir-spike/bound_nm/src/main.nr | head -30Repository: Apertrue/circuits Length of output: 1852 🏁 Script executed: # Check binding_wrapper to understand line 29 in full context
cat -n experiments/conoir-spike/binding_wrapper/src/main.nr | head -35Repository: Apertrue/circuits Length of output: 1901 🏁 Script executed: # Search for any tests or documentation about Poseidon2 commitment equivalence
fd -t f '.*test.*\|.*spec.*' experiments/conoir-spike/ | head -20Repository: Apertrue/circuits Length of output: 155 🏁 Script executed: # Look for Poseidon2 imports and their origins
rg -n 'use.*Poseidon2|use.*poseidon2_permutation' experiments/conoir-spike/Repository: Apertrue/circuits Length of output: 371 🏁 Script executed: # Search for any test files that test commitments
fd -t f '\.(test|spec)\.' --type f . | xargs grep -l 'commit\|Poseidon2' 2>/dev/null | head -20Repository: Apertrue/circuits Length of output: 155 🏁 Script executed: # Check if there are any comments or documentation about the equivalence
rg -n 'poseidon2_permutation|equivalence|commitment.*hash' experiments/conoir-spike/ --type nr -B 2 -A 2Repository: Apertrue/circuits Length of output: 182 🏁 Script executed: # Check the README for any notes about this issue
cat experiments/conoir-spike/README.mdRepository: Apertrue/circuits Length of output: 7911 🏁 Script executed: # Search for any tests in the entire repository that might test this
find . -name '*.nr' -type f | xargs grep -l 'fn.*test\|#\[test\]' 2>/dev/null | head -10Repository: Apertrue/circuits Length of output: 577 Unify the commitment primitive across
🤖 Prompt for AI Agents |
||
|
|
||
| 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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| [package] | ||
| name = "imt_nm_mini" | ||
| type = "bin" | ||
| compiler_version = ">=1.0.0" | ||
| authors = ["Apertrue"] | ||
|
|
||
| [dependencies] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix markdown heading hierarchy violation.
Line 2 is marked as h3 (
###) but should be h2 (##) to comply with markdown heading-level incrementing rules (no skipping levels). This is flagged by markdownlint MD001.🔧 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 2-2: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
🤖 Prompt for AI Agents
Source: Linters/SAST tools