Skip to content

WIP - Concurrent psbt rewrite - #78

Draft
arminsabouri wants to merge 3 commits into
mainfrom
concurrent-psbt-rewrite
Draft

arminsabouri wants to merge 3 commits into
mainfrom
concurrent-psbt-rewrite

Conversation

@arminsabouri

@arminsabouri arminsabouri commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

I left both commits so we can see what changed. This ended up being mostly a rewrite of the wording of this BIP to be less jargony and dense. I found the writing to be hard to parse even tho the ideas are quite simple. So no new ideas being presented just a restructing of what was there.

For example, I reconciled all of the background related text into a background subsection.
THere are still many todos and cleanups. But more to rewrite and the flow of ideas can be improved.
i.e:

  • what the current issues are with the combiner and joinpsbt
  • The new fields
  • Strict combining rules for unordered psbts
  • Rules for sorting the pbst
  • thing to be careful about and edge cases
  • reference etc...

Any language specific to collaborative tx construction is treated as outside the scope.
Anything that was removed is in scratch section.

@arminsabouri
arminsabouri force-pushed the concurrent-psbt-rewrite branch from 01cd573 to 1b45dc3 Compare October 23, 2026 19:04
@github-actions

Copy link
Copy Markdown
range-diff for force push 01cd573ddd → 1b45dc344a October 23, 2026 19:04 UTC: 1 modified, 1 unchanged

reproduce: git range-diff b9e78fd..01cd573 b9e78fd..1b45dc3

1:  e8823d9851 = 1:  e8823d9851 Concurrent PSBT spec
2:  01cd573ddd ! 2:  1b45dc344a WIP - cpsbt rewrite
2: 01cd573ddd ! 2: 1b45dc344a WIP - cpsbt rewrite
@@ psbt-concurrent.md: New fields and rules for combining PSBTs in a distributed se
  
 -## Specification
 +Bitcoin Core additionally implements a `joinpsbts` RPC command which does allow merging unique PSBTs, so long as their spent output sets are disjoint.
-+
-+`joinpsbts` is not idempotent with respect to inputs (writing it as $+$, idempotence would mean that $A + A = A$). Attempting to join a PSBT more than once fails closed: if $B$ contains an input, and $A + B$ is defined, then $(A + B) + B$ will fail, because a transaction cannot spend the same coin twice.
  
 -TODO diagram for the combined state machine, indicate when downconversion (to v2 or v0) can happen
-+`joinpsbts` is also not idempotent with respect to the outputs, potentially leading to overspending: if $B$ contains just an output, then $B + B$ will contain two copies of that output.
++`joinpsbts` is not idempotent with respect to inputs (writing it as $+$, idempotence would mean that $A + A = A$). Attempting to join a PSBT more than once fails closed: if $B$ contains an input, and $A + B$ is defined, then $(A + B) + B$ will fail, because a transaction cannot spend the same coin twice.
  
 -    creator -> constructor<modifiable, unordered> -> constructor<modifiable> (bip 370) -> updater -> signer -> combiner -> finalizer -> extractor
-+// TODO:
++`joinpsbts` is also not idempotent with respect to the outputs, potentially leading to overspending: if $B$ contains just an output, then $B + B$ will contain two copies of that output.
  
 -TODO discuss scrubbing after signer and before combiner
 +Although not strictly associative or commutative, due to use of shuffling, in regards to transaction effects it does hold that $(A + B) + C = A + (B + C)$ and that $A + B = B + A$.
@@ psbt-concurrent.md: The new field types for distributed PSBT construction are as
 -The BIP 174 *Combiner* role specifies how to merge diverging copies of the same PSBT. PSBTs are considered "the same", by identifying them based on the unsigned transaction. Two PSBTs with the same unsigned transaction but potentially different key value pairs are considered two versions of the same PSBT.
 -
 -BIP 370 introduces the notion of a [unique ID](https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#user-content-Unique_Identification) which is computed as a hash that commits to UTXOs being spent and the new outputs being created, thereby ensuring PSBTs with the same ID have the same structure, but abstracting away some details of the transaction, thereby introducing a notion of equivalence that is a bit more general.
-+    creator -> constructor<modifiable, unordered> -> constructor<modifiable> (bip 370) -> updater -> signer -> combiner -> finalizer -> extractor
- 
--The effects of a transaction on the distribution of money are determined by its effects the UTXO set: which outputs does it spend and create. However, with regards to money changing hands, we would like to treat equivalent outputs as interchangeable by disregarding the specific outpoints by which specific coins are identified.
 +Example collaborative protocol (*N* wallets). **Creator** and **Constructor** share an empty PSBT with every wallet. Each wallet *i* runs the same pipeline below in parallel. **Combiner**, **Finalizer**, and **Extractor** fan in once all wallets have signed.
 +
 +```mermaid
@@ psbt-concurrent.md: The new field types for distributed PSBT construction are as
 +    COMB --> SIGNED
 +```
  
+-The effects of a transaction on the distribution of money are determined by its effects the UTXO set: which outputs does it spend and create. However, with regards to money changing hands, we would like to treat equivalent outputs as interchangeable by disregarding the specific outpoints by which specific coins are identified.
+-
 -Both notions of identity ensure that PSBTs with the same ID have the same effects, but it is not true that PSBTs with the same effects will have the same ID, since depends on the specific order of the inputs and outputs, among other things. By abstracting over these details we obtain a more general notion of PSBT identity than in BIP 370. The details of how to compute this ID are [provided below](#unique-identifiers).
-+TODO discuss scrubbing after signer and before combiner
- 
+-
 -### Combiner role and transaction effects
+-
+-The BIP 174 *Combiner* role requires that the PSBTs have the same identity. In order to relax this constraint and allow the *Combiner* role to be used in conjunction with (a more general notion of) the *Constructor* role, the *Combiner* role has to be made more conservative in other regards.
 +#### Strict Combiner
  
--The BIP 174 *Combiner* role requires that the PSBTs have the same identity. In order to relax this constraint and allow the *Combiner* role to be used in conjunction with (a more general notion of) the *Constructor* role, the *Combiner* role has to be made more conservative in other regards.
+-BIP 174 specifies that fields with duplicate keys and more than one distinct value may be combined by [picking one of the values arbitrarily](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#handling-duplicated-keys). This is safe within the scope of BIP 174 because any inconsistency will only affect the validity of a transaction. In other words, when choosing one value over another for a duplicate field, although signing may fail to produce a valid signature, no money will be lost because if the IDs are the same, the the effects are identical.
 +BIP 174 lets a Combiner resolve a key that has two different values by picking either one. That is safe when every copy shares the same unsigned transaction, because the worst outcome is an invalid signature. Once inputs and outputs can differ between copies, picking one value can silently change what the transaction spends or pays, and the result depends on the order in which copies were combined.
  
--BIP 174 specifies that fields with duplicate keys and more than one distinct value may be combined by [picking one of the values arbitrarily](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#handling-duplicated-keys). This is safe within the scope of BIP 174 because any inconsistency will only affect the validity of a transaction. In other words, when choosing one value over another for a duplicate field, although signing may fail to produce a valid signature, no money will be lost because if the IDs are the same, the the effects are identical.
+-This notion of safety can be extend beyond just combining signing related data, so that effects of a transaction formed by combining PSBTs do not depend in any way on the order in which the PSBTs were combined, nor even on combination being done sequentially. Note that this does not require that the effects of the constituent PSBT be identical, only that all of them are combined at some point. More formally, in order to be safe, combining should be monotone in the transaction effects.
 +A strict Combiner therefore MUST fail when the same key has conflicting values, whenever bit 0 or bit 1 of `PSBT_GLOBAL_TX_MODIFIABLE` is set. With this rule, the transaction effects of combining a set of PSBTs don't depend on the order or grouping of the combinations, or on whether a PSBT is combined more than once. The following sections define exceptions that keep these properties.
  
--This notion of safety can be extend beyond just combining signing related data, so that effects of a transaction formed by combining PSBTs do not depend in any way on the order in which the PSBTs were combined, nor even on combination being done sequentially. Note that this does not require that the effects of the constituent PSBT be identical, only that all of them are combined at some point. More formally, in order to be safe, combining should be monotone in the transaction effects.
+-Unfortunately choosing between conflicting values arbitrarily does not preserve this notion of safety when merging PSBTs where the inputs or outputs of one PSBT don't correspond to the inputs or outputs which occupy the same positions in the second PSBT. For example, if the first inputs of two PSBTs spend different coins, and one PSBT takes precedence, the input in the other transaction will be omitted. If the `PSBT_IN_PREVIOUS_TXID` field of one PSBT overrides the other but this precedence is reversed for `PSBT_IN_OUTPUT_INDEX`, the one remaining input will be corrupted.
 +##### Combining unique PSBTs
  
--Unfortunately choosing between conflicting values arbitrarily does not preserve this notion of safety when merging PSBTs where the inputs or outputs of one PSBT don't correspond to the inputs or outputs which occupy the same positions in the second PSBT. For example, if the first inputs of two PSBTs spend different coins, and one PSBT takes precedence, the input in the other transaction will be omitted. If the `PSBT_IN_PREVIOUS_TXID` field of one PSBT overrides the other but this precedence is reversed for `PSBT_IN_OUTPUT_INDEX`, the one remaining input will be corrupted.
+-#### `joinpsbts` vs. the Combiner role
 +When `PSBT_GLOBAL_TX_UNORDERED` is set, the input or output maps are not distinguished by their index, but instead by a unique identifier defined for each input or output.
  
--#### `joinpsbts` vs. the Combiner role
+-Bitcoin Core additionally implements a `joinpsbts` RPC command which does allow merging PSBTs with different identities, so long as their spent output sets are disjoint.
 +Inputs or outputs with identical unique IDs are merged by combining their key-value pairs with the stricter, idempotent combination defined above. After merging the inputs and outputs that appear in both PSBTs, the strict combiner additionally keeps the ones that appear in only one of them.
  
--Bitcoin Core additionally implements a `joinpsbts` RPC command which does allow merging PSBTs with different identities, so long as their spent output sets are disjoint.
+-`joinpsbts` is not idempotent with respect to inputs (writing it as $+$, idempotence would mean that $A + A = A$). Attempting to join a PSBT more than once fails closed: if $B$ contains an input, and $A + B$ is defined, then $(A + B) + B$ will fail, because a transaction cannot spend the same coin twice.
 +Distinct identifiers are only allowed if the corresponding `PSBT_GLOBAL_TX_MODIFIABLE` flag is set, because such combinations represent additions of inputs or outputs. If this is not the case but a distinct input or output is present, then combining must fail.
  
--`joinpsbts` is not idempotent with respect to inputs (writing it as $+$, idempotence would mean that $A + A = A$). Attempting to join a PSBT more than once fails closed: if $B$ contains an input, and $A + B$ is defined, then $(A + B) + B$ will fail, because a transaction cannot spend the same coin twice.
+-`joinpsbts` is also not idempotent with respect to the outputs, but unfortunately also does not fail closed, potentially leading to overspending: if $B$ contains just an output, then $B + B$ will contain two copies of that output.
 +##### Unique identifiers for inputs
  
--`joinpsbts` is also not idempotent with respect to the outputs, but unfortunately also does not fail closed, potentially leading to overspending: if $B$ contains just an output, then $B + B$ will contain two copies of that output.
+-Although not strictly associative or commutative, due to use of shuffling, in regards to transaction effects it does hold that $(A + B) + C = A + (B + C)$ and that $A + B = B + A$.
 +Inputs are identified by their outpoint, i.e. the tuple of their `PSBT_IN_PREVIOUS_TXID` and `PSBT_IN_OUTPUT_INDEX`, both of which must be set.
  
--Although not strictly associative or commutative, due to use of shuffling, in regards to transaction effects it does hold that $(A + B) + C = A + (B + C)$ and that $A + B = B + A$.
+-#### Strict Combiner
 +##### Unique identifiers for outputs
  
--#### Strict Combiner
--
 -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 duplicate fields have conflicting (unequal) values.
 +Outputs are identified by a new `PSBT_OUT_UNIQUE_ID` field, which must be universally unique (e.g. 16 bytes of randomness).
  
@@ psbt-concurrent.md: The new field types for distributed PSBT construction are as
 -Rationale: Since there is no known use case that requires input or output to be marked unordered or ordered separately from each other, that is omitted from the spec. The value `0x03` simplifies forward compatibility with any specification that might amend this by allowing either bit to be set independently, so long as it overlaps with this specification when both are set.
 -
 -If set, the `Has SIGHASH_SINGLE` flag of `PSBT_GLOBAL_TX_MODIFIABLE` must not be set. `SIGHASH_SINGLE` inputs [MUST not be used](#divergence-and-sighash-flags) in unordered transactions.
--
++Since there is no known use case that requires input or output to be marked unordered or ordered separately from each other, that is omitted from the spec. The value `0x03` simplifies forward compatibility with any specification that might amend this by allowing either bit to be set independently, so long as it overlaps with this specification when both are set.
+ 
 -Unordered PSBTs MUST be shuffled prior to reserialization. This is easily accomplished by storing them in randomized hashmaps, keyed by their unique identifiers as defined below. If all inputs and outputs have [sort keys](#ordering-inputs-and-outputs), the key based ordering may be used instead of shuffling.
--
++Unordered PSBTs MUST be shuffled prior to reserialization. If all inputs and outputs have [sort keys](#ordering-inputs-and-outputs), the key based ordering may be used instead of shuffling.
+ 
 -[fn:Join is implicitly defined on equivalence class of all permutations of inputs/outputs, but the PSBT contents is a random representative.
 -
 -Rationale: Not shuffling presents a privacy hazard, leaking information about the sequence of actions leading to a transaction's construction. When a transaction is unordered, shuffling by definition doesn't change its meaning, but ensures the incidental order can't accidentally be relied upon.
@@ psbt-concurrent.md: The new field types for distributed PSBT construction are as
 -#### Unique identifiers for outputs
 -
 -Outputs are identified by a new `PSBT_OUT_UNIQUE_ID` field, which must be universally unique (e.g. 16 bytes of randomness).
-+Since there is no known use case that requires input or output to be marked unordered or ordered separately from each other, that is omitted from the spec. The value `0x03` simplifies forward compatibility with any specification that might amend this by allowing either bit to be set independently, so long as it overlaps with this specification when both are set.
- 
+-
 -This ensures that repeated addition of the same output is idempotent, but that if two different distributed constructors both attempt to add an identical output (same `PSBT_OUT_VALUE` and `PSBT_OUT_SCRIPT`), that will still result in two copies of the output as intended. This is important because a transaction with only a single copy will appear correct but end up losing the value of all missing copies to mining fees.
-+Unordered PSBTs MUST be shuffled prior to reserialization. If all inputs and outputs have [sort keys](#ordering-inputs-and-outputs), the key based ordering may be used instead of shuffling.
- 
+-
 -Outputs with an identical `PSBT_OUT_SCRIPT` can be merged, and their values summed together after `PSBT_GLOBAL_TX_UNORDERED` is cleared and an order is determined, using BIP 370 construction rules. See the next section on ordering.
 +Not shuffling presents a privacy hazard, leaking information about the sequence of actions leading to a transaction's construction. When a transaction is unordered, shuffling by definition doesn't change its meaning, but ensures the incidental order can't accidentally be relied upon.
  
@@ psbt-concurrent.md: The new field types for distributed PSBT construction are as
  
  - TODO: unique ID can be derived by sorting even if sort key is empty, but txn finalization must not be unless all sort keys are provided explicitly (regardless of DETERMINISTIC)
 -- add section for unique ID derivation a la BIP 370
-+- TODO: add section for unique ID derivation a la BIP 370
  
  Clearing the `PSBT_GLOBAL_TX_UNORDERED` field indicates the order of the inputs and outputs. Due to the shuffling requirement, the given order will in general not be consistent across replicas if this is done without determining an order, resulting in diverging unsigned transactions.
  
@@ psbt-concurrent.md: For consistent ordering we define `PSBT_IN_SORT_KEY` and `PS
 +If `PSBT_GLOBAL_SORT_DETERMINISTIC` is set to `0x00`, explicit sort keys are required to be set and used to sort the prior to clearing the `PSBT_GLOBAL_TX_UNORDERED` field and fixing the transaction order.
 +
 +If any `PSBT_IN_SORT_KEY` and `PSBT_OUT_SORT_KEY` are omitted, the global `PSBT_GLOBAL_SORT_SEED` field is set, and `PSBT_GLOBAL_SORT_DETERMINISTIC` is either omitted or set to `0x01`, the sort keys are derived deterministically.
++
++A missing `PSBT_IN_SORT_KEY` is derived as:
  
 -If any `PSBT_IN_SORT_KEY` and `PSBT_OUT_SORT_KEY` are omitted, the global `PSBT_GLOBAL_SORT_SEED` field is set, and `PSBT_GLOBAL_SORT_DETERMINISTIC` is either omitted or set to `0x01`, the sort keys are derived deterministically. A missing `PSBT_IN_SORT_KEY` is derived as `H(PSBT_GLOBAL_SORT_SEED || PSBT_IN_PREVIOUS_TXID || PSBT_IN_OUTPUT_INDEX)` and `PSBT_OUT_SORT_KEY` as `H(PSBT_GLOBAL_SORT_SEED || PSBT_OUT_UNIQUE_ID)`, where `H_s` is a BIP 341 style tagged hash, with the tag preimage being `BIP ???? deterministic ordering` (TODO pick a real domain separator).
-+A missing `PSBT_IN_SORT_KEY` is derived as:
-+
 +- `H(PSBT_GLOBAL_SORT_SEED || PSBT_IN_PREVIOUS_TXID || PSBT_IN_OUTPUT_INDEX)` for the inputs
 +- `PSBT_OUT_SORT_KEY` as `H(PSBT_GLOBAL_SORT_SEED || PSBT_OUT_UNIQUE_ID)` for the outputs
 +
@@ psbt-concurrent.md: For consistent ordering we define `PSBT_IN_SORT_KEY` and `PS
 -If that is not the case, the *Creator* should initialize `PSBT_GLOBAL_SORT_SEED` with sufficient randomness thereby ensure some order can always be determined, or set `PSBT_GLOBAL_SORT_DETERMINISTIC` to `0x00` to indicate explicit sort keys are required..
 +This can provides additional integrity to a collaborative transaction construction protocol, since [`SIGHASH_ALL`](#divergence-and-sighash-flags) signatures must agree on the ordering.
  
- TODO global sort seed || hash of fee contributions or expect application to commit to that in transcript if appropriate?
+-TODO global sort seed || hash of fee contributions or expect application to commit to that in transcript if appropriate?
  
- TODO: Rationale for allowing explicit sort key: lightning interactive-tx `serial_id` field. should always use odd parity form, encoded in network order so that lexicographical sorting works the same as the interactive-tx sorting rule.
+-TODO: Rationale for allowing explicit sort key: lightning interactive-tx `serial_id` field. should always use odd parity form, encoded in network order so that lexicographical sorting works the same as the interactive-tx sorting rule.
++### Unique identification
  
 -### Unique identification of unordered PSBTs
-+### Unique identification
-+
 +A canonical ID MAY be needed to if replicas are attempting to tell if they are holding the same PSBT.
 +For unordered transactions (i.e `PSBT_GLOBAL_TX_UNORDERED` is set), the `PSBT_GLOBAL_SORT_SEED` and `PSBT_IN_SORT_KEY` and `PSBT_OUT_SORT_KEY` fields are cleared, and then the ordering procedure of the previous section is used without the restriction of a non-empty `PSBT_GLOBAL_SORT_SEED` (sorting with an empty seed).
  
@@ psbt-concurrent.md: TODO: domain separation from PSBT v2, it doesn't make sense
 -Support for removal is entirely optional. Implementations may explicitly fail when combining with a PSBT that sets either `PSBT_GLOBAL_REMOVED_INPUT` or `PSBT_GLOBAL_REMOVED_OUTPUT`, but completely ignoring these fields is fail safe.
 -
 -Assuming [`SIGHASH_ALL` is used](#divergence-and-sighash-flags), it's sufficient to simply rely on the signature hashing. If an output was removed, a peer which does not implement removal it will diverge from those that do, and therefore produce a signature that will not be valid for the transaction supporting peers will produce and subsequently sign, and vice versa, ensuring that neither transaction is valid.
+-
+-This optionality extends the `PSBT_IN_UNIQUE_ID` field, a removal and re-insertion with incompatible data will either cause combination to fail due to a mismatch or succeed by ignoring the removal and treating the insertion as idempotent, but otherwise diverge.
 +If a signer uses `SIGHASH_ANYONECANPAY` or `SIGHASH_NONE`, the signature stays valid for a family of transactions that do not have the same effects.
  
--This optionality extends the `PSBT_IN_UNIQUE_ID` field, a removal and re-insertion with incompatible data will either cause combination to fail due to a mismatch or succeed by ignoring the removal and treating the insertion as idempotent, but otherwise diverge.
+-While this would allow limited interoperability among clients, that is not reccomended, and is described as an argument for the safety of omitting any removal related functionality. Implementations which omit removal functionality SHOULD fail or at least warn if combining a PSBT that signals removal.
 +That at least makes the txid malleable, so it cannot be used with any protocol that needs pre-signed descendant transactions.
  
--While this would allow limited interoperability among clients, that is not reccomended, and is described as an argument for the safety of omitting any removal related functionality. Implementations which omit removal functionality SHOULD fail or at least warn if combining a PSBT that signals removal.
+-TODO: while combine is monotone of data / PSBT fields including removals, it is no longer monotone in transaction effects. This does not present a safety issue if [only `SIGHASH_ALL` is used](#divergence-and-sighash-flags).
 +Those transactions can also differ enough in what they spend or pay that funds can be lost, not only because someone is cheating, but even because a contribution was left out. `SIGHASH_ALL` commits to the full output list, so a signature cannot authorize a transaction the signer did not intend.
  
--TODO: while combine is monotone of data / PSBT fields including removals, it is no longer monotone in transaction effects. This does not present a safety issue if [only `SIGHASH_ALL` is used](#divergence-and-sighash-flags).
--
 -Rationale for optionality: not implementing is safe, couldn't find usage of `tx_remove_{input,output}`, non-monotonic state makes termination / limits trickier.
 -
 -TODO for interactive-tx, set `PSBT_IN_UNIQUE_ID` to odd parity `serial_id`. this makes it possible to support `tx_remove_{input,output}` operations.
@@ scratch (new)
 +
 +- discuss liveness. safety WRT transaction effects is nice but we also want to
 +  minimize irrecoverable failures. discuss both generally and in CRDT section.
++
++
++
++TODO global sort seed || hash of fee contributions or expect application to commit to that in transcript if appropriate?
++
++TODO: Rationale for allowing explicit sort key: lightning interactive-tx `serial_id` field. should always use odd parity form, encoded in network order so that lexicographical sorting works the same as the interactive-tx sorting rule.
+ \ No newline at end of file

Comment thread psbt-concurrent.md

A *Creator* creating an unordered transaction SHOULD set `PSBT_GLOBAL_SORT_DETERMINISTIC` to enure consistent ordering rules are enforced.

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 or something similar.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deterministic flag requires a seed that is left unset

The spec requires PSBT_GLOBAL_SORT_SEED with at least 128 bits of randomness when PSBT_GLOBAL_SORT_DETERMINISTIC is 0x01, but this paragraph tells the Creator to set 0x01 and leave the seed unset, so a validator enforcing the MUST rejects the Creator's intermediate PSBT.

oak · automated · 1b45dc3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fully deterministic requires a seed at the time of sorting, not throughout

in fungi this seed is a hash of the protocol transcript set by each client independently at the very end

Comment thread psbt-concurrent.md Resolved
Comment thread psbt-concurrent.md Outdated
where `H_s` is a BIP 341 style tagged hash, with the tag preimage being `BIP ???? deterministic ordering` (TODO pick a real domain separator).

If any sort key is missing, ordering can only proceed after `PSBT_GLOBAL_SORT_SEED` is set.
If `PSBT_GLOBAL_SORT_SEED` is set and an individual field-level sort seed is set. The inidivudal sort seed will be ignored and replaced by the determinsitic key defined above.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Undefined field-level sort seed leaves ordering ambiguous

The new sentence refers to an "individual field-level sort seed" that is never defined; the only per-map ordering fields are PSBT_IN_SORT_KEY and PSBT_OUT_SORT_KEY. If it means the sort key, it contradicts the preceding rule that derived keys are used only when explicit keys are omitted, so replicas can sort the same set differently before clearing PSBT_GLOBAL_TX_UNORDERED.

oak · automated · 1b45dc3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

not sure what "field level sort seed" is

the only way in which the sort seed is used is to derive sort keys by a hash, so there's one sort seed for the entire transaction

Comment thread psbt-concurrent.md Resolved
Comment thread psbt-concurrent.md

Clearing the `PSBT_GLOBAL_TX_UNORDERED` field indicates the order of the inputs and outputs. Due to the shuffling requirement, the given order will in general not be consistent across replicas if this is done without determining an order, resulting in diverging unsigned transactions.

For consistent ordering we define `PSBT_IN_SORT_KEY` and `PSBT_OUT_SORT_KEY` fields for inputs and outputs respectively, that may include up to 32 bytes of arbitrary data, used as a sort key for lexicographical sorting.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Equal sort keys leave input order undefined

Sort keys are arbitrary bytes with no uniqueness requirement and may be empty, but lexicographic sorting has no tie-break, so replicas can order equal-key maps differently and produce divergent unsigned transactions.

oak · automated · 1b45dc3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is a fair point

not likely to matter for cases we actually care about where collisions of 128-256 bit random strings are cryptographically negligible, but this probably should be addressed

stable sort is unfortunately not enough.... tie breaking seems awkward. since we already have related error conditions in sortting (e.g. missing keys if non-deterministic) i think we should just add another restriction that sort keys must be unique?

@oakagent oakagent 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.

Typos in what this pull request adds:

psbt-concurrent.md

  • l.15 - "integrate information add by either" → "integrate information added by either"
  • l.15 - "intended for for PSBTs" → "intended for PSBTs"
  • l.120 - "the transaction in unordered" → "the transaction is unordered"
  • l.128 - "Ordering inputs and ouputs" → "Ordering inputs and outputs"
  • l.151 - "inidivudal sort seed" → "individual sort seed"
  • l.151 - "determinsitic key" → "deterministic key"
  • l.153 - "to enure consistent" → "to ensure consistent"
  • l.162 - "This can provides" → "This can provide"
  • l.167 - "needed to if replicas" → "needed if replicas"
  • l.170 - "its the same" → "it's the same"
  • l.170 - "same prodcedure" → "same procedure"

oak · automated

@yuval-block yuval-block left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

first review pass, approach ACK, i might have some more comments where i'll be a stickler for particular phrasing but on the whole this looks like a big improvement, thanks!

Comment thread psbt-concurrent.md Resolved
Comment thread psbt-concurrent.md Resolved
Comment thread psbt-concurrent.md
BIP 370 introduces the notion of a [unique ID](https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#user-content-Unique_Identification) which is computed as a hash that commits to UTXOs being spent and the new outputs being created, thereby ensuring PSBTs with the same ID have the same structure, but abstracting away some details of the transaction, thereby introducing a notion of equivalence that is a bit more general.
Example collaborative protocol (*N* wallets). **Creator** and **Constructor** share an empty PSBT with every wallet. Each wallet *i* runs the same pipeline below in parallel. **Combiner**, **Finalizer**, and **Extractor** fan in once all wallets have signed.

```mermaid

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if this was laid out horizontally i think that would help its readability

also maybe a second diagram that shows how forking and merging of the state is possible would be helpful? this can also remove the "wallet i" caption which is implying that

FWIW there was some pushback on using mermaid diagrams in the BIP 77 PR since that requires client side javascript, i think that is fine if thee diagrams are not essential for reading and we can just add a rendered version, but just wanted to take note of this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah i see this as a useful guide.

Comment thread psbt-concurrent.md Outdated
Comment on lines -55 to -57
The effects of a transaction on the distribution of money are determined by its effects the UTXO set: which outputs does it spend and create. However, with regards to money changing hands, we would like to treat equivalent outputs as interchangeable by disregarding the specific outpoints by which specific coins are identified.

Both notions of identity ensure that PSBTs with the same ID have the same effects, but it is not true that PSBTs with the same effects will have the same ID, since depends on the specific order of the inputs and outputs, among other things. By abstracting over these details we obtain a more general notion of PSBT identity than in BIP 370. The details of how to compute this ID are [provided below](#unique-identifiers).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

was this deliberately removed? seems like it's worth discussing in which sense are unordered PSBTs a representative of an equivalence class of ordered ones, and since BIP 174 and 370 define a notion of PSBT ID i think the spec would be incomplete without this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

was this deliberately removed?

yes. i had a hard time understanding what this was trying to convey. It seems like the point its trying to convey are already covered in strict combiner section?

Comment thread psbt-concurrent.md
#### Strict Combiner

BIP 174 specifies that fields with duplicate keys and more than one distinct value may be combined by [picking one of the values arbitrarily](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#handling-duplicated-keys). This is safe within the scope of BIP 174 because any inconsistency will only affect the validity of a transaction. In other words, when choosing one value over another for a duplicate field, although signing may fail to produce a valid signature, no money will be lost because if the IDs are the same, the the effects are identical.
BIP 174 lets a Combiner resolve a key that has two different values by picking either one. That is safe when every copy shares the same unsigned transaction, because the worst outcome is an invalid signature. Once inputs and outputs can differ between copies, picking one value can silently change what the transaction spends or pays, and the result depends on the order in which copies were combined.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"within the scope of bip 174" is a bit more subtle than "shares the same unsigned transaction" because equivocated values may have safety implications... bip 174 stipulates that this could only produce an invalid or valid transaction which is correct in its scope, but for security to carry through more generally may mean more than just "on any equivocation the transaction isn't valid", in particular where some notion of secret sharing is in effect that could potentially lead to subtle bugs like key cancellation, leaking secrets through nonces etc, which is why the strict combiner is a safer baseline (in addition to being a useful definition for our general merging procedure)

Comment thread psbt-concurrent.md
A strict Combiner therefore MUST fail when the same key has conflicting values, whenever bit 0 or bit 1 of `PSBT_GLOBAL_TX_MODIFIABLE` is set. With this rule, the transaction effects of combining a set of PSBTs don't depend on the order or grouping of the combinations, or on whether a PSBT is combined more than once. The following sections define exceptions that keep these properties.

Unfortunately choosing between conflicting values arbitrarily does not preserve this notion of safety when merging PSBTs where the inputs or outputs of one PSBT don't correspond to the inputs or outputs which occupy the same positions in the second PSBT. For example, if the first inputs of two PSBTs spend different coins, and one PSBT takes precedence, the input in the other transaction will be omitted. If the `PSBT_IN_PREVIOUS_TXID` field of one PSBT overrides the other but this precedence is reversed for `PSBT_IN_OUTPUT_INDEX`, the one remaining input will be corrupted.
##### Combining unique PSBTs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what is meant by "unique PSBTs"? my only guess is that it's the joinpsbts specific restriction, which we want to relax

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

psbts with different txids. Is there a more canonical term for this?

Comment thread psbt-concurrent.md Outdated
This ensures that repeated addition of the same output is idempotent, but that if two different distributed constructors both attempt to add an identical output (same `PSBT_OUT_VALUE` and `PSBT_OUT_SCRIPT`), that will still result in two copies of the output as intended. This is important because a transaction with only a single copy will appear correct but end up losing the value of all missing copies to mining fees.

This establishes a more conservative foundation for ensuring that the effects of a transaction formed by combining a set of PSBTs will always be the same regardless how these PSBTs were combined to obtain a complete transaction.
// TODO: if we merge two output how do we decide which id identifies the new one with the usmmer value?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

merging of same script txout is deterministic and applied by or after the Sorter, so unique IDs are no longer required after since by that point txouts would be identified by position

Comment thread psbt-concurrent.md
If the global field `PSBT_GLOBAL_SORT_DETERMINISTIC` is set (unsigned 8 bit integer) to `0x01`, then sort keys MUST NOT be set, and `PSBT_GLOBAL_SORT_SEED` MUST be set and contain at least 128 bits of randomness.

If `PSBT_GLOBAL_SORT_DETERMINISTIC` is set to `0x00`, explicit sort keys are required to be set and used prior to clearing the `PSBT_GLOBAL_TX_UNORDERED` field and fixing the transaction order.
// TODO: and sort key must not be set? and you have both PSBT_GLOBAL_SORT_DETERMINISTIC and SORT_SEED?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the rationale here is that sorting itself is only done by sort key, fully deterministic sorting treats the sort keys as purely a computed property, but in between there's a potentially useful (for compatibility with lightning interactive-tx, and potentially for coinjoins that also atomically trade ordinals) to have a specified order for some of the inputs/outputs and deterministically order the remaining ones.

so the GLOBAL_SORT_DETERMINSTIC field controls whether we're in this purely-by-explicit-key, purely deterministic, or this mixed mode without defining two or three distinct procedures

@bc1cindy

Copy link
Copy Markdown
Member
Captura de Tela 2026-10-27 às 16 33 44 Captura de Tela 2026-10-27 às 16 34 22 Captura de Tela 2026-10-27 às 16 36 02 Captura de Tela 2026-10-27 às 16 36 43 Captura de Tela 2026-10-27 às 16 37 18 Captura de Tela 2026-10-27 às 16 37 49

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread psbt-concurrent.md
where `H_s` is a BIP 341 style tagged hash, with the tag preimage being `BIP ???? deterministic ordering` (TODO pick a real domain separator).

If any sort key is missing, ordering can only proceed after `PSBT_GLOBAL_SORT_SEED` is set.
If `PSBT_GLOBAL_SORT_SEED` is set, any explicit `PSBT_IN_SORT_KEY` or `PSBT_OUT_SORT_KEY` is ignored and replaced by the deterministic key defined above.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sort-seed rule contradicts explicit-key fallback

Line 151 says any explicit PSBT_IN_SORT_KEY/PSBT_OUT_SORT_KEY is ignored whenever PSBT_GLOBAL_SORT_SEED is set, but line 40 says the seed derives keys only when explicit keys are absent and line 141 derives only omitted keys. With both present, the table keeps explicit keys while line 151 discards them, so replicas can order differently and produce divergent unsigned transactions.

oak · automated · 626af58

Comment thread scratch

Every receiver whose balance is net negative (they send more than they receive) must wait for confirmation from all receivers for whom it is a sender before it confirms the unique ID to the senders for whom it is a receiver.

If there is a cyclic payment graph, for example Alice pays Bob, Bob pays Carol, and Carol pays Alice, net positive receivers can safely initiate confirmations, breaking the circular dependency, because senders will sign will only occur after all receivers have confirmed and if all net positive receivers have confirmed then all funds are accounted for. At least one such node must exist in every cyclic payment structure, since the sum of the payments along a cycle will be 0.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confirmation deadlocks in net-negative payment cycle

A net-negative receiver must wait for every receiver it pays, so a payment cycle whose nodes all have negative net balances due to external payments forms a wait cycle. The net-positive receiver outside the cycle confirming does not unblock them, so confirmation never completes.

oak · automated · 626af58

Comment thread psbt-concurrent.md
COMB --> SIGNED
```

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

Strict Combiner nested under per-output types

#### Strict Combiner sits under ### New per-output types, so the combiner rules render as a subsection of the per-output types section rather than a top-level specification section.

oak · automated · 626af58

This branch has not been deployed

No deployments
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.

5 participants