Recompute a silent payment's output scripts, rather than trust them - #762
Conversation
There was a problem hiding this comment.
Sorry @fametrano, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
Reviewer's GuideImplements BIP375’s Signer and Transaction Extractor roles for silent payments in PSBTs, adds shared derivation helpers, and wires the new functionality into the public psbt API, docs, and test suite while resolving a spec vs. vector discrepancy in k-ordering. Sequence diagram for BIP375 Signer and Transaction Extractor over silent payments PSBTsequenceDiagram
actor Signer
participant Psbt
participant psbt_silent_payments as btclib_psbt_silent_payments
actor Extractor
Signer->>Psbt: construct PSBT with silent payment outputs
Signer->>psbt_silent_payments: set_input_share(psbt, vin_i, prv_key, aux)
Signer->>psbt_silent_payments: set_global_share(psbt, prv_keys, aux)
Signer->>psbt_silent_payments: set_output_scripts(psbt)
psbt_silent_payments->>Psbt: write script_pub_key, clear tx_modifiable
Extractor->>psbt_silent_payments: assert_as_valid(psbt)
psbt_silent_payments->>Psbt: psbt.assert_valid()
psbt_silent_payments->>psbt_silent_payments: _assert_modifiable_cleared(psbt)
psbt_silent_payments->>psbt_silent_payments: assert_shares_as_valid(psbt)
psbt_silent_payments->>psbt_silent_payments: assert_eligibility_as_valid(psbt)
psbt_silent_payments->>psbt_silent_payments: assert_output_scripts_as_valid(psbt)
psbt_silent_payments->>Psbt: PSBT accepted or BTClibValueError raised
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
a72cfad to
984a778
Compare
984a778 to
cb85230
Compare
|
Head moved: rebased onto The five failing checks on the previous head were not this pull request's: Gates re-run after the rebase:
The BIP375 |
btclib.psbt.silent_payments is BIP375's other half: the fields were the codec, and these are the two roles that make carrying them worth anything. A silent payment output script is derived and not signed, so a wrong one is consensus-valid -- it confirms, and the money is gone. The Transaction Extractor is the last party that can notice, which is why the recomputation lives here and not in a wallet. The Signer writes: set_input_share for one input's ECDH share and its BIP374 proof, set_global_share for the single pair that stands for every eligible input, set_output_scripts for what the recipients are paid -- which clears the two modifiable flags with it, the scripts being a function of the input set. Both writers refuse a key that is not the one they would prove against, an error being cheapest before it is published. The Extractor reads: assert_as_valid is the four checks BIP375's own validator publishes, in its order, each naming what failed. input_pub_key is what they all stand on and was the piece missing until now -- btclib.silent_payments.pub_key_from_input reads a signed input's key out of the witness or the scriptSig, where an unsigned one has neither and BIP375 asks an Updater for PSBT_IN_BIP32_DERIVATION instead. bip375_test_vectors.json is now answered in full: all 22 invalid psbts refused and all 19 valid ones accepted, where the codec alone refused five. Each case is held to the check its own category names, so a psbt refused for the wrong reason fails rather than counting as a pass. One rule where the BIP and its own vectors disagree, and the vectors win. BIP375 says the codes of one scan key are sorted lexicographically to order k; the vectors' scripts are the ones output index order derives. The deciding case is published as valid -- "two sp outputs - output 0 uses label=3 / output 1 uses label=1" -- and its spend keys are in descending order, so the two rules assign k the other way round and only index order reproduces the file. Neither reading of "the codes" rescues the prose: the info fields and the bech32m address strings sort that pair the same wrong way, and upstream's own validator walks index order too. Both directions are asserted, so a revision settling it otherwise fails here rather than passing quietly. The two invalid vectors named after ordering turn out not to decide it: their candidate orderings agree, and their scripts match no assignment at all. btclib.silent_payments gains output_key in passing, the last step of BIP352's derivation: the psbt path reaches it from an ECDH share rather than from a private key, so it is what the two paths share instead of output_keys. Closes #760 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cb85230 to
9d5ec8b
Compare
Closes #760, the follow-up to
#641 — which landed BIP375's six
psbt fields and deliberately stopped at the codec.
btclib.psbt.silent_paymentsis the other half. A silent payment outputscript is derived, not signed: get it wrong and the transaction is
still consensus-valid, so it confirms and the money is gone. The shares and
the BIP374 proofs are what make that derivation checkable by somebody
holding none of the keys, and the Transaction Extractor is where the check
has to happen — it is the last party before the bytes go on the wire.
The Signer writes
set_input_shareset_global_shareset_output_scriptsBoth writers refuse a private key that is not the one they would be proving
against: every reader of the psbt would reject such a share, so the signer
would have published a proof of its own mistake.
The Extractor reads
assert_as_validis the four checks BIP375's own validator publishes, inits order —
assert_shares_as_validfor the proofs,assert_eligibility_as_validfor the inputs a silent payment forbids (awitness version above 1, any sighash type but
SIGHASH_ALL),assert_output_scripts_as_validfor the derivation — each naming whatfailed rather than "invalid".
input_pub_keyis what all of it stands on, and is the piece that wasmissing:
btclib.silent_payments.pub_key_from_inputreads a signedinput's key out of the witness or the scriptSig, where an unsigned one has
neither and BIP375 asks an Updater for
PSBT_IN_BIP32_DERIVATIONinstead.The vectors are now answered in full
All 22 invalid psbts refused, all 19 valid ones accepted, where the
codec alone refused five. Each case is held to the check its own category
names, so a psbt refused for the wrong reason fails rather than counting as
a pass.
One rule where the BIP and its own vectors disagree
This is the part worth your eye. BIP375 says the codes of one scan key are
sorted lexicographically to order
k. The vectors' scripts are the onesoutput index order derives.
The case that decides it is published as valid — "two sp outputs - output
0 uses label=3 / output 1 uses label=1" — and its two spend keys are in
descending order, so the two rules assign
kthe other way round and onlyone reproduces the scripts the file carries. I measured both readings of
"the codes": sorting the 66-byte info fields and sorting the bech32m
address strings order that pair the same wrong way. Upstream's own
bip-0375/validator/validate_psbt.pywalks index order too, so two of itsthree artefacts agree and the prose is the outlier.
Index order is therefore what is implemented, and
test_the_k_ordering_is_the_output_indexasserts it in both directions— index order reproduces both scripts, the lexicographic order reproduces
neither — so a revision settling it the other way fails here rather than
passing quietly.
A related finding: the two invalid vectors named after ordering turn out
not to decide it. Their candidate orderings all agree, and their scripts
match no
kassignment at all, so they are refused whatever rule you pick.test_the_two_ordering_vectors_are_refused_whatever_the_orderrecords that,because a reader of the descriptions would reasonably expect otherwise.
Happy to write this up for bitcoin/bips if you want it reported upstream.
Also
btclib.silent_paymentsgainsoutput_key, the last step of BIP352'sderivation: the psbt path reaches it from an ECDH share rather than from a
private key, so it is what the two paths share instead of
output_keys.output_keysis now written in terms of it.Gates
uv run pytest— 26514 passed, coverage 100.00%uv run pre-commit run --all-files— exit 0sphinx-build -W --keep-going— build succeededSummary by Sourcery
Add BIP375 silent payment Signer and Transaction Extractor roles over PSBT, ensuring output scripts are recomputed and validated rather than trusted, and wire the new module into the PSBT package and test suite.
New Features:
Enhancements:
Tests: