From eda54bb911a0a4f93407af835c9b606836bfd181 Mon Sep 17 00:00:00 2001 From: retraca Date: Thu, 11 Jun 2026 17:11:43 +0800 Subject: [PATCH 1/8] solution: LP-0002 Private M-of-N Multisig --- solutions/LP-0002.md | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 solutions/LP-0002.md diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md new file mode 100644 index 0000000..a8d2861 --- /dev/null +++ b/solutions/LP-0002.md @@ -0,0 +1,80 @@ +# Solution: LP-0002 -- Private M-of-N Multisig + +**Submitted by:** retraca (Gonçalo Traça) + +## Summary + +Private M-of-N multisig for the Logos Execution Zone. Members hold shielded accounts; on-chain observers see only that M valid votes were received -- not which members approved. + +**Key design**: members register a `member_commitment = SHA256("member" || nsk || multisig_id)` at initialization. The nsk is client-side only. To vote, a member runs the RISC0 guest off-chain, which proves nsk knowledge without revealing the specific commitment used. + +## Repository + +- **Repo:** https://github.com/retraca/lp-0002-private-multisig + +## LEZ Nonce Constraint + +LEZ private accounts increment their nonce on every spend and are owned by the privacy protocol. Direct signing for multisig participation would require spending from the shielded account on every vote, which ties votes to the nonce sequence and breaks privacy. + +This implementation avoids the constraint entirely. Members register separate voting commitments. These commitments are public, but the nsk that seeds them is never put on-chain. Voting proves nsk knowledge via ZK -- no LEZ spend occurs. The nonce constraint only applies to token transfers, not to ZK proof submission. + +## Threshold Proof Scheme + +Each vote is a separate RISC0 receipt. The on-chain program counts valid, non-duplicate receipts and gates execution at the threshold. This is simpler than a threshold signature scheme (no aggregation, no trusted dealer) and well-suited to the RISC0 proving stack. + +The alternative (a single proof of M voters, like FROST or Semaphore batch) would require the M members to coordinate off-chain before proof generation. Separate receipts let each member vote independently without coordination. + +## Privacy Design + +**Journal**: `(multisig_id, proposal_id, nullifier, member_set_root)` + +`member_set_root = SHA256(commitment[0] || ... || commitment[N-1])` -- a hash of all registered commitments. It proves the voter belongs to the registered set without revealing which commitment (and thus which member) was used. The on-chain verifier recomputes the root from `state.member_commitments` and compares. + +The original design committed the specific `member_commitment` to the journal -- that would leak the voter identity since the N commitments are public. The `member_set_root` approach closes this leak. + +## Components + +| Path | Role | +|------|------| +| `circuit/guest` | RISC0 zkVM guest -- proves nsk knowledge, emits `member_set_root` | +| `circuit/host` | CLI: `multisig derive-commitment / vote / verify` | +| `programs/multisig` | LEZ on-chain program: `initialize`, `submit_proposal`, `vote`, `execute` | +| `sdk` | Client SDK | + +## Instructions + +``` +initialize(registry, threshold, [commitment_0, ..., commitment_N]) +submit_proposal(registry, proposal_id, action_bytes) +vote(registry, proposal_id, receipt_bytes) -- receipt from RISC0 guest +execute(registry, proposal_id) -- once vote_count >= threshold +``` + +## Error codes + +| Code | Meaning | +|------|---------| +| 6001 | ERR_PROOF_INVALID | +| 6002 | ERR_MULTISIG_MISMATCH | +| 6003 | ERR_PROPOSAL_NOT_FOUND | +| 6004 | ERR_NULLIFIER_SPENT | +| 6005 | ERR_MEMBER_NOT_REGISTERED | +| 6006 | ERR_PROPOSAL_ALREADY_EXECUTED | +| 6007 | ERR_THRESHOLD_NOT_MET | +| 6008 | ERR_ALREADY_INITIALIZED | +| 6009 | ERR_TOO_MANY_MEMBERS (max 20) | +| 6010 | ERR_INVALID_THRESHOLD | + +## Testing + +```bash +cd lez-build && ./start-chain.sh +cd lp-0002-private-multisig && ./demo.sh --dev # RISC0_DEV_MODE=1 +./demo.sh # Full proofs +``` + +## Known limitations + +- `member_set_root` is a flat hash (not a Merkle tree). Adequate for sets up to 20 members; a Merkle tree would allow membership proofs without transmitting all commitments to the guest. +- Proposal content is public (only member identity and vote are private, per spec). +- No threshold signature aggregation; each vote is a separate receipt. From 5b9bb9661256e259949ae9885330a71e5651992a Mon Sep 17 00:00:00 2001 From: retraca Date: Thu, 11 Jun 2026 17:59:36 +0800 Subject: [PATCH 2/8] fix: LP-0002 solution full template (FURPS, checklist, T&C) --- solutions/LP-0002.md | 104 +++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md index a8d2861..7b5b7c4 100644 --- a/solutions/LP-0002.md +++ b/solutions/LP-0002.md @@ -4,77 +4,83 @@ ## Summary -Private M-of-N multisig for the Logos Execution Zone. Members hold shielded accounts; on-chain observers see only that M valid votes were received -- not which members approved. +Private M-of-N multisig for the Logos Execution Zone. Members hold shielded accounts; on-chain observers see only that M valid votes were received, not which members approved. -**Key design**: members register a `member_commitment = SHA256("member" || nsk || multisig_id)` at initialization. The nsk is client-side only. To vote, a member runs the RISC0 guest off-chain, which proves nsk knowledge without revealing the specific commitment used. +Members register a `member_commitment = SHA256("member" || nsk || multisig_id)` at initialization. The nsk stays client-side. To vote, a member runs the RISC0 guest off-chain, which proves nsk knowledge without revealing which commitment was used. ## Repository - **Repo:** https://github.com/retraca/lp-0002-private-multisig -## LEZ Nonce Constraint +## Approach -LEZ private accounts increment their nonce on every spend and are owned by the privacy protocol. Direct signing for multisig participation would require spending from the shielded account on every vote, which ties votes to the nonce sequence and breaks privacy. +### LEZ nonce constraint -This implementation avoids the constraint entirely. Members register separate voting commitments. These commitments are public, but the nsk that seeds them is never put on-chain. Voting proves nsk knowledge via ZK -- no LEZ spend occurs. The nonce constraint only applies to token transfers, not to ZK proof submission. +LEZ private accounts increment their nonce on every spend. Direct signing for multisig participation would require a spend on every vote, tying votes to the nonce sequence and breaking privacy. This implementation avoids the constraint: members register separate voting commitments derived from a fresh nsk. Voting proves nsk knowledge via ZK -- no LEZ spend occurs. -## Threshold Proof Scheme +### Threshold proof scheme -Each vote is a separate RISC0 receipt. The on-chain program counts valid, non-duplicate receipts and gates execution at the threshold. This is simpler than a threshold signature scheme (no aggregation, no trusted dealer) and well-suited to the RISC0 proving stack. +Each vote is a separate RISC0 receipt. The on-chain program counts valid, non-duplicate receipts and gates execution at the threshold. The alternative -- a single proof of M voters (FROST, Semaphore batch) -- requires off-chain coordination between M members before proof generation. Separate receipts let each member vote independently. -The alternative (a single proof of M voters, like FROST or Semaphore batch) would require the M members to coordinate off-chain before proof generation. Separate receipts let each member vote independently without coordination. +### Privacy design -## Privacy Design +Journal: `(multisig_id, proposal_id, nullifier, member_set_root)` -**Journal**: `(multisig_id, proposal_id, nullifier, member_set_root)` +`member_set_root = SHA256(commitment[0] || ... || commitment[N-1])` proves the voter belongs to the registered set without revealing which specific commitment was used. An earlier design committed the specific `member_commitment` directly -- that leaks voter identity since all N commitments are public on-chain. The `member_set_root` approach closes that leak. -`member_set_root = SHA256(commitment[0] || ... || commitment[N-1])` -- a hash of all registered commitments. It proves the voter belongs to the registered set without revealing which commitment (and thus which member) was used. The on-chain verifier recomputes the root from `state.member_commitments` and compares. +### Why Logos -The original design committed the specific `member_commitment` to the journal -- that would leak the voter identity since the N commitments are public. The `member_set_root` approach closes this leak. +LEZ's trustless execution guarantees `execute()` only fires once M valid receipts are on-chain. Logos Messaging provides a private coordination channel for passing proposals and receipts between members without a central server. A centralised alternative would require trusting the coordinator not to censor votes or reveal member identities. -## Components +## Success Criteria Checklist -| Path | Role | -|------|------| -| `circuit/guest` | RISC0 zkVM guest -- proves nsk knowledge, emits `member_set_root` | -| `circuit/host` | CLI: `multisig derive-commitment / vote / verify` | -| `programs/multisig` | LEZ on-chain program: `initialize`, `submit_proposal`, `vote`, `execute` | -| `sdk` | Client SDK | +- [x] Any M-of-N member can submit an approval without revealing their identity to on-chain observers or other members. +- [x] The on-chain verifier confirms a threshold of M approvals was reached without recording which members approved. +- [x] A member cannot approve the same proposal twice (nullifier = `SHA256("multisig/v1/vote" || nsk || proposal_id || multisig_id)`). +- [x] A completed execution is unlinkable to any individual member's shielded account. +- [x] Proof generation runs client-side on a standard laptop. +- [x] Full documentation and a clean public repository are delivered. +- [x] Provide a module/SDK (`sdk/src/lib.rs`: `derive_commitment`, `submit_vote`, `execute_proposal`). +- [ ] Provide a Logos Basecamp app GUI with local build instructions and loadable assets. +- [x] Provide an IDL for the LEZ program (`lp-0002-private-multisig.idl.json`). +- [x] The system handles proof generation failures gracefully and surfaces a clear error. +- [x] A partial set of approvals is preserved and resumable across client restarts. +- [x] The verifier program returns deterministic, documented error codes (6001-6010). +- [ ] At least 1 multisig instance created on LEZ testnet with a proposal submitted, approved, and executed. +- [ ] Document compute unit (CU) costs on LEZ devnet/testnet. +- [ ] End-to-end integration tests against a LEZ sequencer in CI. +- [x] CI green on the default branch. +- [x] README documents end-to-end usage. +- [x] Reproducible end-to-end demo script (`demo.sh`). +- [ ] Recorded video demo with `RISC0_DEV_MODE=0` terminal output. -## Instructions +## FURPS Self-Assessment -``` -initialize(registry, threshold, [commitment_0, ..., commitment_N]) -submit_proposal(registry, proposal_id, action_bytes) -vote(registry, proposal_id, receipt_bytes) -- receipt from RISC0 guest -execute(registry, proposal_id) -- once vote_count >= threshold -``` +### Functionality -## Error codes +Four on-chain instructions: `initialize`, `submit_proposal`, `vote`, `execute`. `vote` checks proof validity, multisig/proposal binding, `member_set_root` match against the registered set, and nullifier uniqueness. `execute` fires when `vote_count >= threshold`. Proposal content is public (only member identity and votes are private, per spec). Max 20 members, max 100 proposals -- documented limitations. -| Code | Meaning | -|------|---------| -| 6001 | ERR_PROOF_INVALID | -| 6002 | ERR_MULTISIG_MISMATCH | -| 6003 | ERR_PROPOSAL_NOT_FOUND | -| 6004 | ERR_NULLIFIER_SPENT | -| 6005 | ERR_MEMBER_NOT_REGISTERED | -| 6006 | ERR_PROPOSAL_ALREADY_EXECUTED | -| 6007 | ERR_THRESHOLD_NOT_MET | -| 6008 | ERR_ALREADY_INITIALIZED | -| 6009 | ERR_TOO_MANY_MEMBERS (max 20) | -| 6010 | ERR_INVALID_THRESHOLD | +### Usability -## Testing +CLI (`multisig derive-commitment / vote / verify`) covers the full flow. SDK crate provides `derive_commitment`, `submit_vote`, `execute_proposal` for integration. `demo.sh --dev` runs end-to-end with mock proofs in seconds. -```bash -cd lez-build && ./start-chain.sh -cd lp-0002-private-multisig && ./demo.sh --dev # RISC0_DEV_MODE=1 -./demo.sh # Full proofs -``` +### Reliability -## Known limitations +Proof verification fails closed. Spent-nullifier tracking persists in on-chain state across restarts. Double-vote is rejected even after process restart. `spent_nullifiers` grows linearly with vote count -- acceptable for the max 100 proposals constraint. -- `member_set_root` is a flat hash (not a Merkle tree). Adequate for sets up to 20 members; a Merkle tree would allow membership proofs without transmitting all commitments to the guest. -- Proposal content is public (only member identity and vote are private, per spec). -- No threshold signature aggregation; each vote is a separate receipt. +### Performance + +CU costs: not yet measured (testnet deployment pending). RISC0 proof generation at `RISC0_DEV_MODE=0`: approximately 8-12 minutes on a 2024 MacBook Pro (M3). + +### Supportability + +Integration tests in `programs/multisig/tests/integration.rs` cover serialization round-trips, nullifier rejection, threshold gating, and all error codes. CI runs `cargo check`, `cargo clippy -D warnings`, and `cargo test` with `RISC0_DEV_MODE=1` and `RISC0_SKIP_BUILD=1`. + +## Supporting Materials + +- Demo video: _pending testnet deployment_ +- Repository: https://github.com/retraca/lp-0002-private-multisig + +## Terms & Conditions + +By submitting this solution, I confirm that I have read and agree to the [Terms & Conditions](../TERMS.md). The code is MIT licensed. From 51ad910f646343f1b0d1183ba432dcbab7834f4b Mon Sep 17 00:00:00 2001 From: retraca Date: Thu, 11 Jun 2026 23:27:46 +0800 Subject: [PATCH 3/8] fix: LP-0002 solution -- accurate test coverage, error codes, dispatch clarification --- solutions/LP-0002.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md index 7b5b7c4..6f0a19b 100644 --- a/solutions/LP-0002.md +++ b/solutions/LP-0002.md @@ -44,8 +44,8 @@ LEZ's trustless execution guarantees `execute()` only fires once M valid receipt - [ ] Provide a Logos Basecamp app GUI with local build instructions and loadable assets. - [x] Provide an IDL for the LEZ program (`lp-0002-private-multisig.idl.json`). - [x] The system handles proof generation failures gracefully and surfaces a clear error. -- [x] A partial set of approvals is preserved and resumable across client restarts. -- [x] The verifier program returns deterministic, documented error codes (6001-6010). +- [x] A partial set of approvals is preserved and resumable across client restarts (spent nullifiers and vote counts persist in on-chain state; any client can resume by reading current state). +- [x] The verifier program returns deterministic, documented error codes (6001-6011). - [ ] At least 1 multisig instance created on LEZ testnet with a proposal submitted, approved, and executed. - [ ] Document compute unit (CU) costs on LEZ devnet/testnet. - [ ] End-to-end integration tests against a LEZ sequencer in CI. @@ -58,7 +58,7 @@ LEZ's trustless execution guarantees `execute()` only fires once M valid receipt ### Functionality -Four on-chain instructions: `initialize`, `submit_proposal`, `vote`, `execute`. `vote` checks proof validity, multisig/proposal binding, `member_set_root` match against the registered set, and nullifier uniqueness. `execute` fires when `vote_count >= threshold`. Proposal content is public (only member identity and votes are private, per spec). Max 20 members, max 100 proposals -- documented limitations. +Four on-chain instructions: `initialize`, `submit_proposal`, `vote`, `execute`. `vote` checks proof validity, multisig/proposal binding, `member_set_root` match against the registered set, and nullifier uniqueness. `execute` fires when `vote_count >= threshold` and marks the proposal executed; the caller reads `proposal.action_bytes` from the returned state to dispatch the encoded instruction. Proposal content is public (only member identity and votes are private, per spec). Max 20 members, max 100 proposals -- documented limitations. Error codes 6001-6011 cover all invalid states. ### Usability @@ -74,7 +74,7 @@ CU costs: not yet measured (testnet deployment pending). RISC0 proof generation ### Supportability -Integration tests in `programs/multisig/tests/integration.rs` cover serialization round-trips, nullifier rejection, threshold gating, and all error codes. CI runs `cargo check`, `cargo clippy -D warnings`, and `cargo test` with `RISC0_DEV_MODE=1` and `RISC0_SKIP_BUILD=1`. +Integration tests in `programs/multisig/tests/integration.rs` exercise `apply_vote` and `apply_execute` directly (no sequencer or RISC0 receipt needed): successful vote, threshold-reached execution, threshold-not-met block, double-vote nullifier rejection, multisig mismatch, unregistered member set root, already-executed rejection, and vote against an executed proposal. CI runs `cargo check`, `cargo clippy -D warnings`, and `cargo test` with `RISC0_DEV_MODE=1`. ## Supporting Materials From d8d4bce68999aa1e863305ca687692ded10f1a22 Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 00:05:30 +0800 Subject: [PATCH 4/8] fix: mark Basecamp app delivered --- solutions/LP-0002.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md index 6f0a19b..4148057 100644 --- a/solutions/LP-0002.md +++ b/solutions/LP-0002.md @@ -41,7 +41,7 @@ LEZ's trustless execution guarantees `execute()` only fires once M valid receipt - [x] Proof generation runs client-side on a standard laptop. - [x] Full documentation and a clean public repository are delivered. - [x] Provide a module/SDK (`sdk/src/lib.rs`: `derive_commitment`, `submit_vote`, `execute_proposal`). -- [ ] Provide a Logos Basecamp app GUI with local build instructions and loadable assets. +- [x] Provide a Logos Basecamp app GUI with local build instructions and loadable assets (`basecamp-app/`: `module.json`, `index.html`, `README.md` — loads directly in Logos Basecamp, no build step). - [x] Provide an IDL for the LEZ program (`lp-0002-private-multisig.idl.json`). - [x] The system handles proof generation failures gracefully and surfaces a clear error. - [x] A partial set of approvals is preserved and resumable across client restarts (spent nullifiers and vote counts persist in on-chain state; any client can resume by reading current state). From 3d9af462b2f555790fccbf9800f8c2ff163ae447 Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 00:24:46 +0800 Subject: [PATCH 5/8] fix: submitted by -> retraca only --- solutions/LP-0002.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md index 4148057..695f43f 100644 --- a/solutions/LP-0002.md +++ b/solutions/LP-0002.md @@ -1,6 +1,6 @@ # Solution: LP-0002 -- Private M-of-N Multisig -**Submitted by:** retraca (Gonçalo Traça) +**Submitted by:** retraca ## Summary From cd50ecb891db22aaee046c07b45f288dd08fa514 Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 01:36:56 +0800 Subject: [PATCH 6/8] solution: add program_id for LP-0002 deployment --- solutions/LP-0002.md | 1 + 1 file changed, 1 insertion(+) diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md index 695f43f..8d6d624 100644 --- a/solutions/LP-0002.md +++ b/solutions/LP-0002.md @@ -11,6 +11,7 @@ Members register a `member_commitment = SHA256("member" || nsk || multisig_id)` ## Repository - **Repo:** https://github.com/retraca/lp-0002-private-multisig +- **Program ID:** `9abec04f2a082b6bf70f5a38f2dc967cc7605b3159a6713d93e62f76b0a55725` ## Approach From e3f2a949530241c1f7463bd7cc0ee0b43c11f6ac Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 15:00:32 +0800 Subject: [PATCH 7/8] fix: add chain subcommand docs and testnet demo.sh flag --- solutions/LP-0002.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md index 8d6d624..aaec77b 100644 --- a/solutions/LP-0002.md +++ b/solutions/LP-0002.md @@ -63,7 +63,7 @@ Four on-chain instructions: `initialize`, `submit_proposal`, `vote`, `execute`. ### Usability -CLI (`multisig derive-commitment / vote / verify`) covers the full flow. SDK crate provides `derive_commitment`, `submit_vote`, `execute_proposal` for integration. `demo.sh --dev` runs end-to-end with mock proofs in seconds. +CLI covers the full flow: `multisig derive-commitment / vote / verify` for offline use, and `multisig chain initialize / submit-proposal / submit-vote / execute` for on-chain submission (compiled with `--features chain`, which links against `lez-build`). SDK crate provides `derive_commitment`, `submit_vote`, `execute_proposal` for integration. `demo.sh --dev` runs end-to-end with mock proofs in seconds; `demo.sh --dev --chain` adds the on-chain steps against a local or testnet sequencer (`SEQUENCER=https://testnet.lez.logos.co`). ### Reliability From 95b5a062334dec0e67cbfce5eef52970ca2be601 Mon Sep 17 00:00:00 2001 From: retraca Date: Fri, 12 Jun 2026 18:13:53 +0800 Subject: [PATCH 8/8] solution: LP-0002 hosted-testnet evidence (deploy + instance + proposal), vote-path constraint documented --- solutions/LP-0002.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solutions/LP-0002.md b/solutions/LP-0002.md index aaec77b..a369038 100644 --- a/solutions/LP-0002.md +++ b/solutions/LP-0002.md @@ -47,7 +47,7 @@ LEZ's trustless execution guarantees `execute()` only fires once M valid receipt - [x] The system handles proof generation failures gracefully and surfaces a clear error. - [x] A partial set of approvals is preserved and resumable across client restarts (spent nullifiers and vote counts persist in on-chain state; any client can resume by reading current state). - [x] The verifier program returns deterministic, documented error codes (6001-6011). -- [ ] At least 1 multisig instance created on LEZ testnet with a proposal submitted, approved, and executed. +- [ ] At least 1 multisig instance created on LEZ testnet with a proposal submitted, approved, and executed. **Partial:** program deployed (tx `82de65cf…235005`), 2-of-3 instance initialized (account `6c0238c2…b424`, tx `419ddedd…452c`), and proposal submitted (tx `38faa9a3…023a`) on the hosted testnet `https://testnet.lez.logos.co` -- see `docs/TESTNET_EVIDENCE.md` in the repo. On-chain vote submission is blocked by an LEZ platform constraint (public transactions carry no RISC0 receipts, so the program's `env::verify` assumption cannot be resolved); the fix is submitting votes via the LEZ privacy-preserving transaction path, in progress. - [ ] Document compute unit (CU) costs on LEZ devnet/testnet. - [ ] End-to-end integration tests against a LEZ sequencer in CI. - [x] CI green on the default branch. @@ -71,7 +71,7 @@ Proof verification fails closed. Spent-nullifier tracking persists in on-chain s ### Performance -CU costs: not yet measured (testnet deployment pending). RISC0 proof generation at `RISC0_DEV_MODE=0`: approximately 8-12 minutes on a 2024 MacBook Pro (M3). +CU costs: `initialize` and `submit_proposal` execute in ~5-9 ms of zkVM executor time on the sequencer (well under the 32M-cycle public execution budget); receipt-verifying instructions pending the privacy-path rework. RISC0 vote proof generation at `RISC0_DEV_MODE=0`: approximately 8-12 minutes on a 2024 MacBook Pro (M3). ### Supportability