Repository navigation
feat(exo-0d3): lez-msig — an M-of-N multisig on LEZ, private by default, run from the room - #344
Merged
Merged
Conversation
…ult (design, core, v0.3 program) RFP-005's multisig, with the rules in one place and the room as its coordination layer (docs/design/lez-msig.md): - msig_core: the rules as two pure functions with no LEZ dependency. claims() is what the instruction alone says (the window, any outflow), for a plan that sees no data; apply() is every check against the stored state. Approvals are BIP-340 by per-multisig keys, verified in-program (R.1): distinct members, APPROVE role, weighted, proposer signed. A config epoch every approval commits to (R.2, cutoff = the queue), time lock via a pessimistic ETA, expiry, cancels that are never time-locked, roles, an action set and target list, spending limits per period, and a 256-nonce window against replay. - msig_v03 + the guest: the v0.3 run_program(plan, apply) adapter. The plan re-derives the multisig's account from the create key's seed and its placement (public PDA, or a group-owned private PDA) and refuses any other row, so a counterfeit state is never granted the seed (the exo-325 class, closed by construction). Outflows are chained calls granted the multisig's own seed only. - infra/lez/localnet.sh: prefer the host's libclang and libpcsclite; nixpkgs' need a glibc as new as their own and fail on Ubuntu 24.04's 2.39. Tested-Behavior: programs/lez-msig/core/tests/rules.rs (24 tests, cargo test): R.1 approvals, R.2 epoch + queue cutoff, time lock/expiry/cancel, roles, action set, spending limits, nonce window (properties); the guest builds (cargo build -p msig_methods) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…ted guest, muster's Nim port of its bytes - msig_host: one multisig at its placement. A public PDA, or a group-owned private PDA whose keys every member derives from the group master secret (key_protocol's GroupKeyHolder). It builds rows and envelopes from the core instruction alone, so host and guest agree by construction; builds public or privacy-preserving transactions; and decrypts the multisig's new note as a member would. - host/tests/v03_state.rs, on LEE's own V03State: the public posture end to end (create with funds already sent, 2-of-3 pay, below threshold moves nothing); a state anywhere but its PDA refused; rows other than the outflow's refused; a time lock landing only from its ETA; R.2 on chain; a spending limit; and the private posture: create a private PDA, shield funds into it, pay 2-of-3 out of it with both approvals verified inside the proof. The chain never holds a public account for the multisig. - The guest uses RISC Zero's accelerated k256 / crypto-bigint / sha2 forks (guest workspace only). - module/src/lez/msig.nim: muster's port of the types, borsh, tagged hashes and public PDA, held byte for byte to vectors the program's host generates. A BIP-340 signature made in Nim equals the host's. - msig_bench: RFP P.1/P.2 with real proving (refuses RISC0_DEV_MODE). Tested-Behavior: cargo test -p msig_host (8, public + private posture on V03State); module/tests/run-suite.sh unit lez_msig_bytes (every encoding, hash, envelope and signature against the program's vectors) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…2e against the real program A muster room creates and uses a lez-msig multisig end to end on a LEZ v0.3 zone: - drivers/lez_msig.nim (family lez.msig): the account is the multisig's public PDA, re-derived from (program, create key) on disclosure. The effect is the proposal's exact borsh bytes, so the card's decode is of what approvals sign (RFP U.6). canonicalize commits to the proposal id under the chain. A contribution is a BIP-340 approval by a member's per-multisig key, named msig:<key>, signed and attested in-app by that one key. - coordination/msig_room.nim: the creation ceremony (lmsig/<ck>/open, author-signed lmsig/<ck>/key/<who>, each approval key proved by itself over room + create key + identity), and proposing at the chain's epoch and the next nonce free on chain and in the room. Proposing is the proposer's approval. - wallet/lez_msig_chain.nim: the chain seam, live over the user's own sequencer. Every instruction goes from the member's own funded account, which pays its fee. - settlement LezMsigSettlement: reads the program's state and sends ExecuteQueued, Queue or Execute with the driver-verified approvals. Refuses a stale epoch, a spent nonce, an expired proposal or too little weight, by name. watch judges by state (a refused call is included on v0.3), and executes a queued action itself once its lock ends. Settlement.watchDeadlineS lets a time-locked watch outlive the default. - attest: msig:<key> attestations verify as BIP-340 over keccak256(P). Tested-Behavior: lez_msig_live_e2e on infra/lez/localnet.sh with the real program (ceremony, create from a member's account, disclose, 2-of-3 pay in one tx, R.2 stale-epoch refusal, time lock queued then executed by the watch, below threshold sends nothing); lez_msig_driver_test (disclosure, effect, contributions, in-app sign + attest, weights, conformance) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…gistry + corpus, real-proving benchmark - muster.lidl: lez_msig_open, lez_msig_ceremonies, coordinate_propose_lez_msig, lez_msig_state (regenerated muster_gen.nim). Settings → lez-msig-program. - muster_module: the intents tick joins every ceremony that invites this member, and the opener's client sends Create from its own account and discloses the account once the chain holds it. coordinate_submit reaches LezMsigSettlement over the member's own sequencer. Approvals read as this member's own (myNames: msig:<key> per disclosed multisig). A disclosure takes members, k and config from the program's state, never from the discloser. A pending settle may wait out its time lock (watchDeadlineS). - readiness grades lez-msig-key from the driver's own answer. - The family registry gains lez.msig (built, contract locus, explicit binding); the action corpus gains lez-msig/transfer and lez-msig/config, with an authored lez.msig.json (create, transfer, config); the atlas and the landscape table are regenerated. - programs/lez-msig/BENCHMARKS.md, real proving on a 64-core CPU. A private payment with M approvals: 150.7 s (M=1), 208.5 s (M=5), 288.1 s (M=10), about 15 s per approval. The transaction is a constant 228 KB. Accelerated k256 is 2.9x stock. M >= 5 is viable (P.2). Tested-Behavior: module/tests/run-suite.sh (277/277: kinds, profile, conformance, phase A exit, action corpus with lez-msig; the hosted surface compiles in probe_return_marshalling_host); msig_bench with real proving Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…cutor (msig_exec) - host/tests/live_private.rs, against a real LEZ v0.3.0 sequencer with the loader-deployed program. A group-owned private PDA is created (only a commitment on chain, nothing public at the multisig's id), funded by a shield, and pays a public recipient 2-of-3 in one privacy-preserving transaction; below threshold, it cannot even be proved. - bin/msig_exec: the private posture's executor for muster (derive / exec / fund), JSON in and out. It proves against the chain's membership proof for the multisig's current note and answers with the new note in plaintext, for the room to carry sealed. Tested-Behavior: MSIG_LIVE_RPC=http://127.0.0.1:3040 MSIG_LIVE_PROGRAM=<deployed> RISC0_DEV_MODE=1 cargo test -p msig_host --test live_private (create, shield, 2-of-3 pay on the live local zone; below threshold refused at proving) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
TDD-Exempt: issue tracking only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…card decodes the signed bytes
- The room's accounts panel gains "LEZ multisig (approvals in the room)":
- open a ceremony (M of the room's members, a time lock in minutes), and see each
ceremony's progress until it is created and disclosed;
- the account as its program holds it (vault, M of N, epoch, time lock, queue);
- propose a payment, a new time lock, or the cancel of a queued action.
Proposing sets the room's policy to lez-msig@<account> first.
- The card shows a lez-msig proposal decoded by the module from the exact bytes its
approvals sign (RFP U.6): what it does, its nonce, epoch, expiry and proposal id, and that
a configuration change or the expiry voids it. effectSummary reads the same bytes, so the
card's line and the room history agree.
- muster_ui.rep: lezMsigOpen, lezMsigPropose, loadLezMsig, and the lezMsig*Json properties,
in the C++ backend and the Nim app's (refreshed on the intents tick). lez_msig_state
caches its chain read for 4 s.
Tested-Behavior: lez_msig_driver_test (the card's summary line from the decoded bytes; non-decoding bytes named unknown); probe_return_marshalling_host compiles the hosted surface; the QML is linted and seen on display in the runner next
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…d the self-test - The C++ backend's autopilot drives the slots the accounts panel and the card call: MUSTER_AUTOMSIG_OPEN, _PAY, _APPROVE, _SETTLE. Each tick logs MSIG (the payer account, the ceremonies, the state, the intents). - scripts/lez-msig-self-test.sh: two offscreen runners in one room, the program deployed on infra/lez/localnet.sh. A opens a 2-of-2; both join with their own approval keys; A creates it and discloses it; the script funds the vault; A proposes a payment, B approves, A settles. It passes when the recipient holds exactly the payment and both runners fold the intent final. - msigTick sends Create again if a minute passes with nothing on chain (an unfunded account's Create, or one the sequencer dropped, lands nothing). Tested-Behavior: scripts/lez-msig-self-test.sh, run against the runner build next Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…, for the CLI and the Logos module Tested-Behavior: msig_exec derive / missing field / bad JSON answer as before; cargo build -p msig_host Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…rences, plain words (no nonce / epoch) Tested-Behavior: scripts/ui-lint.sh: 0 new findings against the baseline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
… (pipefail + grep -q), report real seconds Tested-Behavior: MUSTER_FLEET=local scripts/lez-msig-self-test.sh: SUCCESS — a 2-of-2 lez-msig made in the room paid exactly 1200, both runners final Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
… Logos module - lez_msig_module.lidl: health, derive, exec, fund (development only, behind LEZ_MSIG_DEV_FUND=1), job, forget; event job_done. generated/provider_gen.rs comes from the logos-lidl-gen the module builder 0.3.3 runs (logos-rust-sdk 040e94e8; tools/regen.sh checks the pin). - module/: the trait over msig_host::exec::exec_request. A proof is a job: one at a time, on its own thread under catch_unwind, readable only by the caller that submitted it, and never on disk. - The guest is a committed artifact (artifacts/msig.bin, the deployed build). methods/build.rs embeds it and computes its image id, so a build needs no risc0 guest toolchain and the program's identity cannot drift. - lee with "prove": risc0 proves in process (LocalProver), as LEZ's own wallet-ffi does, not through an r0vm on PATH. - exec answers carry the commitment-set root the membership proof was read against, so muster can record it as an external read (invariant 10). Packaging (flake.nix, metadata.json, the lgx) is the next step. Tested-Behavior: MSIG_LIVE_RPC=… RISC0_DEV_MODE=1 cargo test -p lez_msig_module (health, derive, refusals; a private Create on the live local zone as a job: answered at once, busy meanwhile, done with the derived account's note, gone after forget); cargo build --release with lee/prove Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…provers by name, no nonce/epoch
Seen on a display (Xvfb, two runners): the card, the accounts panel, Settle on-chain and the
panel's propose form all work. Fixed what looking showed:
- amounts went through a float ("1.2e-06 LEZ", "Vault 5e-06 LEZ"): base units are now turned
into an exact decimal string, and the summary's amount and unit are LEZ;
- the expiry was a long date: now the locale's short form;
- the other approver read "msig:b17…": a lez-msig approval key is named as the member whose
ceremony join bound it (memberName → msigMemberOf);
- the summary line on Home and the banner said "nonce 0, epoch 0": now "proposal #0".
Tested-Behavior: lez_msig_driver_test (the summary in LEZ, "proposal #0", no "nonce"); scripts/ui-lint.sh 0 new; probe_return_marshalling_host compiles; looked at again on a display after the rebuild
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…is signed in the keystore - packaging: metadata.json, CMakeLists.txt and flake.nix build `.#lgx` on logos-module-builder 0.3.3 (Basecamp 0.3.2's generation). LEZ v0.3.0's artifacts/ reach the vendored lee and programs crates through a setup hook; the circuits, rapidsnark and the recursion zkr are pinned as LEZ's own flake pins them. linux-amd64 only for now. - fund no longer takes a secret. It proves the deposit from the payer's public key and answers with the message hash to sign; the new `send` method takes the signature back, checks it against the message, and sends. No key reaches the module, so fund is no longer development-only. - msig_vectors gives a note vector: muster decodes the multisig's account from a note. Tested-Behavior: cargo test -p lez_msig_module (fund without a request → bad-request; send with an unreadable message → failed); nix build .#lgx built the module before this change (18 min), and a rebuild with it is running; lez_msig_private_e2e (muster) signs and sends a deposit through this path on a local zone Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…ez_msig_module
The private posture: a group-owned private PDA whose state is a note only members read. Each
action is one privacy-preserving transaction, with no member, approval, config or create key
on chain.
- the room: lez_msig_open {private: true} seals a fresh group secret to each invited member
(lmsig/<ck>/gms/<who>; only the opener's copy counts). After every transaction the chain
includes, the member who sent it seals the new note to every member
(lmsig/<ck>/note/<n>/<who>). Both are author-signed (authorship.nim).
- the prover seam (wallet/msig_prover.nim): LpMsigProver reaches lez_msig_module over lp_*
(msig_prover_lp.nim, kept apart so tests link without lp_*); CliMsigProver runs msig_exec.
- the chain seam (wallet/lez_msig_private.nim): MsigPrivate is MsigLive except that it
- reads the state from the newest note,
- sends the instruction through the prover,
- follows the job, then the transaction.
So LezMsigSettlement's rules are one piece of code for both postures. A refusal surfaces
while proving, and nothing is sent.
- a deposit: lez_msig_fund proves a transfer from the member's own public account, signs its
hash in the keystore, and sends it.
- the host: the opener's tick creates through the prover and discloses the account
(placement private). A member holding the group secret checks the address through the
prover; the rest of the room reads unknown.
- family lez.msig-private (registry, profile: the policy and signers are never revealed),
with its actions in the atlas corpus.
- the UI: "Create a private M-of-N LEZ multisig", the private note on the vault, and "Put in
from my account".
Tested-Behavior: lez_msig_private_e2e on a local LEZ v0.3.0 zone in dev mode, 7/7:
- every member opens the one sealed secret; an outsider opens none;
- Create goes through the prover and all three read its note;
- a deposit is signed in the keystore;
- Bob proposes, Carol approves and settles: the recipient is paid and everyone reads 3800;
- the chain's copy names no member key and no create key;
- an overdraft is refused while proving;
- below threshold, nothing reaches the prover.
Also lez_msig_driver_test (private placement, family, rvNever), profile_test (the private
family declared), the whole suite 276/277 before the profile fix and green after it,
probe_return_marshalling_host, ui-lint 0 new, check-family-registry, check-action-corpus.
Not seen on a display yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…r + catalogue), exo-abc (note recovery) TDD-Exempt: issue tracker events only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…re's prover muster_ui depends on lez_msig_module (programs/lez-msig, this tree's copy, like muster_module), so the standalone runner loads it and the UI's "Create a private…" reaches a prover. muster_module declares it as an optional dependency, so Basecamp under --access-policy enforce lets muster call it, and a host without it still loads muster. make build overrides and relocks it alongside muster_module. TDD-Exempt: build wiring; verified by make build and a display run of the private posture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…posits into it MUSTER_AUTOMSIG_PRIVATE opens the ceremony private (the panel's "Create a private…"); MUSTER_AUTOMSIG_FUND=<amount> deposits from the member's own account once the private account is disclosed (the panel's "Put in from my account"). TDD-Exempt: self-test hooks; exercised by the private display run after make build Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
The builder puts only `dependencies` in the runner's module set, so bad95fc made the prover a hard dependency of muster_ui. That would make every Basecamp install of Muster require a module that builds for linux-amd64 alone. ui/flake.nix now builds the runner from a copy of metadata.json with lez_msig_module added; the published .lgx keeps the real one. muster_module still declares it optional, so Basecamp loads it wherever it is installed. TDD-Exempt: build wiring; verified by make build (the runner loads lez_msig_module) and the private display run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
… release builds lock the prover input The runner reads the icon relative to its metadata file, so a builtins.toFile config (in /nix/store itself) failed: "cannot stat /nix/store/src/icons/icon.png". The runner's config is now ui/metadata.runner.json, and evaluation refuses it unless it is metadata.json with lez_msig_module appended. scripts/catalog-release.sh and the lgx-variants workflow override the UI flake's new relative lez_msig_module input as they do muster_module. TDD-Exempt: build wiring; nix eval .#runner passes, make build follows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…saction hash, not the prover's job
Seen on a display: two runners made a private 2-of-2 through the bundled lez_msig_module
(created and disclosed in 66 s), A deposited 5000 from its own account, B approved, and Settle
on-chain on A's screen went final with the vault at 3800. The settled line and Copy hash showed
"job:403b…", the prover's job id. LezMsigSettlement.chainRefOf gives the transaction's hash once
the prover has sent it ("" before, so nothing is recorded early), and the pump records that.
Tested-Behavior: lez_msig_private_e2e 7/7 (on final, chainRefOf is the 64-hex transaction hash the prover sent); probe_return_marshalling_host compiles the host
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…hain The member whose transaction lands seals the new note into the room. If they go away first, the others hold a spent note, and no proof can be made over it. - lez_msig_module gains scan: it walks every block from `from` to the tip (getBlockRange) and decrypts each privacy-preserving transaction's private actions with the group's viewing key (Msig::decrypt_actions). Each note spends the one before, so the newest is the state. It sends nothing. - MsigPrivate.recover follows the scan like any job; the note it finds becomes the state and is sealed to every member. - the host: lez_msig_recover, which also starts by itself when a private settlement fails. The private vault gets "Read it from the chain". - found on the way: a payment's credit to a public payee is a deferred public effect and can land a block after the multisig's note. The e2e now waits for it; muster already judges a payment by the multisig's note. Tested-Behavior: lez_msig_private_e2e 9/9 on a local zone. §8: Carol settles but never seals her note; Alice reads 3800 from the room, recovers 3500 from the chain, and Bob reads it in the room. §9: a further payment settles from the recovered note. cargo test -p lez_msig_module; probe_return_marshalling_host; ui-lint 0 new; check-action-corpus Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
The private LEZ multisig's prover is an optional dependency of muster_module that no Logos catalogue publishes, so Muster's does. catalog-release.sh builds it here (programs/lez-msig#lgx-portable, linux-amd64 only, at its own version), checks its manifest, publishes it as lez_msig_module-<version>-linux-amd64.lgx, and lets the index name it. A --local test on another platform goes without it; a release needs a linux-amd64 host. release-appimage.sh installs it into the AppImage, so a private multisig works there with nothing to install. TDD-Exempt: release scripts; exercised by the 0.3.3 catalogue release (manifest check, index validate) and the AppImage looked at Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
TDD-Exempt: a version bump; the release gate (run-suite, ui-parity, the AppImage looked at) runs on this commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…-default multisig with an encrypted room What is built, measured and verified, with the milestones that remain. The team, contact, budget per milestone and the audit quote are left as [placeholders] for the operator. TDD-Exempt: a document Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
TDD-Exempt: issue tracker events only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
… as 0.4.0; the runner on v0.3.3 Found by the release gate, a fresh catalogue install into Basecamp 0.3.2. Basecamp's default choice for delivery_module is 0.4.0, the newest in Logos Official since 2026-10-14, and it then refuses to load Muster: "Delivery Module — requires ~0.3.2, found 0.4.0". Every fresh catalogue install of Muster 0.3.2 has hit this since that day. delivery_module 0.4.0 is logos-delivery-module 1e1fb56: v0.3.2 plus two commits (mix routing with a shared RLN backend, #148; the LEZ v0.3.1 RLN registry for logos.test, #160). v0.3.3 is that commit plus only its release commit, and the two contracts are identical except for the version line. So muster_module accepts >=0.3.2, as Logos Official's chat_module and chat_ui accept >=0.3.0. module/ and ui/ pin v0.3.3, so the runner and its tests run the code a catalogue install gets. The AppImage keeps its tested bundle (delivery 0.3.2), which the range still admits. Tested-Behavior: make build on v0.3.3, then module/tests/run-suite.sh and scripts/ui-parity.sh (two-instance, invite, relaunch, splits) on that runner; a fresh catalogue install into Basecamp 0.3.2 that takes delivery 0.4.0 loads Muster Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
…sed, exo-bfd's gates recorded TDD-Exempt: docs and tracker events Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: 82cf96d0-9403-5a71-95d3-2ef04ee3e51c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
lez-msig (epic exo-0d3): an M-of-N multisig on LEZ that is private by default, with a muster room as its coordination layer. It answers RFP-005 (
logos-co/rfp). Design and decision log:docs/design/lez-msig.md.The program (
programs/lez-msig/), new, rather than a port of lez-multisig:msig_coreholds every rule as pure functions with no LEZ dependency. 24 tests, two of them property tests.run_program(plan, apply)adapter. The rules sit outside it, so moving todev's transactional actors (LEZ PR #935) is another thin adapter.Both postures, one program:
module/tests/lez_msig_live_e2e.nim).programs/lez-msig/host/tests/live_private.rs).Real-proving benchmark (
programs/lez-msig/BENCHMARKS.md, 64-core CPU, RISC Zero's accelerated k256): a private payment with M approvals proves in 150.7 s at M=1, 208.5 s at M=5 and 288.1 s at M=10. The transaction is a constant 228 KB. M ≥ 5 is viable (P.2).Muster:
lez.msigdriver: the effect is the proposal's exact bytes, which the card decodes (U.6). Its contributor ismsig:<key>, attested by that same key.lez_msig_open,lez_msig_ceremonies,coordinate_propose_lez_msig,lez_msig_state, and Settings →lez-msig-program.lez/msig.nim) is held byte for byte to vectors the program's own host generates.The UI (exo-e38): the room's accounts panel creates a multisig (public or private), shows who has joined, pays, sets the time lock and cancels a queued action. The card decodes the signed bytes: the amount in LEZ, the expiry, and the approvers by name. Seen on a display (Xvfb, two runners);
scripts/lez-msig-self-test.shdrives two real runners through create → propose → approve → settle.The private posture from the room (exo-efa):
lez_msig_module, a Rust Logos module (programs/lez-msig/module,.#lgxon logos-module-builder 0.3.3), proves and sends private transactions. It holds no key: a deposit is proved from the payer's public key, signed in the member's keystore, and sent bysend.MsigPrivatereads the state from the newest note and sends through the prover, so the settlement rules are the public posture's code.lez.msig-privatefamily, alez_msig_fundmethod, and the UI's "Create a private…" and "Put in from my account" buttons.infra/lez/localnet.shprefers the host's libclang and libpcsclite, because nixpkgs' copies fail on Ubuntu 24.04's glibc.Recovery (exo-abc): a member whose room never got the newest note reads it back from the chain with the group's viewing key (
lez_msig_recover, also started automatically when a private settle fails).Release 0.3.3:
lez_msig_moduleis published in Muster's catalogue and bundled in the runner and the AppImage. The publishedmuster_uidoes not require it.>=0.3.2. Basecamp's default, delivery 0.4.0 (the same code as 0.3.3), had made it refuse every fresh catalogue install of Muster.muster-v0.3.3is published from6a74158. Its catalogue index is catalog: Muster 0.3.3, and lez_msig_module 0.1.0 #345; merge this PR first.Not in this PR:
Tests
module/tests/run-suite.sh: the whole suite (re-run on the final head before merge).module/tests/run-suite.sh e2e lez_msig_liveoninfra/lez/localnet.sh, with the program deployed (MUSTER_LEZ_MSIG_BIN).module/tests/run-suite.sh e2e lez_msig_private(dev-mode zone, themsig_execCLI as prover), 7/7:On a display (two runners, Xvfb, a dev-mode local zone), the private path ran through the bundled prover:
The settled line now shows the transaction hash, not the prover's job id.
cargo test -p lez_msig_module;scripts/lez-msig-self-test.sh(two runners);scripts/ui-lint.sh0 new.In
programs/lez-msig:cargo test(core and the in-process state machine), the live private test against the local zone, andmsig_benchwith real proving.🤖 Generated with Claude Code