diff --git a/bip-0375.mediawiki b/bip-0375.mediawiki index c50d62cc4e..2e743878d7 100644 --- a/bip-0375.mediawiki +++ b/bip-0375.mediawiki @@ -249,7 +249,156 @@ Silent payment capable PSBTs are backwards compatible with PSBTv2 once all outpu ==Test Vectors== -Todo +A [[bip-0375/bip375_test_vectors.json|collection of test vectors in JSON format]] is provided. Each test vector contains a base64-encoded PSBT string, which alone can be used to verify sending Silent Payments with PSBTs. +Validation is performed in 4 sequential checks. This [[bip-0375/validator/validate_psbt.py|Python implementation]] demonstrates the validation logic for each: + +# '''PSBT Structure''' - Verify BIP375 field requirements +# '''ECDH Coverage''' - Verify ECDH share presence and correctness using BIP374 DLEQ +# '''Input Eligibility''' - Verify input constraints when silent payment outputs are present +# '''Output Scripts''' - Verify output scripts match silent payment derivation + +Valid PSBTs are organized into 2 categories: +# '''Can Finalize''' - Signed and ready to finalize +# '''In Progress''' - Incomplete but valid + +Use the provided [[bip-0375/test_runner.py|test runner]] to validate each test vector. See the [[bip-0375/README.md|README]] for more details. + +===Invalid PSBTs=== + +{| +! Category +! Description +|- +| PSBT Structure +| missing PSBT_OUT_SP_V0_INFO field when PSBT_OUT_SP_V0_LABEL set +|- +| PSBT Structure +| incorrect byte length for PSBT_OUT_SP_V0_INFO field +|- +| PSBT Structure +| incorrect byte length for PSBT_IN_SP_ECDH_SHARE field +|- +| PSBT Structure +| incorrect byte length for PSBT_IN_SP_DLEQ field +|- +| PSBT Structure +| PSBT_GLOBAL_TX_MODIFIABLE field is non-zero when PSBT_OUT_SCRIPT set for sp output +|- +| PSBT Structure +| missing PSBT_OUT_SCRIPT field when sending to non-sp output +|- +| PSBT Structure +| empty PSBT_OUT_SCRIPT field when sending to non-sp output +|- +| ECDH Coverage +| only one ineligible P2MS input when PSBT_OUT_SCRIPT set for sp output +|- +| ECDH Coverage +| missing PSBT_IN_SP_ECDH_SHARE field for input 0 when PSBT_OUT_SCRIPT set for sp output +|- +| ECDH Coverage +| missing PSBT_IN_SP_DLEQ field for input when PSBT_IN_SP_ECDH_SHARE set +|- +| ECDH Coverage +| missing PSBT_GLOBAL_SP_DLEQ field when PSBT_GLOBAL_SP_ECDH_SHARE set +|- +| ECDH Coverage +| invalid proof in PSBT_IN_SP_DLEQ field +|- +| ECDH Coverage +| invalid proof in PSBT_GLOBAL_SP_DLEQ field +|- +| ECDH Coverage +| missing PSBT_IN_BIP32_DERIVATION field for input when PSBT_IN_SP_DLEQ set +|- +| ECDH Coverage +| output 1 missing ECDH share for scan key with one input / three sp outputs (different scan keys) +|- +| ECDH Coverage +| input 1 missing ECDH share for output 1 with two inputs / two sp outputs (different scan keys) +|- +| ECDH Coverage +| input 1 missing ECDH share for scan key with two inputs / one sp output +|- +| Input Eligibility +| segwit version greater than 1 in transaction inputs with sp output +|- +| Input Eligibility +| non-SIGHASH_ALL signature on input with sp output +|- +| Output Scripts +| P2TR input with NUMS internal key cannot derive sp output +|- +| Output Scripts +| PSBT_OUT_SCRIPT does not match derived sp output +|- +| Output Scripts +| two sp outputs (same scan / different spend keys) not sorted lexicographically by spend key +|- +| Output Scripts +| k values assigned to wrong output indices with three sp outputs (same scan / spend keys) +|} + +===Valid PSBTs=== + +{| +! State +! Description +|- +| Can Finalize +| one input single-signer +|- +| Can Finalize +| two inputs single-signer using global ECDH share +|- +| Can Finalize +| two inputs single-signer using per-input ECDH shares +|- +| Can Finalize +| two inputs / two sp outputs with mixed global and per-input ECDH shares +|- +| Can Finalize +| one input / one sp output with both global and per-input ECDH shares +|- +| Can Finalize +| three sp outputs (different scan keys) with multiple global ECDH shares +|- +| Can Finalize +| one P2WPKH input / two mixed outputs - labeled sp output and BIP 32 change +|- +| Can Finalize +| one input / two sp outputs - output 0 has no label / output 1 uses label=0 convention for sp change +|- +| Can Finalize +| two sp outputs - output 0 uses label=3 / output 1 uses label=1 +|- +| Can Finalize +| two mixed input types - only eligible inputs contribute ECDH shares (P2SH excluded) +|- +| Can Finalize +| two mixed input types - only eligible inputs contribute ECDH shares (NUMS internal key excluded) +|- +| Can Finalize +| three sp outputs (same scan key) - each output has distinct k value +|- +| Can Finalize +| three sp outputs (same scan key) / two regular outputs - k values assigned independently of output index +|- +| In Progress +| two P2TR inputs, neither is signed +|- +| In Progress +| one P2TR input / one sp output with no ECDH shares when PSBT_OUT_SCRIPT field is not set +|- +| In Progress +| two inputs / one sp output, input 1 missing ECDH share when PSBT_OUT_SCRIPT field is not set +|- +| In Progress +| one input / two sp outputs, input 0 missing ECDH share for output 0 when PSBT_OUT_SCRIPT field is not set +|- +| In Progress +| large PSBT with nine mixed inputs / six outputs - some inputs signed +|} ==Rationale== diff --git a/bip-0375/README.md b/bip-0375/README.md new file mode 100644 index 0000000000..b9ce1119a9 --- /dev/null +++ b/bip-0375/README.md @@ -0,0 +1,32 @@ +# BIP-375 Validation Reference + +A reference validation implementation for BIP-375: Sending Silent Payments with PSBTs. + +## Core Files +- **`validator/bip352_crypto.py`** - Silent payment output script derivation +- **`validator/inputs.py`** - PSBT input utility functions +- **`validator/psbt_bip375.py`** - BIP-375 specific PSBT/PSBTMap extensions +- **`validator/validate_psbt.py`** - Main BIP-375 validation functions +- **`test_runner.py`** - Test infrastructure (executable) + +## Dependencies +- **`deps/bitcoin_test/psbt.py`** - Bitcoin test framework PSBT module - [PR #21283](https://github.com/bitcoin/bitcoin/pull/21283) +- **`deps/bitcoin_test/messages.py`** - Bitcoin test framework primitives and message structures +- **`deps/dleq.py`** - Reference DLEQ implementation from BIP-374 +- **`deps/secp256k1lab/`** - vendored copy of [secp256k1lab](https://github.com/secp256k1lab/secp256k1lab/commit/44dc4bd893b8f03e621585e3bf255253e0e0fbfb) library at version 1.0.0 + +## Testing + +### Run Tests + +```bash +python test_runner.py # Run all tests +python test_runner.py -v # Verbose mode with detailed validation status +python test_runner.py -vv # More verbose with validation check failure reason + +python test_runner.py -f vectors.json # Use custom test vector file +``` + +### Generating Test Vectors + +Test vectors were generated using [test_generator.py](https://github.com/macgyver13/bip375-test-generator/) diff --git a/bip-0375/bip375_test_vectors.json b/bip-0375/bip375_test_vectors.json new file mode 100644 index 0000000000..7b9a627385 --- /dev/null +++ b/bip-0375/bip375_test_vectors.json @@ -0,0 +1,1717 @@ +{ + "description": "BIP-375 Test Vectors", + "version": "1.1", + "notes": [ + "Generated by https://github.com/macgyver13/bip375-test-generator/", + "Each vector includes: base64-encoded psbt and description", + "Supplementary material (inputs, outputs, sp_proofs) is included for diagnostics and should not be used for validation", + "'checks' overrides the validation sequence, e.g. skip structure checks to test input eligibility" + ], + "invalid": [ + { + "description": "psbt structure: missing PSBT_OUT_SP_V0_INFO field when PSBT_OUT_SP_V0_LABEL set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgfCTD+UpVH5TLj1wOA8yaFwh/sU1xSl0vTp/Ux4nT02YBCgQBAAAAAA==", + "supplementary": {} + }, + { + "description": "psbt structure: incorrect byte length for PSBT_OUT_SP_V0_INFO field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUECekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84AA=", + "supplementary": {} + }, + { + "description": "psbt structure: incorrect byte length for PSBT_IN_SP_ECDH_SHARE field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GggA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QoiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaECKE7OYVUX3K9bolxSuuQmz41SoQqm7i1bNDt7SHfihmVmLMSKKSeC9fpXOEFP3xbKKy1Q6aHB2AOPOiYIu4yAhAAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": {} + }, + { + "description": "psbt structure: incorrect byte length for PSBT_IN_SP_DLEQ field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4Gg/ihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgAAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": {} + }, + { + "description": "psbt structure: PSBT_GLOBAL_TX_MODIFIABLE field is non-zero when PSBT_OUT_SCRIPT set for sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAQABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": {} + }, + { + "description": "psbt structure: missing PSBT_OUT_SCRIPT field when sending to non-sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAA=", + "supplementary": {} + }, + { + "description": "psbt structure: empty PSBT_OUT_SCRIPT field when sending to non-sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEAAA=", + "supplementary": {} + }, + { + "description": "ecdh coverage: only one ineligible P2MS input when PSBT_OUT_SCRIPT set for sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiBPa2xI91PQstsuV3qY9lLMD4wBpxZ0Jhhhb5ag+ezmZwEPBAAAAAABASughgEAAAAAACIAIOr39e5jDu+omlGHtlFF4GmTiHzQv1oD8JZFgXkxw/hiAQVHUiEDaGidhEg6mw9BaLYu3+hU5d5PRyw42+0S4nN0susnfjohArPI8nEqzmYLPLqq/KShgFDY2qzp8YMkjzj0SWjwUgPjUq4BEAT+////AAEDCJBfAQAAAAAAAQQiUSDN452LBbSW+PGPILJ9CvmjIzMJ4EciGeCBP/O103h+KQEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "", + "public_key": "", + "prevout_txid": "4f6b6c48f753d0b2db2e577a98f652cc0f8c01a716742618616f96a0f9ece667", + "prevout_index": 0, + "prevout_scriptpubkey": "52210368689d84483a9b0f4168b62edfe854e5de4f472c38dbed12e27374b2eb277e3a2102b3c8f2712ace660b3cbaaafca4a18050d8daace9f183248f38f44968f05203e352ae", + "amount": 100000, + "witness_utxo": "a086010000000000220020eaf7f5ee630eefa89a5187b65145e06993887cd0bf5a03f09645817931c3f862", + "sequence": 4294967294, + "signed": false + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_IN_SP_ECDH_SHARE field for input 0 when PSBT_OUT_SCRIPT set for sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBHxAnAAAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0BAwQBAAAAIgYCQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsIAAAAgAEAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghAuV6etywGNj7MVToDBS5elCZS0I1aEHqn8ExcGFEAOY3Ih4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhA9/o4HeILO0WcbkJsYhPUF1MI8kgqROMyhCMmJNTa/Vcymb868WqzAsViY2OjfUSHB/fnoevqBke8zUhiA7srpgABAwgYcwEAAAAAAAEEIlEgzeOdiwW0lvjxjyCyfQr5oyMzCeBHIhnggT/ztdN4fikBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 10000, + "witness_utxo": "10270000000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02e57a7adcb018d8fb3154e80c14b97a50994b42356841ea9fc13170614400e637", + "dleq_proof": "f7fa381de20b3b459c6e426c6213d4175308f2482a44e33284232624d4dafd573299bf3af16ab302c5626363a37d448707f7e7a1ebea0647bccd486203bb2ba6", + "input_index": 1 + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_IN_SP_DLEQ field for input when PSBT_IN_SP_ECDH_SHARE set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbAAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": null, + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_GLOBAL_SP_DLEQ field when PSBT_GLOBAL_SP_ECDH_SHARE set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBACIHAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWwABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEiBgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwgAAACAAAAAAAEQBP7///8AAQMIGHMBAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b" + } + ] + } + }, + { + "description": "ecdh coverage: invalid proof in PSBT_IN_SP_DLEQ field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAbvHreDwFl9ZoVl4787j95b7SgVKV9D4XAVEk1EXcy9rdKkne51lhePEhbl1NuRdP/dpjNON8kDEQjAjOE3TeDwABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "6ef1eb783c0597d668565e3bf3b8fde5bed2815295f43e17015124d445dccbdadd2a49dee7596178f1216e5d4db9174ffdda6334e37c9031108c08ce1374de0f", + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: invalid proof in PSBT_GLOBAL_SP_DLEQ field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBACIHAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIIAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQOfI/UQrlNiKKmoskrQ0ZI+gB8n+CARyozeA0LUjKETQm9iLP+6G0S1DhsRlf7t4Xi0vFL/PkYV0P+EqyCUPldkAAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfoIYBAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/RzBEAiAJB3pqpkrBSuehqk/mjAKs4gbJ6scjSQYa52A5ALkibgIgbxQypXj44zCvC5ACVnRryfTlKqLozHkCncjnajwpSwYBIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "2430479d2da1310a024d2aab8e4c7b49d76cd51f45ee8127c159b0393a675b6f75c581ebe3d0817ddaf3bb598cfd7a022f1a87ed479664f6b947a47db5941cb2" + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_IN_BIP32_DERIVATION field for input when PSBT_IN_SP_DLEQ set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIGHMBAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: output 1 missing ECDH share for scan key with one input / three sp outputs (different scan keys)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEDAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR9ADQMAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdArlFYVfpRu8H90tU+gm+Sn6Tjcr7+ye0s3LFc8BZ8SqwIQLChkR0sISqyB9uMKxe30ZnOTzmBNgI0/SZoJhOi1JJdCIdA2UdLAc/ywKk2C3aU/HVAdd6A1BmZ5hUZHOyEmts0afeIQN2iSMnxOeAru7UzhvJK7ASK3t5ENlvCtUUi0lvnD6S+yIeArlFYVfpRu8H90tU+gm+Sn6Tjcr7+ye0s3LFc8BZ8SqwQPCxHVHD/ROzjgpsjvwu4Mpxdzr2dLMVHmiSFJJcS1fIFojJ4Hi3UIhabKsoa1d+k6JJfrSWNrGvG2vILnyRElEiHgNlHSwHP8sCpNgt2lPx1QHXegNQZmeYVGRzshJrbNGn3kCY8UrEWvZFmrDITUfaU2TPsUT/kL2scy+cq8bbAA3PGpXjsx97cKBCmKUbb9RRbIHXFsmVT4rxDD1ZgJruK704AAEDCJBfAQAAAAAAAQQiUSDTlxRqgoZbVOsSuepx1oJP6rD5KkgTf3p5OmhZazNnpAEJQgNlHSwHP8sCpNgt2lPx1QHXegNQZmeYVGRzshJrbNGn3gPycOClYyGDtqsBB06HfmgA2wD1x8XS+vu2j5wo1IVFJAABAwgQJwAAAAAAAAEEIlEgzeOdiwW0lvjxjyCyfQr5oyMzCeBHIhnggT/ztdN4fikBCUIDUteMQTkAMq2RgWppf8dA2OuQnPBNcIhSZLBR8jheJewC4MTBh7chX5l8hIWnW0WRtsKEd7laESO5lpItMVSGC/gAAQMIIE4AAAAAAAABBCJRINLSQezkGFFKoY45kuyVPE1K1exwSmk+IQa6IsOwWnqDAQlCArlFYVfpRu8H90tU+gm+Sn6Tjcr7+ye0s3LFc8BZ8SqwAhJoHj8J6uOKjy8/7NfdVvxYbPoCjoqzP3KpD4QKbSJcAA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de", + "ecdh_share": "0376892327c4e780aeeed4ce1bc92bb0122b7b7910d96f0ad5148b496f9c3e92fb", + "dleq_proof": "98f14ac45af6459ab0c84d47da5364cfb144ff90bdac732f9cabc6db000dcf1a95e3b31f7b70a04298a51b6fd4516c81d716c9954f8af10c3d59809aee2bbd38", + "input_index": 0 + }, + { + "scan_key": "02b9456157e946ef07f74b54fa09be4a7e938dcafbfb27b4b372c573c059f12ab0", + "ecdh_share": "02c2864474b084aac81f6e30ac5edf4667393ce604d808d3f499a0984e8b524974", + "dleq_proof": "f0b11d51c3fd13b38e0a6c8efc2ee0ca71773af674b3151e689214925c4b57c81688c9e078b750885a6cab286b577e93a2497eb49636b1af1b6bc82e7c911251", + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: input 1 missing ECDH share for output 1 with two inputs / two sp outputs (different scan keys)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsIQMJH9SAORd/RNe3npYWiEnGLrP4eADEZ2fFCo84+Y79FSIdA2UdLAc/ywKk2C3aU/HVAdd6A1BmZ5hUZHOyEmts0afeIQN2iSMnxOeAru7UzhvJK7ASK3t5ENlvCtUUi0lvnD6S+yIeA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsQL4acjrvVeiYG5lZmpvBKzj4froBWMxwZokPImnK3kY7dzQal9KScZZNAcAkHMMi/VOQwpuZFg+CbzKTZYmMvwIiHgNlHSwHP8sCpNgt2lPx1QHXegNQZmeYVGRzshJrbNGn3kCY8UrEWvZFmrDITUfaU2TPsUT/kL2scy+cq8bbAA3PGpXjsx97cKBCmKUbb9RRbIHXFsmVT4rxDD1ZgJruK704AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0BAwQBAAAAIgYCQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsIAAAAgAEAAAABEAT+////Ih0DZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp94hAnUEQAf+LQMNV3wymRzyo+deU1BCRbPL5bMFOufF8iBaIh4DZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp95AOLnjgGtJvr7vxs7gZcKwZv2wCuzs8ei1sqr0fPBBeyma4KJW7Xy2yCcC63E31zv1eSccQoTQBuO1nXCpuWfYiQABAwiQXwEAAAAAAAEEIlEg9Jwu8ZtnBhG4bd7+iQzvlCaKgq4zHkb+GVHWxuNyPLEBCUIDZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp94D8nDgpWMhg7arAQdOh35oANsA9cfF0vr7to+cKNSFRSQAAQMIkF8BAAAAAAABBCJRID2h4XbGT7RZU+xG4FA2NLx+jjQ6do2+77sKPojMSkyaAQlCA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsAuDEwYe3IV+ZfISFp1tFkbbChHe5WhEjuZaSLTFUhgv4AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 100000, + "witness_utxo": "a0860100000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de", + "ecdh_share": "0376892327c4e780aeeed4ce1bc92bb0122b7b7910d96f0ad5148b496f9c3e92fb", + "dleq_proof": "98f14ac45af6459ab0c84d47da5364cfb144ff90bdac732f9cabc6db000dcf1a95e3b31f7b70a04298a51b6fd4516c81d716c9954f8af10c3d59809aee2bbd38", + "input_index": 0 + }, + { + "scan_key": "0352d78c41390032ad91816a697fc740d8eb909cf04d70885264b051f2385e25ec", + "ecdh_share": "03091fd48039177f44d7b79e96168849c62eb3f87800c46767c50a8f38f98efd15", + "dleq_proof": "be1a723aef55e8981b99599a9bc12b38f87eba0158cc7066890f2269cade463b77341a97d29271964d01c0241cc322fd5390c29b99160f826f329365898cbf02", + "input_index": 0 + }, + { + "scan_key": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de", + "ecdh_share": "0275044007fe2d030d577c32991cf2a3e75e53504245b3cbe5b3053ae7c5f2205a", + "dleq_proof": "38b9e3806b49bebeefc6cee065c2b066fdb00aececf1e8b5b2aaf47cf0417b299ae0a256ed7cb6c82702eb7137d73bf579271c4284d006e3b59d70a9b967d889", + "input_index": 1 + } + ] + } + }, + { + "description": "ecdh coverage: input 1 missing ECDH share for scan key with two inputs / one sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR9QwwAAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4gvp1lyDkV+wkkirvfyoyj7ubONQxVT2Xo1Sbl31J6tSIBDwQAAAAAAQEfUMMAAAAAAAAWABRGM9WcrRNDR5ly4MmWkh0KXa22XSIGAkOg1A6d4IgnlBYXUrrkK6kJUnhCCfBjQw8K8lY1SoW7CAAAAIABAAAAARAE/v///wABAwgYcwEAAAAAAAEEIlEgzeOdiwW0lvjxjyCyfQr5oyMzCeBHIhnggT/ztdN4fikBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 50000, + "witness_utxo": "50c3000000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 50000, + "witness_utxo": "50c30000000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ] + } + }, + { + "description": "input eligibility: segwit version greater than 1 in transaction inputs with sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABZSFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIHLzeIUvuENyYHLyUHbw53Vg7UwuBHFm7mpibHW/2znWAiAhKq5fCVdONB9YhvX/8y9XFuq5AwpKU3nmAWtqTULcJAEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "5214229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000165214229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ] + }, + "checks": [ + "input_eligibility" + ] + }, + { + "description": "input eligibility: non-SIGHASH_ALL signature on input with sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIHLzeIUvuENyYHLyUHbw53Vg7UwuBHFm7mpibHW/2znWAiAhKq5fCVdONB9YhvX/8y9XFuq5AwpKU3nmAWtqTULcJAEBAwQCAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ] + } + }, + { + "description": "output scripts: P2TR input with NUMS internal key cannot derive sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASsQJwAAAAAAACJRIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAAQMEAQAAAAEQBP7///8BFyBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wCIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIHCUAAAAAAAABBCJRICdoyIruoiMiW6OthOeGNS+YKdLT6TeKOToL/1mvh83QAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "amount": 10000, + "witness_utxo": "102700000000000022512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 9500, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51202768c88aeea223225ba3ad84e786352f9829d2d3e9378a393a0bff59af87cdd0" + } + ] + } + }, + { + "description": "output scripts: PSBT_OUT_SCRIPT does not match derived sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIGHMBAAAAAAABBCJRIM3jnYsFtJb48Y8gsn0K+aMjMwngRyIZ4IE/87XTeH4pAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120cde39d8b05b496f8f18f20b27d0af9a3233309e0472219e0813ff3b5d3787e29" + } + ] + } + }, + { + "description": "output scripts: two sp outputs (same scan / different spend keys) not sorted lexicographically by spend key", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCID9iBcRocGpGJF8XE6u4uLmjp7/YOsdF98ByDUQxtM38AiBxWKv7gg8r9a1nRfVvwHCcmVzMrP4XCY2KobYfjJ/y/wEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwiQXwEAAAAAAAEEIlEgdUe4Fj1bvFSYQL5MwUaq0JUgc2e565RwbeZwjUCPk/kBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgCi4D5lDNIleEbx2x1q254/iwluzMvIXshHEAXvx9nfYYAAQMIECcAAAAAAAABBCJRIGziw1OV8XDyS20tgNCkDEb0a6S0hD9qsxGpNLMcXYTcAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA6r4Yq7amM+SU5r33DN10dpfsGsSHvCh8DGp9zgv1T27AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068028b80f994334895e11bc76c75ab6e78fe2c25bb332f217b211c4017bf1f677d86", + "script": "51207547b8163d5bbc549840be4cc146aad095207367b9eb94706de6708d408f93f9" + }, + { + "output_index": 1, + "amount": 10000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe06803aaf862aeda98cf92539af7dc3375d1da5fb06b121ef0a1f031a9f7382fd53dbb", + "script": "51206ce2c35395f170f24b6d2d80d0a40c46f46ba4b4843f6ab311a934b31c5d84dc" + } + ] + } + }, + { + "description": "output scripts: k values assigned to wrong output indices with three sp outputs (same scan / spend keys)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEDAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIFg36hg2U7tRrGNQq+bDhHvokGogZdwwSNbSCOp8DkyCAiAQ+0/6sv7Vqmy2KAqTxHnPsE5Gyp7fF6owXNFnSiQsSQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwiQXwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkAAQMIECcAAAAAAAABBCJRIJcUQsifBHhuCCcorhFu6rgC8/qelf2w03U6aEAgIAbeAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AAEDCBAnAAAAAAAAAQQiUSA/y66kkIf44D2d79Ory9bejAMrWD+Fpl0FeIHExEPNwAEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + }, + { + "output_index": 1, + "amount": 10000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120971442c89f04786e082728ae116eeab802f3fa9e95fdb0d3753a6840202006de" + }, + { + "output_index": 2, + "amount": 10000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51203fcbaea49087f8e03d9defd3abcbd6de8c032b583f85a65d057881c4c443cdc0" + } + ] + } + } + ], + "valid": [ + { + "description": "can finalize: one input single-signer", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + } + ] + } + }, + { + "description": "can finalize: two inputs single-signer using global ECDH share", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBACIHAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQLTJicjNSYH6E/K92UfjS1jfgIK6obfrYDTBs1PSM/MYiIIAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQMZTtx+UaBipW9w58JGERbyQOgh2tzgGDp5dsdzsMKvfVOC18XhXP1EZ3nGqY60ZtEnjvLZUY5Nsi67qTZFzkrkAAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfoIYBAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/RzBEAiAXBfzaBiZu2zKxaYZ3s39nu25F1JLr/OZWqIDufxOOtgIgPc3jI53ZBjavZPskEspB45fJWKAwd3z/nIlAucd310wBIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAUE5NQEXxXeUGphYArue3vwROTK8ciAgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3EcwRAIgA4mx4K7v1/q2Ce+rAYv7AWrP8hu4EPPEjDi6ckQYDpUCIBUP7GDiVvQMR1dN/RKaCOr3rB2Xq+4x9AaZ+Hk1RhnzASIGAvW1n6XkkiIev1W6eK1EJgW+rpUWa6HroyUNC7qsfi7cCAAAAIABAAAAARAE/v///wABAwgYcwEAAAAAAAEEIlEgEz1qT1CQlbV5T3SR+8PxLDxFvC2bYK8luLunBMgHKq8BCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 100000, + "witness_utxo": "a086010000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02d3262723352607e84fcaf7651f8d2d637e020aea86dfad80d306cd4f48cfcc62", + "dleq_proof": "c653b71f946818a95bdc39f0918445bc903a0876b738060e9e5db1dcec30abdf54e0b5f178573f5119de71aa63ad19b449e3bcb65463936c8baeea4d917392b9" + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120133d6a4f509095b5794f7491fbc3f12c3c45bc2d9b60af25b8bba704c8072aaf" + } + ] + } + }, + { + "description": "can finalize: two inputs single-signer using per-input ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIBcF/NoGJm7bMrFphnezf2e7bkXUkuv85laogO5/E462AiA9zeMjndkGNq9k+yQSykHjl8lYoDB3fP+ciUC5x3fXTAEiBgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwgAAACAAAAAAAEQBP7///8iHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCED7KT/Ebco4uD2DOYiKUOm/1W52V9ie/mpnQhLyHLVClsiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaECKE7OYVUX3K9bolxSuuQmz41SoQqm7i1bNDt7SHfihmVmLMSKKSeC9fpXOEFP3xbKKy1Q6aHB2AOPOiYIu4yAhAAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAUE5NQEXxXeUGphYArue3vwROTK8ciAgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3EcwRAIgA4mx4K7v1/q2Ce+rAYv7AWrP8hu4EPPEjDi6ckQYDpUCIBUP7GDiVvQMR1dN/RKaCOr3rB2Xq+4x9AaZ+Hk1RhnzAQEDBAEAAAAiBgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3AgAAACAAQAAAAEQBP7///8iHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCEDNNLO907e1EZCJ2B/xZrB3oDBKiQzpWJxUo7cz1D6bCsiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaEA3wAwLQqBz9rZfDEtuVuBndLOAwd894otkCxIe4LO0z3hQ30VuC12OGXXd/yUmLIOB4g+FLNhwveP2qDKmMlvMAAEDCBhzAQAAAAAAAQQiUSATPWpPUJCVtXlPdJH7w/EsPEW8LZtgryW4u6cEyAcqrwEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 100000, + "witness_utxo": "a086010000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + }, + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "0334d2cef74eded4464227607fc59ac1de80c12a2433a56271528edccf50fa6c2b", + "dleq_proof": "37c00c0b42a073f6b65f0c4b6e56e06774b380c1df3de28b640b121ee0b3b4cf7850df456e0b5d8e1975ddff25262c8381e20f852cd870bde3f6a832a6325bcc", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120133d6a4f509095b5794f7491fbc3f12c3c45bc2d9b60af25b8bba704c8072aaf" + } + ] + } + }, + { + "description": "can finalize: two inputs / two sp outputs with mixed global and per-input ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQECAQYBACIHAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQNmz8f9zgbgjtSbGzlSuNFRqPfgdf5fbdWN1o+dre7KlCIIAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQF6riwhtSFD+nRyflhqR+Ze2HKjQhQr8BqeL3zx0nvVAUOaStT+1sIbl54eT8B5203R/ieMXen/v9vjBYX8bBuMAAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfQA0DAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/SDBFAiEA1k6e8CREAEbpyjkwLrh7LfYN/FHJwKkX1oLzz4EFkIwCIB6+EqXx/SGW8u+11Tr+V850W5Af6ImXitWa7+FKT1A3AQEDBAEAAAAiBgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwgAAACAAAAAAAEQBP7///8iHQKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVECED+9Fz2cSNWMUCJCbQUni8oXuajiKCZmkBobALngS3mUkiHgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEEDAXaQQSHz67DeP6+avUAXjrmyGI2gYsOVbIUAX61Bz7e9AF5V1tdzaqQY0kayLg/0GJNQXek0t1JlMRkDWuMzeAAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH0ANAwAAAAAAFgAUE5NQEXxXeUGphYArue3vwROTK8ciAgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3EcwRAIgWoJ5pshwRPECyFkwyJ7C/wGUKGJBraOMeFTy6DZR1fICICvMZkqflwTVcxLbwryDaGcIDyLEdYuR27ktF6HkEeqpAQEDBAEAAAAiBgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3AgAAACAAQAAAAEQBP7///8iHQKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVECEDpqibG5hqmVUz4XvtZFALVBuw1zkHHv4q4rfvsKDKa88iHgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEEDDSmopQLvppzVsHMb/41jdREHbWIl8upnCEut1KF59oeyhHHksl6OASTNFqyJN6dlMKzbuhSauI1KBcaMjHeczAAEDCJBfAQAAAAAAAQQiUSCA65gxi89t1hGsF6lksJFzbATK6t8ubodOXO+Gx8YFFwEJQgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKAMSZlgePoQcu5TeYupzVYOiMsTIiPpu3vLsL4cHifwd1AABAwiQXwEAAAAAAAEEIlEgELYvxakKvKAWeWHb/0zq6aoyTvssfyRmJ+i1oz3jfIQBCUICqfH6wsxFdoTzPekAc/4g0iyUfJIWO4YrFNCrod9HFRACSkjXbiDUC89fLhpcP/6z3CqLO4jXmkhtVMnZ6qj6GI4A", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 200000, + "witness_utxo": "400d030000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "0366cfc7fdce06e08ed49b1b3952b8d151a8f7e075fe5f6dd58dd68f9dadeeca94", + "dleq_proof": "5eab8b086d4850fe9d1c9f961a91f997b61ca8d0850afc06a78bdf3c749ef54050e692b53fb5b086e5e78793f01e76d3747f89e3177a7feff6f8c1617f1b06e3" + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03fbd173d9c48d58c5022426d05278bca17b9a8e2282666901a1b00b9e04b79949", + "dleq_proof": "c05da410487cfaec378febe6af5005e3ae6c86236818b0e55b214017eb5073edef40179575b5dcdaa9063491ac8b83fd0624d4177a4d2dd4994c4640d6b8ccde", + "input_index": 0 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03a6a89b1b986a995533e17bed64500b541bb0d739071efe2ae2b7efb0a0ca6bcf", + "dleq_proof": "c34a6a2940bbe9a7356c1cc6ffe358dd4441db58897cba99c212eb75285e7da1eca11c792c97a380493345ab224de9d94c2b36ee8526ae23528171a3231de733", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "512080eb98318bcf6dd611ac17a964b091736c04caeadf2e6e874e5cef86c7c60517" + }, + { + "output_index": 1, + "amount": 90000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "512010b62fc5a90abca0167961dbff4ceae9aa324efb2c7f246627e8b5a33de37c84" + } + ] + } + }, + { + "description": "can finalize: one input / one sp output with both global and per-input ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBACIHAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQMKGLm7QZm4kzDrF/juWseG4IU9zS88zfF0S5fOTt4FPiIIAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQCf7kMXWHHjjEVIA/9GD0o0KtIOAYslUGTTGQW1CZ84xvUKH6TWUUsCBevDvTVDP9XhDaw0o+e2PIbeaRHZAgI4AAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfQA0DAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/RzBEAiA3eo+fWjPY+iVcM7OSJtO88CKd+tlgRw74AWpxQvIzWAIgCBvWlIh63us2Ia9aLeTw05yxNpLQQb99vM00oysFQekBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQMKGLm7QZm4kzDrF/juWseG4IU9zS88zfF0S5fOTt4FPiIeAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQAATg9RmG+hyXvKQSPSMkR+J9fNDF9fAwKzGDvZfG1K//3qW2DObYl34Orq1eqJz7c2aNceU2suxyGEXG7JnB18AAQMIkF8BAAAAAAABBCJRINxNRNnjYeXnQ2V5x44lklVB4in9wVOtWYmtiuawYkx4AQlCAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oAxJmWB4+hBy7lN5i6nNVg6IyxMiI+m7e8uwvhweJ/B3UAA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "030a18b9bb4199b89330eb17f8ee5ac786e0853dcd2f3ccdf1744b97ce4ede053e", + "dleq_proof": "27fb90c5d61c78e3115200ffd183d28d0ab4838062c9541934c6416d4267ce31bd4287e9359452c0817af0ef4d50cff578436b0d28f9ed8f21b79a447640808e" + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "5120dc4d44d9e361e5e7436579c78e25925541e229fdc153ad5989ad8ae6b0624c78" + } + ] + } + }, + { + "description": "can finalize: three sp outputs (different scan keys) with multiple global ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEDAQYBACIHAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQIQLC2d9g7mmlqGYE3ZpigsIu+OLJJ22m5ZWlRj83Gq3uJCIHAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQLcfS7KxWiwEVF1ujzVSW/0xLbZCUItvMrrjyUvpU+L0CIHA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsIQJwvvXAapqFX8Y7/yvQvi1wAzyCC2FTQTyNbtpR8lobKSIIAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQQOyCQr97WIHO4GBWbH8TzEfkcQyJomRYwgMBswMeSBc7wL6gFoDTN1616AYRqw1Qix5Jq7VxsYeAMqrknwsC99IiCAMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKEDqQ0cOXhvjKRtIoLy1P1WVLGSGDnY177pQA8yt7XfdEo4hZpew9SO+IX7Gh1B3l8zOvFzZQ2KvG0bYA2Qr1flAIggDjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGxAG9/lTtSioG//mwAJrk49gPxHYWR1r98hJEz+PG0QTfJHQah1UU14ZU+5yhEY5tPlUvOPjKujVS6DM8UBvwy9PwABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR8goQcAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCLg3ekbsHGTj/QlvLhIwW7hrrfzKDP2b9X3JYim55whAIgfn3H3eyoVVwXfr7/yUnqTDNSD+W+7Hun4nJ+NPvUjf0BIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0iAgJDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFu0gwRQIhAPmSLe+tUsYSCQS6pacAUymDbsHD2uFsoGjAFrJrBNLYAiBw+V8k+cklbkGWlGnDUqz6yOiUMjUC6s0WPJSi42nuHwEiBgJDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFuwgAAACAAQAAAAEQBP7///8AAQMIkF8BAAAAAAABBCJRIKFPr9x+36QEQnF0sSasoOmZMTP+F+JCCurtIO9U/DjcAQlCAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oAxJmWB4+hBy7lN5i6nNVg6IyxMiI+m7e8uwvhweJ/B3UAAEDCJBfAQAAAAAAAQQiUSB99fJJWUQ0FLvv6J0tqJSGSlYotoAzuSPoaGvcw96dlAEJQgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEAJKSNduINQLz18uGlw//rPcKos7iNeaSG1UydnqqPoYjgABAwiQXwEAAAAAAAEEIlEgsvEqf1cMxQV4QPpjk/cfBpABQgiBgYEMNLuIdh2SlQ0BCUIDjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGwD+BRzhEyddMkh/xelr0ELYzc0jXr7/3OJG8XLDLJMsMAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 500000, + "witness_utxo": "20a1070000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 100000, + "witness_utxo": "a0860100000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02dc7d2ecac568b0115175ba3cd5496ff4c4b6d909422dbccaeb8f252fa54f8bd0", + "dleq_proof": "ea43470e5e1be3291b48a0bcb53f55952c64860e7635efba5003ccaded77dd128e216697b0f523be217ec687507797cccebc5cd94362af1b46d803642bd5f940" + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02c2d9df60ee69a5a86604dd9a6282c22ef8e2c9276da6e595a5463f371aadee24", + "dleq_proof": "ec8242bf7b5881cee060566c7f13cc47e4710c89a26458c20301b3031e48173bc0bea01680d3375eb5e80611ab0d508b1e49abb571b1878032aae49f0b02f7d2" + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "0270bef5c06a9a855fc63bff2bd0be2d70033c820b6153413c8d6eda51f25a1b29", + "dleq_proof": "1bdfe54ed4a2a06fff9b0009ae4e3d80fc47616475afdf21244cfe3c6d104df24741a875514d78654fb9ca1118e6d3e552f38f8caba3552e8333c501bf0cbd3f" + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "5120a14fafdc7edfa404427174b126aca0e9993133fe17e2420aeaed20ef54fc38dc" + }, + { + "output_index": 1, + "amount": 90000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "51207df5f24959443414bbefe89d2da894864a5628b68033b923e8686bdcc3de9d94" + }, + { + "output_index": 2, + "amount": 90000, + "sp_v0_info": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c03f81473844c9d74c921ff17a5af410b6337348d7afbff73891bc5cb0cb24cb0c0", + "script": "5120b2f12a7f570cc5057840fa6393f71f06900142088181810c34bb88761d92950d" + } + ] + } + }, + { + "description": "can finalize: one P2WPKH input / two mixed outputs - labeled sp output and BIP 32 change", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR8goQcAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCspcmETsmSLIHmcIF+My2i3RXSxdOpRiq21l5TtoKJXgIgTfMs6jgucsQ8BYQE/a5wWVHmuj3jPYM8g6lbsBg4CscBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIoIYBAAAAAAABBCJRIHwkw/lKVR+Uy49cDgPMmhcIf7FNcUpdL06f1MeJ09NmAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoAuJBKtaGGMwJH8G/tuRd4l0b3BZXsljCbRYdEtLosDafAQoEAQAAAAABAwhw8wUAAAAAAAEEFgAUOMGPNd2AY+OApw/+tPmP9O80gXciAgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwwAAAAAAAAAAAAAAAEA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 500000, + "witness_utxo": "20a1070000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "sp_v0_label": 1, + "script": "51207c24c3f94a551f94cb8f5c0e03cc9a17087fb14d714a5d2f4e9fd4c789d3d366" + }, + { + "output_index": 1, + "amount": 390000, + "script": "001438c18f35dd8063e380a70ffeb4f98ff4ef348177" + } + ] + } + }, + { + "description": "can finalize: one input / two sp outputs - output 0 has no label / output 1 uses label=0 convention for sp change", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR/gkwQAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCICJa45kPVF365uoUKpN0arSqgSfjyBNY63wha08anrtiAiBl42BSGRArJQdeOVXz80zjvPKjXR4QFd1dw7/tPs81qQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts0hA84eNnXZDZkmSfqd2xH5AlSbs7AvVvbonMCgTIuMN92UIh0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts1ADolhPR9rJTbbj6kkIA2zpvjvyhciVWTVaxEuUIZbTTAuBY+m9hwp4jZqSnJp1FVaXGHWVVnb+/rJ4urf5TY31SIeAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoQHlAgZ626fJYwVg4fApkzBb1F8fvRAkSLM6qlPTi/1N2xCR7/BcNmyY7Dj0ga2aMh+lLNN7q+gCheSsEeteIdbsAAQMIoIYBAAAAAAABBCJRIKBIDZZnnBk25au9SLqw4B/6yx7n0cnhaC8VQBaZZYXZAQlCAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoA7RDRzNcY/UF5V4mirKA8xi0pqoUSS/zecNmih9U74ybAAEDCDDmAgAAAAAAAQQiUSB2Hb00kpiEpmcp69zbrPFnSC32LBTwmOTvGNcP4Grv0AEJQgJfDsGzjZmEp+ti8VvkAwIEEPFC7fTWnjY/VhWv0qa2zQK+EFEqlVNkHkB62OUJo0GEUqkC6FVqDboumD9eAPe8UQEKBAAAAAAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 300000, + "witness_utxo": "e093040000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "025f0ec1b38d9984a7eb62f15be403020410f142edf4d69e363f5615afd2a6b6cd", + "ecdh_share": "03ce1e3675d90d992649fa9ddb11f902549bb3b02f56f6e89cc0a04c8b8c37dd94", + "dleq_proof": "0e89613d1f6b2536db8fa924200db3a6f8efca17225564d56b112e50865b4d302e058fa6f61c29e2366a4a7269d4555a5c61d65559dbfbfac9e2eadfe53637d5", + "input_index": 0 + }, + { + "scan_key": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d966568", + "ecdh_share": "025581bfaa733156f6b98e71a26f70fd76b3678649f62b1fc5e77b8e9ab8e61050", + "dleq_proof": "7940819eb6e9f258c158387c0a64cc16f517c7ef4409122cceaa94f4e2ff5376c4247bfc170d9b263b0e3d206b668c87e94b34deeafa00a1792b047ad78875bb", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", + "script": "5120a0480d96679c1936e5abbd48bab0e01ffacb1ee7d1c9e1682f154016996585d9" + }, + { + "output_index": 1, + "amount": 190000, + "sp_v0_info": "025f0ec1b38d9984a7eb62f15be403020410f142edf4d69e363f5615afd2a6b6cd037cd462358c69c40525247a8793944d9e67484b248fd84a1e7ee4f920b4da41b5", + "sp_v0_label": 0, + "script": "5120761dbd34929884a66729ebdcdbacf167482df62c14f098e4ef18d70fe06aefd0" + } + ] + } + }, + { + "description": "can finalize: two sp outputs - output 0 uses label=3 / output 1 uses label=1", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR/gkwQAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCICJa45kPVF365uoUKpN0arSqgSfjyBNY63wha08anrtiAiBl42BSGRArJQdeOVXz80zjvPKjXR4QFd1dw7/tPs81qQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWhAeUCBnrbp8ljBWDh8CmTMFvUXx+9ECRIszqqU9OL/U3bEJHv8Fw2bJjsOPSBrZoyH6Us03ur6AKF5KwR614h1uwABAwighgEAAAAAAAEEIlEgjkFVhZ37qH3uVCd4eSDGlYuRbXrHP02lCyzrTAnwYY8BCUICi3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWgD8+Tq+VWJ7fdfgshaz/nQvOSCnhaA85Xsfq1iaXfwhRMBCgQDAAAAAAEDCDDmAgAAAAAAAQQiUSDj5vwH7Pa/a1m0ubaqUxM11laKeL6AjUVOAg1+jH/aUwEJQgKLeQ4Jh/3p8NEjmOtr4wxzdnk8ZHhloqAvLvyWDZZlaALcqxqjkFTkguCjoiOo8Sgs1xGXZTHGeaoggF9/X6TxawEKBAEAAAAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 300000, + "witness_utxo": "e093040000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d966568", + "ecdh_share": "025581bfaa733156f6b98e71a26f70fd76b3678649f62b1fc5e77b8e9ab8e61050", + "dleq_proof": "7940819eb6e9f258c158387c0a64cc16f517c7ef4409122cceaa94f4e2ff5376c4247bfc170d9b263b0e3d206b668c87e94b34deeafa00a1792b047ad78875bb", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", + "sp_v0_label": 3, + "script": "51208e4155859dfba87dee5427787920c6958b916d7ac73f4da50b2ceb4c09f0618f" + }, + { + "output_index": 1, + "amount": 190000, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", + "sp_v0_label": 1, + "script": "5120e3e6fc07ecf6bf6b59b4b9b6aa531335d6568a78be808d454e020d7e8c7fda53" + } + ] + } + }, + { + "description": "can finalize: two mixed input types - only eligible inputs contribute ECDH shares (P2SH excluded)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCLTLcGPIp7P5Ia4ABZbNd4jlRA4dY+8e4bzsjCrJQMogIgNw2OmoWgzI3SWwuwgfMaotzFugoiSOqGCoFlHKNKDGgBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4g0iImLyaHKbt8hMVYEppdaYYYXO9TXsFJwN9g3UqSwgQBDwQAAAAAAQBTAgAAAAFA5vqmEmjxehAQHWJVNYKIKuSFi+4TNj24D98oH4Jf/AAAAAAA/////wHwSQIAAAAAABepFPRfjMomjsJuS76JkXDxOCUNfPVehwAAAAABBEdSIQKHfKAvFEBZvYLQDhs5muN094pSzvehyjfyjXiLNA0veSEDTIekSHL14fAG002IoAlZKCvUU150d8PWjDFlmR6hs5ZSrgEQBP7///8AAQMIkF8BAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "", + "public_key": "", + "prevout_txid": "d222262f268729bb7c84c558129a5d6986185cef535ec149c0df60dd4a92c204", + "prevout_index": 0, + "prevout_scriptpubkey": "a914f45f8cca268ec26e4bbe899170f138250d7cf55e87", + "amount": 150000, + "witness_utxo": "020000000140e6faa61268f17a10101d62553582882ae4858bee13363db80fdf281f825ffc0000000000ffffffff01f04902000000000017a914f45f8cca268ec26e4bbe899170f138250d7cf55e8700000000", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + } + ] + } + }, + { + "description": "can finalize: two mixed input types - only eligible inputs contribute ECDH shares (NUMS internal key excluded)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASsQJwAAAAAAACJRIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARAE/v///wEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAAAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0iAgJDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFu0gwRQIhAPRhsN/2Q/wKVsFSIUU2OBYiEkh+UGpZ4GnXXtHwq4T2AiBSxx6U6TEuqKkAH80mNyvnCZfdwqpinFqBFtxR9bfpogEBAwQBAAAAIgYCQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsIAAAAgAEAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghAuV6etywGNj7MVToDBS5elCZS0I1aEHqn8ExcGFEAOY3Ih4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhA9/o4HeILO0WcbkJsYhPUF1MI8kgqROMyhCMmJNTa/Vcymb868WqzAsViY2OjfUSHB/fnoevqBke8zUhiA7srpgABAwgcJQAAAAAAAAEEIlEgBXZyJSSDmoybydLZc46Yu0DdLsUCO9x9p/cGI818hfsBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "amount": 10000, + "witness_utxo": "102700000000000022512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 100000, + "witness_utxo": "a0860100000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02e57a7adcb018d8fb3154e80c14b97a50994b42356841ea9fc13170614400e637", + "dleq_proof": "f7fa381de20b3b459c6e426c6213d4175308f2482a44e33284232624d4dafd573299bf3af16ab302c5626363a37d448707f7e7a1ebea0647bccd486203bb2ba6", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 9500, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51200576722524839a8c9bc9d2d9738e98bb40dd2ec5023bdc7da7f70623cd7c85fb" + } + ] + } + }, + { + "description": "can finalize: three sp outputs (same scan key) - each output has distinct k value", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEEAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+AGgYAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIA3+9XB4r8pV2yK0ze42yxF/h2HkeAgptCHRBkisr62PAiBk5vWJD0/QW3emSnGDHJiGTZyYTJ7x/r6afFC08VkHIQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwighgEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkAAQMIoIYBAAAAAAABBCJRID/LrqSQh/jgPZ3v06vL1t6MAytYP4WmXQV4gcTEQ83AAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AAEDCKCGAQAAAAAAAQQiUSCXFELInwR4bggnKK4Rbuq4AvP6npX9sNN1OmhAICAG3gEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQABAwiQXwEAAAAAAAEEIlEgee9WiKP6f+/DkidFWx8cLwjHAIdCTWoA+EwxQhrKjaQA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 400000, + "witness_utxo": "801a060000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + }, + { + "output_index": 1, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51203fcbaea49087f8e03d9defd3abcbd6de8c032b583f85a65d057881c4c443cdc0" + }, + { + "output_index": 2, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120971442c89f04786e082728ae116eeab802f3fa9e95fdb0d3753a6840202006de" + }, + { + "output_index": 3, + "amount": 90000, + "script": "512079ef5688a3fa7fefc39227455b1f1c2f08c70087424d6a00f84c31421aca8da4" + } + ] + } + }, + { + "description": "can finalize: three sp outputs (same scan key) / two regular outputs - k values assigned independently of output index", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEGAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR8goQcAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQD333KAYwDd2+SyTNcz9TsypkayNsKUYFfeR65QOyqxaAIgTGlAW3e+2vOyB+3ll0m7o9VMRhWoi7025Ugkr3fJ5zEBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIoIYBAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AAEDCFDDAAAAAAAAAQQiUSCcaj0jgY6CDtHQz965Y2V8jsMne32bhlx411V/Xe3WogABAwighgEAAAAAAAEEIlEgP8uupJCH+OA9ne/Tq8vW3owDK1g/haZdBXiBxMRDzcABCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkAAQMIUMMAAAAAAAABBCJRIHnvVoij+n/vw5InRVsfHC8IxwCHQk1qAPhMMUIayo2kAAEDCKCGAQAAAAAAAQQiUSCXFELInwR4bggnKK4Rbuq4AvP6npX9sNN1OmhAICAG3gEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQABAwjILAEAAAAAAAEEIlEgb8UG+AUqn1qKqbmQ1emU9JXd7W1HusRMqs2t2NiZHNAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 500000, + "witness_utxo": "20a1070000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + }, + { + "output_index": 1, + "amount": 50000, + "script": "51209c6a3d23818e820ed1d0cfdeb963657c8ec3277b7d9b865c78d7557f5dedd6a2" + }, + { + "output_index": 2, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51203fcbaea49087f8e03d9defd3abcbd6de8c032b583f85a65d057881c4c443cdc0" + }, + { + "output_index": 3, + "amount": 50000, + "script": "512079ef5688a3fa7fefc39227455b1f1c2f08c70087424d6a00f84c31421aca8da4" + }, + { + "output_index": 4, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120971442c89f04786e082728ae116eeab802f3fa9e95fdb0d3753a6840202006de" + }, + { + "output_index": 5, + "amount": 77000, + "script": "51206fc506f8052a9f5a8aa9b990d5e994f495dded6d47bac44caacdadd8d8991cd0" + } + ] + } + }, + { + "description": "in progress: two P2TR inputs, neither is signed", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASughgEAAAAAACJRIMgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/AQMEAQAAAAEQBP7///8BFyDIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvyIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4gK5A3/0HdF05VIvD0BFw/QENiMq8mANTaXJ6hPMPKtLUBDwQAAAAAAQErECcAAAAAAAAiUSBDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFuwEDBAEAAAABEAT+////ARcgQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsiHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCEC5Xp63LAY2PsxVOgMFLl6UJlLQjVoQeqfwTFwYUQA5jciHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaED3+jgd4gs7RZxuQmxiE9QXUwjySCpE4zKEIyYk1Nr9VzKZvzrxarMCxWJjY6N9RIcH9+eh6+oGR7zNSGIDuyumAAEDCLj5AgAAAAAAAQQiUSDLH766UNBuv/tpC9qdX/Oc4vrbT87clmMu/xukHMDAewEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "5120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "amount": 100000, + "witness_utxo": "a086010000000000225120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "2b9037ff41dd174e5522f0f4045c3f40436232af2600d4da5c9ea13cc3cab4b5", + "prevout_index": 0, + "prevout_scriptpubkey": "512043a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "amount": 10000, + "witness_utxo": "102700000000000022512043a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + }, + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02e57a7adcb018d8fb3154e80c14b97a50994b42356841ea9fc13170614400e637", + "dleq_proof": "f7fa381de20b3b459c6e426c6213d4175308f2482a44e33284232624d4dafd573299bf3af16ab302c5626363a37d448707f7e7a1ebea0647bccd486203bb2ba6", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 195000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120cb1fbeba50d06ebffb690bda9d5ff39ce2fadb4fcedc96632eff1ba41cc0c07b" + } + ] + } + }, + { + "description": "in progress: one P2TR input / one sp output with no ECDH shares when PSBT_OUT_SCRIPT field is not set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASughgEAAAAAACJRIMgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/ARAE/v///wEXIMgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/AAEDCBhzAQAAAAAAAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "5120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "amount": 100000, + "witness_utxo": "a086010000000000225120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039" + } + ] + } + }, + { + "description": "in progress: two inputs / one sp output, input 1 missing ECDH share when PSBT_OUT_SCRIPT field is not set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4gvp1lyDkV+wkkirvfyoyj7ubONQxVT2Xo1Sbl31J6tSIBDwQAAAAAAQEf8EkCAAAAAAAWABRGM9WcrRNDR5ly4MmWkh0KXa22XSIGAkOg1A6d4IgnlBYXUrrkK6kJUnhCCfBjQw8K8lY1SoW7CAAAAIABAAAAARAE/v///wABAwhADQMAAAAAAAEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 150000, + "witness_utxo": "f0490200000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 200000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039" + } + ] + } + }, + { + "description": "in progress: one input / two sp outputs, input 0 missing ECDH share for output 0 when PSBT_OUT_SCRIPT field is not set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR9ADQMAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsIQMJH9SAORd/RNe3npYWiEnGLrP4eADEZ2fFCo84+Y79FSIeA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsQL4acjrvVeiYG5lZmpvBKzj4froBWMxwZokPImnK3kY7dzQal9KScZZNAcAkHMMi/VOQwpuZFg+CbzKTZYmMvwIAAQMIkF8BAAAAAAABCUIDZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp94D8nDgpWMhg7arAQdOh35oANsA9cfF0vr7to+cKNSFRSQAAQMIkF8BAAAAAAABBCJRIENYHfbIefmqrcU2YN6xVrnxIr0a7KczIDvOh/zx84buAQlCA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsAuDEwYe3IV+ZfISFp1tFkbbChHe5WhEjuZaSLTFUhgv4AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "0352d78c41390032ad91816a697fc740d8eb909cf04d70885264b051f2385e25ec", + "ecdh_share": "03091fd48039177f44d7b79e96168849c62eb3f87800c46767c50a8f38f98efd15", + "dleq_proof": "be1a723aef55e8981b99599a9bc12b38f87eba0158cc7066890f2269cade463b77341a97d29271964d01c0241cc322fd5390c29b99160f826f329365898cbf02", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de03f270e0a5632183b6ab01074e877e6800db00f5c7c5d2fafbb68f9c28d4854524" + }, + { + "output_index": 1, + "amount": 90000, + "sp_v0_info": "0352d78c41390032ad91816a697fc740d8eb909cf04d70885264b051f2385e25ec02e0c4c187b7215f997c8485a75b4591b6c28477b95a1123b996922d3154860bf8", + "script": "512043581df6c879f9aaadc53660deb156b9f122bd1aeca733203bce87fcf1f386ee" + } + ] + } + }, + { + "description": "in progress: large PSBT with nine mixed inputs / six outputs - some inputs signed", + "psbt": "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", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 100000, + "witness_utxo": "a086010000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 2, + "private_key": "e6db4a04e2fc29745ea1a9dcf1fed7449c8228ffe0503d8171e64126d5399800", + "public_key": "02655ef5daf05d28d1ca73ba8e211300db2b70416737183e0fa8c355ee4c1cbac6", + "prevout_txid": "d041c742ca364472d1a18c7a8eae07e6e904a9ea1b85f198525fb83ef276e343", + "prevout_index": 0, + "prevout_scriptpubkey": "00142307fc774ca174b42ada814bc2e6e444ff3d830c", + "amount": 100000, + "witness_utxo": "a0860100000000001600142307fc774ca174b42ada814bc2e6e444ff3d830c", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 3, + "private_key": "f5208ed67b0019ba4e306aff029d247931e0dfacd1fe6e69091bc6bc70a42619", + "public_key": "0303a0f4528d264e8e95256634b94414e2748f72962704fe57e8b0a2cc44d2383d", + "prevout_txid": "52262cd5d402bf32616d07fc03286a8f7fe78af7ac411dfff41655ddc1011e6f", + "prevout_index": 0, + "prevout_scriptpubkey": "00147018bce965288d8bf0ccf3e0ec48b6f1dbbfe548", + "amount": 100000, + "witness_utxo": "a0860100000000001600147018bce965288d8bf0ccf3e0ec48b6f1dbbfe548", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 4, + "private_key": "0c19c34dc1005feb5ca60104d20472f7f2f97bd4d521264618196bf71155b20a", + "public_key": "03a8911beb5a48fe119cb015088cf0c3a118cf026772448972d1b70c764ac59412", + "prevout_txid": "e298eaf24fe1ca68b41e4b6b83cc3d91ced99c6e78d5b992cfb15771b5457064", + "prevout_index": 0, + "prevout_scriptpubkey": "00149332bb020cb03d49eb4d04712c1cc08e2b06d8d8", + "amount": 100000, + "witness_utxo": "a0860100000000001600149332bb020cb03d49eb4d04712c1cc08e2b06d8d8", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 5, + "private_key": "5aaa151cbdeb566911784d166b01ce57f35993b98f96846efec1cfefcc0d8f56", + "public_key": "02d155713c520ce2d8276164215d659ab623c5f7306f8652c1756e26833e886e01", + "prevout_txid": "1730833ecb5be01362d7149522604484216e1c7b18cf313d1cf1543ef85df3b6", + "prevout_index": 0, + "prevout_scriptpubkey": "5120d155713c520ce2d8276164215d659ab623c5f7306f8652c1756e26833e886e01", + "amount": 100000, + "witness_utxo": "a086010000000000225120d155713c520ce2d8276164215d659ab623c5f7306f8652c1756e26833e886e01", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 6, + "private_key": "d92c1506167b3a00a21f4d90d7b5ccb4713c00194aa66f9c7deb162c467f9643", + "public_key": "02d00e15fb626a7efee85c5bce676d636b6048914e9a34ace0b4cbc44bc948077a", + "prevout_txid": "e93532e0eda39c4d39da2d3f91d14529bc435fc2efb9196e25fbaac4b4e89b81", + "prevout_index": 0, + "prevout_scriptpubkey": "5120d00e15fb626a7efee85c5bce676d636b6048914e9a34ace0b4cbc44bc948077a", + "amount": 100000, + "witness_utxo": "a086010000000000225120d00e15fb626a7efee85c5bce676d636b6048914e9a34ace0b4cbc44bc948077a", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 7, + "private_key": "d8bc8d0e1233a97133f3573d6dceb6736e8b6d2e4839925145022ea3efdad8f8", + "public_key": "020500f167cf669e7dac39e6aecda1f28c66b78ec7097eefef25d54f7be785e7f6", + "prevout_txid": "b6368bbc330cddfe9e2550edb7dfc15ae74cac9bcc7d967fe81e6f27f8597b1d", + "prevout_index": 0, + "prevout_scriptpubkey": "a9140df101357f73e6658500cf56f3a132d7bb8afbdd87", + "amount": 100000, + "witness_utxo": "a08601000000000017a9140df101357f73e6658500cf56f3a132d7bb8afbdd87", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 8, + "private_key": "81e30f5b5d049bdf5c5bea9c3329bf57cc8b473240182a9f2a6eee07435bd54a", + "public_key": "03199d2f155f32ebb167aeb0ccca6e5afb7340898df1b7ab9936500f6d4391902a", + "prevout_txid": "03bceba5379d73daeafe677972b03dcf7ef30b7f10eaa83ad04bc0bf00d8b2f7", + "prevout_index": 0, + "prevout_scriptpubkey": "76a914569bf311e68a383767b43f83042dfabe60093ede88ac", + "amount": 100000, + "witness_utxo": "02000000012a34ac1b0db77a8198c1e29002cc47bbfcd6ed830960304d2c9a3ab548486b5d0000000000ffffffff01a0860100000000001976a914569bf311e68a383767b43f83042dfabe60093ede88ac00000000", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "030a18b9bb4199b89330eb17f8ee5ac786e0853dcd2f3ccdf1744b97ce4ede053e", + "dleq_proof": "001383d4661be8725ef29048f48c911f89f5f34317d7c0c0acc60ef65f1b52bfff7a96d8339b625df83abab57aa273edcd9a35c794dacbb1c861171bb267075f", + "input_index": 0 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03fbd173d9c48d58c5022426d05278bca17b9a8e2282666901a1b00b9e04b79949", + "dleq_proof": "c05da410487cfaec378febe6af5005e3ae6c86236818b0e55b214017eb5073edef40179575b5dcdaa9063491ac8b83fd0624d4177a4d2dd4994c4640d6b8ccde", + "input_index": 0 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "037372d179f4f628da82ccbd4e4217bdb0809f1417c9abeaa35fa805d48ad6554c", + "dleq_proof": "a6872d4eb119fe2af70953f752a22236b54a3b57f70e511f10bd8b42ff56b5acf6365093c480ea9f13776784945b59318ac2927d915aae244cfb683f4188a3a7", + "input_index": 0 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02f6ae7b8aac9a0f9c9f04eab484062172df2e38d9f2f6ec31b404bb5a286aabf0", + "dleq_proof": "334aced8106ebaa4287daca56cca26795f2d2a739784cf6b734da3370757f499e120c0e40be40436cd74c4ef26905706f52fa7fa6e8e76451e8583783051a843", + "input_index": 1 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03a6a89b1b986a995533e17bed64500b541bb0d739071efe2ae2b7efb0a0ca6bcf", + "dleq_proof": "c34a6a2940bbe9a7356c1cc6ffe358dd4441db58897cba99c212eb75285e7da1eca11c792c97a380493345ab224de9d94c2b36ee8526ae23528171a3231de733", + "input_index": 1 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "02b1d3ed2fa4bc316a08eb1485fa75ed7dd54ca7a9ae18d0c4a17da93f30c385d5", + "dleq_proof": "d841a373ad2896b972b8cfea1634ff14a30f80d84cfd6ef18893397b8f346c80519c9b161a72244d0e9b64e5436c98ce9dd4e17a0728fc997b5c7bcf39cecb68", + "input_index": 1 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "03bfb86253beb96f58b8c469e442cfcf9f78a5413a95423be8e686764f2fa7bd3f", + "dleq_proof": "52dffbf0095f29b1de24c9d68d549d1c54bce6900454fe37fd10f5c903dfee85f7bad8a72ada8e52cd9092f51d9e90b5a7e4f36ca04df98e77fbad1347305bc7", + "input_index": 2 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03df706ff26d672fe50c6f56a2ca1e7174e75055c920bbd337e1a7d6bf2826f6dc", + "dleq_proof": "fd2e8c22b9b672d3bb9e26d4b6da2dc3f125455a969fd8e2b2c1ba791076ad71b38c01f9218c3d6776fd895d2f3067484996d85e268390c2b433e6c4b21891f1", + "input_index": 2 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "038604aa8cff309dddf49b1f366f89c269616587fc41db608540f9431536f6646f", + "dleq_proof": "93e1e5d4888bc1cfe02eecfa9dea144ead84f23a10fc1a301103c1e56393527b63417c6c4a5af45359ff8b6e179af73978e4d62e862eedd2a286f5278e4cb340", + "input_index": 2 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02980bac7264e14bf820cc435b69a78df726c177eb026f1b81b52de811272f58eb", + "dleq_proof": "38e03463b6450dbd051065a812a6f2631087f7951e46ac84e66da811755717c093180e4d49827b8c097ca7093fe92d393f95a5d7ad849a41802dc4e4558abde9", + "input_index": 3 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "038029998c73c2b139db75557ec3011ec65ecd212447963006571852a3e92289cc", + "dleq_proof": "381ce3c1ac9fc7f6a51536456252984768e8fbd014070239f4cb13b16793c74cc5ed677c8a7eacc173e988d353a3f289c7e2141db7a4b4145c5499ec837f36ae", + "input_index": 3 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "03a27d9f1fa53e43f70835a6990954b78a35f747066f7b82b4a9ea82e8bb5e06a6", + "dleq_proof": "732aa0957a9db2c759e39aca5d0167ef13d2a34a43c812e9cc9d712d458f7b2285da7511eb16f61f9600d20be4e94dfdd2bc3ae37fd943fc34947dfc3fdc6797", + "input_index": 3 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "027eb7c1c4afcd7e62ab6ad3d2ba2733a3745220641f91903ebfe7bcc5dbdc6b5d", + "dleq_proof": "77b1572336d9bd4e2f73411eb8f6a96235a130f444cbeb07a489f447921e37489d71fd3db28a0bbaa35b88c37b7579ecdbf6ce1638700db81dfc86bf79cd1be7", + "input_index": 4 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02bef4738c6f327f1724b1407988959cdf2bd19210894bfd043535b61138270818", + "dleq_proof": "0a584927102820b7b6cad6bcfb031a23a37312ad87d23f1cb5a8359be2d5ff39b6a2a2fe37cd0d53ee347f13e6b3525769b2e789f0699cb985588eac2daf10dd", + "input_index": 4 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "03ca88bcdb6d1f5291e90c75d3da601a8bd29991eabbf7f94b2f39483e3858c9a1", + "dleq_proof": "5d0de8a27f0d528802cf3354a1af507bcf0fed419da641c9c0807cd754e07af8790b511c705d5fbf63305ab22afe0497facf070b5afeafcee3a6900547767756", + "input_index": 4 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "03802b69b360da4585512b1cf967b184010226d75ce5b762e77a0128bcb068ccbb", + "dleq_proof": "103ae05c10e43298376c3fee1dc5f0d04389f4f20af49346d48ead324d615d6f28fca0a318445130ea56a862019b2de0c2637db3c418c0b074b58548ae8b20c9", + "input_index": 5 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "024f2a15a4b15a6b78f0a632fc7fdf112929f64bd4dd14f67a63cf4fb8f325a204", + "dleq_proof": "8f223de27986dd8792a03d9d5b662323e8697830ebabb67363881fe72c86482ae61a09268d083b8354cfc2bad35c148eeb291af622dd460634606332af1b8297", + "input_index": 5 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "02407615505ecbdea881312504eef6f4681a26316ef73d33537a7fd793f4658ea9", + "dleq_proof": "f055990738544d099b159290e7f5442cb91226260174aa9d8c1c8310b7cb01ce2b94cc5d2b8fdf81667e02fb796db0e5c27877de0d41a2a0e709b01445baf296", + "input_index": 5 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "020faa3a0c31ae7695e769546e30aa782abd31fb681b970a13aa35cc21dfd13800", + "dleq_proof": "e4074a6ec893eab806d5df9a74ed257e785ff378643dbaa7933c7813a7b5f6c90ca82b62539f39b38c2c72fe5cf81c22a5e641f673644f6c486b3db577464463", + "input_index": 6 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02153fcdfe96ae70363fd1542646d309c2132b8325540e3dd7f88e37f9796079a6", + "dleq_proof": "4320e9f60fd3c2a6f737aace546c23b0c7d76e84159c0ad289c1df4c0f8a500e51209f296f9d7d223a1d292bd41d266faacb1be843becf4b706eb15dba702806", + "input_index": 6 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "03f313f8f475eb01e3cecbc0eaac2f726d29032c6ff7c3ad6cc3bf8bb10186527c", + "dleq_proof": "3b29e12cf693f272139f74452f2bccc2bcd07432dce8fb107354c1458a12a47c8544d2d6ca1e33bce0578b6508cf8a5934673f9fc435a5f84a22a54b5adcbcfc", + "input_index": 6 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "026f027b235a7593ca8943c6d7b7e37d92eab339617f7f0c344106c58dd0cbc6aa", + "dleq_proof": "16b58148f7baff783e4310f185503fe92417dba43b966c83a2184322cb1f20424cb9d040ae6a1aea4e7444bdc786258a975644ce1a0195564c2b9fc3c606a292", + "input_index": 7 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02fe7841cddfb46fdb2ca9bde56c9558c2d1b36121665659080224e24031438408", + "dleq_proof": "88072551662c3eebee545f188b8dafc682973a2f218e7ca07fc8e42ff57a1134630449fd703177d7a12d0c9ced024844dc1b9e0ecad67587dd5c479431b36a1a", + "input_index": 7 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "020611450c6c1944ecdecb78f61816c767fba4368f21f7e9b169779c76f23cf4cc", + "dleq_proof": "4ab36bdd69eb1cd0d4b0aeae403dce4db5228c5ba3a291dc725de23382d1736d265d7bc33f83690a5ca5609719f19e81952d4c8d5e288fd0cb8c6411104b80a3", + "input_index": 7 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02fa1ced932598a27fa28410b955ea67479b5a4033b69f2c99e4e26767bba0a556", + "dleq_proof": "a0948c517d70976c0e39ca67abf19e7217040dbe172bb96534133e6ffa3941c1659416f540662b7561efb70064b4d71a9b17b63bc563f630db4422f273e0df12", + "input_index": 8 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02f3c175b08b207637616aa74e67baac1a1807c94bce70800f76f5b17b52c0082d", + "dleq_proof": "99225658fede7a8c2437eb3821bdc9d3700abc896e8b6dcef84e29347b78bc2b03e6da33836e4f40767390c36644ce016b36b1d546963540fa51d0b0407db6d7", + "input_index": 8 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "0300e0fd1710dc56ad7c173aa9141d731577009bc3dffcba8576a48e77ab34d970", + "dleq_proof": "ee0aaa65ca73ccc547b31c2b7ed2ae31a4f4ccdaea73f2add24630d608ede9aea5d808688186ebca42b877efab13f35e60f210d4f620602f850fc0731c201e91", + "input_index": 8 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 30000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "51201afa5d88188cef9b91b3f1002b11e0f9e94ad3981978c4d12a0605564c081233" + }, + { + "output_index": 1, + "amount": 30000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "5120cabf7e4a0de424ec9bbadd7f7242cce7673e1701f49d6a19a97055da332b1324" + }, + { + "output_index": 2, + "amount": 20000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "51204aadc9e0a590d21e0c19cb2024f27140f9b19c2a1282d50ea7c5f45eb03fd4a2" + }, + { + "output_index": 3, + "amount": 20000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "51202fa43700ea1d28fd7ad4dcb332b3e5e5a0d8cb54258f6661c4de0377d72804e5" + }, + { + "output_index": 4, + "amount": 10000, + "sp_v0_info": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c03f81473844c9d74c921ff17a5af410b6337348d7afbff73891bc5cb0cb24cb0c0", + "script": "512051986faee319436b51b38409c1b300f4396e2b32e3cc9a12ae2a4be135da6f6e" + }, + { + "output_index": 5, + "amount": 50000, + "script": "51206fc506f8052a9f5a8aa9b990d5e994f495dded6d47bac44caacdadd8d8991cd0" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/bip-0375/deps/bitcoin_test/messages.py b/bip-0375/deps/bitcoin_test/messages.py new file mode 100644 index 0000000000..90d2630301 --- /dev/null +++ b/bip-0375/deps/bitcoin_test/messages.py @@ -0,0 +1,449 @@ +#!/usr/bin/env python3 +# Copyright (c) 2010 ArtForz -- public domain half-a-node +# Copyright (c) 2012 Jeff Garzik +# Copyright (c) 2010-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Bitcoin test framework primitive and message structures + +CBlock, CTransaction, CBlockHeader, CTxIn, CTxOut, etc....: + data structures that should map to corresponding structures in + bitcoin/primitives + +msg_block, msg_tx, msg_headers, etc.: + data structures that represent network messages + +ser_*, deser_*: functions that handle serialization/deserialization. + +Classes use __slots__ to ensure extraneous attributes aren't accidentally added +by tests, compromising their intended effect. +""" + +######################################################################## +# Adapted from Bitcoin Core test framework messages.py +# for BIP-375 PSBT validation tests. +######################################################################## + +import copy +import hashlib +import math +from io import BytesIO + +COIN = 100000000 # 1 btc in satoshis +WITNESS_SCALE_FACTOR = 4 + +# ============================================================================ +# Serialization utilities +# ============================================================================ + +def hash160(s: bytes) -> bytes: + return hashlib.new("ripemd160", sha256(s)).digest() + + +def sha256(s: bytes) -> bytes: + return hashlib.sha256(s).digest() + + +def hash256(s: bytes) -> bytes: + return sha256(sha256(s)) + + +def ser_compact_size(l): + r = b"" + if l < 253: + r = l.to_bytes(1, "little") + elif l < 0x10000: + r = (253).to_bytes(1, "little") + l.to_bytes(2, "little") + elif l < 0x100000000: + r = (254).to_bytes(1, "little") + l.to_bytes(4, "little") + else: + r = (255).to_bytes(1, "little") + l.to_bytes(8, "little") + return r + + +def deser_compact_size(f): + nit = int.from_bytes(f.read(1), "little") + if nit == 253: + nit = int.from_bytes(f.read(2), "little") + elif nit == 254: + nit = int.from_bytes(f.read(4), "little") + elif nit == 255: + nit = int.from_bytes(f.read(8), "little") + return nit + + +def ser_varint(l): + r = b"" + while True: + r = bytes([(l & 0x7f) | (0x80 if len(r) > 0 else 0x00)]) + r + if l <= 0x7f: + return r + l = (l >> 7) - 1 + + +def deser_varint(f): + n = 0 + while True: + dat = f.read(1)[0] + n = (n << 7) | (dat & 0x7f) + if (dat & 0x80) > 0: + n += 1 + else: + return n + + +def deser_string(f): + nit = deser_compact_size(f) + return f.read(nit) + + +def ser_string(s): + return ser_compact_size(len(s)) + s + + +def deser_uint256(f): + return int.from_bytes(f.read(32), 'little') + + +def ser_uint256(u): + return u.to_bytes(32, 'little') + + +def uint256_from_str(s): + return int.from_bytes(s[:32], 'little') + + +def uint256_from_compact(c): + nbytes = (c >> 24) & 0xFF + v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) + return v + + +# deser_function_name: Allow for an alternate deserialization function on the +# entries in the vector. +def deser_vector(f, c, deser_function_name=None): + nit = deser_compact_size(f) + r = [] + for _ in range(nit): + t = c() + if deser_function_name: + getattr(t, deser_function_name)(f) + else: + t.deserialize(f) + r.append(t) + return r + + +# ser_function_name: Allow for an alternate serialization function on the +# entries in the vector (we use this for serializing the vector of transactions +# for a witness block). +def ser_vector(l, ser_function_name=None): + r = ser_compact_size(len(l)) + for i in l: + if ser_function_name: + r += getattr(i, ser_function_name)() + else: + r += i.serialize() + return r + + +def deser_uint256_vector(f): + nit = deser_compact_size(f) + r = [] + for _ in range(nit): + t = deser_uint256(f) + r.append(t) + return r + + +def ser_uint256_vector(l): + r = ser_compact_size(len(l)) + for i in l: + r += ser_uint256(i) + return r + + +def deser_string_vector(f): + nit = deser_compact_size(f) + r = [] + for _ in range(nit): + t = deser_string(f) + r.append(t) + return r + + +def ser_string_vector(l): + r = ser_compact_size(len(l)) + for sv in l: + r += ser_string(sv) + return r + +# like from_hex, but without the hex part +def from_binary(cls, stream): + """deserialize a binary stream (or bytes object) into an object""" + # handle bytes object by turning it into a stream + was_bytes = isinstance(stream, bytes) + if was_bytes: + stream = BytesIO(stream) + obj = cls() + obj.deserialize(stream) + if was_bytes: + assert len(stream.read()) == 0 + return obj + + +# ============================================================================ +# Transaction data structures +# ============================================================================ + +class COutPoint: + __slots__ = ("hash", "n") + + def __init__(self, hash=0, n=0): + self.hash = hash + self.n = n + + def deserialize(self, f): + self.hash = deser_uint256(f) + self.n = int.from_bytes(f.read(4), "little") + + def serialize(self): + r = b"" + r += ser_uint256(self.hash) + r += self.n.to_bytes(4, "little") + return r + + def __repr__(self): + return "COutPoint(hash=%064x n=%i)" % (self.hash, self.n) + +class CTxIn: + __slots__ = ("nSequence", "prevout", "scriptSig") + + def __init__(self, outpoint=None, scriptSig=b"", nSequence=0): + if outpoint is None: + self.prevout = COutPoint() + else: + self.prevout = outpoint + self.scriptSig = scriptSig + self.nSequence = nSequence + + def deserialize(self, f): + self.prevout = COutPoint() + self.prevout.deserialize(f) + self.scriptSig = deser_string(f) + self.nSequence = int.from_bytes(f.read(4), "little") + + def serialize(self): + r = b"" + r += self.prevout.serialize() + r += ser_string(self.scriptSig) + r += self.nSequence.to_bytes(4, "little") + return r + + def __repr__(self): + return "CTxIn(prevout=%s scriptSig=%s nSequence=%i)" \ + % (repr(self.prevout), self.scriptSig.hex(), + self.nSequence) + + +class CTxOut: + __slots__ = ("nValue", "scriptPubKey") + + def __init__(self, nValue=0, scriptPubKey=b""): + self.nValue = nValue + self.scriptPubKey = scriptPubKey + + def deserialize(self, f): + self.nValue = int.from_bytes(f.read(8), "little", signed=True) + self.scriptPubKey = deser_string(f) + + def serialize(self): + r = b"" + r += self.nValue.to_bytes(8, "little", signed=True) + r += ser_string(self.scriptPubKey) + return r + + def __repr__(self): + return "CTxOut(nValue=%i.%08i scriptPubKey=%s)" \ + % (self.nValue // COIN, self.nValue % COIN, + self.scriptPubKey.hex()) + + +class CScriptWitness: + __slots__ = ("stack",) + + def __init__(self): + # stack is a vector of strings + self.stack = [] + + def __repr__(self): + return "CScriptWitness(%s)" % \ + (",".join([x.hex() for x in self.stack])) + + def is_null(self): + if self.stack: + return False + return True + + +class CTxInWitness: + __slots__ = ("scriptWitness",) + + def __init__(self): + self.scriptWitness = CScriptWitness() + + def deserialize(self, f): + self.scriptWitness.stack = deser_string_vector(f) + + def serialize(self): + return ser_string_vector(self.scriptWitness.stack) + + def __repr__(self): + return repr(self.scriptWitness) + + def is_null(self): + return self.scriptWitness.is_null() + + +class CTxWitness: + __slots__ = ("vtxinwit",) + + def __init__(self): + self.vtxinwit = [] + + def deserialize(self, f): + for i in range(len(self.vtxinwit)): + self.vtxinwit[i].deserialize(f) + + def serialize(self): + r = b"" + # This is different than the usual vector serialization -- + # we omit the length of the vector, which is required to be + # the same length as the transaction's vin vector. + for x in self.vtxinwit: + r += x.serialize() + return r + + def __repr__(self): + return "CTxWitness(%s)" % \ + (';'.join([repr(x) for x in self.vtxinwit])) + + def is_null(self): + for x in self.vtxinwit: + if not x.is_null(): + return False + return True + + +class CTransaction: + __slots__ = ("nLockTime", "version", "vin", "vout", "wit") + + def __init__(self, tx=None): + if tx is None: + self.version = 2 + self.vin = [] + self.vout = [] + self.wit = CTxWitness() + self.nLockTime = 0 + else: + self.version = tx.version + self.vin = copy.deepcopy(tx.vin) + self.vout = copy.deepcopy(tx.vout) + self.nLockTime = tx.nLockTime + self.wit = copy.deepcopy(tx.wit) + + def deserialize(self, f): + self.version = int.from_bytes(f.read(4), "little") + self.vin = deser_vector(f, CTxIn) + flags = 0 + if len(self.vin) == 0: + flags = int.from_bytes(f.read(1), "little") + # Not sure why flags can't be zero, but this + # matches the implementation in bitcoind + if (flags != 0): + self.vin = deser_vector(f, CTxIn) + self.vout = deser_vector(f, CTxOut) + else: + self.vout = deser_vector(f, CTxOut) + if flags != 0: + self.wit.vtxinwit = [CTxInWitness() for _ in range(len(self.vin))] + self.wit.deserialize(f) + else: + self.wit = CTxWitness() + self.nLockTime = int.from_bytes(f.read(4), "little") + + def serialize_without_witness(self): + r = b"" + r += self.version.to_bytes(4, "little") + r += ser_vector(self.vin) + r += ser_vector(self.vout) + r += self.nLockTime.to_bytes(4, "little") + return r + + # Only serialize with witness when explicitly called for + def serialize_with_witness(self): + flags = 0 + if not self.wit.is_null(): + flags |= 1 + r = b"" + r += self.version.to_bytes(4, "little") + if flags: + dummy = [] + r += ser_vector(dummy) + r += flags.to_bytes(1, "little") + r += ser_vector(self.vin) + r += ser_vector(self.vout) + if flags & 1: + if (len(self.wit.vtxinwit) != len(self.vin)): + # vtxinwit must have the same length as vin + self.wit.vtxinwit = self.wit.vtxinwit[:len(self.vin)] + for _ in range(len(self.wit.vtxinwit), len(self.vin)): + self.wit.vtxinwit.append(CTxInWitness()) + r += self.wit.serialize() + r += self.nLockTime.to_bytes(4, "little") + return r + + # Regular serialization is with witness -- must explicitly + # call serialize_without_witness to exclude witness data. + def serialize(self): + return self.serialize_with_witness() + + @property + def wtxid_hex(self): + """Return wtxid (transaction hash with witness) as hex string.""" + return hash256(self.serialize())[::-1].hex() + + @property + def wtxid_int(self): + """Return wtxid (transaction hash with witness) as integer.""" + return uint256_from_str(hash256(self.serialize_with_witness())) + + @property + def txid_hex(self): + """Return txid (transaction hash without witness) as hex string.""" + return hash256(self.serialize_without_witness())[::-1].hex() + + @property + def txid_int(self): + """Return txid (transaction hash without witness) as integer.""" + return uint256_from_str(hash256(self.serialize_without_witness())) + + def is_valid(self): + for tout in self.vout: + if tout.nValue < 0 or tout.nValue > 21000000 * COIN: + return False + return True + + # Calculate the transaction weight using witness and non-witness + # serialization size (does NOT use sigops). + def get_weight(self): + with_witness_size = len(self.serialize_with_witness()) + without_witness_size = len(self.serialize_without_witness()) + return (WITNESS_SCALE_FACTOR - 1) * without_witness_size + with_witness_size + + def get_vsize(self): + return math.ceil(self.get_weight() / WITNESS_SCALE_FACTOR) + + def __repr__(self): + return "CTransaction(version=%i vin=%s vout=%s wit=%s nLockTime=%i)" \ + % (self.version, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime) \ No newline at end of file diff --git a/bip-0375/deps/bitcoin_test/psbt.py b/bip-0375/deps/bitcoin_test/psbt.py new file mode 100644 index 0000000000..2820430865 --- /dev/null +++ b/bip-0375/deps/bitcoin_test/psbt.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +######################################################################## +# Adapted from Bitcoin Core test framework psbt.py +# for BIP-375 PSBT validation tests. +######################################################################## + +import base64 +import struct + +from io import BytesIO + +from .messages import ( + CTransaction, + deser_string, + deser_compact_size, + from_binary, + ser_compact_size, +) + + +# global types +PSBT_GLOBAL_UNSIGNED_TX = 0x00 +PSBT_GLOBAL_XPUB = 0x01 +PSBT_GLOBAL_TX_VERSION = 0x02 +PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03 +PSBT_GLOBAL_INPUT_COUNT = 0x04 +PSBT_GLOBAL_OUTPUT_COUNT = 0x05 +PSBT_GLOBAL_TX_MODIFIABLE = 0x06 +PSBT_GLOBAL_VERSION = 0xfb +PSBT_GLOBAL_PROPRIETARY = 0xfc + +# per-input types +PSBT_IN_NON_WITNESS_UTXO = 0x00 +PSBT_IN_WITNESS_UTXO = 0x01 +PSBT_IN_PARTIAL_SIG = 0x02 +PSBT_IN_SIGHASH_TYPE = 0x03 +PSBT_IN_REDEEM_SCRIPT = 0x04 +PSBT_IN_WITNESS_SCRIPT = 0x05 +PSBT_IN_BIP32_DERIVATION = 0x06 +PSBT_IN_FINAL_SCRIPTSIG = 0x07 +PSBT_IN_FINAL_SCRIPTWITNESS = 0x08 +PSBT_IN_POR_COMMITMENT = 0x09 +PSBT_IN_RIPEMD160 = 0x0a +PSBT_IN_SHA256 = 0x0b +PSBT_IN_HASH160 = 0x0c +PSBT_IN_HASH256 = 0x0d +PSBT_IN_PREVIOUS_TXID = 0x0e +PSBT_IN_OUTPUT_INDEX = 0x0f +PSBT_IN_SEQUENCE = 0x10 +PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11 +PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12 +PSBT_IN_TAP_KEY_SIG = 0x13 +PSBT_IN_TAP_SCRIPT_SIG = 0x14 +PSBT_IN_TAP_LEAF_SCRIPT = 0x15 +PSBT_IN_TAP_BIP32_DERIVATION = 0x16 +PSBT_IN_TAP_INTERNAL_KEY = 0x17 +PSBT_IN_TAP_MERKLE_ROOT = 0x18 +PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a +PSBT_IN_MUSIG2_PUB_NONCE = 0x1b +PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c +PSBT_IN_PROPRIETARY = 0xfc + +# per-output types +PSBT_OUT_REDEEM_SCRIPT = 0x00 +PSBT_OUT_WITNESS_SCRIPT = 0x01 +PSBT_OUT_BIP32_DERIVATION = 0x02 +PSBT_OUT_AMOUNT = 0x03 +PSBT_OUT_SCRIPT = 0x04 +PSBT_OUT_TAP_INTERNAL_KEY = 0x05 +PSBT_OUT_TAP_TREE = 0x06 +PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 +PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08 +PSBT_OUT_PROPRIETARY = 0xfc + + +class PSBTMap: + """Class for serializing and deserializing PSBT maps""" + + def __init__(self, map=None): + self.map = map if map is not None else {} + + def deserialize(self, f): + m = {} + while True: + k = deser_string(f) + if len(k) == 0: + break + v = deser_string(f) + if len(k) == 1: + k = k[0] + assert k not in m + m[k] = v + self.map = m + + def serialize(self): + m = b"" + for k,v in self.map.items(): + if isinstance(k, int) and 0 <= k and k <= 255: + k = bytes([k]) + if isinstance(v, list): + assert all(type(elem) is bytes for elem in v) + v = b"".join(v) # simply concatenate the byte-strings w/o size prefixes + m += ser_compact_size(len(k)) + k + m += ser_compact_size(len(v)) + v + m += b"\x00" + return m + +class PSBT: + """Class for serializing and deserializing PSBTs""" + + def __init__(self, *, g=None, i=None, o=None): + self.g = g if g is not None else PSBTMap() + self.i = i if i is not None else [] + self.o = o if o is not None else [] + self.in_count = len(i) if i is not None else None + self.out_count = len(o) if o is not None else None + self.version = None + + def deserialize(self, f): + assert f.read(5) == b"psbt\xff" + self.g = from_binary(PSBTMap, f) + + self.version = 0 + if PSBT_GLOBAL_VERSION in self.g.map: + assert PSBT_GLOBAL_INPUT_COUNT in self.g.map + assert PSBT_GLOBAL_OUTPUT_COUNT in self.g.map + self.version = struct.unpack(" int: + if m is not None: + assert len(m) == 32 + m = bytes([]) if m is None else m + return int.from_bytes( + tagged_hash( + DLEQ_TAG_CHALLENGE, + A.to_bytes_compressed() + + B.to_bytes_compressed() + + C.to_bytes_compressed() + + G.to_bytes_compressed() + + R1.to_bytes_compressed() + + R2.to_bytes_compressed() + + m, + ), + "big", + ) + + +def dleq_generate_proof( + a: int, B: GE, r: bytes, G: GE = G, m: bytes | None = None +) -> bytes | None: + assert len(r) == 32 + if not (0 < a < GE.ORDER): + return None + if B.infinity: + return None + if m is not None: + assert len(m) == 32 + A = a * G + C = a * B + t = xor_bytes(a.to_bytes(32, "big"), tagged_hash(DLEQ_TAG_AUX, r)) + m_prime = bytes([]) if m is None else m + rand = tagged_hash( + DLEQ_TAG_NONCE, t + A.to_bytes_compressed() + C.to_bytes_compressed() + m_prime + ) + k = int.from_bytes(rand, "big") % GE.ORDER + if k == 0: + return None + R1 = k * G + R2 = k * B + e = dleq_challenge(A, B, C, R1, R2, m, G) + s = (k + e * a) % GE.ORDER + proof = e.to_bytes(32, "big") + s.to_bytes(32, "big") + if not dleq_verify_proof(A, B, C, proof, G=G, m=m): + return None + return proof + + +def dleq_verify_proof( + A: GE, B: GE, C: GE, proof: bytes, G: GE = G, m: bytes | None = None +) -> bool: + if A.infinity or B.infinity or C.infinity or G.infinity: + return False + assert len(proof) == 64 + e = int.from_bytes(proof[:32], "big") + s = int.from_bytes(proof[32:], "big") + if s >= GE.ORDER: + return False + R1 = s * G - e * A + if R1.infinity: + return False + R2 = s * B - e * C + if R2.infinity: + return False + if e != dleq_challenge(A, B, C, R1, R2, m, G): + return False + return True diff --git a/bip-0375/deps/secp256k1lab/.github/workflows/main.yml b/bip-0375/deps/secp256k1lab/.github/workflows/main.yml new file mode 100644 index 0000000000..4950b96550 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/.github/workflows/main.yml @@ -0,0 +1,17 @@ +name: Tests +on: [push, pull_request] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v5 + - run: uvx ruff check . + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v5 + - run: uvx mypy . diff --git a/bip-0375/deps/secp256k1lab/.python-version b/bip-0375/deps/secp256k1lab/.python-version new file mode 100644 index 0000000000..bd28b9c5c2 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/.python-version @@ -0,0 +1 @@ +3.9 diff --git a/bip-0375/deps/secp256k1lab/CHANGELOG.md b/bip-0375/deps/secp256k1lab/CHANGELOG.md new file mode 100644 index 0000000000..15779717c4 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2025-03-31 + +Initial release. diff --git a/bip-0375/deps/secp256k1lab/COPYING b/bip-0375/deps/secp256k1lab/COPYING new file mode 100644 index 0000000000..e8f2163641 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/COPYING @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2009-2024 The Bitcoin Core developers +Copyright (c) 2009-2024 Bitcoin Developers +Copyright (c) 2025- The secp256k1lab Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bip-0375/deps/secp256k1lab/README.md b/bip-0375/deps/secp256k1lab/README.md new file mode 100644 index 0000000000..dbc9dbd04c --- /dev/null +++ b/bip-0375/deps/secp256k1lab/README.md @@ -0,0 +1,13 @@ +secp256k1lab +============ + +![Dependencies: None](https://img.shields.io/badge/dependencies-none-success) + +An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes written in Python, intended for prototyping, experimentation and education. + +Features: +* Low-level secp256k1 field and group arithmetic. +* Schnorr signing/verification and key generation according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). +* ECDH key exchange. + +WARNING: The code in this library is slow and trivially vulnerable to side channel attacks. diff --git a/bip-0375/deps/secp256k1lab/pyproject.toml b/bip-0375/deps/secp256k1lab/pyproject.toml new file mode 100644 index 0000000000..a0bdd19f42 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/pyproject.toml @@ -0,0 +1,34 @@ +[project] +name = "secp256k1lab" +version = "1.0.0" +description = "An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes, intended for prototyping, experimentation and education" +readme = "README.md" +authors = [ + { name = "Pieter Wuille", email = "pieter@wuille.net" }, + { name = "Tim Ruffing", email = "me@real-or-random.org" }, + { name = "Jonas Nick", email = "jonasd.nick@gmail.com" }, + { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" } +] +maintainers = [ + { name = "Tim Ruffing", email = "me@real-or-random.org" }, + { name = "Jonas Nick", email = "jonasd.nick@gmail.com" }, + { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" } +] +requires-python = ">=3.9" +license = "MIT" +license-files = ["COPYING"] +keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Topic :: Security :: Cryptography", +] +dependencies = [] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/bip-0375/deps/secp256k1lab/src/secp256k1lab/__init__.py b/bip-0375/deps/secp256k1lab/src/secp256k1lab/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bip-0375/deps/secp256k1lab/src/secp256k1lab/bip340.py b/bip-0375/deps/secp256k1lab/src/secp256k1lab/bip340.py new file mode 100644 index 0000000000..ba839d16e1 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/src/secp256k1lab/bip340.py @@ -0,0 +1,73 @@ +# The following functions are based on the BIP 340 reference implementation: +# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py + +from .secp256k1 import FE, GE, G +from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash + + +def pubkey_gen(seckey: bytes) -> bytes: + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= GE.ORDER - 1): + raise ValueError("The secret key must be an integer in the range 1..n-1.") + P = d0 * G + assert not P.infinity + return P.to_bytes_xonly() + + +def schnorr_sign( + msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340" +) -> bytes: + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= GE.ORDER - 1): + raise ValueError("The secret key must be an integer in the range 1..n-1.") + if len(aux_rand) != 32: + raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand)) + P = d0 * G + assert not P.infinity + d = d0 if P.has_even_y() else GE.ORDER - d0 + t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand)) + k0 = ( + int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg)) + % GE.ORDER + ) + if k0 == 0: + raise RuntimeError("Failure. This happens only with negligible probability.") + R = k0 * G + assert not R.infinity + k = k0 if R.has_even_y() else GE.ORDER - k0 + e = ( + int_from_bytes( + tagged_hash( + tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg + ) + ) + % GE.ORDER + ) + sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER) + assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix) + return sig + + +def schnorr_verify( + msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340" +) -> bool: + if len(pubkey) != 32: + raise ValueError("The public key must be a 32-byte array.") + if len(sig) != 64: + raise ValueError("The signature must be a 64-byte array.") + try: + P = GE.from_bytes_xonly(pubkey) + except ValueError: + return False + r = int_from_bytes(sig[0:32]) + s = int_from_bytes(sig[32:64]) + if (r >= FE.SIZE) or (s >= GE.ORDER): + return False + e = ( + int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg)) + % GE.ORDER + ) + R = s * G - e * P + if R.infinity or (not R.has_even_y()) or (R.x != r): + return False + return True diff --git a/bip-0375/deps/secp256k1lab/src/secp256k1lab/ecdh.py b/bip-0375/deps/secp256k1lab/src/secp256k1lab/ecdh.py new file mode 100644 index 0000000000..73f47fa1a7 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/src/secp256k1lab/ecdh.py @@ -0,0 +1,16 @@ +import hashlib + +from .secp256k1 import GE, Scalar + + +def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE: + """TODO""" + shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey) + assert not shared_secret.infinity # prime-order group + return shared_secret + + +def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes: + """TODO""" + shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey) + return hashlib.sha256(shared_secret.to_bytes_compressed()).digest() diff --git a/bip-0375/deps/secp256k1lab/src/secp256k1lab/keys.py b/bip-0375/deps/secp256k1lab/src/secp256k1lab/keys.py new file mode 100644 index 0000000000..3e28897e99 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/src/secp256k1lab/keys.py @@ -0,0 +1,15 @@ +from .secp256k1 import GE, G +from .util import int_from_bytes + +# The following function is based on the BIP 327 reference implementation +# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py + + +# Return the plain public key corresponding to a given secret key +def pubkey_gen_plain(seckey: bytes) -> bytes: + d0 = int_from_bytes(seckey) + if not (1 <= d0 <= GE.ORDER - 1): + raise ValueError("The secret key must be an integer in the range 1..n-1.") + P = d0 * G + assert not P.infinity + return P.to_bytes_compressed() diff --git a/bip-0375/deps/secp256k1lab/src/secp256k1lab/py.typed b/bip-0375/deps/secp256k1lab/src/secp256k1lab/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bip-0375/deps/secp256k1lab/src/secp256k1lab/secp256k1.py b/bip-0375/deps/secp256k1lab/src/secp256k1lab/secp256k1.py new file mode 100644 index 0000000000..6e262bf51e --- /dev/null +++ b/bip-0375/deps/secp256k1lab/src/secp256k1lab/secp256k1.py @@ -0,0 +1,454 @@ +# Copyright (c) 2022-2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only implementation of low-level secp256k1 field and group arithmetic + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. + +Exports: +* FE: class for secp256k1 field elements +* GE: class for secp256k1 group elements +* G: the secp256k1 generator point +""" + +# TODO Docstrings of methods still say "field element" +class APrimeFE: + """Objects of this class represent elements of a prime field. + + They are represented internally in numerator / denominator form, in order to delay inversions. + """ + + # The size of the field (also its modulus and characteristic). + SIZE: int + + def __init__(self, a=0, b=1): + """Initialize a field element a/b; both a and b can be ints or field elements.""" + if isinstance(a, type(self)): + num = a._num + den = a._den + else: + num = a % self.SIZE + den = 1 + if isinstance(b, type(self)): + den = (den * b._num) % self.SIZE + num = (num * b._den) % self.SIZE + else: + den = (den * b) % self.SIZE + assert den != 0 + if num == 0: + den = 1 + self._num = num + self._den = den + + def __add__(self, a): + """Compute the sum of two field elements (second may be int).""" + if isinstance(a, type(self)): + return type(self)(self._num * a._den + self._den * a._num, self._den * a._den) + if isinstance(a, int): + return type(self)(self._num + self._den * a, self._den) + return NotImplemented + + def __radd__(self, a): + """Compute the sum of an integer and a field element.""" + return type(self)(a) + self + + @classmethod + # REVIEW This should be + # def sum(cls, *es: Iterable[Self]) -> Self: + # but Self needs the typing_extension package on Python <= 3.12. + def sum(cls, *es): + """Compute the sum of field elements. + + sum(a, b, c, ...) is identical to (0 + a + b + c + ...).""" + return sum(es, start=cls(0)) + + def __sub__(self, a): + """Compute the difference of two field elements (second may be int).""" + if isinstance(a, type(self)): + return type(self)(self._num * a._den - self._den * a._num, self._den * a._den) + if isinstance(a, int): + return type(self)(self._num - self._den * a, self._den) + return NotImplemented + + def __rsub__(self, a): + """Compute the difference of an integer and a field element.""" + return type(self)(a) - self + + def __mul__(self, a): + """Compute the product of two field elements (second may be int).""" + if isinstance(a, type(self)): + return type(self)(self._num * a._num, self._den * a._den) + if isinstance(a, int): + return type(self)(self._num * a, self._den) + return NotImplemented + + def __rmul__(self, a): + """Compute the product of an integer with a field element.""" + return type(self)(a) * self + + def __truediv__(self, a): + """Compute the ratio of two field elements (second may be int).""" + if isinstance(a, type(self)) or isinstance(a, int): + return type(self)(self, a) + return NotImplemented + + def __pow__(self, a): + """Raise a field element to an integer power.""" + return type(self)(pow(self._num, a, self.SIZE), pow(self._den, a, self.SIZE)) + + def __neg__(self): + """Negate a field element.""" + return type(self)(-self._num, self._den) + + def __int__(self): + """Convert a field element to an integer in range 0..SIZE-1. The result is cached.""" + if self._den != 1: + self._num = (self._num * pow(self._den, -1, self.SIZE)) % self.SIZE + self._den = 1 + return self._num + + def sqrt(self): + """Compute the square root of a field element if it exists (None otherwise).""" + raise NotImplementedError + + def is_square(self): + """Determine if this field element has a square root.""" + # A more efficient algorithm is possible here (Jacobi symbol). + return self.sqrt() is not None + + def is_even(self): + """Determine whether this field element, represented as integer in 0..SIZE-1, is even.""" + return int(self) & 1 == 0 + + def __eq__(self, a): + """Check whether two field elements are equal (second may be an int).""" + if isinstance(a, type(self)): + return (self._num * a._den - self._den * a._num) % self.SIZE == 0 + return (self._num - self._den * a) % self.SIZE == 0 + + def to_bytes(self): + """Convert a field element to a 32-byte array (BE byte order).""" + return int(self).to_bytes(32, 'big') + + @classmethod + def from_int_checked(cls, v): + """Convert an integer to a field element (no overflow allowed).""" + if v >= cls.SIZE: + raise ValueError + return cls(v) + + @classmethod + def from_int_wrapping(cls, v): + """Convert an integer to a field element (reduced modulo SIZE).""" + return cls(v % cls.SIZE) + + @classmethod + def from_bytes_checked(cls, b): + """Convert a 32-byte array to a field element (BE byte order, no overflow allowed).""" + v = int.from_bytes(b, 'big') + return cls.from_int_checked(v) + + @classmethod + def from_bytes_wrapping(cls, b): + """Convert a 32-byte array to a field element (BE byte order, reduced modulo SIZE).""" + v = int.from_bytes(b, 'big') + return cls.from_int_wrapping(v) + + def __str__(self): + """Convert this field element to a 64 character hex string.""" + return f"{int(self):064x}" + + def __repr__(self): + """Get a string representation of this field element.""" + return f"{type(self).__qualname__}(0x{int(self):x})" + + +class FE(APrimeFE): + SIZE = 2**256 - 2**32 - 977 + + def sqrt(self): + # Due to the fact that our modulus p is of the form (p % 4) == 3, the Tonelli-Shanks + # algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply + # raising the argument to the power (p + 1) / 4. + + # To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group, + # and thus only half of the non-zero field elements are squares. An element a is + # a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're + # looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent + # to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to + # x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p. + v = int(self) + s = pow(v, (self.SIZE + 1) // 4, self.SIZE) + if s**2 % self.SIZE == v: + return type(self)(s) + return None + + +class Scalar(APrimeFE): + """TODO Docstring""" + SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + + +class GE: + """Objects of this class represent secp256k1 group elements (curve points or infinity) + + GE objects are immutable. + + Normal points on the curve have fields: + * x: the x coordinate (a field element) + * y: the y coordinate (a field element, satisfying y^2 = x^3 + 7) + * infinity: False + + The point at infinity has field: + * infinity: True + """ + + # TODO The following two class attributes should probably be just getters as + # classmethods to enforce immutability. Unfortunately Python makes it hard + # to create "classproperties". `G` could then also be just a classmethod. + + # Order of the group (number of points on the curve, plus 1 for infinity) + ORDER = Scalar.SIZE + + # Number of valid distinct x coordinates on the curve. + ORDER_HALF = ORDER // 2 + + @property + def infinity(self): + """Whether the group element is the point at infinity.""" + return self._infinity + + @property + def x(self): + """The x coordinate (a field element) of a non-infinite group element.""" + assert not self.infinity + return self._x + + @property + def y(self): + """The y coordinate (a field element) of a non-infinite group element.""" + assert not self.infinity + return self._y + + def __init__(self, x=None, y=None): + """Initialize a group element with specified x and y coordinates, or infinity.""" + if x is None: + # Initialize as infinity. + assert y is None + self._infinity = True + else: + # Initialize as point on the curve (and check that it is). + fx = FE(x) + fy = FE(y) + assert fy**2 == fx**3 + 7 + self._infinity = False + self._x = fx + self._y = fy + + def __add__(self, a): + """Add two group elements together.""" + # Deal with infinity: a + infinity == infinity + a == a. + if self.infinity: + return a + if a.infinity: + return self + if self.x == a.x: + if self.y != a.y: + # A point added to its own negation is infinity. + assert self.y + a.y == 0 + return GE() + else: + # For identical inputs, use the tangent (doubling formula). + lam = (3 * self.x**2) / (2 * self.y) + else: + # For distinct inputs, use the line through both points (adding formula). + lam = (self.y - a.y) / (self.x - a.x) + # Determine point opposite to the intersection of that line with the curve. + x = lam**2 - (self.x + a.x) + y = lam * (self.x - x) - self.y + return GE(x, y) + + @staticmethod + def sum(*ps): + """Compute the sum of group elements. + + GE.sum(a, b, c, ...) is identical to (GE() + a + b + c + ...).""" + return sum(ps, start=GE()) + + @staticmethod + def batch_mul(*aps): + """Compute a (batch) scalar group element multiplication. + + GE.batch_mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3, + but more efficient.""" + # Reduce all the scalars modulo order first (so we can deal with negatives etc). + naps = [(int(a), p) for a, p in aps] + # Start with point at infinity. + r = GE() + # Iterate over all bit positions, from high to low. + for i in range(255, -1, -1): + # Double what we have so far. + r = r + r + # Add then add the points for which the corresponding scalar bit is set. + for (a, p) in naps: + if (a >> i) & 1: + r += p + return r + + def __rmul__(self, a): + """Multiply an integer with a group element.""" + if self == G: + return FAST_G.mul(Scalar(a)) + return GE.batch_mul((Scalar(a), self)) + + def __neg__(self): + """Compute the negation of a group element.""" + if self.infinity: + return self + return GE(self.x, -self.y) + + def __sub__(self, a): + """Subtract a group element from another.""" + return self + (-a) + + def __eq__(self, a): + """Check if two group elements are equal.""" + return (self - a).infinity + + def has_even_y(self): + """Determine whether a non-infinity group element has an even y coordinate.""" + assert not self.infinity + return self.y.is_even() + + def to_bytes_compressed(self): + """Convert a non-infinite group element to 33-byte compressed encoding.""" + assert not self.infinity + return bytes([3 - self.y.is_even()]) + self.x.to_bytes() + + def to_bytes_compressed_with_infinity(self): + """Convert a group element to 33-byte compressed encoding, mapping infinity to zeros.""" + if self.infinity: + return 33 * b"\x00" + return self.to_bytes_compressed() + + def to_bytes_uncompressed(self): + """Convert a non-infinite group element to 65-byte uncompressed encoding.""" + assert not self.infinity + return b'\x04' + self.x.to_bytes() + self.y.to_bytes() + + def to_bytes_xonly(self): + """Convert (the x coordinate of) a non-infinite group element to 32-byte xonly encoding.""" + assert not self.infinity + return self.x.to_bytes() + + @staticmethod + def lift_x(x): + """Return group element with specified field element as x coordinate (and even y).""" + y = (FE(x)**3 + 7).sqrt() + if y is None: + raise ValueError + if not y.is_even(): + y = -y + return GE(x, y) + + @staticmethod + def from_bytes_compressed(b): + """Convert a compressed to a group element.""" + assert len(b) == 33 + if b[0] != 2 and b[0] != 3: + raise ValueError + x = FE.from_bytes_checked(b[1:]) + r = GE.lift_x(x) + if b[0] == 3: + r = -r + return r + + @staticmethod + def from_bytes_uncompressed(b): + """Convert an uncompressed to a group element.""" + assert len(b) == 65 + if b[0] != 4: + raise ValueError + x = FE.from_bytes_checked(b[1:33]) + y = FE.from_bytes_checked(b[33:]) + if y**2 != x**3 + 7: + raise ValueError + return GE(x, y) + + @staticmethod + def from_bytes(b): + """Convert a compressed or uncompressed encoding to a group element.""" + assert len(b) in (33, 65) + if len(b) == 33: + return GE.from_bytes_compressed(b) + else: + return GE.from_bytes_uncompressed(b) + + @staticmethod + def from_bytes_xonly(b): + """Convert a point given in xonly encoding to a group element.""" + assert len(b) == 32 + x = FE.from_bytes_checked(b) + r = GE.lift_x(x) + return r + + @staticmethod + def is_valid_x(x): + """Determine whether the provided field element is a valid X coordinate.""" + return (FE(x)**3 + 7).is_square() + + def __str__(self): + """Convert this group element to a string.""" + if self.infinity: + return "(inf)" + return f"({self.x},{self.y})" + + def __repr__(self): + """Get a string representation for this group element.""" + if self.infinity: + return "GE()" + return f"GE(0x{int(self.x):x},0x{int(self.y):x})" + + def __hash__(self): + """Compute a non-cryptographic hash of the group element.""" + if self.infinity: + return 0 # 0 is not a valid x coordinate + return int(self.x) + + +# The secp256k1 generator point +G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) + + +class FastGEMul: + """Table for fast multiplication with a constant group element. + + Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with + its powers of 2: + + table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P] + + During multiplication, the points corresponding to each bit set in the scalar are added up, + i.e. on average ~128 point additions take place. + """ + + def __init__(self, p): + self.table = [p] # table[i] = (2^i) * p + for _ in range(255): + p = p + p + self.table.append(p) + + def mul(self, a): + result = GE() + a = int(a) + for bit in range(a.bit_length()): + if a & (1 << bit): + result += self.table[bit] + return result + +# Precomputed table with multiples of G for fast multiplication +FAST_G = FastGEMul(G) diff --git a/bip-0375/deps/secp256k1lab/src/secp256k1lab/util.py b/bip-0375/deps/secp256k1lab/src/secp256k1lab/util.py new file mode 100644 index 0000000000..d8c744b795 --- /dev/null +++ b/bip-0375/deps/secp256k1lab/src/secp256k1lab/util.py @@ -0,0 +1,24 @@ +import hashlib + + +# This implementation can be sped up by storing the midstate after hashing +# tag_hash instead of rehashing it all the time. +def tagged_hash(tag: str, msg: bytes) -> bytes: + tag_hash = hashlib.sha256(tag.encode()).digest() + return hashlib.sha256(tag_hash + tag_hash + msg).digest() + + +def bytes_from_int(x: int) -> bytes: + return x.to_bytes(32, byteorder="big") + + +def xor_bytes(b0: bytes, b1: bytes) -> bytes: + return bytes(x ^ y for (x, y) in zip(b0, b1)) + + +def int_from_bytes(b: bytes) -> int: + return int.from_bytes(b, byteorder="big") + + +def hash_sha256(b: bytes) -> bytes: + return hashlib.sha256(b).digest() diff --git a/bip-0375/test_runner.py b/bip-0375/test_runner.py new file mode 100644 index 0000000000..c4fb03ac0d --- /dev/null +++ b/bip-0375/test_runner.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Process test vectors JSON file and run validation checks""" + +import argparse +import json +from pathlib import Path +import sys +from typing import Tuple + +project_root = Path(__file__).parent +deps_dir = project_root / "deps" +secp256k1lab_dir = deps_dir / "secp256k1lab" / "src" +for path in [str(deps_dir), str(secp256k1lab_dir)]: + if path not in sys.path: + sys.path.insert(0, path) + +from validator.psbt_bip375 import BIP375PSBT +from validator.validate_psbt import ( + validate_psbt_structure, + validate_ecdh_coverage, + validate_input_eligibility, + validate_output_scripts, +) + +CHECK_FUNCTIONS = { + "psbt_structure": validate_psbt_structure, + "ecdh_coverage": validate_ecdh_coverage, + "input_eligibility": validate_input_eligibility, + "output_scripts": validate_output_scripts, +} + + +def validate_bip375_psbt( + psbt_data: str, checks: list[str], debug: bool = False +) -> Tuple[bool, str]: + """Performs sequential validation of a PSBT against BIP-375 rules""" + psbt = BIP375PSBT.from_base64(psbt_data) + + if checks is None: + checks = [ + "psbt_structure", + "ecdh_coverage", + "input_eligibility", + "output_scripts", + ] + + for check_name in checks: + if check_name not in CHECK_FUNCTIONS: + return False, f"Unknown check: {check_name}" + + check_fn = CHECK_FUNCTIONS[check_name] + + is_valid, msg = check_fn(psbt) + if debug: + msg = f"{check_name.upper()}: {msg}" if msg else msg + + if not is_valid: + return False, msg + + return True, "All checks passed" + + +def load_test_vectors(filename: str) -> dict: + """Load test vectors from JSON file""" + try: + with open(filename, "r") as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: Test vector file '{filename}' not found") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in test vector file: {e}") + sys.exit(1) + + +def run_validation_tests(test_data: dict, verbosity: int = 0) -> tuple[int, int]: + """Run validation checks for each test vector""" + passed = 0 + failed = 0 + + # Process invalid PSBTs (should fail validation) + invalid_tests = test_data.get("invalid", []) + print(f"Invalid PSBTs: {len(invalid_tests)}") + for test_vector in invalid_tests: + is_valid, result = validate_bip375_psbt( + test_vector["psbt"], test_vector.get("checks"), debug=verbosity >= 2 + ) + print(f"{test_vector['description']}") + if not is_valid: + passed += 1 + if verbosity >= 1: + print(f" {result}") + else: + failed += 1 + if result: + print(f" ERROR: {result}") + + # Process valid PSBTs (should pass validation) + valid_tests = test_data.get("valid", []) + print("") + print(f"Valid PSBTs: {len(valid_tests)}") + for test_vector in valid_tests: + is_valid, result = validate_bip375_psbt( + test_vector["psbt"], test_vector.get("checks"), debug=verbosity >= 2 + ) + + print(f"{test_vector['description']}") + if is_valid: + passed += 1 + if verbosity >= 1: + print(f" {result}") + else: + failed += 1 + if result: + print(f" ERROR: {result}") + + return passed, failed + + +def main(): + parser = argparse.ArgumentParser( + description="Silent Payments PSBT Validator", + ) + parser.add_argument( + "--test-file", + "-f", + default=str(project_root / "bip375_test_vectors.json"), + help="Test vector file to run (default: bip375_test_vectors.json)", + ) + parser.add_argument( + "-v", + dest="verbosity", + action="count", + default=0, + help="Verbosity level: -v shows pass/fail details, -vv enables debug output", + ) + + args = parser.parse_args() + + test_data = load_test_vectors(args.test_file) + + print(f"Description: {test_data.get('description', 'N/A')}") + print(f"Version: {test_data.get('version', 'N/A')}") + print() + + passed, failed = run_validation_tests(test_data, args.verbosity) + + print() + print(f"Summary: {passed} passed, {failed} failed") + + sys.exit(0 if failed == 0 else 1) + + +if __name__ == "__main__": + main() diff --git a/bip-0375/validator/bip352_crypto.py b/bip-0375/validator/bip352_crypto.py new file mode 100644 index 0000000000..552383367f --- /dev/null +++ b/bip-0375/validator/bip352_crypto.py @@ -0,0 +1,51 @@ +""" +Silent payment output script derivation +""" + +from typing import List + +from deps.bitcoin_test.messages import COutPoint +from secp256k1lab.secp256k1 import G, GE, Scalar +from secp256k1lab.ecdh import ecdh_compressed_in_raw_out +from secp256k1lab.util import tagged_hash + + +def compute_silent_payment_output_script( + outpoints: List[COutPoint], + summed_pubkey_bytes: bytes, + ecdh_share_bytes: bytes, + spend_pubkey_bytes: bytes, + k: int, +) -> bytes: + """Compute silent payment output script per BIP-352""" + input_hash_bytes = get_input_hash(outpoints, GE.from_bytes(summed_pubkey_bytes)) + + # Compute shared_secret = input_hash * ecdh_share + shared_secret_bytes = ecdh_compressed_in_raw_out( + input_hash_bytes, ecdh_share_bytes + ).to_bytes_compressed() + + # Compute t_k = hash_BIP0352/SharedSecret(shared_secret || k) + t_k = Scalar.from_bytes_checked( + tagged_hash("BIP0352/SharedSecret", shared_secret_bytes + ser_uint32(k)) + ) + + # Compute P_k = B_spend + t_k * G + B_spend = GE.from_bytes(spend_pubkey_bytes) + P_k = B_spend + t_k * G + + # Return P2TR script (x-only pubkey) + return bytes([0x51, 0x20]) + P_k.to_bytes_xonly() + + +def get_input_hash(outpoints: List[COutPoint], sum_input_pubkeys: GE) -> bytes: + """Compute input hash per BIP-352""" + lowest_outpoint = sorted(outpoints, key=lambda outpoint: outpoint.serialize())[0] + return tagged_hash( + "BIP0352/Inputs", + lowest_outpoint.serialize() + sum_input_pubkeys.to_bytes_compressed(), + ) + + +def ser_uint32(u: int) -> bytes: + return u.to_bytes(4, "big") diff --git a/bip-0375/validator/inputs.py b/bip-0375/validator/inputs.py new file mode 100644 index 0000000000..d8fe380005 --- /dev/null +++ b/bip-0375/validator/inputs.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +""" +PSBT input utility functions +""" + +import struct +from typing import Optional, Tuple + +from deps.bitcoin_test.messages import CTransaction, CTxOut, from_binary +from deps.bitcoin_test.psbt import ( + PSBT, + PSBT_IN_BIP32_DERIVATION, + PSBT_IN_NON_WITNESS_UTXO, + PSBT_IN_OUTPUT_INDEX, + PSBT_IN_REDEEM_SCRIPT, + PSBT_IN_TAP_INTERNAL_KEY, + PSBT_IN_WITNESS_UTXO, +) +from secp256k1lab.secp256k1 import GE + +from .psbt_bip375 import BIP375PSBTMap, PSBT_GLOBAL_SP_ECDH_SHARE, PSBT_IN_SP_ECDH_SHARE + + +def collect_input_ecdh_and_pubkey( + psbt: PSBT, scan_key: bytes +) -> Tuple[Optional[bytes], Optional[bytes]]: + """ + Collect combined ECDH share and summed pubkey for a scan key. + + Checks global ECDH share first, falls back to per-input shares. + Returns (ecdh_share_bytes, summed_pubkey_bytes) or (None, None). + """ + # Check for global ECDH share + summed_pubkey = None + ecdh_share = psbt.g.get_by_key(PSBT_GLOBAL_SP_ECDH_SHARE, scan_key) + if ecdh_share: + summed_pubkey = None + for input_map in psbt.i: + pubkey = pubkey_from_eligible_input(input_map) + if pubkey is not None: + summed_pubkey = ( + pubkey if summed_pubkey is None else summed_pubkey + pubkey + ) + + if summed_pubkey: + return ecdh_share, summed_pubkey.to_bytes_compressed() + + # Check for per-input ECDH shares + combined_ecdh = None + for input_map in psbt.i: + input_ecdh = input_map.get_by_key(PSBT_IN_SP_ECDH_SHARE, scan_key) + if input_ecdh: + ecdh_point = GE.from_bytes(input_ecdh) + combined_ecdh = ( + ecdh_point if combined_ecdh is None else combined_ecdh + ecdh_point + ) + pubkey = pubkey_from_eligible_input(input_map) + if pubkey is not None: + summed_pubkey = ( + pubkey if summed_pubkey is None else summed_pubkey + pubkey + ) + + if combined_ecdh and summed_pubkey: + return combined_ecdh.to_bytes_compressed(), summed_pubkey.to_bytes_compressed() + return None, None + + +def pubkey_from_eligible_input(input_map: BIP375PSBTMap) -> Optional[GE]: + """ + Extract the public key from a PSBT input map if eligible for silent payments + + Returns a GE point (public key), or None if not found + """ + if not is_input_eligible(input_map)[0]: + return None + + # Try BIP32 derivation first (key_data is the pubkey) + derivations = input_map.get_all_by_type(PSBT_IN_BIP32_DERIVATION) + if derivations: + pubkey, _ = derivations[0] + if len(pubkey) == 33: + return GE.from_bytes(pubkey) + + # Try PSBT_IN_WITNESS_UTXO for P2TR inputs + spk = parse_witness_utxo(input_map[PSBT_IN_WITNESS_UTXO]) + if spk and _is_p2tr(spk): + return GE.from_bytes(bytes([0x02]) + spk[2:34]) + return None + + +# ============================================================================ +# scriptPubKey helpers +# ============================================================================ + + +def _script_pubkey_from_psbt_input(input_map: BIP375PSBTMap) -> Optional[bytes]: + """Extract scriptPubKey from PSBT input fields""" + script_pubkey = None + + # Try WITNESS_UTXO first + if PSBT_IN_WITNESS_UTXO in input_map: + script_pubkey = parse_witness_utxo(input_map[PSBT_IN_WITNESS_UTXO]) + + # Try NON_WITNESS_UTXO for legacy inputs + elif PSBT_IN_NON_WITNESS_UTXO in input_map: + non_witness_utxo = input_map[PSBT_IN_NON_WITNESS_UTXO] + # Get the output index from PSBT_IN_OUTPUT_INDEX field + if PSBT_IN_OUTPUT_INDEX in input_map: + output_index_bytes = input_map[PSBT_IN_OUTPUT_INDEX] + if len(output_index_bytes) == 4: + output_index = struct.unpack(" bytes: + """Extract scriptPubKey from witness_utxo""" + utxo = from_binary(CTxOut, witness_utxo) + return utxo.scriptPubKey + + +def _parse_non_witness_utxo(non_witness_utxo: bytes, output_index: int) -> bytes: + """Extract scriptPubKey from non_witness_utxo""" + tx = from_binary(CTransaction, non_witness_utxo) + assert output_index < len(tx.vout), "Invalid output index" + return tx.vout[output_index].scriptPubKey + + +# ============================================================================ +# Input eligibility helpers +# ============================================================================ + + +def is_input_eligible(input_map: BIP375PSBTMap) -> Tuple[bool, str]: + """Check if input is eligible for silent payments""" + script_pubkey = _script_pubkey_from_psbt_input(input_map) + assert script_pubkey is not None, ( + "scriptPubKey could not be extracted from PSBT input" + ) + + if not _has_eligible_script_type(script_pubkey): + return False, "ineligible input type" + + NUMS_H = bytes.fromhex( + "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" + ) + if _is_p2tr(script_pubkey): + tap_internal_key = input_map.get(PSBT_IN_TAP_INTERNAL_KEY) + if tap_internal_key == NUMS_H: + return False, "P2TR uses NUMS point H as internal key" + + if _is_p2sh(script_pubkey): + if PSBT_IN_REDEEM_SCRIPT in input_map: + redeem_script = input_map[PSBT_IN_REDEEM_SCRIPT] + if not _is_p2wpkh(redeem_script): + return False, "P2SH is not P2SH-P2WPKH" + else: + assert False, "P2SH input missing PSBT_IN_REDEEM_SCRIPT" + return True, None + + +def _has_eligible_script_type(script_pubkey: bytes) -> bool: + """True if scriptPubKey is eligible for silent payments""" + return ( + _is_p2pkh(script_pubkey) + or _is_p2wpkh(script_pubkey) + or _is_p2tr(script_pubkey) + or _is_p2sh(script_pubkey) + ) + + +def _is_p2tr(spk: bytes) -> bool: + if len(spk) != 34: + return False + # OP_1 OP_PUSHBYTES_32 <32 bytes> + return (spk[0] == 0x51) & (spk[1] == 0x20) + + +def _is_p2wpkh(spk: bytes) -> bool: + if len(spk) != 22: + return False + # OP_0 OP_PUSHBYTES_20 <20 bytes> + return (spk[0] == 0x00) & (spk[1] == 0x14) + + +def _is_p2sh(spk: bytes) -> bool: + if len(spk) != 23: + return False + # OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUAL + return (spk[0] == 0xA9) & (spk[1] == 0x14) & (spk[-1] == 0x87) + + +def _is_p2pkh(spk: bytes) -> bool: + if len(spk) != 25: + return False + # OP_DUP OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG + return ( + (spk[0] == 0x76) + & (spk[1] == 0xA9) + & (spk[2] == 0x14) + & (spk[-2] == 0x88) + & (spk[-1] == 0xAC) + ) diff --git a/bip-0375/validator/psbt_bip375.py b/bip-0375/validator/psbt_bip375.py new file mode 100644 index 0000000000..daf36e99fa --- /dev/null +++ b/bip-0375/validator/psbt_bip375.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +BIP-375 PSBT map extensions + +BIP375PSBTMap (a PSBTMap subclass with BIP-375 field access helpers) +BIP375PSBT (a PSBT subclass that deserializes into BIP375PSBTMap instances) +""" + +from io import BytesIO +import struct +from typing import List, Optional, Tuple + +from deps.bitcoin_test.messages import CTransaction, deser_compact_size, from_binary +from deps.bitcoin_test.psbt import ( + PSBT, + PSBTMap, + PSBT_GLOBAL_VERSION, + PSBT_GLOBAL_INPUT_COUNT, + PSBT_GLOBAL_OUTPUT_COUNT, + PSBT_GLOBAL_UNSIGNED_TX, +) + +PSBT_GLOBAL_SP_ECDH_SHARE = 0x07 +PSBT_GLOBAL_SP_DLEQ = 0x08 + +PSBT_IN_SP_ECDH_SHARE = 0x1D +PSBT_IN_SP_DLEQ = 0x1E + +PSBT_OUT_SP_V0_INFO = 0x09 +PSBT_OUT_SP_V0_LABEL = 0x0A + + +class BIP375PSBTMap(PSBTMap): + """PSBTMap with BIP-375 field access helpers""" + + def __getitem__(self, key): + return self.map[key] + + def __contains__(self, key): + return key in self.map + + def get(self, key, default=None): + return self.map.get(key, default) + + def get_all_by_type(self, key_type: int) -> List[Tuple[bytes, bytes]]: + """ + Get all entries with the given key_type + + Returns list of (key_data, value_data) tuples. For single-byte keys (no + key_data), key_data is b''. + """ + result = [] + for key, value_data in self.map.items(): + if isinstance(key, int) and key == key_type: + result.append((b"", value_data)) + elif isinstance(key, bytes) and len(key) > 0 and key[0] == key_type: + result.append((key[1:], value_data)) + return result + + def get_by_key(self, key_type: int, key_data: bytes) -> Optional[bytes]: + """Get value_data for a specific key_type + key_data combination""" + if key_data == b"": + return self.map.get(key_type) + return self.map.get(bytes([key_type]) + key_data) + + +class BIP375PSBT(PSBT): + """PSBT that deserializes maps as BIP375PSBTMap instances""" + + def deserialize(self, f): + assert f.read(5) == b"psbt\xff" + self.g = from_binary(BIP375PSBTMap, f) + + self.version = 0 + if PSBT_GLOBAL_VERSION in self.g.map: + assert PSBT_GLOBAL_INPUT_COUNT in self.g.map + assert PSBT_GLOBAL_OUTPUT_COUNT in self.g.map + self.version = struct.unpack(" Tuple[bool, str]: + """ + Validate PSBT structure requirements + + Checks: + - Each output must have PSBT_OUT_SCRIPT or PSBT_OUT_SP_V0_INFO + - PSBT_OUT_SP_V0_LABEL requires PSBT_OUT_SP_V0_INFO + - SP_V0_INFO must be 66 bytes (33-byte scan key + 33-byte spend key) + - ECDH shares must be 33 bytes + - DLEQ proofs must be 64 bytes + - TX_MODIFIABLE is zero when PSBT_OUT_SCRIPT set for SP output + """ + # Check output requirements + for i, output_map in enumerate(psbt.o): + has_script = ( + PSBT_OUT_SCRIPT in output_map and len(output_map[PSBT_OUT_SCRIPT]) > 0 + ) + has_sp_info = PSBT_OUT_SP_V0_INFO in output_map + has_sp_label = PSBT_OUT_SP_V0_LABEL in output_map + + # Output must have script or SP info + if not has_script and not has_sp_info: + return ( + False, + f"Output {i} must have either PSBT_OUT_SCRIPT or PSBT_OUT_SP_V0_INFO", + ) + + # SP label requires SP info + if has_sp_label and not has_sp_info: + return ( + False, + f"Output {i} has PSBT_OUT_SP_V0_LABEL but missing PSBT_OUT_SP_V0_INFO", + ) + + # Validate SP_V0_INFO field length + if has_sp_info: + sp_info = output_map[PSBT_OUT_SP_V0_INFO] + if len(sp_info) != 66: + return ( + False, + f"Output {i} SP_V0_INFO has wrong length ({len(sp_info)} bytes, expected 66)", + ) + + # Validate ECDH share lengths (global and per-input) + global_ecdh_shares = psbt.g.get_all_by_type(PSBT_GLOBAL_SP_ECDH_SHARE) + for _, ecdh_share in global_ecdh_shares: + if len(ecdh_share) != 33: + return ( + False, + f"Global ECDH share has wrong length ({len(ecdh_share)} bytes, expected 33)", + ) + + for i, input_map in enumerate(psbt.i): + input_ecdh_shares = input_map.get_all_by_type(PSBT_IN_SP_ECDH_SHARE) + for _, ecdh_share in input_ecdh_shares: + if len(ecdh_share) != 33: + return ( + False, + f"Input {i} ECDH share has wrong length ({len(ecdh_share)} bytes, expected 33)", + ) + + # Validate DLEQ proof lengths (global and per-input) + global_dleq_proofs = psbt.g.get_all_by_type(PSBT_GLOBAL_SP_DLEQ) + for _, dleq_proof in global_dleq_proofs: + if len(dleq_proof) != 64: + return ( + False, + f"Global DLEQ proof has wrong length ({len(dleq_proof)} bytes, expected 64)", + ) + + for i, input_map in enumerate(psbt.i): + input_dleq_proofs = input_map.get_all_by_type(PSBT_IN_SP_DLEQ) + for _, dleq_proof in input_dleq_proofs: + if len(dleq_proof) != 64: + return ( + False, + f"Input {i} DLEQ proof has wrong length ({len(dleq_proof)} bytes, expected 64)", + ) + + # Check TX_MODIFIABLE flag when PSBT_OUT_SCRIPT is set + for output_map in psbt.o: + if PSBT_OUT_SP_V0_INFO in output_map and PSBT_OUT_SCRIPT in output_map: + if len(output_map.get(PSBT_OUT_SCRIPT, b"")) > 0: + if psbt.g.get(PSBT_GLOBAL_TX_MODIFIABLE) != b"\x00": + return ( + False, + "PSBT_OUT_SCRIPT set for silent payments output but PSBT_GLOBAL_TX_MODIFIABLE not zeroed", + ) + return True, None + + +def validate_ecdh_coverage(psbt: PSBT) -> Tuple[bool, str]: + """ + Validate ECDH share coverage and DLEQ proof correctness + + Checks: + - Verify ECDH share coverage for each scan key associated with SP outputs + - Every ECDH share must have a corresponding DLEQ proof + - If PSBT_OUT_SCRIPT is set, all eligible inputs must have ECDH coverage + - DLEQ proofs must verify correctly + """ + # Collect unique scan keys from SP outputs + scan_keys = set() + for output_map in psbt.o: + if PSBT_OUT_SP_V0_INFO in output_map: + sp_info = output_map[PSBT_OUT_SP_V0_INFO] + scan_keys.add(sp_info[:33]) + + if not scan_keys: + return True, None # No SP outputs, nothing to check + + # For each scan key, verify ECDH share coverage and DLEQ proofs + for scan_key in scan_keys: + has_global_ecdh = psbt.g.get_by_key(PSBT_GLOBAL_SP_ECDH_SHARE, scan_key) + has_input_ecdh = any( + input_map.get_by_key(PSBT_IN_SP_ECDH_SHARE, scan_key) + for input_map in psbt.i + ) + + scan_key_has_computed_output = any( + PSBT_OUT_SP_V0_INFO in om + and om[PSBT_OUT_SP_V0_INFO][:33] == scan_key + and PSBT_OUT_SCRIPT in om + for om in psbt.o + ) + if scan_key_has_computed_output and not has_global_ecdh and not has_input_ecdh: + return False, "Silent payment output present but no ECDH share for scan key" + + # Verify global DLEQ proof if global ECDH present + if has_global_ecdh: + ecdh_share = psbt.g.get_by_key(PSBT_GLOBAL_SP_ECDH_SHARE, scan_key) + dleq_proof = psbt.g.get_by_key(PSBT_GLOBAL_SP_DLEQ, scan_key) + if not dleq_proof: + return False, "Global ECDH share missing DLEQ proof" + + _, summed_pubkey_bytes = collect_input_ecdh_and_pubkey(psbt, scan_key) + assert summed_pubkey_bytes is not None, "No public keys found for inputs" + A_sum = GE.from_bytes(summed_pubkey_bytes) + + valid, msg = validate_dleq_proof(A_sum, scan_key, ecdh_share, dleq_proof) + if not valid: + return False, f"Global DLEQ proof invalid: {msg}" + + # Verify per-input coverage for eligible inputs + if scan_key_has_computed_output and not has_global_ecdh: + for i, input_map in enumerate(psbt.i): + is_eligible, _ = is_input_eligible(input_map) + ecdh_share = input_map.get_by_key(PSBT_IN_SP_ECDH_SHARE, scan_key) + # Disabled this check for now since it is not strictly forbidden by BIP-375 + if not is_eligible: + continue + # if not is_eligible and ecdh_share: + # return ( + # False, + # f"Input {i} has ECDH share but is ineligible for silent payments", + # ) + if is_eligible and not ecdh_share: + return ( + False, + f"Output script set but eligible input {i} missing ECDH share", + ) + if ecdh_share: + # Verify per-input DLEQ proofs + dleq_proof = input_map.get_by_key(PSBT_IN_SP_DLEQ, scan_key) + if not dleq_proof: + return False, f"Input {i} ECDH share missing DLEQ proof" + + # Get input public key A + A = pubkey_from_eligible_input(input_map) + if A is None: + return ( + False, + f"Input {i} missing public key for DLEQ verification", + ) + + valid, msg = validate_dleq_proof( + A, scan_key, ecdh_share, dleq_proof + ) + if not valid: + return False, f"Input {i} DLEQ proof invalid: {msg}" + return True, None + + +def validate_dleq_proof( + A: GE, + scan_key: bytes, + ecdh_share: bytes, + dleq_proof: bytes, +) -> Tuple[bool, str]: + """ + Verify a DLEQ proof for silent payments + + Checks: + - ECDH share and DLEQ proof lengths + - Verify DLEQ proof correctness + """ + if len(ecdh_share) != 33: + return ( + False, + f"Invalid ECDH share length: {len(ecdh_share)} bytes (expected 33)", + ) + + if len(dleq_proof) != 64: + return ( + False, + f"Invalid DLEQ proof length: {len(dleq_proof)} bytes (expected 64)", + ) + + B_scan = GE.from_bytes(scan_key) + C_ecdh = GE.from_bytes(ecdh_share) + + # Verify DLEQ proof using BIP-374 reference + result = dleq_verify_proof(A, B_scan, C_ecdh, dleq_proof) + if not result: + return False, "DLEQ proof verification failed" + return True, None + + +def validate_input_eligibility(psbt: PSBT) -> Tuple[bool, str]: + """ + Validate input eligibility constraints for silent payments + + Checks: + - No segwit v>1 inputs when SP outputs present + - SIGHASH_ALL required when SP outputs present + """ + # Check if SP outputs exist + has_sp_outputs = any(PSBT_OUT_SP_V0_INFO in om for om in psbt.o) + if not has_sp_outputs: + return True, None + + # Check segwit version restrictions + for i, input_map in enumerate(psbt.i): + if PSBT_IN_WITNESS_UTXO in input_map: + witness_utxo = input_map[PSBT_IN_WITNESS_UTXO] + script = parse_witness_utxo(witness_utxo) + if script and 0x51 < script[0] <= 0x60: # OP_2 or higher (segwit v2+) + return False, f"Input {i} uses segwit version > 1 with silent payments" + + # Check SIGHASH_ALL requirement - PSBT_IN_SIGHASH_TYPE is optional, but if set it must be SIGHASH_ALL when SP outputs are present + for i, input_map in enumerate(psbt.i): + if PSBT_IN_SIGHASH_TYPE in input_map: + sighash = input_map[PSBT_IN_SIGHASH_TYPE] + if len(sighash) >= 4: + sighash_type = struct.unpack(" Tuple[bool, str]: + """ + Validate computed output scripts match silent payment derivation + + Checks: + - For each SP output with PSBT_OUT_SCRIPT set, recomputes the expected P2TR + script from the ECDH share and input public keys and verifies it matches + - k values are tracked per scan key and incremented for each SP output sharing + the same scan key (outputs with different scan keys use independent k counters) + """ + # Build outpoints list + outpoints = [] + for input_map in psbt.i: + if PSBT_IN_PREVIOUS_TXID in input_map and PSBT_IN_OUTPUT_INDEX in input_map: + output_index_bytes = input_map.get(PSBT_IN_OUTPUT_INDEX) + txid_int = int.from_bytes(input_map[PSBT_IN_PREVIOUS_TXID], "little") + output_index = struct.unpack("