Skip to content

Concurrent PSBT construction#6

Draft
nothingmuch wants to merge 2 commits into
payjoin:mainfrom
nothingmuch:psbt
Draft

Concurrent PSBT construction#6
nothingmuch wants to merge 2 commits into
payjoin:mainfrom
nothingmuch:psbt

Conversation

@nothingmuch

@nothingmuch nothingmuch commented May 2, 2026

Copy link
Copy Markdown
Contributor

rough draft of a BIP discussing when and how PSBTs merging is a join semi-lattice

this is the basis of state machines of the honest (#4), semi honest and BFT variants of multiparty transaction construction, and ensures these protocols do not depend on all peers observing a consistent ordering of protocol messages which simplifies coordination

TODO:

  • various TODO comments
  • diagrams, examples
  • discuss how bip 370 state machine precedes to the bip 174 state machine, and similarly this precedes bip 370 state machine (states closer to the terminal state can be represented with lower PSBT versions)
  • discuss tradeoffs of of PSBT_GLOBAL_TX_UNORDERED as an indicator that the order is random vs. PSBT_GLOBAL_UNORDERED_{INPUT,OUTPUT} with keydata and the input map serialized in the value
    • pro PSBT_GLOBAL_TX_UNORDERED marker and backwards compatible input/output maps:
      • unordered marker matches joinpsbts behavior
      • unique identification semantics are closer to BIP 370
      • only one logical place for authoritative input or output data
    • cons:
      • combine "just works" for unordered fields in this representation if done recursively, even with BIP 370/174 rules for legacy combiners, exploiting the fact that unique id under the old rules is meaningless
      • ambiguous if both positional and keyed input/output data is included, possible safety issue if conflicting due to unintentional forwards compatibility for older implementations

@nothingmuch nothingmuch force-pushed the psbt branch 4 times, most recently from 69fb1a0 to 8cd79f9 Compare May 5, 2026 10:21
@arminsabouri arminsabouri mentioned this pull request May 5, 2026
@nothingmuch

Copy link
Copy Markdown
Contributor Author

PSBT_GLOBAL_TX_UNORDERED is modeled as a bitfield based on BIP 370's MODIFIABLE field, but it's not clear that that is useful, and it complicates implementation.

I think it's sufficient to define PSBT_FLOBAL_TX_UNORDERED as just a bool since i can't think of any situation where simply marking the unordered inputs as non-modifiable isn't sufficient.

it could still be encoded as a bitfield where both bits 0 and 1 have to be set, for forward compatibility, allowing some refinement in the future if a use case arises that requires fixing the order of the inputs or the outputs (so potentially all combinations of { inputs, outputs} x { modifiable, ordered } might be representable but only some make sense to implement (unordered seems to only make sense while at least inputs or outputs are modifiable).

note that this is specific to more than two parties, in the two party setting SIGHASH_SINGLE or some other reason can impose specific ordering requirements, but BIP 370 Constructor is already appropriate for such use cases.

in a multiparty atomic swap/net-settlement transaction involving something like ordinals, my understanding is they are typically managed using dust UTXOs. this can be managed in a hybrid setup: use BIP 370 for those coins in one PSBT, concurrent constructor for the exchange part, and then fix the order of that and concatenate the two transactions with the existing joinpsbts functionality. there is no benefit to privacy from shuffling the ordinals with the fungible bitcoin so long as their containing UTXOs are more or less a fixed amount of dust.

i'm still of the opinion that ordinals are basically an anti fungibility psyop and a zero sum game with externalities, so i don't want to take part in encouraging that, because this can be easily composed i think that's a solid argument for why that should be out of scope and not mentioned in the doc at all

@nothingmuch nothingmuch force-pushed the psbt branch 10 times, most recently from c347dce to 45b1999 Compare May 13, 2026 00:59
@nothingmuch nothingmuch changed the title Distributed PSBT construction Concurrent PSBT construction May 22, 2026

@fjahr fjahr left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only looked at this through the lens of a payjoin/coinjoin with fullagg CISA but that is probably the most restrictive case I can contribute here. I think this works but there are two points that I found that need to be handled more strictly than the document says and that also means the process becomes a bit more fragile.

First, from my understanding, how this would fit with the fullagg payjoin flow:

  1. Everyone builds the unordered PSBT concurrently. Each input carries its aggregation mode (marker byte) since the signature message commits to it anyway.
  2. Termination: fix the seed/sorting and clear PSBT_GLOBAL_TX_UNORDERED. I guess this is "confirmation": confirm unique ID + seed. The seed must be covered by the confirmation, see the second point below.
  3. Fullagg round 1: exchange of the public nonces. This can ride on the confirmations from step 2, so no extra round trip. (Sharing nonces even earlier in step 1 would also be possible)
  4. Fullagg round 2: sharing the partial signatures, allowing anyone to aggregate and finalize. If anything changed since step 2, go back to step 2/3 with fresh nonces from everyone.

Extra PSBT fields (very similar to BIP 373 for MuSig2):

  • PSBT_IN_CISA_AGG_MODE: the marker byte per input (opt-out / half-agg / full-agg), well I said this only focuses on fullagg but I guess we'll certainly need this
  • PSBT_IN_CISA_FULLAGG_PUB_NONCE: fullagg/dahlias public nonce. Practically this is scoped by session id and seed.
  • PSBT_IN_CISA_FULLAGG_PARTIAL_SIG: 32-byte partial signature

So the two points I noticed so far that need to be handled more carefully:

  • Signing more than once, as the text allows, doesn't work for fullagg participants. There can not be more than one partial signature per secret nonce. Producing a second partial signature over a changed transaction with the same nonce leaks the secret key. So a change after round 2 has started means starting over with fresh nonces from everyone. That restart also needs a special path at the PSBT layer, I guess, since a fresh nonce for the same input conflicts with the stale one, and the strict combiner has to fail on it.
  • Since the ordering determines the fullagg message list, the seed must be fixed and confirmed before any partial signature exists. The just-in-time seed variant can still work, but only if the confirmation in step 2 explicitly covers the seed, so that no signer can ever be shown two different orderings within one nonce session.

Comment thread psbt.md

Parties which neither send nor receive payments within a transaction can safely
sign so long as all of their intended outputs are covered by the input
signature. This may require signing more than once if some messages are

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signing more than once would be unsafe for fullagg cisa. We would need to start a new session in this case and also ensure nonces are not reused across sessions.

Comment thread psbt.md
In applications where this is desirable, the *Creator* should only set
`PSBT_GLOBAL_SORT_DETERMINISTIC` to `0x01`, and leave `PSBT_GLOBAL_SORT_SEED`
unset. Each replica will locally provide a value for `PSBT_GLOBAL_SORT_SEED`
just in time, as a commitment to protocol transcript (e.g. the root hash of a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sorting needs to be settled before participants in fullagg can start signing. Probably the creator setting it like described below will be most practical for this case.

Comment thread psbt.md
well.

Before signing, every sender must wait until it has positive confirmation of
the PSBT's unique ID (which commits to the full output set) from every one of

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the sort seed would be good to confirm? At least in the fullagg case this could be critical.

Comment thread psbt.md

### Divergence and SIGHASH flags

Signers MUST sign only using `SIGHASH_ALL`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should probably talk about SIGHASH_DEFAULT here as well?

Comment thread psbt.md

#### Termination

knowing when to proceed to signing

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From fullagg view it's simply when the nonces have been shared aside from the other values mentioned above being fixed

Comment thread psbt.md
In order for combination being deterministic, idempotent and commutative and
associative in regards to transaction effects, when either bits 0 or 1 of
`PSBT_GLOBAL_TX_MODIFIABLE` are set, indicating the transaction is modifiable,
instead of choosing arbitrarily the strict *Combiner* MUST fail whenever

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this failing is critical for fullagg.

Comment thread psbt.md

creator -> constructor<modifiable, unordered> -> constructor<modifiable> (bip 370) -> updater -> signer -> combiner -> finalizer -> extractor

TODO discuss scrubbing after signer and before combiner

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defining scrubbing for fullagg would be interesting as in making sure the shared pubnonces are scoped to session and sorting so they are removed when these change

Comment thread psbt.md
can always be determined, or set `PSBT_GLOBAL_SORT_DETERMINISTIC` to `0x00` to
indicate explicit sort keys are required..

TODO global sort seed || hash of fee contributions or expect application to

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small note: fullagg messes with the fee contribution since the last input, randomly chosen, bears the load of the final fully aggregate signature while everyone else only has the marker.

Comment thread psbt.md

TODO diagram for the combined state machine, indicate when downconversion (to v2 or v0) can happen

creator -> constructor<modifiable, unordered> -> constructor<modifiable> (bip 370) -> updater -> signer -> combiner -> finalizer -> extractor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finalizer would likely be the role/step that needs to be fullagg aware and constructs the finalizes the signature? Just mentioning since there seems to be no other mention here so far.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants