← index

PSBT/MuSig2 coordination over Nostr relays: transport invariants for nonce safety under at-least-once delivery

An archive of delvingbitcoin.org · view original topic →

· Rafael Turon · #1 ·

Most multi-party custody arrangements (2-of-3 multisig, MuSig2 Taproot aggregates, covenant-free time-locked vaults) still move their PSBTs and signing rounds through a coordinator run by the wallet vendor. The on-chain construction distributes authority; the coordination path re-concentrates observation and liveness in one operator. Joinstr, Munstr, Smart Vaults and Nunchuk have each shown that Nostr relays can carry parts of this traffic. What the lineage has not produced is a treatment of the transport itself: its invariants, its threat model, its failure modes at serious stakes.

We published a working note in August as an attempt at that treatment: PSBT (BIP-174) and MuSig2 (BIP-327) coordination over a small mesh of independent relays, borrowing the remote-signer pattern of NIP-46 and composing with BSMS (BIP-129). Design goal: no relay holds key material, decrypts fragments, or learns the spending graph; the only capability an adversarial relay retains is denial of service.

Links

The note names five open problems. I want to lead with the sharpest, because it is the one an aggregate keypath makes concrete rather than theoretical.

Nonce safety under adversarial delivery. BIP-327’s proof assumes a signer never reuses a secret nonce and assumes orderly communication. A relay substrate delivers at-least-once: retries, duplicates, reordering. The transport therefore inherits obligations (single-consumption nonce state, per-session sequencing, idempotent message identity), and property-based testing over that state space finds counterexamples in naive clients but does not prove the invariants sufficient.

Three questions I would value answers to, in decreasing order of how much they would change the design:

  1. Under at-least-once delivery, is per-session monotonic sequencing enforced client-side sufficient for secnonce uniqueness, or does the aggnonce-via-relay path in PartialSigVerify open an identifiability or replay gap that requires a stronger transport commitment?
  2. Policy-blind transport: a relay that validates what it forwards becomes a censor; one that forwards anything becomes a spam vector. Can garbage resistance sit anywhere other than fully client-side, and if not, what is the minimal client-side structure that keeps the relay blind?
  3. Accountability without a global observer: is there a per-message cryptographic structure (signed transcript hashes, chained acknowledgements) that lets honest parties prove relay or peer misbehaviour without reintroducing a party that logs everything?

Criticism of the architecture as a whole is equally welcome; the note is a sketch with named gaps, not a shipped protocol.

· Anzus · #2 ·

From a wallet user’s point of view, it would help to see a simple failure example: what happens if one relay goes offline or sends the same message twice? Does the wallet recover automatically, or does the signing session need to restart? Clear status and recovery steps would matter before users trust this with larger amounts.

· Rafael Turon · #3 ·

Good question, and it splits by what is observable rather than by which component failed.

Relay offline. A dropped connection is observable: the client fails over to the remaining relays in the set fixed at setup and the session continues, since every message is published to all relays and delivery needs only one common live path. A live relay that silently withholds is not observable; from the receiver’s side it is indistinguishable from a slow relay or an offline peer. So recovery cannot depend on diagnosing the cause, only on local session state plus a timeout.

Duplicate delivery. The rule that keeps this safe is local to the signer, not a property of the relays: the secnonce is consumed once, inside Sign (libsecp256k1’s musig module zeroes it on use), and every round output is cached. A retry means resending the same bytes, which is harmless, never recomputing. The hazard is the recompute path: relay delivers aggnonce A, signer signs, completion is withheld, client times out, A′ arrives “for the same session.” A client that re-runs Sign with a retained secnonce leaks its key. The transport obligations in the note (idempotent message identity bound to session, round and signer; per-session sequencing) exist so the client can recognize repeats and reject stale sessions, not so it can trust delivery.

By state:

So: automatic recovery for connection loss and for lost or duplicated messages; explicit restart with fresh nonces on timeout; no path where the wallet silently retries a signature. For a user that reduces to three states: reconnecting, waiting on peer, session expired. Agreed this needs to be specified precisely before anyone trusts it at size; that is the substance of Problem 4.

· Anzus · #4 ·

That three-state explanation makes this much easier to understand. One practical detail may also be worth documenting: can users safely close and reopen the wallet while it is reconnecting or waiting on a peer, or should they keep the session open? That seems like a situation ordinary users could easily encounter.

· F__T__W · #5 ·

(post deleted by author)

· Rafael Turon · #6 · · in reply to #4

Short answer: closing is always safe, provided the wallet defines closing as an abort. What varies is whether it is free or costly, and the line falls at the moment Sign runs.

Before Sign (reconnecting, waiting on peer nonces). No irreversible state exists. Closing discards a secnonce that was never consumed. Reopening starts a fresh session with a new id and fresh nonces. The cost is a round trip.

After Sign (partial signature emitted). The secnonce is already gone by construction. If the cached partial signature survived the close, the client resends it and the session completes. If it did not, the session is dead and has to be restarted with fresh nonces. Either way there is no path back to signing again inside that session, which is the property that matters.

So the user-facing rule is: you can close the app at any point, you may lose the session and have to start over, you cannot lose the key by closing.

The hazard is not closing, it is restoring. On a client that persists session state, nonce safety turns entirely on the consumed marker never being rolled back. Three ways ordinary users reach exactly that:

Each can resurrect a secnonce the signer has already consumed, which is the one failure that costs the key rather than the session. Note this is a UX surface, not only an implementation detail: “restore from backup” is a button ordinary users press.

The conservative default, and what I would specify: session state is device-local, never synced, never restored from a snapshot. A resumable session is opt-in behaviour a wallet earns by writing the consumed marker durably before Sign returns, not after. A wallet that holds the secnonce in memory only, so that closing the app is defined to end the session, is safe by construction and is the right default for most implementations.

· Anzus · #7 ·

Thanks, that makes the distinction clear. The backup restore and multi-device cases seem especially important because users may assume those actions are always safe. A clear warning when a signing session is active would help prevent that mistake.

· Rafael Turon · #8 · · in reply to #7

Agreed that a warning helps, with one caveat: it is the weakest of the three layers and it fails in the case that matters most. A restore is often performed on a new or wiped device after a loss, where there is no live session and no UI state to warn about. The wallet cannot see that the state it is restoring contains a secnonce some earlier instance already consumed.

So the warning is worth having, but as the human-facing layer on top of two structural ones:

  1. Keep the session store out of backup and sync scope entirely. On iOS that means excluding it from iCloud backup, on Android setting the store outside allowBackup. A state file that is never captured cannot be restored.
  2. Bind session identity to a device-local key, so a copy that does reach another device produces messages no peer will accept under that session id. The resurrected copy then fails closed: unrecognized session, abort, start fresh with new nonces.
  3. Warn on the live cases a user can actually act on: closing, exporting or syncing while a session is open.

With 1 and 2 in place, a missed warning costs a session. Without them, a missed warning can cost a key, and no wording is reliable enough to carry that weight alone. I will write these as normative requirements rather than guidance, since the class of failure does not tolerate implementer discretion.