Solution: LP-0005 — Private Balance Attestation#64
Conversation
|
|
c9cb327 to
e39303b
Compare
Aligns the L-Prize repo with logos-co/rfp PR #64, which standardised dependency declarations as a structured `dependencies:` field in YAML frontmatter (a list of `{ id, reason }` objects). - LP-0013: adds frontmatter with the RFP-001 dependency; removes the markdown `## Dependencies` section. - LP-0001, LP-0004, LP-0006: drop the markdown `## Dependencies` section. Their hard deps are platform features (LEZ NFT Program, LEZ timelock, Logos Delivery, Logos Chat) with no canonical RFP-/LP- ID, so no frontmatter entry is added — matching the RFP-003 precedent in logos-co/rfp PR #64. The dependency information is already captured in each file's status banner. - LP-0000.md (template): replaces the markdown `## Dependencies` section with a frontmatter example in an HTML comment, noting that the field is included only when there are real hard deps with canonical IDs and is otherwise omitted entirely (no `[]`), per the L-Prize convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous convention omitted the `dependencies:` field entirely when an LP had no hard deps. This change makes the field mandatory on every LP: use `dependencies: []` for standalone LPs. A missing field now signals the author hasn't considered deps yet (lintable); `[]` signals considered and none. This aligns with the convention in logos-co/rfp PR #64. - LP-0000.md (template): replace the conditional frontmatter example with `dependencies: []` and a comment explaining the new convention. - LP-0001..LP-0017 (excluding LP-0007 which does not exist and LP-0013 which already declares an RFP-001 hard dep): add `dependencies: []` YAML frontmatter at the top of each file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Hey @weboko, thanks for flagging this. Re-submitted, all four are live again:
The testnet had been reset and upgraded to I did not recompile anything. The May binaries went back up as they were and the |
|
Update on the on-chain path, replacing the transactions in my previous note. After posting those I kept auditing the gate and found it under-bound. It checked the balance of whichever account signed, without requiring that to be the account the witness attests to, and the marker it left on chain recorded that a gate had run without recording what it demanded, so a caller pinning a floor of zero produced a marker indistinguishable from an honest one. Both are fixed and the verifier is redeployed.
The gate now binds four things the caller cannot forge: the signer must be the account the witness attests to, its owning program is pinned to authenticated_transfer so the balance is denominated, the balance is read from anchored pre_state, and the enforced floor and context are folded into the marker PDA seed. Re-derive that PDA at a lower floor and it lands on an unclaimed address, so a marker earned at zero cannot pass for one earned at a real threshold. Run ./scripts/verify-onchain-proof.sh to check it from public data, and crates/cu-bench/tests/deep_gate_rejects.rs runs the deployed bytecode through the sequencer's execution path and requires each rejection to fire. The last two hashes are plain rather than linked because the explorer does not show them yet, though getTransaction returns both. Yesterday's deploy did appear, so this may be indexing lag, but yesterday's gated_check never did and the marker PDA page still shows the default program owner where the RPC reports the verifier. Worth a look on your side. Off-chain over Logos Messaging is also in: Logos Delivery is a Waku node, so the transport publishes and subscribes over its REST interface with the same content topics and envelope its send() builds. scripts/demo-offchain-gating.sh gates chat admission with a credential crossing two real nodes, using the 1,479 byte Groth16 wrap since a composite receipt exceeds Waku's cap. |
Corrects claims that no longer held, and records the work done since the original submission. The dead transaction: the write-up still cited 262bbe95...6babfd5e twice with explorer links. That transaction was wiped by the testnet reset, so both links were dead. This is the same problem raised in review, still present in the submission document itself. The on-chain claim: the write-up said the v3 verifier 'validated a real Risc0-receipt-bound ECDSA signature' and presented that as the on-chain path. The v3 gate verifies no zero-knowledge proof, and no program on the LEZ public path could, since the sequencer re-executes rather than proves. The document now describes the deep gate on the privacy-preserving path, where LEZ's privacy circuit discharges a chained call with a real env::verify and the sequencer verifies the receipt against the pinned PRIVACY_PRESERVING_CIRCUIT_ID, and where the threshold is checked against the balance LEZ itself anchors rather than the caller-supplied witness. Also corrected: the transaction table (4 rows to 7), 'Eight on-chain transactions', the superseded outside-party criterion, E2E tests described as being in CI while pointing at ignored tests, 57 tests (now 76), 7 CLI subcommands (now 8), and the off-chain path described as an in-memory backend with a stubbed Qt bridge rather than the Waku transport that now exists. Every transaction hash in the document resolves, and every link was opened.
The deep verifier is redeployed after an audit found the gate under-bound: it read the balance of whichever account signed without requiring that to be the account the witness attests to, and the marker it left on chain recorded that a gate ran without recording what it demanded. Updates the superseded identifiers, states the four bindings the gate now enforces instead of the anchoring claim that did not hold, documents the deployed 3xxx error codes, corrects the test count to 90 and separates user cycles from proving cycles. Also removes a truncated transaction hash that no longer resolves.
|
@edenbd1 make sure CI passes 🙏 |
|
@weboko the red check is not coming from the submission. The job dies at step 3,
I reproduced it rather than guess, one variable at a time: https://github.com/test-readme/checkout-guard-repro. A fork PR on Fix is one line, opened as #109 so you only need to merge it. It affects every fork PR on the repo, not just this one. |
Implementation of LP-0005: a reusable private token balance attestation primitive for the Logos Execution Zone.
solutions/LP-0005.md✅ Deployed live on the public Logos Execution Zone testnet — with a confirmed on-chain
gated_checkhttps://testnet.lez.logos.cohttps://explorer.testnet.lez.logos.coCbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2rPublic on-chain evidence — every tx independently verifiable via
getTransactionJSON-RPC or by clicking the explorer link:wallet deploy-program— attestation circuit (Risc0 guest provingbalance ≥ N, ImageIDdbc40b94…6a9d4d)4593060b…3db989dwallet deploy-program— verifier program v2 (SPEL, deep gate viaChainedCall, ImageID7715f791…d8a1db429)2bf10138…23723a9wallet deploy-program— verifier program v3 (SPEL, shallow gate, ImageIDb32c6662…df85952a)a0ec45bb…d341c5caspel gated_checkagainst the v3 shallow gate. Host-side checks only: context, threshold and an ECDSA challenge response. It verifies no zero-knowledge prooffd9869f7…eafb306dwallet deploy-program— LEZ-native attestation program (ImageID9b6be465…6c27da)674aa03a8a51a2eba660ec2ab136a1b6c9ca17817c7bb3160b68904375726652wallet deploy-program— deep verifier (ImageID1047297a…8261b27c)7a4e46cfcab3a956a159d3c82a781222bdf093faa7ef8d42723f1a95e06eec0dspel gated_check, privacy-preserving. The proof is verified on chain, and the enforced policy is bound into the markere8ed66c79373ebbea77a254db866793d68fd1b71357731ed93d70bade7bbb4abAn earlier deep verifier (
4e2ac5c3…) and itsgated_check(b9488de0…) remain on chain and still resolve, but are superseded: that gate bound neither the signer to the attested account nor the enforced policy to the marker. Both are recorded, struck through and explained, indocs/DEPLOYMENT.md.Full deployment record (with reproduction commands) in
docs/DEPLOYMENT.md.Summary
A presenter holding a shielded LEZ token account can prove
balance ≥ Nwithout revealing the account'snpk, exact balance, or identity. Two verification paths:#[lez_program](crates/verifier-program-spel/methods/guest-deep/src/bin/attestation_verifier_deep.rs), which declares aChainedCallto the LEZ-native attestation program so the privacy circuit composes it withenv::verify, and gates on the anchored balance of the account the witness attests to. Confirmed end to end, row 7 above.crates/verifier-offchain/) that runs the samecheck_gaterules locally against a Risc0 receipt delivered over any transport (the working backend iswaku_rest, which reaches the Waku network Logos Delivery runs on;inmemis for unit tests only).Highlights vs the spec
https://testnet.lez.logos.co. Anyone cangetTransactionthem or open the explorer link.3010, and meeting it requires that account's spending key, since LEZ derives the account id fromnsk); the owning program is pinned to LEZ'sauthenticated_transfer, so the balance is denominated (3011); the balance is read from anchoredpre_stateand LEZ requires its commitment digest to be inroot_history(3009); and the enforced floor and context are folded into the marker PDA's seed, so the public artifact records the policy rather than merely that a gate ran (3012). Re-derive the PDA at a lower floor and it lands on an unclaimed address.crates/cu-bench/tests/deep_gate_rejects.rsruns the deployed bytecode through the sequencer's own execution path and requires each rejection to fire, with the honest call as the control.env::verify(lee/privacy_preserving_circuit/src/execution_state.rs:149), and the sequencer verifies the receipt against the pinnedPRIVACY_PRESERVING_CIRCUIT_IDbefore applying any state. Rows 1 to 4 are the earlier shallow gate, which verifies no proof, and no program on the public path could: a public transaction re-executes rather than proves (lee/state_machine/src/program.rs:73-77). Check it from public data with./scripts/verify-onchain-proof.sh, which confirms the deployed bytecode hashes to the committed binaries, that the transaction isPrivacyPreservingrather thanPublic, that the receipt isSuccinctrather than a dev-mode fake, and that the marker PDA derived from the verifier ImageID and the enforced policy is owned by the verifier.send()builds.scripts/demo-offchain-gating.shstarts two nodes, peers them, and gates admission to a chat group with a credential that crosses between them, refusing replay, an intercepted proof, and an under-funded attestation.scripts/demo.shexportsRISC0_DEV_MODE=0as its first non-comment line. Composite receipt ~300 KB in ~6.5 s; the Groth16 wrap is 1,479 bytes, which is what the off-chain demo uses since Waku caps a message at 153,600 bytes.attestation-core,attestation-verifier-program,attestation-sequencer-client,attestation-delivery-transport. External builders cancargo adddirectly..lgxpublished —app/lp-0005-attestation.lgx2.1 MB,lgx verify ✅, SHA-256193a903a…94c89770.integrations/— governance-gate, chat-gate, premium-features and nostr-auth-gate. Each is an independently consumable crate; none hard-codes a deployed program id. Two are demonstrated end to end rather than merely compiled: chat-gate over a real two-node Waku network, and the full on-chain round trip from an empty chain viascripts/e2e-local.sh.cargo test --workspace), CI green onmain, including ane2ejob that boots a standalone LEZ sequencer, deploys three programs from an empty chain, proves underRISC0_DEV_MODE=0and requiresgated_checkto confirm on chain, then runs the deep gate's rejection tests against the deployed bytecode.scripts/demo.shruns the full pipeline against the public testnet from a clean clone, requires onlycurl,python3,jq, andcargo.Per-criterion compliance map
See
docs/criteria-checklist.mdfor the row-by-row mapping from every line of the prize text to the code, test, or transaction hash that satisfies it.Architecture
LEZ verifier as a SPEL program (justified vs zone-SDK in ADR-001): a single
gated_checkinstruction takes the flattened journal + ECDSA challenge response + caller-pinned context/threshold, runs the host-side gate, and (in the deep variant) declares aChainedCallso the LEZ PPE pipeline composes the inner Risc0 receipt viaenv::verify. Three-piece structure (portable kernel + deep guest + shallow guest) covers both the architecturally-ideal path and the confirmable-today path. Decision write-up: ADR-002.