Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions solutions/LP-0002.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Solution: LP-0002 -- Private M-of-N Multisig

**Submitted by:** retraca

## 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.

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
- **Program ID:** `9abec04f2a082b6bf70f5a38f2dc967cc7605b3159a6713d93e62f76b0a55725`

## Approach

### LEZ nonce constraint

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

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.

### Privacy design

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.

### Why Logos

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.

## Success Criteria Checklist

- [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`).
- [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).
- [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.
- [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.

## FURPS Self-Assessment

### 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` 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

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

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.

### 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` 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

- 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.
Loading