feat: add starforge multisig ceremony command family - #65
Merged
Conversation
Adds an orchestrated M-of-N signing workflow (start/sign/status/submit) for treasury and governance transactions, where signers can operate on separate, potentially air-gapped machines with no shared process state. - src/utils/ceremony.rs: portable ceremony file format (manifest + tx envelope + signature log), manifest/tx-body integrity hashing, and signature collection built on the existing multisig.rs primitives. - src/commands/multisig_ceremony.rs: CLI wiring for `starforge multisig ceremony start|sign|status|submit`, reusing the existing hardware wallet signing path for --hardware. - Tamper detection rejects a ceremony file whose manifest or unsigned transaction body was altered after the ceremony started. - docs/multisig-ceremony.md: full multi-machine/air-gapped walkthrough. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
Adds a
starforge multisig ceremonycommand family that orchestrates thefull lifecycle of an M-of-N Stellar transaction signing session as a single
portable, self-describing file — the workflow that matters most for treasury
and protocol-governance safety, where signers operate on separate
(potentially air-gapped) machines and no single machine should ever hold
enough signing authority to submit alone.
Today, coordinating this requires signers to manually copy XDR blobs around
and hope they combine them correctly. This PR replaces that with four
subcommands built on the existing
utils/multisig.rsandutils/hardware_wallet.rsprimitives:starforge multisig ceremony start— builds an unsigned transactionenvelope plus a manifest (required signers, threshold, expiry) into one
.ceremonyfile.starforge multisig ceremony sign— adds a signer's signature (localwallet or
--hardware ledger|trezor), no network access required.starforge multisig ceremony status— reports collected vs. requiredsignatures, outstanding signers, and time remaining before expiry.
starforge multisig ceremony submit— verifies the threshold is metand submits, refusing to do so if signatures are insufficient, from
unauthorized signers, or if the file has been tampered with.
Ceremony files are plain JSON (base64 XDR embedded as a string) — diffable
and git-friendly, so they can be passed via USB drive, QR code, or a shared
repo/PR for auditability.
What's included
src/utils/ceremony.rs(new) — the ceremony file format (versioned),manifest + transaction-body integrity hashing, and signature
collection/merge logic, built on top of
utils/multisig.rs's existing XDRencode/decode/sign primitives (several of which were changed from private
to
pub(crate)for reuse rather than duplicated).src/commands/multisig_ceremony.rs(new) — CLI wiring for the foursubcommands, reusing the existing hardware wallet signing path for
--hardwarerather than reimplementing device communication.sign/status/submitcall re-derives twoSHA-256 hashes from the current file contents (one over the unsigned
transaction body, one over the manifest's signer set/threshold/expiry) and
compares them to the values recorded at
ceremony start. Any edit toeither after the ceremony began is rejected with a clear error, at the next
sign,status, orsubmit.docs/multisig-ceremony.md(new) + a new README section — a fullwalkthrough of a 3-of-4 treasury payout across multiple machines, including
the air-gapped/USB-transfer use case and the tamper-detection model.
src/utils/ceremony.rs) — threshold math edgecases (exactly at threshold, below threshold, duplicate signer), tamper
detection (transaction body and manifest), expiry handling, round-trip
serialization, and independent multi-machine sign invocations against a
shared on-disk file with no shared process state.
Design notes
--opaccepts inline JSON or a path to a JSON file describing theoperation (currently
payment, reusingutils/tx_batch::BatchOperationand its existing validation).
(
Preconditions::Time), set atceremony startvia--expires-in-minutes, so it doesn't require a live ledger height lookupto check later.
submitcross-checks the signature log against the actual signature countin the raw XDR envelope, so a hand-edited
signature_logclaiming athreshold isn't enough on its own — the envelope has to actually carry that
many signatures.
signer reviewing the transaction contents before approving.
Test plan
cargo build --bin starforge— clean buildcargo test --lib— 233 passed (0 failed), including the 11 newutils::ceremonytests and the existingutils::multisigsuite(unaffected by the
pub(crate)visibility changes)cargo clippy --lib— no new warnings introducedceremony startwith a 2-of-3 thresholdceremony signinvocations (separate processinvocations, no shared state) against the same file
ceremony statusshowing collected/outstanding signers and expirycountdown at each step
ceremony submitwith only 1 of 2 required signatures → rejected with"Not enough signatures to submit: 1 of 3 ... (threshold 2)"
made", signature count unaffected
status,sign, andsubmitwith "Ceremony manifest integrity check failed"submitonce threshold met → reached real Horizon submission (rejectedonly because the test source account's real key wasn't held locally,
confirming the full pipeline runs end-to-end up to network submission)
Closes #59