From e12508514347f19fdd85d2334a01781a5711c9d3 Mon Sep 17 00:00:00 2001 From: retraca Date: Thu, 11 Jun 2026 17:11:43 +0800 Subject: [PATCH 1/5] solution: LP-0005 Private Balance Attestation --- solutions/LP-0005.md | 92 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 solutions/LP-0005.md diff --git a/solutions/LP-0005.md b/solutions/LP-0005.md new file mode 100644 index 0000000..8d2c34a --- /dev/null +++ b/solutions/LP-0005.md @@ -0,0 +1,92 @@ +# Solution: LP-0005 — Private Balance Attestation + +**Submitted by:** retraca (Gonçalo Traça) + +## Summary + +A zero-knowledge balance attestation protocol. A user proves their LEZ account balance exceeds a threshold without revealing the actual balance, their account ID, or their nullifier secret key. The proof is one-shot: a nullifier prevents the same key from re-proving to the same gating program. + +Two components: + +**`circuit/guest`** -- RISC0 zkVM guest that recomputes the LEZ commitment from private inputs (`nsk`, `program_owner`, `balance`, `data`, `nonce`), verifies Merkle membership, checks `balance >= threshold_n`, and commits a nullifier `SHA256("balance-attest/v1" || nsk || context_id || use_nonce)` to the journal. The nullifier is entirely circuit-bound -- it cannot be supplied or forged by the caller. + +**`programs/balance_attestation`** -- LEZ on-chain gating program. Verifies the RISC0 receipt against a compile-time `IMAGE_ID`, checks `context_id` matches the program's own account ID, enforces Merkle root freshness, rejects spent nullifiers, and verifies the presenter's Ed25519 signature over `(context_id || merkle_root || threshold_n || nullifier)`. Any downstream program can call this as a gate. + +**`circuit/host`** -- Off-chain CLI (`balance-attest prove / verify`) that fetches the Merkle proof, runs the prover, and signs the journal for on-chain submission. + +## Repository + +- **Repo:** https://github.com/retraca/lp-0005-balance-attestation + +## Approach + +### Commitment replication + +The guest replicates `nssa/core/src/commitment.rs` exactly: + +``` +NPK = SHA256("LEE/keys" || nsk || 0x07 || [0;23]) +data_hash = SHA256(data) +commitment = SHA256(PREFIX || NPK || program_owner_le || balance_le || nonce_le || data_hash) +``` + +where `PREFIX = b"/LEE/v0.3/Commitment/\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"`. + +The recomputed commitment is verified against the Merkle tree -- if the leaf is in the tree, the balance is genuine. + +### Security properties + +**Nullifier caller-bypass prevented.** The nullifier is computed inside the zkVM guest and committed to the journal. The on-chain verifier reads `journal.nullifier` -- there is no caller-supplied nullifier parameter. + +**Merkle root freshness.** `GateState.accepted_root` holds the current commitment tree root. A proof over a stale root is rejected with `ERR_STALE_ROOT`. + +**Presenter binding.** The caller signs `(context_id || merkle_root || threshold_n || nullifier)` with an Ed25519 key. The verifier checks the signature against `journal.presenter_pk`. This prevents a relayer from submitting someone else's proof on their behalf. + +**IMAGE_ID zero-check.** A `const_assert` at compile time prevents deploying the program before the guest circuit is built. + +### Error codes + +| Code | Meaning | +|------|---------| +| 5001 | ERR_PROOF_INVALID | +| 5002 | ERR_CONTEXT_MISMATCH | +| 5003 | ERR_THRESHOLD_NOT_MET | +| 5004 | ERR_SIGNATURE_INVALID | +| 5005 | ERR_STALE_ROOT | +| 5006 | ERR_NULLIFIER_SPENT | + +## Testing + +```bash +# Start local chain +cd lez-build && ./start-chain.sh + +# Prove balance >= 500 against gating program +balance-attest prove \ + --nsk <64-char-hex> \ + --program-owner <64-char-hex> \ + --balance 1000 \ + --threshold 500 \ + --context-id \ + --presenter-sk <64-char-hex> + +# Verify receipt offline +balance-attest verify \ + --receipt receipt.bin \ + --context-id \ + --threshold 500 \ + --presenter-pk \ + --sig +``` + +## Files + +``` +lp-0005-balance-attestation/ + circuit/ + guest/src/main.rs # RISC0 zkVM guest + host/src/main.rs # CLI: balance-attest prove / verify + host/src/prover.rs # Host prover + Merkle proof fetch + programs/ + balance_attestation/src/lib.rs # LEZ on-chain gating program +``` From 40195a2dfb593c08862a2a509834114740394476 Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 00:25:30 +0800 Subject: [PATCH 2/5] fix: submitted by -> retraca only --- solutions/LP-0005.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/LP-0005.md b/solutions/LP-0005.md index 8d2c34a..dcbdddd 100644 --- a/solutions/LP-0005.md +++ b/solutions/LP-0005.md @@ -1,6 +1,6 @@ # Solution: LP-0005 — Private Balance Attestation -**Submitted by:** retraca (Gonçalo Traça) +**Submitted by:** retraca ## Summary From 09a23f6e105f5fbce0cb93a1333ddbf81305874d Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 12:07:10 +0800 Subject: [PATCH 3/5] solution: add program_id for LP-0005 deployment --- solutions/LP-0005.md | 1 + 1 file changed, 1 insertion(+) diff --git a/solutions/LP-0005.md b/solutions/LP-0005.md index dcbdddd..6fb060e 100644 --- a/solutions/LP-0005.md +++ b/solutions/LP-0005.md @@ -17,6 +17,7 @@ Two components: ## Repository - **Repo:** https://github.com/retraca/lp-0005-balance-attestation +- **Program ID:** `870d3f11c6d7f2902272c9d00009e0febe7f393d95f31c0ace9bb0da113d6719` ## Approach From d1231216dee52a8ae661291e0dfa92561928b793 Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 13:09:37 +0800 Subject: [PATCH 4/5] docs: LP-0005 solution doc -- add required validator sections Add Success Criteria Checklist (honest [x]/[ ] against prize spec), FURPS Self-Assessment (5 subsections with real prose), and Terms & Conditions acknowledgment. Also rewrites Approach to document the env::verify() LEZ-native pattern and the off-chain verifier-lib. --- solutions/LP-0005.md | 128 ++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 50 deletions(-) diff --git a/solutions/LP-0005.md b/solutions/LP-0005.md index 6fb060e..4a2e847 100644 --- a/solutions/LP-0005.md +++ b/solutions/LP-0005.md @@ -4,15 +4,9 @@ ## Summary -A zero-knowledge balance attestation protocol. A user proves their LEZ account balance exceeds a threshold without revealing the actual balance, their account ID, or their nullifier secret key. The proof is one-shot: a nullifier prevents the same key from re-proving to the same gating program. +A zero-knowledge balance attestation protocol. A user proves their LEZ account balance exceeds a threshold without revealing the actual balance, their account ID, or their nullifier secret key. The proof is one-shot: a circuit-bound nullifier prevents the same key from re-proving to the same gating program. -Two components: - -**`circuit/guest`** -- RISC0 zkVM guest that recomputes the LEZ commitment from private inputs (`nsk`, `program_owner`, `balance`, `data`, `nonce`), verifies Merkle membership, checks `balance >= threshold_n`, and commits a nullifier `SHA256("balance-attest/v1" || nsk || context_id || use_nonce)` to the journal. The nullifier is entirely circuit-bound -- it cannot be supplied or forged by the caller. - -**`programs/balance_attestation`** -- LEZ on-chain gating program. Verifies the RISC0 receipt against a compile-time `IMAGE_ID`, checks `context_id` matches the program's own account ID, enforces Merkle root freshness, rejects spent nullifiers, and verifies the presenter's Ed25519 signature over `(context_id || merkle_root || threshold_n || nullifier)`. Any downstream program can call this as a gate. - -**`circuit/host`** -- Off-chain CLI (`balance-attest prove / verify`) that fetches the Merkle proof, runs the prover, and signs the journal for on-chain submission. +Two verification paths: **on-chain** (LEZ SPEL gating program) and **off-chain** (standalone verifier library, partial — Logos Messaging integration not yet wired). ## Repository @@ -21,29 +15,41 @@ Two components: ## Approach -### Commitment replication +### Circuit design -The guest replicates `nssa/core/src/commitment.rs` exactly: +The RISC0 guest (`circuit/guest/src/main.rs`) replicates the LEZ private account commitment format exactly: ``` -NPK = SHA256("LEE/keys" || nsk || 0x07 || [0;23]) -data_hash = SHA256(data) +NPK = SHA256("LEE/keys" || nsk || 0x07 || [0;23]) +data_hash = SHA256(data) commitment = SHA256(PREFIX || NPK || program_owner_le || balance_le || nonce_le || data_hash) ``` where `PREFIX = b"/LEE/v0.3/Commitment/\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"`. -The recomputed commitment is verified against the Merkle tree -- if the leaf is in the tree, the balance is genuine. +Private inputs: `nsk`, `program_owner`, `balance`, `data`, `nonce`, Merkle proof path. Public inputs (journal): `merkle_root`, `threshold_n`, `context_id`, `presenter_pk`, `nullifier`. + +The guest recomputes the commitment from private inputs, verifies Merkle membership against `merkle_root`, checks `balance >= threshold_n`, then commits the nullifier. + +### On-chain verification path + +The LEZ program (`programs/balance_attestation`) is written using the SPEL framework (`#[lez_program]` / `#[instruction]`). Receipt verification uses `env::verify(IMAGE_ID, &journal_words)` inside the guest binary — this is the correct LEZ-native pattern. Calling `Receipt::verify()` at the host level inflates the ELF and fails at runtime with `sys_verify_integrity: no receipt found to resolve assumption`; `env::verify()` defers verification to the assumption-checking mechanism already wired into the zkVM environment. + +Logical flow: deserialise gate state → verify proof via `env::verify` → decode journal → check context match → check threshold → reject spent nullifiers → verify presenter signature → write updated state. + +### Off-chain verification path + +`verifier-lib/src/lib.rs` exposes `verify_attestation(receipt_bytes, image_id, expected_context_id, expected_threshold, expected_presenter_pk, presenter_sig)`. It covers: receipt verification, journal decode, context match, threshold check, presenter public key match, Ed25519 signature verification. Logos Messaging integration is not implemented; the library can be called directly by any recipient with the proof bytes. ### Security properties -**Nullifier caller-bypass prevented.** The nullifier is computed inside the zkVM guest and committed to the journal. The on-chain verifier reads `journal.nullifier` -- there is no caller-supplied nullifier parameter. +**Nullifier caller-bypass prevented.** The nullifier `SHA256("balance-attest/v1" || nsk || context_id || use_nonce)` is computed inside the zkVM guest and committed to the journal. The on-chain verifier reads `journal.nullifier` — there is no caller-supplied nullifier parameter. -**Merkle root freshness.** `GateState.accepted_root` holds the current commitment tree root. A proof over a stale root is rejected with `ERR_STALE_ROOT`. +**Presenter binding.** The caller signs `(context_id || merkle_root || threshold_n || nullifier)` with an Ed25519 key committed in the proof as `presenter_pk`. The verifier checks the signature; a relayer cannot present someone else's proof without the presenter's private key. -**Presenter binding.** The caller signs `(context_id || merkle_root || threshold_n || nullifier)` with an Ed25519 key. The verifier checks the signature against `journal.presenter_pk`. This prevents a relayer from submitting someone else's proof on their behalf. +**Merkle root freshness.** `GateState.accepted_root` stores the current root. Proofs over stale roots are rejected with `ERR_STALE_ROOT`. -**IMAGE_ID zero-check.** A `const_assert` at compile time prevents deploying the program before the guest circuit is built. +**Context binding.** The program reads its own account ID as `context_id` and checks `journal.context_id == context_id`. A proof generated for gate A cannot be replayed at gate B. ### Error codes @@ -56,38 +62,60 @@ The recomputed commitment is verified against the Merkle tree -- if the leaf is | 5005 | ERR_STALE_ROOT | | 5006 | ERR_NULLIFIER_SPENT | -## Testing - -```bash -# Start local chain -cd lez-build && ./start-chain.sh - -# Prove balance >= 500 against gating program -balance-attest prove \ - --nsk <64-char-hex> \ - --program-owner <64-char-hex> \ - --balance 1000 \ - --threshold 500 \ - --context-id \ - --presenter-sk <64-char-hex> - -# Verify receipt offline -balance-attest verify \ - --receipt receipt.bin \ - --context-id \ - --threshold 500 \ - --presenter-pk \ - --sig -``` +## Success Criteria Checklist -## Files +- [x] A shielded token account holder can generate a client-side proof that their balance meets a public threshold N — `circuit/host` CLI (`balance-attest prove`) fetches the Merkle proof and runs the RISC0 prover locally. +- [x] The proof is verifiable without revealing `npk`, exact balance, or account identity — private inputs never appear in the journal; only `merkle_root`, `threshold_n`, `context_id`, `presenter_pk`, `nullifier` are committed. +- [x] The proof is bound to a specific context — `context_id` is checked on-chain against the program's own account ID; the circuit commits it in the journal so it cannot be swapped post-proof. +- [x] The proof is bound to the presenter's identity — Ed25519 signature over `(context_id || merkle_root || threshold_n || nullifier)` is verified against `journal.presenter_pk` before the nullifier is marked spent. +- [x] The circuit targets the existing LEZ private account commitment format (`SHA256(npk || program_owner || balance || nonce || SHA256(data))`) — see `circuit/guest/src/main.rs` and the commitment replication in `circuit/host/src/main.rs::compute_commitment`. +- [x] **On-chain path** — LEZ SPEL gating program (`programs/balance_attestation`) accepts and verifies the proof; the `gate` instruction guards any downstream on-chain action. +- [ ] **Off-chain path via Logos Messaging** — `verifier-lib` provides the full off-chain verification logic; Logos Messaging transport integration is not yet wired. The library can be consumed by any caller; connecting it to Logos Messaging requires adding the send/receive layer. +- [x] A standalone consumer integration demo is included — `basecamp-app/` provides a browser-based commitment-hash tool; `demo.sh` exercises the full prove/verify CLI flow offline. +- [x] Full documentation and clean public repository — `README.md` covers deployment, CLI usage, and Basecamp app loading; `SOLUTION.md` covers circuit design and integration guide. -``` -lp-0005-balance-attestation/ - circuit/ - guest/src/main.rs # RISC0 zkVM guest - host/src/main.rs # CLI: balance-attest prove / verify - host/src/prover.rs # Host prover + Merkle proof fetch - programs/ - balance_attestation/src/lib.rs # LEZ on-chain gating program -``` +## FURPS Self-Assessment + +### Functionality + +The on-chain path is fully functional: proof generation (`balance-attest prove`), on-chain gate deployment (`initialize`), and proof submission (`gate`). The circuit correctly replicates the LEZ commitment format. Context binding, presenter binding, and nullifier deduplication all work. Error codes are distinct and documented for each failure mode. + +The off-chain path is partially functional: `verify_attestation` in `verifier-lib` verifies a proof locally (receipt check, journal decode, context match, threshold check, presenter key match, signature check). Logos Messaging transport is not wired — a consumer integrating the library must handle the message send/receive step. This is the main gap relative to the full prize spec. + +### Usability + +CLI: `balance-attest prove` requires `nsk`, `program_owner`, `balance`, `threshold`, `context-id`, `presenter-sk`, and `sequencer` URL. Output is `receipt.bin` plus printed `presenter_pk`, `sig`, and `nullifier`. `balance-attest verify` checks a receipt file offline without chain access. + +Basecamp app (`basecamp-app/index.html`) loads in Logos Core / Basecamp. It computes the commitment hash client-side using the Web Crypto API given `nsk`, `program_owner`, `balance`, `data`, `nonce` — all computation stays in-browser, `nsk` is never sent anywhere. + +`verifier-lib` ships as a Rust crate with a single public function; its doc example is in `lib.rs`. + +### Reliability + +The on-chain program returns explicit error codes for all invalid-proof cases (see error code table above). `env::verify` failure, journal decode failure, context mismatch, threshold not met, invalid presenter sig, and spent nullifier each produce a distinct code. The circuit never panics on valid input formats — all fallible operations return `Result` propagated through `SpelResult`. + +Off-chain: `verify_attestation` returns an `anyhow::Error` with a message for each failure case, not a panic. Private account data does not appear in error messages. + +### Performance + +Proof generation in `RISC0_DEV_MODE=1`: under 2 seconds on a laptop. Full RISC0 proof (`RISC0_DEV_MODE=0`): expected 5–20 minutes on CPU (consistent with other RISC0-based submissions on this prize). On-chain CU cost: not yet benchmarked against a running LEZ testnet sequencer — the program is not deployed on testnet. This is an open gap. + +### Supportability + +CI (`.github/workflows/ci.yml`) runs on push and PR. Three jobs: `rust-guest` (clippy + check for the zkVM guest), `rust-lib` (clippy + test for `programs/` and `verifier-lib`), `rust-host` (clippy + check for the CLI). All jobs green on the default branch. + +Integration tests: 11 tests in `programs/balance_attestation/tests/integration.rs` cover initialize, valid gate call, nullifier reuse rejection, context mismatch, threshold failure, stale root, and wrong presenter. Tests run against the program logic directly (no live sequencer needed for CI). + +The program is not deployed on LEZ testnet — `demo.sh` runs the offline prove/verify flow using `RISC0_DEV_MODE=1` and does not require a sequencer. Testnet deployment is the remaining step before human evaluation. + +## Supporting Materials + +- Repository: https://github.com/retraca/lp-0005-balance-attestation +- Off-chain verifier library: `verifier-lib/src/lib.rs` +- Basecamp app: `basecamp-app/index.html` +- Offline demo: `demo.sh` +- IDL: `lp-0005-balance-attestation.idl.json` + +## Terms & Conditions + +By submitting this solution, I confirm that I have read and agree to the [Terms & Conditions](../TERMS.md). From 462aa3817f6e1647befaf7a4be0445ff88e3710d Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 15:30:10 +0800 Subject: [PATCH 5/5] solution: LP-0005 off-chain path delivered -- Logos Messaging transport, gatekeeper, replay denial --- solutions/LP-0005.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/solutions/LP-0005.md b/solutions/LP-0005.md index 4a2e847..2efc942 100644 --- a/solutions/LP-0005.md +++ b/solutions/LP-0005.md @@ -6,7 +6,7 @@ A zero-knowledge balance attestation protocol. A user proves their LEZ account balance exceeds a threshold without revealing the actual balance, their account ID, or their nullifier secret key. The proof is one-shot: a circuit-bound nullifier prevents the same key from re-proving to the same gating program. -Two verification paths: **on-chain** (LEZ SPEL gating program) and **off-chain** (standalone verifier library, partial — Logos Messaging integration not yet wired). +Two verification paths: **on-chain** (LEZ SPEL gating program) and **off-chain** (verifier library + Logos Messaging transport: the attestation travels over the Waku relay network and a gatekeeper verifies it locally to grant token-gated group admission, no on-chain transaction). ## Repository @@ -39,7 +39,9 @@ Logical flow: deserialise gate state → verify proof via `env::verify` → deco ### Off-chain verification path -`verifier-lib/src/lib.rs` exposes `verify_attestation(receipt_bytes, image_id, expected_context_id, expected_threshold, expected_presenter_pk, presenter_sig)`. It covers: receipt verification, journal decode, context match, threshold check, presenter public key match, Ed25519 signature verification. Logos Messaging integration is not implemented; the library can be called directly by any recipient with the proof bytes. +`verifier-lib/src/lib.rs` exposes `verify_attestation(receipt_bytes, image_id, expected_context_id, expected_threshold, expected_presenter_pk, presenter_sig)`. It covers: receipt verification, journal decode, context match, threshold check, presenter public key match, Ed25519 signature verification over `(context_id || merkle_root || threshold_n || nullifier)`. + +The Logos Messaging transport lives in `messaging/` (`attest-msg`). A prover publishes the attestation envelope to the content topic `/lp0005/1/attest/json` via a Waku node's REST API. A gatekeeper subscribes on its own node, verifies each incoming attestation locally with `verifier-lib`, tracks spent nullifiers in a persisted state file, and publishes an admit/deny verdict to `/lp0005/1/gate-response/json`. Admission to a token-gated group ("vip-room") is the reference flow. `demo-offchain.sh` runs the full path against two relay-connected nwaku nodes: proof generation, transmission across the relay mesh, local verification, admission, and a replay attempt that is denied because the nullifier is already spent. ### Security properties @@ -70,7 +72,7 @@ Logical flow: deserialise gate state → verify proof via `env::verify` → deco - [x] The proof is bound to the presenter's identity — Ed25519 signature over `(context_id || merkle_root || threshold_n || nullifier)` is verified against `journal.presenter_pk` before the nullifier is marked spent. - [x] The circuit targets the existing LEZ private account commitment format (`SHA256(npk || program_owner || balance || nonce || SHA256(data))`) — see `circuit/guest/src/main.rs` and the commitment replication in `circuit/host/src/main.rs::compute_commitment`. - [x] **On-chain path** — LEZ SPEL gating program (`programs/balance_attestation`) accepts and verifies the proof; the `gate` instruction guards any downstream on-chain action. -- [ ] **Off-chain path via Logos Messaging** — `verifier-lib` provides the full off-chain verification logic; Logos Messaging transport integration is not yet wired. The library can be consumed by any caller; connecting it to Logos Messaging requires adding the send/receive layer. +- [x] **Off-chain path via Logos Messaging** — `messaging/attest-msg` transmits the attestation over the Waku relay network; a gatekeeper verifies it locally with `verifier-lib` and grants token-gated group admission. `demo-offchain.sh` demonstrates the full flow including replay denial via nullifier tracking, across two relay-connected nwaku nodes. - [x] A standalone consumer integration demo is included — `basecamp-app/` provides a browser-based commitment-hash tool; `demo.sh` exercises the full prove/verify CLI flow offline. - [x] Full documentation and clean public repository — `README.md` covers deployment, CLI usage, and Basecamp app loading; `SOLUTION.md` covers circuit design and integration guide. @@ -80,7 +82,7 @@ Logical flow: deserialise gate state → verify proof via `env::verify` → deco The on-chain path is fully functional: proof generation (`balance-attest prove`), on-chain gate deployment (`initialize`), and proof submission (`gate`). The circuit correctly replicates the LEZ commitment format. Context binding, presenter binding, and nullifier deduplication all work. Error codes are distinct and documented for each failure mode. -The off-chain path is partially functional: `verify_attestation` in `verifier-lib` verifies a proof locally (receipt check, journal decode, context match, threshold check, presenter key match, signature check). Logos Messaging transport is not wired — a consumer integrating the library must handle the message send/receive step. This is the main gap relative to the full prize spec. +The off-chain path is fully functional: `verify_attestation` in `verifier-lib` verifies a proof locally (receipt check, journal decode, context match, threshold check, presenter key match, signature check over `context_id || merkle_root || threshold_n || nullifier`). The `messaging/attest-msg` binary handles transmission over the Waku relay network: `send` publishes the attestation envelope and awaits the verdict; `gatekeeper` verifies incoming attestations, persists spent nullifiers across restarts, and admits or denies access to the token-gated group. ### Usability @@ -90,11 +92,13 @@ Basecamp app (`basecamp-app/index.html`) loads in Logos Core / Basecamp. It comp `verifier-lib` ships as a Rust crate with a single public function; its doc example is in `lib.rs`. +Two reproducible demo scripts: `demo.sh --dev` (offline prove + verify) and `demo-offchain.sh --dev` (full Logos Messaging flow against two relay-connected nwaku nodes, including the replay-denial case). Both run without modification from a clean checkout. + ### Reliability The on-chain program returns explicit error codes for all invalid-proof cases (see error code table above). `env::verify` failure, journal decode failure, context mismatch, threshold not met, invalid presenter sig, and spent nullifier each produce a distinct code. The circuit never panics on valid input formats — all fallible operations return `Result` propagated through `SpelResult`. -Off-chain: `verify_attestation` returns an `anyhow::Error` with a message for each failure case, not a panic. Private account data does not appear in error messages. +Off-chain: `verify_attestation` returns an `anyhow::Error` with a message for each failure case, not a panic. Private account data does not appear in error messages. The gatekeeper passes only the public failure category to the denied party; a malformed envelope, an invalid proof, a threshold failure, and a spent nullifier each produce a distinct deny reason. Gatekeeper state (spent nullifiers, members) persists to disk and survives restarts. ### Performance @@ -112,8 +116,10 @@ The program is not deployed on LEZ testnet — `demo.sh` runs the offline prove/ - Repository: https://github.com/retraca/lp-0005-balance-attestation - Off-chain verifier library: `verifier-lib/src/lib.rs` +- Logos Messaging transport: `messaging/src/main.rs` (`attest-msg send / gatekeeper`) - Basecamp app: `basecamp-app/index.html` - Offline demo: `demo.sh` +- Off-chain messaging demo: `demo-offchain.sh` (two nwaku nodes, admission + replay denial) - IDL: `lp-0005-balance-attestation.idl.json` ## Terms & Conditions