mirror of
https://github.com/bitcoin/bips.git
synced 2025-02-22 23:08:01 +01:00
Merge branch 'master' into bip78-mixed-inputs-ok
This commit is contained in:
commit
f1ad9188b4
138 changed files with 9872 additions and 809 deletions
9
.github/workflows/github-action-checks.yml
vendored
9
.github/workflows/github-action-checks.yml
vendored
|
@ -20,3 +20,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- run: scripts/diffcheck.sh
|
- run: scripts/diffcheck.sh
|
||||||
|
Typo-Checks:
|
||||||
|
name: "Typo Checks"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Actions Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check spelling
|
||||||
|
uses: crate-ci/typos@master
|
||||||
|
|
44
.typos.toml
Normal file
44
.typos.toml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
[default]
|
||||||
|
extend-ignore-re = [
|
||||||
|
# NOTE: use here for regex patterns
|
||||||
|
"xpub.*",
|
||||||
|
"xprv.*",
|
||||||
|
"3.*", # address
|
||||||
|
"5.*", # address
|
||||||
|
"private_key .*",
|
||||||
|
"privkey .*",
|
||||||
|
"tt.*", # <tt> tags
|
||||||
|
"code.*", # <code> tags
|
||||||
|
"\\w*<sub>", # prefix for <sub> tags
|
||||||
|
"OP_SUCCESSx|\\d+",
|
||||||
|
"pay.*",
|
||||||
|
"ser.*",
|
||||||
|
"prefix.*",
|
||||||
|
"value: .*",
|
||||||
|
]
|
||||||
|
|
||||||
|
[default.extend-words]
|
||||||
|
# NOTE: use here for false-positives
|
||||||
|
anc = "anc"
|
||||||
|
PSBT = "PSBT"
|
||||||
|
ser = "ser"
|
||||||
|
# Names
|
||||||
|
Atack = "Atack"
|
||||||
|
Meni = "Meni"
|
||||||
|
Ono = "Ono"
|
||||||
|
|
||||||
|
[files]
|
||||||
|
extend-exclude = [
|
||||||
|
"/*/*.csv",
|
||||||
|
"/*.d*",
|
||||||
|
"/*/*.d*",
|
||||||
|
"/*/*.go",
|
||||||
|
"/*/*.json",
|
||||||
|
"/*/*/*.json",
|
||||||
|
"/*/*.mod",
|
||||||
|
"/*/*.proto",
|
||||||
|
"/*/*.py",
|
||||||
|
"scripts",
|
||||||
|
"/*/*.s*",
|
||||||
|
"/*/*.t*",
|
||||||
|
]
|
12
CONTRIBUTING.md
Normal file
12
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
Apart from following [BIP 2](./bip-0002.mediawiki),
|
||||||
|
we do CI checks to ensure that the proposed BIPs do not have common typos.
|
||||||
|
These checks are done using [`typos`](https://github.com/crate-ci/typos).
|
||||||
|
To check for typos locally,
|
||||||
|
install [`typos`](https://github.com/crate-ci/typos)
|
||||||
|
and then run in the root directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
typos
|
||||||
|
```
|
195
README.mediawiki
195
README.mediawiki
|
@ -202,13 +202,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Mike Caldwell, Aaron Voisine
|
| Mike Caldwell, Aaron Voisine
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Draft
|
||||||
|- style="background-color: #ffffcf"
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0039.mediawiki|39]]
|
| [[bip-0039.mediawiki|39]]
|
||||||
| Applications
|
| Applications
|
||||||
| Mnemonic code for generating deterministic keys
|
| Mnemonic code for generating deterministic keys
|
||||||
| Marek Palatinus, Pavol Rusnak, Aaron Voisine, Sean Bowe
|
| Marek Palatinus, Pavol Rusnak, Aaron Voisine, Sean Bowe
|
||||||
| Standard
|
| Standard
|
||||||
| Proposed
|
| Final
|
||||||
|-
|
|-
|
||||||
| 40
|
| 40
|
||||||
| API/RPC
|
| API/RPC
|
||||||
|
@ -251,6 +251,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Manuel Araoz, Ryan X. Charles, Matias Alejo Garcia
|
| Manuel Araoz, Ryan X. Charles, Matias Alejo Garcia
|
||||||
| Standard
|
| Standard
|
||||||
| Proposed
|
| Proposed
|
||||||
|
|-
|
||||||
|
| [[bip-0046.mediawiki|46]]
|
||||||
|
| Applications
|
||||||
|
| Address Scheme for Timelocked Fidelity Bonds
|
||||||
|
| Chris Belcher, Thebora Kompanioni
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|- style="background-color: #cfffcf"
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0047.mediawiki|47]]
|
| [[bip-0047.mediawiki|47]]
|
||||||
| Applications
|
| Applications
|
||||||
|
@ -441,20 +448,20 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Pavol Rusnak
|
| Pavol Rusnak
|
||||||
| Standard
|
| Standard
|
||||||
| Final
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0085.mediawiki|85]]
|
| [[bip-0085.mediawiki|85]]
|
||||||
| Applications
|
| Applications
|
||||||
| Deterministic Entropy From BIP32 Keychains
|
| Deterministic Entropy From BIP32 Keychains
|
||||||
| Ethan Kosakovsky
|
| Ethan Kosakovsky, Aneesh Karve
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0086.mediawiki|86]]
|
| [[bip-0086.mediawiki|86]]
|
||||||
| Applications
|
| Applications
|
||||||
| Key Derivation for Single Key P2TR Outputs
|
| Key Derivation for Single Key P2TR Outputs
|
||||||
| Ava Chow
|
| Ava Chow
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Final
|
||||||
|- style="background-color: #ffffcf"
|
|- style="background-color: #ffffcf"
|
||||||
| [[bip-0087.mediawiki|87]]
|
| [[bip-0087.mediawiki|87]]
|
||||||
| Applications
|
| Applications
|
||||||
|
@ -491,6 +498,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Draft
|
||||||
|-
|
|-
|
||||||
|
| [[bip-0094.mediawiki|94]]
|
||||||
|
| Applications
|
||||||
|
| Testnet 4
|
||||||
|
| Fabian Jahr
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
| [[bip-0098.mediawiki|98]]
|
| [[bip-0098.mediawiki|98]]
|
||||||
| Consensus (soft fork)
|
| Consensus (soft fork)
|
||||||
| Fast Merkle Trees
|
| Fast Merkle Trees
|
||||||
|
@ -627,7 +641,7 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| [[bip-0119.mediawiki|119]]
|
| [[bip-0119.mediawiki|119]]
|
||||||
| Consensus (soft fork)
|
| Consensus (soft fork)
|
||||||
| CHECKTEMPLATEVERIFY
|
| CHECKTEMPLATEVERIFY
|
||||||
| Jeremy Rubin, James O'Beirne
|
| Jeremy Rubin
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Draft
|
||||||
|- style="background-color: #ffcfcf"
|
|- style="background-color: #ffcfcf"
|
||||||
|
@ -665,13 +679,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Eric Lombrozo, William Swanson
|
| Eric Lombrozo, William Swanson
|
||||||
| Informational
|
| Informational
|
||||||
| Rejected
|
| Rejected
|
||||||
|- style="background-color: #ffffcf"
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0125.mediawiki|125]]
|
| [[bip-0125.mediawiki|125]]
|
||||||
| Applications
|
| Applications
|
||||||
| Opt-in Full Replace-by-Fee Signaling
|
| Opt-in Full Replace-by-Fee Signaling
|
||||||
| David A. Harding, Peter Todd
|
| David A. Harding, Peter Todd
|
||||||
| Standard
|
| Standard
|
||||||
| Proposed
|
| Final
|
||||||
|-
|
|-
|
||||||
| [[bip-0126.mediawiki|126]]
|
| [[bip-0126.mediawiki|126]]
|
||||||
|
|
|
|
||||||
|
@ -693,13 +707,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Hugo Nguyen, Peter Gray, Marko Bencun, Aaron Chen, Rodolfo Novak
|
| Hugo Nguyen, Peter Gray, Marko Bencun, Aaron Chen, Rodolfo Novak
|
||||||
| Standard
|
| Standard
|
||||||
| Proposed
|
| Proposed
|
||||||
|- style="background-color: #ffffcf"
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0130.mediawiki|130]]
|
| [[bip-0130.mediawiki|130]]
|
||||||
| Peer Services
|
| Peer Services
|
||||||
| sendheaders message
|
| sendheaders message
|
||||||
| Suhas Daftuar
|
| Suhas Daftuar
|
||||||
| Standard
|
| Standard
|
||||||
| Proposed
|
| Final
|
||||||
|- style="background-color: #ffcfcf"
|
|- style="background-color: #ffcfcf"
|
||||||
| [[bip-0131.mediawiki|131]]
|
| [[bip-0131.mediawiki|131]]
|
||||||
| Consensus (hard fork)
|
| Consensus (hard fork)
|
||||||
|
@ -825,7 +839,7 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Peer Authentication
|
| Peer Authentication
|
||||||
| Jonas Schnelli
|
| Jonas Schnelli
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Deferred
|
||||||
|- style="background-color: #ffcfcf"
|
|- style="background-color: #ffcfcf"
|
||||||
| [[bip-0151.mediawiki|151]]
|
| [[bip-0151.mediawiki|151]]
|
||||||
| Peer Services
|
| Peer Services
|
||||||
|
@ -875,13 +889,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Olaoluwa Osuntokun, Alex Akselrod
|
| Olaoluwa Osuntokun, Alex Akselrod
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Draft
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0159.mediawiki|159]]
|
| [[bip-0159.mediawiki|159]]
|
||||||
| Peer Services
|
| Peer Services
|
||||||
| NODE_NETWORK_LIMITED service bit
|
| NODE_NETWORK_LIMITED service bit
|
||||||
| Jonas Schnelli
|
| Jonas Schnelli
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Final
|
||||||
|- style="background-color: #ffcfcf"
|
|- style="background-color: #ffcfcf"
|
||||||
| [[bip-0171.mediawiki|171]]
|
| [[bip-0171.mediawiki|171]]
|
||||||
| Applications
|
| Applications
|
||||||
|
@ -987,13 +1001,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Karl-Johan Alm
|
| Karl-Johan Alm
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Draft
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0324.mediawiki|324]]
|
| [[bip-0324.mediawiki|324]]
|
||||||
| Peer Services
|
| Peer Services
|
||||||
| Version 2 P2P Encrypted Transport Protocol
|
| Version 2 P2P Encrypted Transport Protocol
|
||||||
| Dhruv Mehta, Tim Ruffing, Jonas Schnelli, Pieter Wuille
|
| Dhruv Mehta, Tim Ruffing, Jonas Schnelli, Pieter Wuille
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Final
|
||||||
|- style="background-color: #ffffcf"
|
|- style="background-color: #ffffcf"
|
||||||
| [[bip-0325.mediawiki|325]]
|
| [[bip-0325.mediawiki|325]]
|
||||||
| Applications
|
| Applications
|
||||||
|
@ -1008,12 +1022,19 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Chris Belcher
|
| Chris Belcher
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Draft
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0327.mediawiki|327]]
|
| [[bip-0327.mediawiki|327]]
|
||||||
|
|
|
|
||||||
| MuSig2 for BIP340-compatible Multi-Signatures
|
| MuSig2 for BIP340-compatible Multi-Signatures
|
||||||
| Jonas Nick, Tim Ruffing, Elliott Jin
|
| Jonas Nick, Tim Ruffing, Elliott Jin
|
||||||
| Informational
|
| Informational
|
||||||
|
| Active
|
||||||
|
|-
|
||||||
|
| [[bip-0328.mediawiki|328]]
|
||||||
|
| Applications
|
||||||
|
| Derivation Scheme for MuSig2 Aggregate Keys
|
||||||
|
| Ava Chow
|
||||||
|
| Informational
|
||||||
| Draft
|
| Draft
|
||||||
|-
|
|-
|
||||||
| [[bip-0329.mediawiki|329]]
|
| [[bip-0329.mediawiki|329]]
|
||||||
|
@ -1037,19 +1058,26 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Draft
|
||||||
|-
|
|-
|
||||||
|
| [[bip-0337.mediawiki|337]]
|
||||||
|
| API/RPC
|
||||||
|
| Compressed Transactions
|
||||||
|
| Tom Briar
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|- style="background-color: #ffcfcf"
|
||||||
| [[bip-0338.mediawiki|338]]
|
| [[bip-0338.mediawiki|338]]
|
||||||
| Peer Services
|
| Peer Services
|
||||||
| Disable transaction relay message
|
| Disable transaction relay message
|
||||||
| Suhas Daftuar
|
| Suhas Daftuar
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Withdrawn
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0339.mediawiki|339]]
|
| [[bip-0339.mediawiki|339]]
|
||||||
| Peer Services
|
| Peer Services
|
||||||
| WTXID-based transaction relay
|
| WTXID-based transaction relay
|
||||||
| Suhas Daftuar
|
| Suhas Daftuar
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Final
|
||||||
|- style="background-color: #cfffcf"
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0340.mediawiki|340]]
|
| [[bip-0340.mediawiki|340]]
|
||||||
|
|
|
|
||||||
|
@ -1082,7 +1110,28 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| [[bip-0345.mediawiki|345]]
|
| [[bip-0345.mediawiki|345]]
|
||||||
| Consensus (soft fork)
|
| Consensus (soft fork)
|
||||||
| OP_VAULT
|
| OP_VAULT
|
||||||
| James O'Beirne, Greg Sanders, Anthony Towns
|
| James O'Beirne, Greg Sanders
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0347.mediawiki|347]]
|
||||||
|
| Consensus (soft fork)
|
||||||
|
| OP_CAT in Tapscript
|
||||||
|
| Ethan Heilman, Armin Sabouri
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0348.md|348]]
|
||||||
|
| Consensus (soft fork)
|
||||||
|
| CHECKSIGFROMSTACK
|
||||||
|
| Brandon Black, Jeremy Rubin
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0349.md|349]]
|
||||||
|
| Consensus (soft fork)
|
||||||
|
| OP_INTERNALKEY
|
||||||
|
| Brandon Black, Jeremy Rubin
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Draft
|
||||||
|- style="background-color: #cfffcf"
|
|- style="background-color: #cfffcf"
|
||||||
|
@ -1099,20 +1148,34 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Alfred Hodler, Clark Moody
|
| Alfred Hodler, Clark Moody
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Draft
|
||||||
|
|- style="background-color: #ffffcf"
|
||||||
|
| [[bip-0352.mediawiki|352]]
|
||||||
|
| Applications
|
||||||
|
| Silent Payments
|
||||||
|
| josibake, Ruben Somsen
|
||||||
|
| Standard
|
||||||
|
| Proposed
|
||||||
|-
|
|-
|
||||||
|
| [[bip-0353.mediawiki|353]]
|
||||||
|
| Applications
|
||||||
|
| DNS Payment Instructions
|
||||||
|
| Matt Corallo, Bastien Teinturier
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0370.mediawiki|370]]
|
| [[bip-0370.mediawiki|370]]
|
||||||
| Applications
|
| Applications
|
||||||
| PSBT Version 2
|
| PSBT Version 2
|
||||||
| Ava Chow
|
| Ava Chow
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0371.mediawiki|371]]
|
| [[bip-0371.mediawiki|371]]
|
||||||
| Applications
|
| Applications
|
||||||
| Taproot Fields for PSBT
|
| Taproot Fields for PSBT
|
||||||
| Ava Chow
|
| Ava Chow
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|-
|
||||||
| [[bip-0372.mediawiki|372]]
|
| [[bip-0372.mediawiki|372]]
|
||||||
| Applications
|
| Applications
|
||||||
|
@ -1121,54 +1184,96 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Standard
|
| Standard
|
||||||
| Draft
|
| Draft
|
||||||
|-
|
|-
|
||||||
|
| [[bip-0373.mediawiki|373]]
|
||||||
|
| Applications
|
||||||
|
| MuSig2 PSBT Fields
|
||||||
|
| Ava Chow
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0374.mediawiki|374]]
|
||||||
|
| Applications
|
||||||
|
| Discrete Log Equality Proofs
|
||||||
|
| Andrew Toth, Ruben Somsen, Sebastian Falbesoner
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0375.mediawiki|375]]
|
||||||
|
| Applications
|
||||||
|
| Sending Silent Payments with PSBTs
|
||||||
|
| Andrew Toth, Ava Chow, josibake
|
||||||
|
| Standard
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0379.md|379]]
|
||||||
|
| Applications
|
||||||
|
| Miniscript
|
||||||
|
| Pieter Wuille, Andrew Poelstra, Sanket Kanjalkar, Antoine Poinsot, Ava Chow
|
||||||
|
| Informational
|
||||||
|
| Draft
|
||||||
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0380.mediawiki|380]]
|
| [[bip-0380.mediawiki|380]]
|
||||||
| Applications
|
| Applications
|
||||||
| Output Script Descriptors General Operation
|
| Output Script Descriptors General Operation
|
||||||
| Pieter Wuille, Ava Chow
|
| Pieter Wuille, Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0381.mediawiki|381]]
|
| [[bip-0381.mediawiki|381]]
|
||||||
| Applications
|
| Applications
|
||||||
| Non-Segwit Output Script Descriptors
|
| Non-Segwit Output Script Descriptors
|
||||||
| Pieter Wuille, Ava Chow
|
| Pieter Wuille, Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0382.mediawiki|382]]
|
| [[bip-0382.mediawiki|382]]
|
||||||
| Applications
|
| Applications
|
||||||
| Segwit Output Script Descriptors
|
| Segwit Output Script Descriptors
|
||||||
| Pieter Wuille, Ava Chow
|
| Pieter Wuille, Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0383.mediawiki|383]]
|
| [[bip-0383.mediawiki|383]]
|
||||||
| Applications
|
| Applications
|
||||||
| Multisig Output Script Descriptors
|
| Multisig Output Script Descriptors
|
||||||
| Pieter Wuille, Ava Chow
|
| Pieter Wuille, Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0384.mediawiki|384]]
|
| [[bip-0384.mediawiki|384]]
|
||||||
| Applications
|
| Applications
|
||||||
| combo() Output Script Descriptors
|
| combo() Output Script Descriptors
|
||||||
| Pieter Wuille, Ava Chow
|
| Pieter Wuille, Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0385.mediawiki|385]]
|
| [[bip-0385.mediawiki|385]]
|
||||||
| Applications
|
| Applications
|
||||||
| raw() and addr() Output Script Descriptors
|
| raw() and addr() Output Script Descriptors
|
||||||
| Pieter Wuille, Ava Chow
|
| Pieter Wuille, Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|-
|
|- style="background-color: #cfffcf"
|
||||||
| [[bip-0386.mediawiki|386]]
|
| [[bip-0386.mediawiki|386]]
|
||||||
| Applications
|
| Applications
|
||||||
| tr() Output Script Descriptors
|
| tr() Output Script Descriptors
|
||||||
| Pieter Wuille, Ava Chow
|
| Pieter Wuille, Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Final
|
||||||
|
|- style="background-color: #cfffcf"
|
||||||
|
| [[bip-0387.mediawiki|387]]
|
||||||
|
| Applications
|
||||||
|
| Tapscript Multisig Output Script Descriptors
|
||||||
|
| Pieter Wuille, Ava Chow
|
||||||
|
| Informational
|
||||||
|
| Final
|
||||||
|
|- style="background-color: #ffffcf"
|
||||||
|
| [[bip-0388.mediawiki|388]]
|
||||||
|
| Applications
|
||||||
|
| Wallet Policies for Descriptor Wallets
|
||||||
|
| Salvatore Ingala
|
||||||
|
| Standard
|
||||||
|
| Proposed
|
||||||
|-
|
|-
|
||||||
| [[bip-0389.mediawiki|389]]
|
| [[bip-0389.mediawiki|389]]
|
||||||
| Applications
|
| Applications
|
||||||
|
@ -1176,6 +1281,20 @@ Those proposing changes should consider that ultimately consent may rest with th
|
||||||
| Ava Chow
|
| Ava Chow
|
||||||
| Informational
|
| Informational
|
||||||
| Draft
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0390.mediawiki|390]]
|
||||||
|
| Applications
|
||||||
|
| musig() Descriptor Key Expression
|
||||||
|
| Ava Chow
|
||||||
|
| Informational
|
||||||
|
| Draft
|
||||||
|
|-
|
||||||
|
| [[bip-0431.mediawiki|431]]
|
||||||
|
| Applications
|
||||||
|
| Topology Restrictions for Pinning
|
||||||
|
| Gloria Zhao
|
||||||
|
| Informational
|
||||||
|
| Draft
|
||||||
|}
|
|}
|
||||||
|
|
||||||
<!-- IMPORTANT! See the instructions at the top of this page, do NOT JUST add BIPs here! -->
|
<!-- IMPORTANT! See the instructions at the top of this page, do NOT JUST add BIPs here! -->
|
||||||
|
|
|
@ -362,28 +362,28 @@ In this case, only the acceptable license(s) should be listed in the License and
|
||||||
* BSD-2-Clause: [https://opensource.org/licenses/BSD-2-Clause OSI-approved BSD 2-clause license]
|
* BSD-2-Clause: [https://opensource.org/licenses/BSD-2-Clause OSI-approved BSD 2-clause license]
|
||||||
* BSD-3-Clause: [https://opensource.org/licenses/BSD-3-Clause OSI-approved BSD 3-clause license]
|
* BSD-3-Clause: [https://opensource.org/licenses/BSD-3-Clause OSI-approved BSD 3-clause license]
|
||||||
* CC0-1.0: [https://creativecommons.org/publicdomain/zero/1.0/ Creative Commons CC0 1.0 Universal]
|
* CC0-1.0: [https://creativecommons.org/publicdomain/zero/1.0/ Creative Commons CC0 1.0 Universal]
|
||||||
* GNU-All-Permissive: [http://www.gnu.org/prep/maintain/html_node/License-Notices-for-Other-Files.html GNU All-Permissive License]
|
* GNU-All-Permissive: [https://www.gnu.org/prep/maintain/html_node/License-Notices-for-Other-Files.html GNU All-Permissive License]
|
||||||
|
|
||||||
In addition, it is recommended that literal code included in the BIP be dual-licensed under the same license terms as the project it modifies. For example, literal code intended for Bitcoin Core would ideally be dual-licensed under the MIT license terms as well as one of the above with the rest of the BIP text.
|
In addition, it is recommended that literal code included in the BIP be dual-licensed under the same license terms as the project it modifies. For example, literal code intended for Bitcoin Core would ideally be dual-licensed under the MIT license terms as well as one of the above with the rest of the BIP text.
|
||||||
|
|
||||||
====Not recommended, but acceptable licenses====
|
====Not recommended, but acceptable licenses====
|
||||||
|
|
||||||
* Apache-2.0: [http://www.apache.org/licenses/LICENSE-2.0 Apache License, version 2.0]
|
* Apache-2.0: [https://www.apache.org/licenses/LICENSE-2.0 Apache License, version 2.0]
|
||||||
* BSL-1.0: [http://www.boost.org/LICENSE_1_0.txt Boost Software License, version 1.0]
|
* BSL-1.0: [https://www.boost.org/LICENSE_1_0.txt Boost Software License, version 1.0]
|
||||||
* CC-BY-4.0: [https://creativecommons.org/licenses/by/4.0/ Creative Commons Attribution 4.0 International]
|
* CC-BY-4.0: [https://creativecommons.org/licenses/by/4.0/ Creative Commons Attribution 4.0 International]
|
||||||
* CC-BY-SA-4.0: [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0 International]
|
* CC-BY-SA-4.0: [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0 International]
|
||||||
* MIT: [https://opensource.org/licenses/MIT Expat/MIT/X11 license]
|
* MIT: [https://opensource.org/licenses/MIT Expat/MIT/X11 license]
|
||||||
* AGPL-3.0+: [http://www.gnu.org/licenses/agpl-3.0.en.html GNU Affero General Public License (AGPL), version 3 or newer]
|
* AGPL-3.0+: [https://www.gnu.org/licenses/agpl-3.0.en.html GNU Affero General Public License (AGPL), version 3 or newer]
|
||||||
* FDL-1.3: [http://www.gnu.org/licenses/fdl-1.3.en.html GNU Free Documentation License, version 1.3]
|
* FDL-1.3: [https://www.gnu.org/licenses/fdl-1.3.en.html GNU Free Documentation License, version 1.3]
|
||||||
* GPL-2.0+: [http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html GNU General Public License (GPL), version 2 or newer]
|
* GPL-2.0+: [https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html GNU General Public License (GPL), version 2 or newer]
|
||||||
* LGPL-2.1+: [http://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html GNU Lesser General Public License (LGPL), version 2.1 or newer]
|
* LGPL-2.1+: [https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html GNU Lesser General Public License (LGPL), version 2.1 or newer]
|
||||||
|
|
||||||
====Not acceptable licenses====
|
====Not acceptable licenses====
|
||||||
|
|
||||||
All licenses not explicitly included in the above lists are not acceptable terms for a Bitcoin Improvement Proposal unless a later BIP extends this one to add them.
|
All licenses not explicitly included in the above lists are not acceptable terms for a Bitcoin Improvement Proposal unless a later BIP extends this one to add them.
|
||||||
However, BIPs predating the acceptance of this BIP were allowed under other terms, and should use these abbreviation when no other license is granted:
|
However, BIPs predating the acceptance of this BIP were allowed under other terms, and should use these abbreviation when no other license is granted:
|
||||||
|
|
||||||
* OPL: [http://opencontent.org/openpub/ Open Publication License, version 1.0]
|
* OPL: [https://opencontent.org/openpub/ Open Publication License, version 1.0]
|
||||||
* PD: Released into the public domain
|
* PD: Released into the public domain
|
||||||
|
|
||||||
===Rationale===
|
===Rationale===
|
||||||
|
|
|
@ -36,7 +36,7 @@ Their FirstBits alias becomes:
|
||||||
|
|
||||||
It is enough information to be given the FirstBits alias ''1brmlab''. When someone wishes to make a purchase, without FirstBits, they either have to type out their address laboriously by hand, scan their QR code (which requires a mobile handset that this author does not own) or find their address on the internet to copy and paste into the client to send bitcoins. FirstBits alleviates this impracticality by providing an easy method to make payments.
|
It is enough information to be given the FirstBits alias ''1brmlab''. When someone wishes to make a purchase, without FirstBits, they either have to type out their address laboriously by hand, scan their QR code (which requires a mobile handset that this author does not own) or find their address on the internet to copy and paste into the client to send bitcoins. FirstBits alleviates this impracticality by providing an easy method to make payments.
|
||||||
|
|
||||||
Together with [[vanitygen|Vanitygen (vanity generator)]], it becomes possible to create memorable unique named addresses. Addresses that are meaningful, rather than an odd assemblage of letters and numbers but add context to the destination.
|
Together with Vanitygen (vanity generator), it becomes possible to create memorable unique named addresses. Addresses that are meaningful, rather than an odd assemblage of letters and numbers but add context to the destination.
|
||||||
|
|
||||||
However FirstBits has its own problems. One is that the possible aliases one is able to generate is limited by the available computing power available. It may not be feasible to generate a complete or precise alias that is wanted- only approximates may be possible. It is also computationally resource intensive which means a large expenditure of power for generating unique aliases in the future, and may not scale up to the level of individuals at home or participants with hand-held devices in an environment of ubiquitous computing.
|
However FirstBits has its own problems. One is that the possible aliases one is able to generate is limited by the available computing power available. It may not be feasible to generate a complete or precise alias that is wanted- only approximates may be possible. It is also computationally resource intensive which means a large expenditure of power for generating unique aliases in the future, and may not scale up to the level of individuals at home or participants with hand-held devices in an environment of ubiquitous computing.
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ NameResolutionService::~NameResolutionService()
|
||||||
|
|
||||||
void NameResolutionService::ExplodeHandle(const string& strHandle, string& strNickname, string& strDomain)
|
void NameResolutionService::ExplodeHandle(const string& strHandle, string& strNickname, string& strDomain)
|
||||||
{
|
{
|
||||||
// split address at @ furthrest to the right
|
// split address at @ furthest to the right
|
||||||
size_t nPosAtsym = strHandle.rfind('@');
|
size_t nPosAtsym = strHandle.rfind('@');
|
||||||
strNickname = strHandle.substr(0, nPosAtsym);
|
strNickname = strHandle.substr(0, nPosAtsym);
|
||||||
strDomain = strHandle.substr(nPosAtsym + 1, strHandle.size());
|
strDomain = strHandle.substr(nPosAtsym + 1, strHandle.size());
|
||||||
|
|
|
@ -39,7 +39,7 @@ This proposal is hereby placed in the public domain.
|
||||||
:'' '''User story:''' As a Bitcoin user who uses paper wallets, I would like the ability to add encryption, so that my Bitcoin paper storage can be two factor: something I have plus something I know.''
|
:'' '''User story:''' As a Bitcoin user who uses paper wallets, I would like the ability to add encryption, so that my Bitcoin paper storage can be two factor: something I have plus something I know.''
|
||||||
:'' '''User story:''' As a Bitcoin user who would like to pay a person or a company with a private key, I do not want to worry that any part of the communication path may result in the interception of the key and theft of my funds. I would prefer to offer an encrypted private key, and then follow it up with the password using a different communication channel (e.g. a phone call or SMS).''
|
:'' '''User story:''' As a Bitcoin user who would like to pay a person or a company with a private key, I do not want to worry that any part of the communication path may result in the interception of the key and theft of my funds. I would prefer to offer an encrypted private key, and then follow it up with the password using a different communication channel (e.g. a phone call or SMS).''
|
||||||
:'' '''User story:''' (EC-multiplied keys) As a user of physical bitcoins, I would like a third party to be able to create password-protected Bitcoin private keys for me, without them knowing the password, so I can benefit from the physical bitcoin without the issuer having access to the private key. I would like to be able to choose a password whose minimum length and required format does not preclude me from memorizing it or engraving it on my physical bitcoin, without exposing me to an undue risk of password cracking and/or theft by the manufacturer of the item.''
|
:'' '''User story:''' (EC-multiplied keys) As a user of physical bitcoins, I would like a third party to be able to create password-protected Bitcoin private keys for me, without them knowing the password, so I can benefit from the physical bitcoin without the issuer having access to the private key. I would like to be able to choose a password whose minimum length and required format does not preclude me from memorizing it or engraving it on my physical bitcoin, without exposing me to an undue risk of password cracking and/or theft by the manufacturer of the item.''
|
||||||
:'''''User story:''' (EC-multiplied keys) As a user of paper wallets, I would like the ability to generate a large number of Bitcoin addresses protected by the same password, while enjoying a high degree of security (highly expensive scrypt parameters), but without having to incur the scrypt delay for each address I generate.
|
:'' '''User story:''' (EC-multiplied keys) As a user of paper wallets, I would like the ability to generate a large number of Bitcoin addresses protected by the same password, while enjoying a high degree of security (highly expensive scrypt parameters), but without having to incur the scrypt delay for each address I generate.''
|
||||||
|
|
||||||
==Specification==
|
==Specification==
|
||||||
This proposal makes use of the following functions and definitions:
|
This proposal makes use of the following functions and definitions:
|
||||||
|
@ -47,12 +47,12 @@ This proposal makes use of the following functions and definitions:
|
||||||
*'''AES256Encrypt, AES256Decrypt''': the simple form of the well-known AES block cipher without consideration for initialization vectors or block chaining. Each of these functions takes a 256-bit key and 16 bytes of input, and deterministically yields 16 bytes of output.
|
*'''AES256Encrypt, AES256Decrypt''': the simple form of the well-known AES block cipher without consideration for initialization vectors or block chaining. Each of these functions takes a 256-bit key and 16 bytes of input, and deterministically yields 16 bytes of output.
|
||||||
*'''SHA256''', a well-known hashing algorithm that takes an arbitrary number of bytes as input and deterministically yields a 32-byte hash.
|
*'''SHA256''', a well-known hashing algorithm that takes an arbitrary number of bytes as input and deterministically yields a 32-byte hash.
|
||||||
*'''scrypt''': A well-known key derivation algorithm. It takes the following parameters: (string) password, (string) salt, (int) n, (int) r, (int) p, (int) length, and deterministically yields an array of bytes whose length is equal to the length parameter.
|
*'''scrypt''': A well-known key derivation algorithm. It takes the following parameters: (string) password, (string) salt, (int) n, (int) r, (int) p, (int) length, and deterministically yields an array of bytes whose length is equal to the length parameter.
|
||||||
*'''ECMultiply''': Multiplication of an elliptic curve point by a scalar integer with respect to the [[secp256k1]] elliptic curve.
|
*'''ECMultiply''': Multiplication of an elliptic curve point by a scalar integer with respect to the secp256k1 elliptic curve.
|
||||||
*'''G, N''': Constants defined as part of the [[secp256k1]] elliptic curve. G is an elliptic curve point, and N is a large positive integer.
|
*'''G, N''': Constants defined as part of the secp256k1 elliptic curve. G is an elliptic curve point, and N is a large positive integer.
|
||||||
*'''[[Base58Check]]''': a method for encoding arrays of bytes using 58 alphanumeric characters commonly used in the Bitcoin ecosystem.
|
*'''Base58Check''': a method for encoding arrays of bytes using 58 alphanumeric characters commonly used in the Bitcoin ecosystem.
|
||||||
|
|
||||||
===Prefix===
|
===Prefix===
|
||||||
It is proposed that the resulting Base58Check-encoded string start with a '6'. The number '6' is intended to represent, from the perspective of the user, "a private key that needs something else to be usable" - an umbrella definition that could be understood in the future to include keys participating in multisig transactions, and was chosen with deference to the existing prefix '5' most commonly observed in [[Wallet Import Format]] which denotes an unencrypted private key.
|
It is proposed that the resulting Base58Check-encoded string start with a '6'. The number '6' is intended to represent, from the perspective of the user, "a private key that needs something else to be usable" - an umbrella definition that could be understood in the future to include keys participating in multisig transactions, and was chosen with deference to the existing prefix '5' most commonly observed in Wallet Import Format which denotes an unencrypted private key.
|
||||||
|
|
||||||
It is proposed that the second character ought to give a hint as to what is needed as a second factor, and for an encrypted key requiring a passphrase, the uppercase letter P is proposed.
|
It is proposed that the second character ought to give a hint as to what is needed as a second factor, and for an encrypted key requiring a passphrase, the uppercase letter P is proposed.
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ To recalculate the address:
|
||||||
# Hash the Bitcoin address, and verify that ''addresshash'' from the encrypted private key record matches the hash. If not, report that the passphrase entry was incorrect.
|
# Hash the Bitcoin address, and verify that ''addresshash'' from the encrypted private key record matches the hash. If not, report that the passphrase entry was incorrect.
|
||||||
|
|
||||||
==Backwards compatibility==
|
==Backwards compatibility==
|
||||||
Backwards compatibility is minimally applicable since this is a new standard that at most extends [[Wallet Import Format]]. It is assumed that an entry point for private key data may also accept existing formats of private keys (such as hexadecimal and [[Wallet Import Format]]); this draft uses a key format that cannot be mistaken for any existing one and preserves auto-detection capabilities.
|
Backwards compatibility is minimally applicable since this is a new standard that at most extends Wallet Import Format. It is assumed that an entry point for private key data may also accept existing formats of private keys (such as hexadecimal and Wallet Import Format); this draft uses a key format that cannot be mistaken for any existing one and preserves auto-detection capabilities.
|
||||||
|
|
||||||
==Suggestions for implementers of proposal with alt-chains==
|
==Suggestions for implementers of proposal with alt-chains==
|
||||||
If this proposal is accepted into alt-chains, it is requested that the unused flag bytes not be used for denoting that the key belongs to an alt-chain.
|
If this proposal is accepted into alt-chains, it is requested that the unused flag bytes not be used for denoting that the key belongs to an alt-chain.
|
||||||
|
@ -209,14 +209,10 @@ The preliminary values of 16384, 8, and 8 are hoped to offer the following prope
|
||||||
==Reference implementation==
|
==Reference implementation==
|
||||||
Added to alpha version of Casascius Bitcoin Address Utility for Windows available at:
|
Added to alpha version of Casascius Bitcoin Address Utility for Windows available at:
|
||||||
|
|
||||||
* via https: https://casascius.com/btcaddress-alpha.zip
|
* https://github.com/casascius/Bitcoin-Address-Utility
|
||||||
* at github: https://github.com/casascius/Bitcoin-Address-Utility
|
|
||||||
|
|
||||||
Click "Tools" then "PPEC Keygen" (provisional name)
|
Click "Tools" then "PPEC Keygen" (provisional name)
|
||||||
|
|
||||||
==Other implementations==
|
|
||||||
* Javascript - https://github.com/bitcoinjs/bip38
|
|
||||||
|
|
||||||
==Test vectors==
|
==Test vectors==
|
||||||
|
|
||||||
===No compression, no EC multiply===
|
===No compression, no EC multiply===
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
Sean Bowe <ewillbefull@gmail.com>
|
Sean Bowe <ewillbefull@gmail.com>
|
||||||
Comments-Summary: Unanimously Discourage for implementation
|
Comments-Summary: Unanimously Discourage for implementation
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0039
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0039
|
||||||
Status: Proposed
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2013-09-10
|
Created: 2013-09-10
|
||||||
</pre>
|
</pre>
|
||||||
|
|
|
@ -28,7 +28,7 @@ for two smaller words (This would be a problem with any of the 3 character sets
|
||||||
|
|
||||||
### Spanish
|
### Spanish
|
||||||
|
|
||||||
1. Words can be uniquely determined typing the first 4 characters (sometimes less).
|
1. Words can be uniquely determined by typing the first 4 characters (sometimes less).
|
||||||
|
|
||||||
2. Special Spanish characters like 'ñ', 'ü', 'á', etc... are considered equal to 'n', 'u', 'a', etc... in terms of identifying a word. Therefore, there is no need to use a Spanish keyboard to introduce the passphrase, an application with the Spanish wordlist will be able to identify the words after the first 4 chars have been typed even if the chars with accents have been replaced with the equivalent without accents.
|
2. Special Spanish characters like 'ñ', 'ü', 'á', etc... are considered equal to 'n', 'u', 'a', etc... in terms of identifying a word. Therefore, there is no need to use a Spanish keyboard to introduce the passphrase, an application with the Spanish wordlist will be able to identify the words after the first 4 chars have been typed even if the chars with accents have been replaced with the equivalent without accents.
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ Words chosen using the following rules:
|
||||||
6. No plural words.
|
6. No plural words.
|
||||||
7. No words that remind negative/sad/bad things.
|
7. No words that remind negative/sad/bad things.
|
||||||
8. If both female/male words are available, choose male version.
|
8. If both female/male words are available, choose male version.
|
||||||
9. No words with double vocals (like: lineetta).
|
9. No words with double vowels (like: lineetta).
|
||||||
10. No words already used in other language mnemonic sets.
|
10. No words already used in other language mnemonic sets.
|
||||||
11. If 3 of the first 4 letters are already used in the same sequence in another mnemonic word, there must be at least other 3 different letters.
|
11. If 3 of the first 4 letters are already used in the same sequence in another mnemonic word, there must be at least other 3 different letters.
|
||||||
12. If 3 of the first 4 letters are already used in the same sequence in another mnemonic word, there must not be the same sequence of 3 or more letters.
|
12. If 3 of the first 4 letters are already used in the same sequence in another mnemonic word, there must not be the same sequence of 3 or more letters.
|
||||||
|
@ -92,7 +92,7 @@ Credits: @zizelevak (Jan Lansky zizelevak@gmail.com)
|
||||||
Words chosen using the following rules:
|
Words chosen using the following rules:
|
||||||
|
|
||||||
1. Words are 4-8 letters long.
|
1. Words are 4-8 letters long.
|
||||||
2. Words can be uniquely determined typing the first 4 letters.
|
2. Words can be uniquely determined by typing the first 4 letters.
|
||||||
3. Only words containing all letters without diacritical marks. (It was the hardest task, because one third of all Czech letters has diacritical marks.)
|
3. Only words containing all letters without diacritical marks. (It was the hardest task, because one third of all Czech letters has diacritical marks.)
|
||||||
4. Only nouns, verbs and adverbs, no other word types. All words are in basic form.
|
4. Only nouns, verbs and adverbs, no other word types. All words are in basic form.
|
||||||
5. No personal names or geographical names.
|
5. No personal names or geographical names.
|
||||||
|
@ -104,7 +104,7 @@ Words chosen using the following rules:
|
||||||
|
|
||||||
Credits: @alegotardo @bitmover-studio @brenorb @kuthullu @ninjastic @sabotag3x @Trimegistus
|
Credits: @alegotardo @bitmover-studio @brenorb @kuthullu @ninjastic @sabotag3x @Trimegistus
|
||||||
|
|
||||||
1. Words can be uniquely determined typing the first 4 characters.
|
1. Words can be uniquely determined by typing the first 4 characters.
|
||||||
2. No accents or special characters.
|
2. No accents or special characters.
|
||||||
3. No complex verb forms.
|
3. No complex verb forms.
|
||||||
4. No plural words, unless there's no singular form.
|
4. No plural words, unless there's no singular form.
|
||||||
|
|
193
bip-0046.mediawiki
Normal file
193
bip-0046.mediawiki
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 46
|
||||||
|
Layer: Applications
|
||||||
|
Title: Address Scheme for Timelocked Fidelity Bonds
|
||||||
|
Author: Chris Belcher <belcher@riseup.net>
|
||||||
|
Thebora Kompanioni <theborakompanioni+bip46@gmail.com>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0046
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2022-04-01
|
||||||
|
License: CC0-1.0
|
||||||
|
Post-History: 2022-05-01: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-May/020389.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
== Abstract ==
|
||||||
|
|
||||||
|
This BIP defines the derivation scheme for HD wallets which create timelocked addresses used for creating fidelity bonds. It also gives advice to wallet developers on how to use fidelity bonds to sign over messages, such as certificates, which are needed when using fidelity bonds that are stored offline.
|
||||||
|
|
||||||
|
== Copyright ==
|
||||||
|
|
||||||
|
This document is licensed under the Creative Commons CC0 1.0 Universal license.
|
||||||
|
|
||||||
|
== Motivation ==
|
||||||
|
|
||||||
|
Fidelity bonds are used to resist sybil attacks in certain decentralized anonymous protocols. They are created by locking up bitcoins using the `OP_CHECKLOCKTIMEVERIFY` opcode.
|
||||||
|
|
||||||
|
Having a common derivation scheme allows users of wallet software to have a backup of their fidelity bonds by storing only the HD seed and a reference to this BIP. Importantly the user does not need to backup any timelock values.
|
||||||
|
|
||||||
|
We largely use the same approach used in BIPs 49, 84 and 86 for ease of implementation.
|
||||||
|
|
||||||
|
This allows keeping the private keys of fidelity bonds in cold storage, which increases the sybil resistance of a system without hot wallet risk.
|
||||||
|
|
||||||
|
== Backwards Compatibility ==
|
||||||
|
|
||||||
|
This BIP is not backwards compatible by design as described in the Considerations section of [[bip-0049.mediawiki|BIP 49]]. An incompatible wallet will not discover fidelity bonds at all and the user will notice that something is wrong.
|
||||||
|
|
||||||
|
== Background ==
|
||||||
|
|
||||||
|
=== Fidelity bonds ===
|
||||||
|
|
||||||
|
A fidelity bond is a mechanism where bitcoin value is deliberately sacrificed to make a cryptographic identity expensive to obtain. A way to create a fidelity bond is to lock up bitcoins by sending them to a timelocked address. The valuable thing being sacrificed is the time-value-of-money.
|
||||||
|
|
||||||
|
The sacrifice must be done in a way that can be proven to a third party. This proof can be made by showing the UTXO outpoint, the address redeemscript and a signature which signs a message using the private key corresponding to the public key in the redeemscript.
|
||||||
|
|
||||||
|
The sacrificed value is an objective measurement that can't be faked and which can be verified by anybody (just like, for example PoW mining). Sybil attacks can be made very expensive by forcing a hypothetical sybil attacker to lock up many bitcoins for a long time. JoinMarket implements fidelity bonds for protection from sybil attackers. At the time of writing over 600 BTC in total have been locked up with some for many years. Their UTXOs and signatures have been advertised to the world as proof. We can calculate that for a sybil attacker to succeed in unmixing all the CoinJoins, they would have to lock up over 100k BTC for several years.
|
||||||
|
|
||||||
|
=== Fidelity bonds in cold storage ===
|
||||||
|
|
||||||
|
To allow for holding fidelity bonds in cold storage, there is an intermediate keypair called the certificate.
|
||||||
|
|
||||||
|
UTXO key ---signs---> certificate ---signs---> endpoint
|
||||||
|
|
||||||
|
Where the endpoint might be a IRC nickname or Tor onion hostname. The certificate keypair can be kept online and used to prove ownership of the fidelity bond. Even if the hot wallet private keys are stolen, the coins in the timelocked address will still be safe, although the thief will be able to impersonate the fidelity bond until the expiry.
|
||||||
|
|
||||||
|
== Rationale ==
|
||||||
|
|
||||||
|
It is useful for the user to avoid having to keep a record of the timelocks in the time-locked addresses. So only a limited small set of timelocks are defined by this BIP. This way the user must only store their seed phrase, and knowledge that they have coins stored using this BIP standard. The user doesn't need to remember or store any dates.
|
||||||
|
|
||||||
|
This standard is already implemented and deployed in JoinMarket. As most changes would require a protocol change of a live system, there is limited scope for changing this standard in review. This BIP is more about documenting something which already exists, warts and all.
|
||||||
|
|
||||||
|
== Specifications ==
|
||||||
|
|
||||||
|
This BIP defines the two needed steps to derive multiple deterministic addresses based on a [[bip-0032.mediawiki|BIP 32]] master private key. It also defines the format of the certificate that can be signed by the deterministic address key.
|
||||||
|
|
||||||
|
=== Public key derivation ===
|
||||||
|
|
||||||
|
To derive a public key from the root account, this BIP uses a similar account-structure as defined in BIP [[bip-0084.mediawiki|44]] but with <tt>change</tt> set to <tt>2</tt>.
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
m / 84' / 0' / 0' / 2 / index
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
A key derived with this derivation path pattern will be referred to as <tt>derived_key</tt> further
|
||||||
|
in this document.
|
||||||
|
|
||||||
|
For <tt>index</tt>, addresses are numbered from 0 in a sequentially increasing manner with a fixed upper bound: The index only goes up to <tt>959</tt> inclusive. Only 960 addresses can be derived for a given BIP32 master key. Furthermore there is no concept of a gap limit, instead wallets must always generate all 960 addresses and check for all of them if they have a balance and history.
|
||||||
|
|
||||||
|
=== Timelock derivation ===
|
||||||
|
|
||||||
|
The timelock used in the time-locked address is derived from the <tt>index</tt>. The timelock is a unix time. It is always at the start of the first second at the beginning of the month (see [[#Test vectors|Test vectors]]). The <tt>index</tt> counts upwards the months from January 2020, ending in December 2099. At 12 months per year for 80 years this totals 960 timelocks. Note that care must be taken with the year 2038 problem on 32-bit systems.
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
year = 2020 + index // 12
|
||||||
|
month = 1 + index % 12
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
|
=== Address derivation ===
|
||||||
|
|
||||||
|
To derive the address from the above calculated public key and timelock, we create a <tt>witness script</tt> which locks the funds until the <tt>timelock</tt>, and then checks the signature of the <tt>derived_key</tt>. The <tt>witness script</tt> is hashed with SHA256 to produce a 32-byte hash value that forms the <tt>witness program</tt> in the output script of the P2WSH address.
|
||||||
|
|
||||||
|
witnessScript: <timelock> OP_CHECKLOCKTIMEVERIFY OP_DROP <derived_key> OP_CHECKSIG
|
||||||
|
witness: <signature> <witnessScript>
|
||||||
|
scriptSig: (empty)
|
||||||
|
scriptPubKey: 0 <32-byte-hash>
|
||||||
|
(0x0020{32-byte-hash})
|
||||||
|
|
||||||
|
=== Message signing ===
|
||||||
|
|
||||||
|
In order to support signing of certificates, implementors should support signing ASCII messages.
|
||||||
|
|
||||||
|
The certificate message is defined as `"fidelity-bond-cert" || "|" || cert_pubkey || "|" || cert_expiry`.
|
||||||
|
|
||||||
|
The certificate expiry `cert_expiry` is the number of the 2016-block period after which the certificate is no longer valid. For example, if `cert_expiry` is 330 then the certificate will become invalid after block height 665280 (:=330x2016). The purpose of the expiry parameter is so that in case the certificate keypair is compromised, the attacker can only impersonate the fidelity bond for a limited amount of time.
|
||||||
|
|
||||||
|
A certificate message can be created by another application external to this standard. It is then prepended with the string `0x18 || "Bitcoin Signed Message:\n"` and a byte denoting the length of the certificate message. The whole thing is then signed with the private key of the <tt>derived_key</tt>. This part is identical to the "Sign Message" function which many wallets already implement.
|
||||||
|
|
||||||
|
Almost all wallets implementing this standard can use their already-existing "Sign Message" function to sign the certificate message. As the certificate message itself is always an ASCII string, the wallet may not need to specially implement this section at all but just rely on users copypasting their certificate message into the already-existing "Sign Message" user interface. This works as long as the wallet knows how to use the private key of the timelocked address for signing messages.
|
||||||
|
|
||||||
|
It is most important for wallet implementations of this standard to support creating the certificate signature. Verifying the certificate signature is less important.
|
||||||
|
|
||||||
|
|
||||||
|
== Test vectors ==
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
mnemonic = abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
|
||||||
|
rootpriv = xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu
|
||||||
|
rootpub = xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8
|
||||||
|
|
||||||
|
// First timelocked address = m/84'/0'/0'/2/0
|
||||||
|
derived private_key = L2tQBEdhC48YLeEWNg3e4msk94iKfyVa9hdfzRwUERabZ53TfH3d
|
||||||
|
derived public_key = 02a1b09f93073c63f205086440898141c0c3c6d24f69a18db608224bcf143fa011
|
||||||
|
unix locktime = 1577836800
|
||||||
|
string locktime = 2020-01-01 00:00:00
|
||||||
|
redeemscript = 0400e10b5eb1752102a1b09f93073c63f205086440898141c0c3c6d24f69a18db608224bcf143fa011ac
|
||||||
|
scriptPubKey = 0020bdee9515359fc9df912318523b4cd22f1c0b5410232dc943be73f9f4f07e39ad
|
||||||
|
address = bc1qhhhf29f4nlyalyfrrpfrknxj9uwqk4qsyvkujsa7w0ulfur78xkspsqn84
|
||||||
|
|
||||||
|
// Test certificate using the first timelocked address
|
||||||
|
// Note that as signatures contains a random nonce, it might not be exactly the same when your code generates it
|
||||||
|
// p2pkh address is the p2pkh address corresponding to the derived public key, it can be used to verify the message
|
||||||
|
// signature in any wallet that supports Verify Message.
|
||||||
|
// As mentioned before, it is more important for implementors of this standard to support signing such messages, not verifying them
|
||||||
|
message = fidelity-bond-cert|020000000000000000000000000000000000000000000000000000000000000001|375
|
||||||
|
address = bc1qhhhf29f4nlyalyfrrpfrknxj9uwqk4qsyvkujsa7w0ulfur78xkspsqn84
|
||||||
|
p2pkh address = 16vmiGpY1rEaYnpGgtG7FZgr2uFCpeDgV6
|
||||||
|
signature = H2b/90XcKnIU/D1nSCPhk8OcxrHebMCr4Ok2d2yDnbKDTSThNsNKA64CT4v2kt+xA1JmGRG/dMnUUH1kKqCVSHo=
|
||||||
|
|
||||||
|
// 2nd timelocked address = m/84'/0'/0'/2/1
|
||||||
|
derived private_key = KxctaFBzetyc9KXeUr6jxESCZiCEXRuwnQMw7h7hroP6MqnWN6Pf
|
||||||
|
derived public_key = 02599f6db8b33265a44200fef0be79c927398ed0b46c6a82fa6ddaa5be2714002d
|
||||||
|
unix locktime = 1580515200
|
||||||
|
string locktime = 2020-02-01 00:00:00
|
||||||
|
redeemscript = 0480bf345eb1752102599f6db8b33265a44200fef0be79c927398ed0b46c6a82fa6ddaa5be2714002dac
|
||||||
|
scriptPubKey = 0020b8f898643991608524ed04e0c6779f632a57f1ffa3a3a306cd81432c5533e9ae
|
||||||
|
address = bc1qhrufsepej9sg2f8dqnsvvaulvv490u0l5w36xpkds9pjc4fnaxhq7pcm4h
|
||||||
|
|
||||||
|
// timelocked address after the year 2038 = m/84'/0'/0'/2/240
|
||||||
|
derived private_key = L3SYqae23ZoDDcyEA8rRBK83h1MDqxaDG57imMc9FUx1J8o9anQe
|
||||||
|
derived public_key = 03ec8067418537bbb52d5d3e64e2868e67635c33cfeadeb9a46199f89ebfaab226
|
||||||
|
unix locktime = 2208988800
|
||||||
|
string locktime = 2040-01-01 00:00:00
|
||||||
|
redeemscript = 05807eaa8300b1752103ec8067418537bbb52d5d3e64e2868e67635c33cfeadeb9a46199f89ebfaab226ac
|
||||||
|
scriptPubKey = 0020e7de0ad2720ae1d6cc9b6ad91af57eb74646762cf594c91c18f6d5e7a873635a
|
||||||
|
address = bc1qul0q45njptsadnymdtv34at7karyva3v7k2vj8qc7m2702rnvddq0z20u5
|
||||||
|
|
||||||
|
// last timelocked address = m/84'/0'/0'/2/959
|
||||||
|
derived private_key = L5Z9DDMnj5RZMyyPiQLCvN48Xt7GGmev6cjvJXD8uz5EqiY8trNJ
|
||||||
|
derived public_key = 0308c5751121b1ae5c973cdc7071312f6fc10ab864262f0cbd8134f056166e50f3
|
||||||
|
unix locktime = 4099766400
|
||||||
|
string locktime = 2099-12-01 00:00:00
|
||||||
|
redeemscript = 0580785df400b175210308c5751121b1ae5c973cdc7071312f6fc10ab864262f0cbd8134f056166e50f3ac
|
||||||
|
scriptPubKey = 0020803268e042008737cf439748cbb5a4449e311da9aa64ae3ac56d84d059654f85
|
||||||
|
address = bc1qsqex3czzqzrn0n6rjayvhddygj0rz8df4fj2uwk9dkzdqkt9f7zs5c493u
|
||||||
|
|
||||||
|
// Test certificate and endpoint signing using the first timelocked address = m/84'/0'/0'/2/0 (see above)
|
||||||
|
bond private_key = L2tQBEdhC48YLeEWNg3e4msk94iKfyVa9hdfzRwUERabZ53TfH3d
|
||||||
|
bond p2pkh address = 16vmiGpY1rEaYnpGgtG7FZgr2uFCpeDgV6
|
||||||
|
|
||||||
|
certificate private_key = KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d
|
||||||
|
certificate public_key = 0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c
|
||||||
|
certificate p2pkh address = 1JaUQDVNRdhfNsVncGkXedaPSM5Gc54Hso
|
||||||
|
|
||||||
|
certificate message = fidelity-bond-cert|0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c|375
|
||||||
|
certificate signature = INOP3cB9UW7F1e1Aglj8rI9QhnyxmgWDEPt+nOMvl7hJJne7rH/KCNDYvLiqNuB9qWaWUojutjRsgPJrvyDQ+0Y=
|
||||||
|
|
||||||
|
// example endpoint signing two IRC nicknames (used in JoinMarket)
|
||||||
|
endpoint message = J54LS6YyJPoseqFS|J55VZ6U6ZyFDNeuv
|
||||||
|
endpoint signature = H18WE4MugDNoWZIf9jU0njhQptdUyBDUf7lToG9bpMKmeJK0lOoABaDs5bKnohSuZ0e9gnSco5OL9lXdKU7gP5E=
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Code generating these test vectors can be found here: https://github.com/chris-belcher/timelocked-addresses-fidelity-bond-bip-testvectors
|
||||||
|
|
||||||
|
== Reference ==
|
||||||
|
|
||||||
|
* [[https://gist.github.com/chris-belcher/18ea0e6acdb885a2bfbdee43dcd6b5af/|Design for improving JoinMarket's resistance to sybil attacks using fidelity bonds]]
|
||||||
|
* [[https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/fidelity-bonds.md|JoinMarket fidelity bonds doc page]]
|
||||||
|
* [[bip-0065.mediawiki|BIP65 - OP_CHECKLOCKTIMEVERIFY]]
|
||||||
|
* [[bip-0032.mediawiki|BIP32 - Hierarchical Deterministic Wallets]]
|
||||||
|
* [[bip-0044.mediawiki|BIP44 - Multi-Account Hierarchy for Deterministic Wallets]]
|
||||||
|
* [[bip-0049.mediawiki|BIP49 - Derivation scheme for P2WPKH-nested-in-P2SH based accounts]]
|
||||||
|
* [[bip-0084.mediawiki|BIP84 - Derivation scheme for P2WPKH based accounts]]
|
||||||
|
* [[bip-0086.mediawiki|BIP86 - Key Derivation for Single Key P2TR Outputs]]
|
|
@ -17,7 +17,7 @@ RECENT CHANGES:
|
||||||
|
|
||||||
==Status==
|
==Status==
|
||||||
|
|
||||||
This BIP can be be considered final in terms of enabling compatibility with wallets that implement version 1 and version 2 reusable payment codes, however future developments of the reusable payment codes specification will not be distributed via the BIP process.
|
This BIP can be considered final in terms of enabling compatibility with wallets that implement version 1 and version 2 reusable payment codes, however future developments of the reusable payment codes specification will not be distributed via the BIP process.
|
||||||
|
|
||||||
The Open Bitcoin Privacy Project RFC repo should be consulted for specifications related to version 3 or higher payment codes: https://github.com/OpenBitcoinPrivacyProject/rfc
|
The Open Bitcoin Privacy Project RFC repo should be consulted for specifications related to version 3 or higher payment codes: https://github.com/OpenBitcoinPrivacyProject/rfc
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ Normal operation of a payment code-enabled wallet can be performed by an SPV cli
|
||||||
|
|
||||||
Recovering a wallet from a seed, however, does require access to a fully-indexed blockchain.
|
Recovering a wallet from a seed, however, does require access to a fully-indexed blockchain.
|
||||||
|
|
||||||
The required data may be obtained from copy of the blockchain under the control of the user, or via a publicly-queriable blockchain explorer.
|
The required data may be obtained from copy of the blockchain under the control of the user, or via a publicly-queryable blockchain explorer.
|
||||||
|
|
||||||
When querying a public blockchain explorer, wallets SHOULD connect to the explorer through Tor (or equivalent) and SHOULD avoid grouping queries in a manner that associates ephemeral addresses with each other.
|
When querying a public blockchain explorer, wallets SHOULD connect to the explorer through Tor (or equivalent) and SHOULD avoid grouping queries in a manner that associates ephemeral addresses with each other.
|
||||||
|
|
||||||
|
@ -350,12 +350,12 @@ Version 2 payment codes behave identifically to version 1 payment codes, except
|
||||||
|
|
||||||
====Definitions====
|
====Definitions====
|
||||||
|
|
||||||
* Notification change output: the change output from a notification transaction which which resides in the sender's wallet, but can be automatically located by the intended recipient
|
* Notification change output: the change output from a notification transaction which resides in the sender's wallet, but can be automatically located by the intended recipient
|
||||||
* Payment code identifier: a 33 byte representation of a payment code constructed by prepending 0x02 to the SHA256 hash of the binary serialization of the payment code
|
* Payment code identifier: a 33 byte representation of a payment code constructed by prepending 0x02 to the SHA256 hash of the binary serialization of the payment code
|
||||||
|
|
||||||
====Notification Transaction====
|
====Notification Transaction====
|
||||||
|
|
||||||
Note: this procedure is used if Bob uses a version 2 payment code (regardless of the the version of Alice's payment code). If Bob's payment code is not version 2, see the appropriate section in this specification.
|
Note: this procedure is used if Bob uses a version 2 payment code (regardless of the version of Alice's payment code). If Bob's payment code is not version 2, see the appropriate section in this specification.
|
||||||
|
|
||||||
# Construct a notification transaction as per the version 1 instructions, except do not create the output to Bob's notification address
|
# Construct a notification transaction as per the version 1 instructions, except do not create the output to Bob's notification address
|
||||||
# Create a notification change address as follows:
|
# Create a notification change address as follows:
|
||||||
|
|
|
@ -92,10 +92,10 @@ This BIP is not backwards compatible by design as described under [[#considerati
|
||||||
// Account 0, first receiving private key = m/49'/1'/0'/0/0
|
// Account 0, first receiving private key = m/49'/1'/0'/0/0
|
||||||
account0recvPrivateKey = cULrpoZGXiuC19Uhvykx7NugygA3k86b3hmdCeyvHYQZSxojGyXJ
|
account0recvPrivateKey = cULrpoZGXiuC19Uhvykx7NugygA3k86b3hmdCeyvHYQZSxojGyXJ
|
||||||
account0recvPrivateKeyHex = 0xc9bdb49cfbaedca21c4b1f3a7803c34636b1d7dc55a717132443fc3f4c5867e8
|
account0recvPrivateKeyHex = 0xc9bdb49cfbaedca21c4b1f3a7803c34636b1d7dc55a717132443fc3f4c5867e8
|
||||||
account0recvPublickKeyHex = 0x03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f
|
account0recvPublicKeyHex = 0x03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f
|
||||||
|
|
||||||
// Address derivation
|
// Address derivation
|
||||||
keyhash = HASH160(account0recvPublickKeyHex) = 0x38971f73930f6c141d977ac4fd4a727c854935b3
|
keyhash = HASH160(account0recvPublicKeyHex) = 0x38971f73930f6c141d977ac4fd4a727c854935b3
|
||||||
scriptSig = <0 <keyhash>> = 0x001438971f73930f6c141d977ac4fd4a727c854935b3
|
scriptSig = <0 <keyhash>> = 0x001438971f73930f6c141d977ac4fd4a727c854935b3
|
||||||
addressBytes = HASH160(scriptSig) = 0x336caa13e08b96080a32b5d818d59b4ab3b36742
|
addressBytes = HASH160(scriptSig) = 0x336caa13e08b96080a32b5d818d59b4ab3b36742
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ Proving the sacrifice of some limited resource is a common technique in a
|
||||||
variety of cryptographic protocols. Proving sacrifices of coins to mining fees
|
variety of cryptographic protocols. Proving sacrifices of coins to mining fees
|
||||||
has been proposed as a ''universal public good'' to which the sacrifice could
|
has been proposed as a ''universal public good'' to which the sacrifice could
|
||||||
be directed, rather than simply destroying the coins. However doing so is
|
be directed, rather than simply destroying the coins. However doing so is
|
||||||
non-trivial, and even the best existing technqiue - announce-commit sacrifices
|
non-trivial, and even the best existing technique - announce-commit sacrifices
|
||||||
- could encourage mining centralization. CHECKLOCKTIMEVERIFY can be used to
|
- could encourage mining centralization. CHECKLOCKTIMEVERIFY can be used to
|
||||||
create outputs that are provably spendable by anyone (thus to mining fees
|
create outputs that are provably spendable by anyone (thus to mining fees
|
||||||
assuming miners behave optimally and rationally) but only at a time
|
assuming miners behave optimally and rationally) but only at a time
|
||||||
|
|
|
@ -219,7 +219,7 @@ message EncryptedProtocolMessage {
|
||||||
==Payment Protocol Process with InvoiceRequests==
|
==Payment Protocol Process with InvoiceRequests==
|
||||||
The full process overview for using '''InvoiceRequests''' in the Payment Protocol is defined below.
|
The full process overview for using '''InvoiceRequests''' in the Payment Protocol is defined below.
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
All Payment Protocol messages MUST be encapsulated in either a [[#ProtocolMessage|ProtocolMessage]] or [[#EncryptedProcotolMessage|EncryptedProtocolMessage]]. Once the process begins using [[#EncryptedProtocolMessage|EncryptedProtocolMessage]] messages, all subsequent communications MUST use [[#EncryptedProtocolMessage|EncryptedProtocolMessages]].
|
All Payment Protocol messages MUST be encapsulated in either a [[#ProtocolMessage|ProtocolMessage]] or [[#EncryptedProtocolMessage|EncryptedProtocolMessage]]. Once the process begins using [[#EncryptedProtocolMessage|EncryptedProtocolMessage]] messages, all subsequent communications MUST use [[#EncryptedProtocolMessage|EncryptedProtocolMessages]].
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
All Payment Protocol messages SHOULD be communicated using [[#EncryptedProtocolMessage|EncryptedProtocolMessage]] encapsulating messages with the exception that an [[#InvoiceRequest|InvoiceRequest]] MAY be communicated using the [[#ProtocolMessage|ProtocolMessage]] if the receiver's public key is unknown.
|
All Payment Protocol messages SHOULD be communicated using [[#EncryptedProtocolMessage|EncryptedProtocolMessage]] encapsulating messages with the exception that an [[#InvoiceRequest|InvoiceRequest]] MAY be communicated using the [[#ProtocolMessage|ProtocolMessage]] if the receiver's public key is unknown.
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
|
@ -95,7 +95,7 @@ The payjoin proposal PSBT is sent in the HTTP response body, base64 serialized w
|
||||||
|
|
||||||
To ensure compatibility with web-wallets and browser-based-tools, all responses (including errors) must contain the HTTP header <code>Access-Control-Allow-Origin: *</code>.
|
To ensure compatibility with web-wallets and browser-based-tools, all responses (including errors) must contain the HTTP header <code>Access-Control-Allow-Origin: *</code>.
|
||||||
|
|
||||||
The sender must ensure that the url refers to a scheme or protocol using authenticated encryption, for example TLS with certificate validation, or a .onion link to a hidden service whose public key identifier has already been communicated via a TLS connection. Senders SHOULD NOT accept a url representing an unencrypted or unauthenticated connection.
|
The sender must ensure that the URL refers to a scheme or protocol using authenticated encryption, for example TLS with certificate validation, or a .onion link to a hidden service whose public key identifier has already been communicated via a TLS connection. Senders SHOULD NOT accept a URL representing an unencrypted or unauthenticated connection.
|
||||||
|
|
||||||
The original PSBT MUST:
|
The original PSBT MUST:
|
||||||
* Have all the <code>witnessUTXO</code> or <code>nonWitnessUTXO</code> information filled in.
|
* Have all the <code>witnessUTXO</code> or <code>nonWitnessUTXO</code> information filled in.
|
||||||
|
@ -111,7 +111,7 @@ The original PSBT SHOULD NOT:
|
||||||
|
|
||||||
The payjoin proposal MUST:
|
The payjoin proposal MUST:
|
||||||
* Use all the inputs from the original PSBT.
|
* Use all the inputs from the original PSBT.
|
||||||
* Use all the outputs which do not belongs to the receiver from the original PSBT.
|
* Use all the outputs which do not belong to the receiver from the original PSBT.
|
||||||
* Only finalize the inputs added by the receiver. (Referred later as <code>additional inputs</code>)
|
* Only finalize the inputs added by the receiver. (Referred later as <code>additional inputs</code>)
|
||||||
* Only fill the <code>witnessUTXO</code> or <code>nonWitnessUTXO</code> for the additional inputs.
|
* Only fill the <code>witnessUTXO</code> or <code>nonWitnessUTXO</code> for the additional inputs.
|
||||||
|
|
||||||
|
@ -195,10 +195,10 @@ The well-known error codes are:
|
||||||
|The receiver rejected the original PSBT.
|
|The receiver rejected the original PSBT.
|
||||||
|}
|
|}
|
||||||
|
|
||||||
The receiver is allowed to return implementation specific errors which may assist the sender to diagnose any issue.
|
The receiver is allowed to return implementation-specific errors which may assist the sender to diagnose any issue.
|
||||||
|
|
||||||
However, it is important that error codes that are not well-known and that the message do not appear on the sender's software user interface.
|
However, it is important that error codes that are not well-known and that the message do not appear on the sender's software user interface.
|
||||||
Such error codes or messages could be used maliciously to phish a non technical user.
|
Such error codes or messages could be used maliciously to phish a non-technical user.
|
||||||
Instead those errors or messages can only appear in debug logs.
|
Instead those errors or messages can only appear in debug logs.
|
||||||
|
|
||||||
It is advised to hard code the description of the well known error codes into the sender's software.
|
It is advised to hard code the description of the well known error codes into the sender's software.
|
||||||
|
@ -221,7 +221,7 @@ To prevent this, the sender can agree to pay more fee so the receiver make sure
|
||||||
|
|
||||||
* The sender's transaction is time sensitive.
|
* The sender's transaction is time sensitive.
|
||||||
|
|
||||||
When a sender pick a specific fee rate, the sender expects the transaction to be confirmed after a specific amount of time. But if the receiver adds an input without bumping the fee of the transaction, the payjoin transaction fee rate will be lower, and thus, longer to confirm.
|
When a sender picks a specific fee rate, the sender expects the transaction to be confirmed after a specific amount of time. But if the receiver adds an input without bumping the fee of the transaction, the payjoin transaction fee rate will be lower, and thus, longer to confirm.
|
||||||
|
|
||||||
Our recommendation for <code>maxadditionalfeecontribution=</code> is <code>originalPSBTFeeRate * 110</code>.
|
Our recommendation for <code>maxadditionalfeecontribution=</code> is <code>originalPSBTFeeRate * 110</code>.
|
||||||
|
|
||||||
|
@ -232,8 +232,8 @@ The receiver needs to do some check on the original PSBT before proceeding:
|
||||||
* Non-interactive receivers (like a payment processor) need to check that the original PSBT is broadcastable. <code>*</code>
|
* Non-interactive receivers (like a payment processor) need to check that the original PSBT is broadcastable. <code>*</code>
|
||||||
* If the sender included inputs in the original PSBT owned by the receiver, the receiver must either return error <code>original-psbt-rejected</code> or make sure they do not sign those inputs in the payjoin proposal.
|
* If the sender included inputs in the original PSBT owned by the receiver, the receiver must either return error <code>original-psbt-rejected</code> or make sure they do not sign those inputs in the payjoin proposal.
|
||||||
* Make sure that the inputs included in the original transaction have never been seen before.
|
* Make sure that the inputs included in the original transaction have never been seen before.
|
||||||
** This prevent [[#probing-attack|probing attacks]].
|
** This prevents [[#probing-attack|probing attacks]].
|
||||||
** This prevent reentrant payjoin, where a sender attempts to use payjoin transaction as a new original transaction for a new payjoin.
|
** This prevents reentrant payjoin, where a sender attempts to use payjoin transaction as a new original transaction for a new payjoin.
|
||||||
|
|
||||||
<code>*</code>: Interactive receivers are not required to validate the original PSBT because they are not exposed to [[#probing-attack|probing attacks]].
|
<code>*</code>: Interactive receivers are not required to validate the original PSBT because they are not exposed to [[#probing-attack|probing attacks]].
|
||||||
|
|
||||||
|
@ -245,25 +245,24 @@ The sender should check the payjoin proposal before signing it to prevent a mali
|
||||||
* If the receiver's BIP21 signalled <code>pjos=0</code>, disable payment output substitution.
|
* If the receiver's BIP21 signalled <code>pjos=0</code>, disable payment output substitution.
|
||||||
* Verify that the transaction version, and the nLockTime are unchanged.
|
* Verify that the transaction version, and the nLockTime are unchanged.
|
||||||
* Check that the sender's inputs' sequence numbers are unchanged.
|
* Check that the sender's inputs' sequence numbers are unchanged.
|
||||||
* For each inputs in the proposal:
|
* For each input in the proposal:
|
||||||
** Verify that no keypaths is in the PSBT input
|
** Verify that no keypaths are in the PSBT input
|
||||||
** Verify that no partial signature has been filled
|
** Verify that no partial signature has been filled
|
||||||
** If it is one of the sender's input
|
** If it is one of the sender's inputs:
|
||||||
*** Verify that input's sequence is unchanged.
|
*** Verify that input's sequence is unchanged.
|
||||||
*** Verify the PSBT input is not finalized
|
*** Verify the PSBT input is not finalized
|
||||||
*** Verify that <code>non_witness_utxo</code> and <code>witness_utxo</code> are not specified.
|
** If it is one of the receiver's inputs:
|
||||||
** If it is one of the receiver's input
|
|
||||||
*** Verify the PSBT input is finalized
|
*** Verify the PSBT input is finalized
|
||||||
*** Verify that <code>non_witness_utxo</code> or <code>witness_utxo</code> are filled in.
|
*** Verify that <code>non_witness_utxo</code> or <code>witness_utxo</code> are filled in.
|
||||||
** Verify that the payjoin proposal did not introduced mixed input's sequence.
|
** Verify that the payjoin proposal inputs all specify the same sequence value.
|
||||||
** Verify that all of sender's inputs from the original PSBT are in the proposal.
|
** Verify that all of sender's inputs from the original PSBT are in the proposal.
|
||||||
* For each outputs in the proposal:
|
* For each output in the proposal:
|
||||||
** Verify that no keypaths is in the PSBT output
|
** Verify that no keypaths are in the PSBT output
|
||||||
** If the output is the [[#fee-output|fee output]]:
|
** If the output is the [[#fee-output|fee output]]:
|
||||||
*** The amount that was subtracted from the output's value is less than or equal to <code>maxadditionalfeecontribution</code>. Let's call this amount <code>actual contribution</code>.
|
*** The amount that was subtracted from the output's value is less than or equal to <code>maxadditionalfeecontribution</code>. Let's call this amount <code>actual contribution</code>.
|
||||||
*** Make sure the actual contribution is only paying fee: The <code>actual contribution</code> is less than or equals to the difference of absolute fee between the payjoin proposal and the original PSBT.
|
*** Make sure the actual contribution is only going towards fees: The <code>actual contribution</code> is less than or equals to the difference of absolute fee between the payjoin proposal and the original PSBT.
|
||||||
*** Make sure the actual contribution is only paying for fee incurred by additional inputs: <code>actual contribution</code> is less than or equals to <code>originalPSBTFeeRate * vsize(sender_input_type) * (count(payjoin_proposal_inputs) - count(original_psbt_inputs))</code>. (see [[#fee-output|Fee output]] section)
|
*** Make sure the actual contribution is only paying for fees incurred by additional inputs: <code>actual contribution</code> is less than or equal to <code>originalPSBTFeeRate * vsize(sender_input_type) * (count(payjoin_proposal_inputs) - count(original_psbt_inputs))</code>. (see [[#fee-output|Fee output]] section)
|
||||||
** If the output is the payment output and payment output substitution is allowed.
|
** If the output is the payment output and payment output substitution is allowed,
|
||||||
*** Do not make any check
|
*** Do not make any check
|
||||||
** Else
|
** Else
|
||||||
*** Make sure the output's value did not decrease.
|
*** Make sure the output's value did not decrease.
|
||||||
|
@ -274,8 +273,8 @@ The sender must be careful to only sign the inputs that were present in the orig
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
* The sender must allow the receiver to add/remove or modify the receiver's own outputs. (if payment output substitution is disabled, the receiver's outputs must not be removed or decreased in value)
|
* The sender must allow the receiver to add/remove or modify the receiver's own outputs. (if payment output substitution is disabled, the receiver's outputs must not be removed or decreased in value)
|
||||||
* The sender should allow the receiver to not add any inputs. This is useful for the receiver to change the paymout output scriptPubKey type.
|
* The sender should allow the receiver to not add any inputs. This is useful for the receiver to change the payment output scriptPubKey type.
|
||||||
* If no input have been added, the sender's wallet implementation should accept the payjoin proposal, but not mark the transaction as an actual payjoin in the user interface.
|
* If the receiver added no inputs, the sender's wallet implementation should accept the payjoin proposal, but not mark the transaction as an actual payjoin in the user interface.
|
||||||
|
|
||||||
Our method of checking the fee allows the receiver and the sender to batch payments in the payjoin transaction.
|
Our method of checking the fee allows the receiver and the sender to batch payments in the payjoin transaction.
|
||||||
It also allows the receiver to pay the fee for batching adding his own outputs.
|
It also allows the receiver to pay the fee for batching adding his own outputs.
|
||||||
|
@ -331,7 +330,7 @@ On top of this the receiver can poison analysis by randomly faking a round amoun
|
||||||
|
|
||||||
===<span id="output-substitution"></span>Payment output substitution===
|
===<span id="output-substitution"></span>Payment output substitution===
|
||||||
|
|
||||||
Unless disallowed by sender explicitly via `disableoutputsubstitution=true` or by the BIP21 url via query parameter the `pjos=0`, the receiver is free to decrease the amount, or change the scriptPubKey output paying to himself.
|
Unless disallowed by the sender explicitly via <code>disableoutputsubstitution=true</code> or by the BIP21 URL via the query parameter <code>pjos=0</code>, the receiver is free to decrease the amount or change the scriptPubKey output paying to himself.
|
||||||
Note that if payment output substitution is disallowed, the reveiver can still increase the amount of the output. (See [[#reference-impl|the reference implementation]])
|
Note that if payment output substitution is disallowed, the reveiver can still increase the amount of the output. (See [[#reference-impl|the reference implementation]])
|
||||||
|
|
||||||
For example, if the sender's scriptPubKey type is P2WPKH while the receiver's payment output in the original PSBT is P2SH, then the receiver can substitute the payment output to be P2WPKH to match the sender's scriptPubKey type.
|
For example, if the sender's scriptPubKey type is P2WPKH while the receiver's payment output in the original PSBT is P2SH, then the receiver can substitute the payment output to be P2WPKH to match the sender's scriptPubKey type.
|
||||||
|
@ -345,7 +344,7 @@ A compromised payjoin server could steal the hot wallet outputs of the receiver,
|
||||||
|
|
||||||
===Impacted heuristics===
|
===Impacted heuristics===
|
||||||
|
|
||||||
Our proposal of payjoin is breaking the following blockchain heuristics:
|
Our proposal of payjoin breaks the following blockchain heuristics:
|
||||||
|
|
||||||
* Common inputs heuristics.
|
* Common inputs heuristics.
|
||||||
|
|
||||||
|
@ -395,7 +394,7 @@ With payjoin, the maximum amount of money that can be lost is equal to two payme
|
||||||
==<span id="reference-impl"></span>Reference sender's implementation==
|
==<span id="reference-impl"></span>Reference sender's implementation==
|
||||||
|
|
||||||
Here is pseudo code of a sender implementation.
|
Here is pseudo code of a sender implementation.
|
||||||
<code>RequestPayjoin</code> takes the bip21 URI of the payment, the wallet and the <code>signedPSBT</code>.
|
<code>RequestPayjoin</code> takes the BIP21 URI of the payment, the wallet and the <code>signedPSBT</code>.
|
||||||
|
|
||||||
The <code>signedPSBT</code> represents a PSBT which has been fully signed, but not yet finalized.
|
The <code>signedPSBT</code> represents a PSBT which has been fully signed, but not yet finalized.
|
||||||
We then prepare <code>originalPSBT</code> from the <code>signedPSBT</code> via the <code>CreateOriginalPSBT</code> function and get back the <code>proposal</code>.
|
We then prepare <code>originalPSBT</code> from the <code>signedPSBT</code> via the <code>CreateOriginalPSBT</code> function and get back the <code>proposal</code>.
|
||||||
|
@ -483,9 +482,6 @@ public async Task<PSBT> RequestPayjoin(
|
||||||
// Verify the PSBT input is not finalized
|
// Verify the PSBT input is not finalized
|
||||||
if (proposedPSBTInput.IsFinalized())
|
if (proposedPSBTInput.IsFinalized())
|
||||||
throw new PayjoinSenderException("The receiver finalized one of our inputs");
|
throw new PayjoinSenderException("The receiver finalized one of our inputs");
|
||||||
// Verify that <code>non_witness_utxo</code> and <code>witness_utxo</code> are not specified.
|
|
||||||
if (proposedPSBTInput.NonWitnessUtxo != null || proposedPSBTInput.WitnessUtxo != null)
|
|
||||||
throw new PayjoinSenderException("The receiver added non_witness_utxo or witness_utxo to one of our inputs");
|
|
||||||
sequences.Add(proposedTxIn.Sequence);
|
sequences.Add(proposedTxIn.Sequence);
|
||||||
|
|
||||||
// Fill up the info from the original PSBT input so we can sign and get fees.
|
// Fill up the info from the original PSBT input so we can sign and get fees.
|
||||||
|
@ -628,7 +624,7 @@ A successful exchange with:
|
||||||
<pre>cHNidP8BAHMCAAAAAY8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////AtyVuAUAAAAAF6kUHehJ8GnSdBUOOv6ujXLrWmsJRDCHgIQeAAAAAAAXqRR3QJbbz0hnQ8IvQ0fptGn+votneofTAAAAAAEBIKgb1wUAAAAAF6kU3k4ekGHKWRNbA1rV5tR5kEVDVNCHAQcXFgAUx4pFclNVgo1WWAdN1SYNX8tphTABCGsCRzBEAiB8Q+A6dep+Rz92vhy26lT0AjZn4PRLi8Bf9qoB/CMk0wIgP/Rj2PWZ3gEjUkTlhDRNAQ0gXwTO7t9n+V14pZ6oljUBIQMVmsAaoNWHVMS02LfTSe0e388LNitPa1UQZyOihY+FFgABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUAAA=</pre>
|
<pre>cHNidP8BAHMCAAAAAY8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////AtyVuAUAAAAAF6kUHehJ8GnSdBUOOv6ujXLrWmsJRDCHgIQeAAAAAAAXqRR3QJbbz0hnQ8IvQ0fptGn+votneofTAAAAAAEBIKgb1wUAAAAAF6kU3k4ekGHKWRNbA1rV5tR5kEVDVNCHAQcXFgAUx4pFclNVgo1WWAdN1SYNX8tphTABCGsCRzBEAiB8Q+A6dep+Rz92vhy26lT0AjZn4PRLi8Bf9qoB/CMk0wIgP/Rj2PWZ3gEjUkTlhDRNAQ0gXwTO7t9n+V14pZ6oljUBIQMVmsAaoNWHVMS02LfTSe0e388LNitPa1UQZyOihY+FFgABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUAAA=</pre>
|
||||||
|
|
||||||
<code>payjoin proposal</code>
|
<code>payjoin proposal</code>
|
||||||
<pre>cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAAEBIICEHgAAAAAAF6kUyPLL+cphRyyI5GTUazV0hF2R2NWHAQcXFgAUX4BmVeWSTJIEwtUb5TlPS/ntohABCGsCRzBEAiBnu3tA3yWlT0WBClsXXS9j69Bt+waCs9JcjWtNjtv7VgIge2VYAaBeLPDB6HGFlpqOENXMldsJezF9Gs5amvDQRDQBIQJl1jz1tBt8hNx2owTm+4Du4isx0pmdKNMNIjjaMHFfrQAAAA==</pre>
|
<pre>cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcAAQEggIQeAAAAAAAXqRTI8sv5ymFHLIjkZNRrNXSEXZHY1YcBBxcWABRfgGZV5ZJMkgTC1RvlOU9L+e2iEAEIawJHMEQCIGe7e0DfJaVPRYEKWxddL2Pr0G37BoKz0lyNa02O2/tWAiB7ZVgBoF4s8MHocYWWmo4Q1cyV2wl7MX0azlqa8NBENAEhAmXWPPW0G3yE3HajBOb7gO7iKzHSmZ0o0w0iONowcV+tAAAA</pre>
|
||||||
|
|
||||||
<code>payjoin proposal filled with sender's information</code>
|
<code>payjoin proposal filled with sender's information</code>
|
||||||
<pre>cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcBBBYAFMeKRXJTVYKNVlgHTdUmDV/LaYUwIgYDFZrAGqDVh1TEtNi300ntHt/PCzYrT2tVEGcjooWPhRYYSFzWUDEAAIABAACAAAAAgAEAAAAAAAAAAAEBIICEHgAAAAAAF6kUyPLL+cphRyyI5GTUazV0hF2R2NWHAQcXFgAUX4BmVeWSTJIEwtUb5TlPS/ntohABCGsCRzBEAiBnu3tA3yWlT0WBClsXXS9j69Bt+waCs9JcjWtNjtv7VgIge2VYAaBeLPDB6HGFlpqOENXMldsJezF9Gs5amvDQRDQBIQJl1jz1tBt8hNx2owTm+4Du4isx0pmdKNMNIjjaMHFfrQABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUIgICygvBWB5prpfx61y1HDAwo37kYP3YRJBvAjtunBAur3wYSFzWUDEAAIABAACAAAAAgAEAAAABAAAAAAA=</pre>
|
<pre>cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcBBBYAFMeKRXJTVYKNVlgHTdUmDV/LaYUwIgYDFZrAGqDVh1TEtNi300ntHt/PCzYrT2tVEGcjooWPhRYYSFzWUDEAAIABAACAAAAAgAEAAAAAAAAAAAEBIICEHgAAAAAAF6kUyPLL+cphRyyI5GTUazV0hF2R2NWHAQcXFgAUX4BmVeWSTJIEwtUb5TlPS/ntohABCGsCRzBEAiBnu3tA3yWlT0WBClsXXS9j69Bt+waCs9JcjWtNjtv7VgIge2VYAaBeLPDB6HGFlpqOENXMldsJezF9Gs5amvDQRDQBIQJl1jz1tBt8hNx2owTm+4Du4isx0pmdKNMNIjjaMHFfrQABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUIgICygvBWB5prpfx61y1HDAwo37kYP3YRJBvAjtunBAur3wYSFzWUDEAAIABAACAAAAAgAEAAAABAAAAAAA=</pre>
|
||||||
|
@ -643,7 +639,7 @@ A successful exchange with:
|
||||||
|
|
||||||
==Backward compatibility==
|
==Backward compatibility==
|
||||||
|
|
||||||
The receivers are advertising payjoin capabilities through [[bip-0021.mediawiki|BIP21's URI Scheme]].
|
The receivers advertise payjoin capabilities through [[bip-0021.mediawiki|BIP21's URI Scheme]].
|
||||||
|
|
||||||
Senders not supporting payjoin will just ignore the <code>pj</code> variable and thus, will proceed to normal payment.
|
Senders not supporting payjoin will just ignore the <code>pj</code> variable and thus, will proceed to normal payment.
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ After adding inputs to the transaction, the receiver generally will want to adju
|
||||||
|
|
||||||
=== Returning the partial transaction ===
|
=== Returning the partial transaction ===
|
||||||
|
|
||||||
The receiver must sign all contributed inputs in the partial transaction. The partial transaction should also remove all witnesses from the the original template transaction as they are no longer valid, and need to be recalculated by the sender. The receiver returns the partial transaction as a binary-encoded HTTP response with a status code of 200. To ensure compatibility with web-wallets and browser-based-tools, all responses (including errors) must contain the HTTP header "Access-Control-Allow-Origin: *"
|
The receiver must sign all contributed inputs in the partial transaction. The partial transaction should also remove all witnesses from the original template transaction as they are no longer valid, and need to be recalculated by the sender. The receiver returns the partial transaction as a binary-encoded HTTP response with a status code of 200. To ensure compatibility with web-wallets and browser-based-tools, all responses (including errors) must contain the HTTP header "Access-Control-Allow-Origin: *"
|
||||||
|
|
||||||
|
|
||||||
=== Sender Validation ===
|
=== Sender Validation ===
|
||||||
|
|
|
@ -61,7 +61,7 @@ Additional registered version bytes are listed in [[https://github.com/satoshila
|
||||||
|
|
||||||
==Backwards Compatibility==
|
==Backwards Compatibility==
|
||||||
|
|
||||||
This BIP is not backwards compatible by design as described under [#considerations]. An incompatible wallet will not discover accounts at all and the user will notice that something is wrong.
|
This BIP is not backwards compatible by design as described under [[#considerations|considerations]]. An incompatible wallet will not discover accounts at all and the user will notice that something is wrong.
|
||||||
|
|
||||||
==Test vectors==
|
==Test vectors==
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
Layer: Applications
|
Layer: Applications
|
||||||
Title: Deterministic Entropy From BIP32 Keychains
|
Title: Deterministic Entropy From BIP32 Keychains
|
||||||
Author: Ethan Kosakovsky <ethankosakovsky@protonmail.com>
|
Author: Ethan Kosakovsky <ethankosakovsky@protonmail.com>
|
||||||
|
Aneesh Karve <dowsing.seaport0d@icloud.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0085
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0085
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2020-03-20
|
Created: 2020-03-20
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
@ -14,15 +15,19 @@
|
||||||
|
|
||||||
==Abstract==
|
==Abstract==
|
||||||
|
|
||||||
''"One Seed to rule them all,''
|
''"One Seed to rule them all,''<br>
|
||||||
''One Key to find them,''
|
''One Key to find them,''<br>
|
||||||
''One Path to bring them all,''
|
''One Path to bring them all,''<br>
|
||||||
''And in cryptography bind them."''
|
''And in cryptography bind them."''
|
||||||
|
|
||||||
It is not possible to maintain one single (mnemonic) seed backup for all keychains used across various wallets because there are a variety of incompatible standards. Sharing of seeds across multiple wallets is not desirable for security reasons. Physical storage of multiple seeds is difficult depending on the security and redundancy required.
|
It is not possible to maintain one single (mnemonic) seed backup for all keychains used across various wallets because there are a variety of incompatible standards. Sharing of seeds across multiple wallets is not desirable for security reasons. Physical storage of multiple seeds is difficult depending on the security and redundancy required.
|
||||||
|
|
||||||
As HD keychains are essentially derived from initial entropy, this proposal provides a way to derive entropy from the keychain which can be fed into whatever method a wallet uses to derive the initial mnemonic seed or root key.
|
As HD keychains are essentially derived from initial entropy, this proposal provides a way to derive entropy from the keychain which can be fed into whatever method a wallet uses to derive the initial mnemonic seed or root key.
|
||||||
|
|
||||||
|
==Copyright==
|
||||||
|
|
||||||
|
This BIP is dual-licensed under the Open Publication License and BSD 2-clause license.
|
||||||
|
|
||||||
==Definitions==
|
==Definitions==
|
||||||
|
|
||||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
|
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
|
||||||
|
@ -33,13 +38,16 @@ The terminology related to keychains used in the wild varies widely, for example
|
||||||
# '''BIP39 mnemonic''' is the mnemonic phrase that is calculated from the entropy used before hashing of the mnemonic in BIP39.
|
# '''BIP39 mnemonic''' is the mnemonic phrase that is calculated from the entropy used before hashing of the mnemonic in BIP39.
|
||||||
# '''BIP39 seed''' is the result of hashing the BIP39 mnemonic seed.
|
# '''BIP39 seed''' is the result of hashing the BIP39 mnemonic seed.
|
||||||
|
|
||||||
|
When in doubt, assume big endian byte serialization, such that the leftmost
|
||||||
|
byte is the most significant.
|
||||||
|
|
||||||
==Motivation==
|
==Motivation==
|
||||||
|
|
||||||
Most wallets implement BIP32 which defines how a BIP32 root key can be used to derive keychains. As a consequence, a backup of just the BIP32 root key is sufficient to include all keys derived from it. BIP32 does not have a human friendly serialization of the BIP32 root key (or BIP32 extended keys in general) which makes paper backups or manually restoring the key more error-prone. BIP39 was designed to solve this problem but rather than serialize the BIP32 root key, it takes some entropy, encoded to a "seed mnemonic", which is then hashed to derive the BIP39 seed which can be turned into the BIP32 root key. Saving the BIP39 mnemonic is enough to reconstruct the entire BIP32 keychain, but a BIP32 root key cannot be reversed back to the BIP39 mnemonic.
|
Most wallets implement BIP32 which defines how a BIP32 root key can be used to derive keychains. As a consequence, a backup of just the BIP32 root key is sufficient to include all keys derived from it. BIP32 does not have a human-friendly serialization of the BIP32 root key (or BIP32 extended keys in general), which makes paper backups or manually restoring the key more error-prone. BIP39 was designed to solve this problem, but rather than serialize the BIP32 root key, it takes some entropy, encoded to a "seed mnemonic", which is then hashed to derive the BIP39 seed, which can be turned into the BIP32 root key. Saving the BIP39 mnemonic is enough to reconstruct the entire BIP32 keychain, but a BIP32 root key cannot be reversed back to the BIP39 mnemonic.
|
||||||
|
|
||||||
Most wallets implement BIP39, so on initialization or restoration, the user must interact with a BIP39 mnemonic. Most wallets do not support BIP32 extended private keys, so each wallet must either share the same BIP39 mnemonic, or have a separate BIP39 mnemonic entirely. Neither scenarios are particularly satisfactory for security reasons. For example, some wallets may be inherently less secure like hot wallets on smartphones, Join Market servers, or Lightning Network nodes. Having multiple seeds is far from desirable, especially for those who rely on split key or redundancy backups in different geological locations. Adding is necessarily difficult and may result in users being more lazy with subsequent keys, resulting in compromised security or loss of keys.
|
Most wallets implement BIP39, so on initialization or restoration, the user must interact with a BIP39 mnemonic. Most wallets do not support BIP32 extended private keys, so each wallet must either share the same BIP39 mnemonic, or have a separate BIP39 mnemonic entirely. Neither scenario is particularly satisfactory for security reasons. For example, some wallets may be inherently less secure, like hot wallets on smartphones, JoinMarket servers, or Lightning Network nodes. Having multiple seeds is far from desirable, especially for those who rely on split key or redundancy backups in different geological locations. Adding keys is necessarily difficult and may result in users being more lazy with subsequent keys, resulting in compromised security or loss of keys.
|
||||||
|
|
||||||
There is added complication with wallets that implement other standards, or no standards at all. Bitcoin Core wallet uses a WIF as the ''hdseed'', and yet other wallets like Electrum use different mnemonic schemes to derive the BIP32 root key. Other cryptocurrencies like Monero also use an entirely different mnemonic scheme.
|
There is an added complication with wallets that implement other standards, or no standards at all. The Bitcoin Core wallet uses a WIF as the ''hdseed'', and yet other wallets, like Electrum, use different mnemonic schemes to derive the BIP32 root key. Other cryptocurrencies, like Monero, use an entirely different mnemonic scheme.
|
||||||
|
|
||||||
Ultimately, all of the mnemonic/seed schemes start with some "initial entropy" to derive a mnemonic/seed, and then process the mnemonic into a BIP32 key, or private key. We can use BIP32 itself to derive the "initial entropy" to then recreate the same mnemonic or seed according to the specific application standard of the target wallet. We can use a BIP44-like categorization to ensure uniform derivation according to the target application type.
|
Ultimately, all of the mnemonic/seed schemes start with some "initial entropy" to derive a mnemonic/seed, and then process the mnemonic into a BIP32 key, or private key. We can use BIP32 itself to derive the "initial entropy" to then recreate the same mnemonic or seed according to the specific application standard of the target wallet. We can use a BIP44-like categorization to ensure uniform derivation according to the target application type.
|
||||||
|
|
||||||
|
@ -47,9 +55,13 @@ Ultimately, all of the mnemonic/seed schemes start with some "initial entropy" t
|
||||||
|
|
||||||
We assume a single BIP32 master root key. This specification is not concerned with how this was derived (e.g. directly or via a mnemonic scheme such as BIP39).
|
We assume a single BIP32 master root key. This specification is not concerned with how this was derived (e.g. directly or via a mnemonic scheme such as BIP39).
|
||||||
|
|
||||||
For each application that requires its own wallet, a unique private key is derived from the BIP32 master root key using a fully hardened derivation path. The resulting private key (k) is then processed with HMAC-SHA512, where the key is "bip-entropy-from-k", and the message payload is the private key k: <code>HMAC-SHA512(key="bip-entropy-from-k", msg=k)</code>. The result produces 512 bits of entropy. Each application SHOULD use up to the required number of bits necessary for their operation truncating the rest.
|
For each application that requires its own wallet, a unique private key is derived from the BIP32 master root key using a fully hardened derivation path. The resulting private key (k) is then processed with HMAC-SHA512, where the key is "bip-entropy-from-k", and the message payload is the private key k: <code>HMAC-SHA512(key="bip-entropy-from-k", msg=k)</code>
|
||||||
|
<ref name="hmac-sha512">
|
||||||
|
The reason for running the derived key through HMAC-SHA512 and truncating the result as necessary is to prevent leakage of the parent tree should the derived key (''k'') be compromised. While the specification requires the use of hardended key derivation which would prevent this, we cannot enforce hardened derivation, so this method ensures the derived entropy is hardened. Also, from a semantic point of view, since the purpose is to derive entropy and not a private key, we are required to transform the child key. This is done out of an abundance of caution, in order to ward off unwanted side effects should ''k'' be used for a dual purpose, including as a nonce ''hash(k)'', where undesirable and unforeseen interactions could occur.
|
||||||
|
</ref>.
|
||||||
|
The result produces 512 bits of entropy. Each application SHOULD use up to the required number of bits necessary for their operation, and truncate the rest.
|
||||||
|
|
||||||
The HMAC-SHA512 function is specified in [http://tools.ietf.org/html/rfc4231 RFC 4231].
|
The HMAC-SHA512 function is specified in [https://tools.ietf.org/html/rfc4231 RFC 4231].
|
||||||
|
|
||||||
===Test vectors===
|
===Test vectors===
|
||||||
|
|
||||||
|
@ -78,7 +90,7 @@ BIP85-DRNG-SHAKE256 is a deterministic random number generator for cryptographic
|
||||||
RSA key generation is an example of a function that requires orders of magnitude more than 64 bytes of random input. Further, it is not possible to precalculate the amount of random input required until the function has completed.
|
RSA key generation is an example of a function that requires orders of magnitude more than 64 bytes of random input. Further, it is not possible to precalculate the amount of random input required until the function has completed.
|
||||||
|
|
||||||
drng_reader = BIP85DRNG.new(bip85_entropy)
|
drng_reader = BIP85DRNG.new(bip85_entropy)
|
||||||
rsa_key = RSA.generate_key(4096, drng_reader.read())
|
rsa_key = RSA.generate_key(4096, drng_reader.read)
|
||||||
|
|
||||||
===Test Vectors===
|
===Test Vectors===
|
||||||
INPUT:
|
INPUT:
|
||||||
|
@ -91,28 +103,13 @@ OUTPUT
|
||||||
|
|
||||||
* DRNG(80 bytes)=b78b1ee6b345eae6836c2d53d33c64cdaf9a696487be81b03e822dc84b3f1cd883d7559e53d175f243e4c349e822a957bbff9224bc5dde9492ef54e8a439f6bc8c7355b87a925a37ee405a7502991111
|
* DRNG(80 bytes)=b78b1ee6b345eae6836c2d53d33c64cdaf9a696487be81b03e822dc84b3f1cd883d7559e53d175f243e4c349e822a957bbff9224bc5dde9492ef54e8a439f6bc8c7355b87a925a37ee405a7502991111
|
||||||
|
|
||||||
==Reference Implementation==
|
|
||||||
|
|
||||||
* Python library implementation: [https://github.com/ethankosakovsky/bip85]
|
|
||||||
* JavaScript library implementation: [https://github.com/hoganri/bip85-js]
|
|
||||||
|
|
||||||
===Other Implementations===
|
|
||||||
|
|
||||||
* JavaScript library implementation: [https://github.com/hoganri/bip85-js]
|
|
||||||
|
|
||||||
* Coldcard Firmware: [https://github.com/Coldcard/firmware/pull/39]
|
|
||||||
|
|
||||||
* Ian Coleman's Mnemonic Code Converter: [https://github.com/iancoleman/bip39] and [https://iancoleman.io/bip39/]
|
|
||||||
|
|
||||||
* AirGap Vault: [https://github.com/airgap-it/airgap-vault/commit/d64332fc2f332be622a1229acb27f621e23774d6]
|
|
||||||
|
|
||||||
* btc_hd_wallet: [https://github.com/scgbckbone/btc-hd-wallet]
|
|
||||||
|
|
||||||
==Applications==
|
==Applications==
|
||||||
|
|
||||||
The Application number defines how entropy will be used post processing. Some basic examples follow:
|
The Application number defines how entropy will be used post processing. Some basic examples follow:
|
||||||
|
|
||||||
Derivation path uses the format <code>m/83696968'/{app_no}'/{index}'</code> where ''{app_no}'' is the path for the application, and ''{index}'' is the index.
|
Derivation paths follow the format <code>m/83696968'/{app_no}'/{index}'</code>, where ''{app_no}'' is the path for the application, and ''{index}'' is the index.
|
||||||
|
|
||||||
|
Application numbers should be semantic in some way, such as a BIP number or ASCII character code sequence.
|
||||||
|
|
||||||
===BIP39===
|
===BIP39===
|
||||||
Application number: 39'
|
Application number: 39'
|
||||||
|
@ -155,6 +152,10 @@ Language Table
|
||||||
|-
|
|-
|
||||||
| Czech
|
| Czech
|
||||||
| 8'
|
| 8'
|
||||||
|
|-
|
||||||
|
| Portuguese
|
||||||
|
| 9'
|
||||||
|
|-
|
||||||
|}
|
|}
|
||||||
|
|
||||||
Words Table
|
Words Table
|
||||||
|
@ -168,10 +169,18 @@ Words Table
|
||||||
| 128 bits
|
| 128 bits
|
||||||
| 12'
|
| 12'
|
||||||
|-
|
|-
|
||||||
|
| 15 words
|
||||||
|
| 160 bits
|
||||||
|
| 15'
|
||||||
|
|-
|
||||||
| 18 words
|
| 18 words
|
||||||
| 192 bits
|
| 192 bits
|
||||||
| 18'
|
| 18'
|
||||||
|-
|
|-
|
||||||
|
| 21 words
|
||||||
|
| 224 bits
|
||||||
|
| 21'
|
||||||
|
|-
|
||||||
| 24 words
|
| 24 words
|
||||||
| 256 bits
|
| 256 bits
|
||||||
| 24'
|
| 24'
|
||||||
|
@ -219,7 +228,16 @@ OUTPUT:
|
||||||
===HD-Seed WIF===
|
===HD-Seed WIF===
|
||||||
Application number: 2'
|
Application number: 2'
|
||||||
|
|
||||||
Uses 256 bits[1] of entropy as the secret exponent to derive a private key and encode as a compressed WIF which will be used as the hdseed for Bitcoin Core wallets.
|
Uses the most significant 256 bits<ref name="curve-order">
|
||||||
|
There is a very small chance that you'll make an invalid
|
||||||
|
key that is zero or larger than the order of the curve. If this occurs, software
|
||||||
|
should hard fail (forcing users to iterate to the next index). From BIP32:
|
||||||
|
<blockquote>
|
||||||
|
In case parse<sub>256</sub>(I<sub>L</sub>) ≥ n or k<sub>i</sub> = 0, the resulting key is invalid, and one should proceed with the next value for i. (Note: this has probability lower than 1 in 2<sup>127</sup>.)
|
||||||
|
</blockquote>
|
||||||
|
</ref>
|
||||||
|
of entropy as the secret exponent to derive a private key and encode as a compressed
|
||||||
|
WIF that will be used as the hdseed for Bitcoin Core wallets.
|
||||||
|
|
||||||
Path format is <code>m/83696968'/2'/{index}'</code>
|
Path format is <code>m/83696968'/2'/{index}'</code>
|
||||||
|
|
||||||
|
@ -234,7 +252,11 @@ OUTPUT
|
||||||
===XPRV===
|
===XPRV===
|
||||||
Application number: 32'
|
Application number: 32'
|
||||||
|
|
||||||
Taking 64 bytes of the HMAC digest, the first 32 bytes are the chain code, and second 32 bytes[1] are the private key for BIP32 XPRV value. Child number, depth, and parent fingerprint are forced to zero.
|
Taking 64 bytes of the HMAC digest, the first 32 bytes are the chain code, and the second 32 bytes<ref name="curve-order" /> are the private key for the BIP32 XPRV value. Child number, depth, and parent fingerprint are forced to zero.
|
||||||
|
|
||||||
|
''Warning'': The above order reverses the order of BIP32, which takes the first 32 bytes as the private key, and the second 32 bytes as the chain code.
|
||||||
|
|
||||||
|
Applications may support Testnet by emitting TPRV keys if and only if the input root key is a Testnet key.
|
||||||
|
|
||||||
Path format is <code>m/83696968'/32'/{index}'</code>
|
Path format is <code>m/83696968'/32'/{index}'</code>
|
||||||
|
|
||||||
|
@ -269,7 +291,7 @@ The derivation path format is: <code>m/83696968'/707764'/{pwd_len}'/{index}'</co
|
||||||
|
|
||||||
`20 <= pwd_len <= 86`
|
`20 <= pwd_len <= 86`
|
||||||
|
|
||||||
[https://datatracker.ietf.org/doc/html/rfc4648 Base64] encode the all 64 bytes of entropy.
|
[https://datatracker.ietf.org/doc/html/rfc4648 Base64] encode all 64 bytes of entropy.
|
||||||
Remove any spaces or new lines inserted by Base64 encoding process. Slice base64 result string
|
Remove any spaces or new lines inserted by Base64 encoding process. Slice base64 result string
|
||||||
on index 0 to `pwd_len`. This slice is the password. As `pwd_len` is limited to 86, passwords will not contain padding.
|
on index 0 to `pwd_len`. This slice is the password. As `pwd_len` is limited to 86, passwords will not contain padding.
|
||||||
|
|
||||||
|
@ -297,7 +319,7 @@ INPUT:
|
||||||
* PATH: m/83696968'/707764'/21'/0'
|
* PATH: m/83696968'/707764'/21'/0'
|
||||||
|
|
||||||
OUTPUT
|
OUTPUT
|
||||||
* DERIVED ENTROPY=d7ad61d4a76575c5bad773feeb40299490b224e8e5df6c8ad8fe3d0a6eed7b85ead9fef7bcca8160f0ee48dc6e92b311fc71f2146623cc6952c03ce82c7b63fe
|
* DERIVED ENTROPY=74a2e87a9ba0cdd549bdd2f9ea880d554c6c355b08ed25088cfa88f3f1c4f74632b652fd4a8f5fda43074c6f6964a3753b08bb5210c8f5e75c07a4c2a20bf6e9
|
||||||
* DERIVED PWD=dKLoepugzdVJvdL56ogNV
|
* DERIVED PWD=dKLoepugzdVJvdL56ogNV
|
||||||
|
|
||||||
===PWD BASE85===
|
===PWD BASE85===
|
||||||
|
@ -307,7 +329,7 @@ The derivation path format is: <code>m/83696968'/707785'/{pwd_len}'/{index}'</co
|
||||||
|
|
||||||
`10 <= pwd_len <= 80`
|
`10 <= pwd_len <= 80`
|
||||||
|
|
||||||
Base85 encode the all 64 bytes of entropy.
|
Base85 encode all 64 bytes of entropy.
|
||||||
Remove any spaces or new lines inserted by Base64 encoding process. Slice base85 result string
|
Remove any spaces or new lines inserted by Base64 encoding process. Slice base85 result string
|
||||||
on index 0 to `pwd_len`. This slice is the password. `pwd_len` is limited to 80 characters.
|
on index 0 to `pwd_len`. This slice is the password. `pwd_len` is limited to 80 characters.
|
||||||
|
|
||||||
|
@ -327,7 +349,7 @@ Entropy = log2(R ** L)<br>
|
||||||
|-
|
|-
|
||||||
| 30 || 192.0
|
| 30 || 192.0
|
||||||
|-
|
|-
|
||||||
| 20 || 512.0
|
| 80 || 512.0
|
||||||
|}
|
|}
|
||||||
|
|
||||||
INPUT:
|
INPUT:
|
||||||
|
@ -368,31 +390,90 @@ GPG capable smart-cards SHOULD be loaded as follows: The encryption slot SHOULD
|
||||||
|
|
||||||
However, depending on available slots on the smart-card, and preferred policy, the CERTIFY capable key MAY be flagged with CERTIFY and SIGNATURE capabilities and loaded into the SIGNATURE capable slot (for example where the smart-card has only three slots and the CERTIFY capability is required on the same card). In this case, the SIGNATURE capable sub-key would be disregarded because the CERTIFY capable key serves a dual purpose.
|
However, depending on available slots on the smart-card, and preferred policy, the CERTIFY capable key MAY be flagged with CERTIFY and SIGNATURE capabilities and loaded into the SIGNATURE capable slot (for example where the smart-card has only three slots and the CERTIFY capability is required on the same card). In this case, the SIGNATURE capable sub-key would be disregarded because the CERTIFY capable key serves a dual purpose.
|
||||||
|
|
||||||
|
===DICE===
|
||||||
|
|
||||||
|
Application number: 89101'
|
||||||
|
|
||||||
|
The derivation path format is: <code>m/83696968'/89101'/{sides}'/{rolls}'/{index}'</code>
|
||||||
|
|
||||||
|
2 <= sides <= 2^32 - 1
|
||||||
|
1 <= rolls <= 2^32 - 1
|
||||||
|
|
||||||
|
Use this application to generate PIN numbers, numeric secrets, and secrets over custom alphabets.
|
||||||
|
For example, applications could generate alphanumeric passwords from a 62-sided die (26 + 26 + 10).
|
||||||
|
|
||||||
|
Roll values are zero-indexed, such that an N-sided die produces values in the range
|
||||||
|
<code>[0, N-1]</code>, inclusive. Applications should separate printed rolls by a comma or similar.
|
||||||
|
|
||||||
|
Create a BIP85 DRNG whose seed is the derived entropy.
|
||||||
|
|
||||||
|
Calculate the following integers:
|
||||||
|
|
||||||
|
bits_per_roll = ceil(log_2(sides))
|
||||||
|
bytes_per_roll = ceil(bits_per_roll / 8)
|
||||||
|
|
||||||
|
Read <code>bytes_per_roll</code> bytes from the DRNG.
|
||||||
|
Trim any bits in excess of <code>bits_per_roll</code> (retain the most
|
||||||
|
significant bits). The resulting integer represents a single roll or trial.
|
||||||
|
If the trial is greater than or equal to the number of sides, skip it and
|
||||||
|
move on to the next one. Repeat as needed until all rolls are complete.
|
||||||
|
|
||||||
|
INPUT:
|
||||||
|
* MASTER BIP32 ROOT KEY: xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
|
||||||
|
* PATH: m/83696968'/89101'/6'/10'/0'
|
||||||
|
OUTPUT
|
||||||
|
* DERIVED ENTROPY=5e41f8f5d5d9ac09a20b8a5797a3172b28c806aead00d27e36609e2dd116a59176a738804236586f668da8a51b90c708a4226d7f92259c69f64c51124b6f6cd2
|
||||||
|
* DERIVED ROLLS=1,0,0,2,0,1,5,5,2,4
|
||||||
|
|
||||||
==Backwards Compatibility==
|
==Backwards Compatibility==
|
||||||
|
|
||||||
This specification is not backwards compatible with any other existing specification.
|
This specification is not backwards compatible with any other existing specification.
|
||||||
|
|
||||||
This specification relies on BIP32 but is agnostic to how the BIP32 root key is derived. As such, this standard is able to derive wallets with initialization schemes like BIP39 or Electrum wallet style mnemonics.
|
This specification relies on BIP32 but is agnostic to how the BIP32 root key is derived. As such, this standard is able to derive wallets with initialization schemes like BIP39 or Electrum wallet style mnemonics.
|
||||||
|
|
||||||
==Discussion==
|
|
||||||
|
|
||||||
The reason for running the derived key through HMAC-SHA512 and truncating the result as necessary is to prevent leakage of the parent tree should the derived key (''k'') be compromized. While the specification requires the use of hardended key derivation which would prevent this, we cannot enforce hardened derivation, so this method ensures the derived entropy is hardened. Also, from a semantic point of view, since the purpose is to derive entropy and not a private key, we are required to transform the child key. This is done out of an abundance of caution, in order to ward off unwanted side effects should ''k'' be used for a dual purpose, including as a nonce ''hash(k)'', where undesirable and unforeseen interactions could occur.
|
|
||||||
|
|
||||||
==Acknowledgements==
|
|
||||||
|
|
||||||
Many thanks to Peter Gray and Christopher Allen for their input, and to Peter for suggesting extra application use cases.
|
|
||||||
|
|
||||||
==References==
|
==References==
|
||||||
|
|
||||||
BIP32, BIP39
|
BIP32, BIP39
|
||||||
|
|
||||||
|
==Reference Implementations==
|
||||||
|
|
||||||
|
* 1.3.0 Python 3.x library implementation: [https://github.com/akarve/bipsea]
|
||||||
|
* 1.1.0 Python 2.x library implementation: [https://github.com/ethankosakovsky/bip85]
|
||||||
|
* 1.0.0 JavaScript library implementation: [https://github.com/hoganri/bip85-js]
|
||||||
|
|
||||||
|
==Changelog==
|
||||||
|
|
||||||
|
===1.3.0 (2024-10-22)===
|
||||||
|
|
||||||
|
====Added====
|
||||||
|
|
||||||
|
* Dice application 89101'
|
||||||
|
* Czech language code to application 39'
|
||||||
|
* TPRV guidance for application 32'
|
||||||
|
* Warning on application 32' key and chain code ordering
|
||||||
|
|
||||||
|
===1.2.0 (2022-12-04)===
|
||||||
|
|
||||||
|
====Added====
|
||||||
|
|
||||||
|
* Base64 application 707764'
|
||||||
|
* Base85 application 707785'
|
||||||
|
|
||||||
|
===1.1.0 (2020-11-19)===
|
||||||
|
|
||||||
|
====Added====
|
||||||
|
|
||||||
|
* BIP85-DRNG-SHAKE256
|
||||||
|
* RSA application 828365'
|
||||||
|
|
||||||
|
===1.0.0 (2020-06-11)===
|
||||||
|
|
||||||
|
* Initial version
|
||||||
|
|
||||||
==Footnotes==
|
==Footnotes==
|
||||||
|
|
||||||
[1] There is a very small chance that you'll make an invalid key that is zero or bigger than the order of the curve. If this occurs, software should hard fail (forcing users to iterate to the next index).
|
<references />
|
||||||
|
|
||||||
From BIP32:
|
==Acknowledgements==
|
||||||
In case parse<sub>256</sub>(I<sub>L</sub>) is 0 or ≥ n, the resulting key is invalid, and one should proceed with the next value for i. (Note: this has probability lower than 1 in 2<sup>127</sup>.)
|
|
||||||
|
|
||||||
==Copyright==
|
Many thanks to Peter Gray and Christopher Allen for their input, and to Peter for suggesting extra application use cases.
|
||||||
|
|
||||||
This BIP is dual-licensed under the Open Publication License and BSD 2-clause license.
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Ava Chow <me@achow101.com>
|
Author: Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0086
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0086
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2021-06-22
|
Created: 2021-06-22
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -48,7 +48,7 @@ The second multisignature "standard" in use is m/48', which specifies:
|
||||||
m / purpose' / coin_type' / account' / script_type' / change / address_index
|
m / purpose' / coin_type' / account' / script_type' / change / address_index
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
Rather than following in BIP 44/49/84's path and having a separate BIP per script after P2SH (BIP45), vendors decided to insert <code>script_type'</code> into the derivation path (where P2SH-P2WSH=1, P2WSH=2, Future_Script=3, etc). As described previously, this is unnecessary, as the descriptor sets the script. While it attempts to reduce maintainence work by getting rid of new BIPs-per-script, it still requires maintaining an updated, redundant, <code>script_type</code> list.
|
Rather than following in BIP 44/49/84's path and having a separate BIP per script after P2SH (BIP45), vendors decided to insert <code>script_type'</code> into the derivation path (where P2SH-P2WSH=1, P2WSH=2, Future_Script=3, etc). As described previously, this is unnecessary, as the descriptor sets the script. While it attempts to reduce maintenance work by getting rid of new BIPs-per-script, it still requires maintaining an updated, redundant, <code>script_type</code> list.
|
||||||
|
|
||||||
The structure proposed later in this paper solves these issues and is quite comprehensive. It allows for the handling of multiple accounts, external and internal chains per account, and millions of addresses per chain, in a multi-party, multisignature, hierarchical deterministic wallet regardless of the script type <ref>'''Why propose this structure only for multisignature wallets?''' Currently, single-sig wallets are able to restore funds using just the master private key data (in the format of BIP39 usually). Even if the user doesn't recall the derivation used, the wallet implementation can iterate through common schemes (BIP44/49/84). With this proposed hierarchy, the user would either have to now backup additional data (the descriptor), or the wallet would have to attempt all script types for every account level when restoring. Because of this, even though the descriptor language handles the signature type just like it does the script type, it is best to restrict this script-agnostic hierarchy to multisignature wallets only.</ref>.
|
The structure proposed later in this paper solves these issues and is quite comprehensive. It allows for the handling of multiple accounts, external and internal chains per account, and millions of addresses per chain, in a multi-party, multisignature, hierarchical deterministic wallet regardless of the script type <ref>'''Why propose this structure only for multisignature wallets?''' Currently, single-sig wallets are able to restore funds using just the master private key data (in the format of BIP39 usually). Even if the user doesn't recall the derivation used, the wallet implementation can iterate through common schemes (BIP44/49/84). With this proposed hierarchy, the user would either have to now backup additional data (the descriptor), or the wallet would have to attempt all script types for every account level when restoring. Because of this, even though the descriptor language handles the signature type just like it does the script type, it is best to restrict this script-agnostic hierarchy to multisignature wallets only.</ref>.
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ This BIP is licensed under the 2-clause BSD license.
|
||||||
|
|
||||||
BIP32 derivation path format is universal, and a number of schemes for derivation were proposed
|
BIP32 derivation path format is universal, and a number of schemes for derivation were proposed
|
||||||
in BIP43 and other documents, such as BIPs 44,45,49,84. The flexibility of the format also allowed
|
in BIP43 and other documents, such as BIPs 44,45,49,84. The flexibility of the format also allowed
|
||||||
industry participants to implement custom derivation shemes that fit particular purposes,
|
industry participants to implement custom derivation schemes that fit particular purposes,
|
||||||
but not necessarily useful in general.
|
but not necessarily useful in general.
|
||||||
|
|
||||||
Even when existing BIPs for derivation schemes are used, their usage is not uniform across
|
Even when existing BIPs for derivation schemes are used, their usage is not uniform across
|
||||||
|
@ -41,18 +41,18 @@ addresses differently than the one they used before.
|
||||||
The problem is common enough to warrant the creation of a dedicated website
|
The problem is common enough to warrant the creation of a dedicated website
|
||||||
([https://walletsrecovery.org/ walletsrecovery.org]) that tracks paths used by different wallets.
|
([https://walletsrecovery.org/ walletsrecovery.org]) that tracks paths used by different wallets.
|
||||||
|
|
||||||
At the time of writing, this website has used their own format to succintly describe multiple
|
At the time of writing, this website has used their own format to succinctly describe multiple
|
||||||
derivation paths. As far as author knows, it was the only publicitly used format to describe
|
derivation paths. As far as author knows, it was the only publicly used format to describe
|
||||||
path templates before introduction of this BIP. The format was not specified anywhere beside
|
path templates before introduction of this BIP. The format was not specified anywhere beside
|
||||||
the main page of the website. It used <code>|</code> to denote alternative derivation indexes
|
the main page of the website. It used <code>|</code> to denote alternative derivation indexes
|
||||||
(example: <code>m/|44'|49'|84'/0'/0'</code>) or whole alternative paths (<code>m/44'/0'/0'|m/44'/1'/0'</code>).
|
(example: <code>m/|44'|49'|84'/0'/0'</code>) or whole alternative paths (<code>m/44'/0'/0'|m/44'/1'/0'</code>).
|
||||||
|
|
||||||
It was not declared as a template format to use for processing by software, and seems to be
|
It was not declared as a template format to use for processing by software, and seems to be
|
||||||
an ad-hoc format only intended for illustration. In contrast to this ad-hoc format, the format
|
an ad-hoc format only intended for illustration. In contrast to this ad-hoc format, the format
|
||||||
described in this BIP is intended for unambigouos parsing by software, and to be easily read by humans
|
described in this BIP is intended for unambiguous parsing by software, and to be easily read by humans
|
||||||
at the same time. Humans can visually detect the 'templated' parts of the path more easily than the use
|
at the same time. Humans can visually detect the 'templated' parts of the path more easily than the use
|
||||||
of <code>|</code> in the template could allow. Wider range of paths can be defined in a single template more
|
of <code>|</code> in the template could allow. Wider range of paths can be defined in a single template more
|
||||||
succintly and unambiguously.
|
succinctly and unambiguously.
|
||||||
|
|
||||||
===Intended use and advantages===
|
===Intended use and advantages===
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ into using well-known paths, or convince other vendors to support their custom p
|
||||||
scales poorly.
|
scales poorly.
|
||||||
|
|
||||||
A flexible approach proposed in this document is to define a standard notation for "BIP32 path templates"
|
A flexible approach proposed in this document is to define a standard notation for "BIP32 path templates"
|
||||||
that succintly describes the constraints to impose on the derivation path.
|
that succinctly describes the constraints to impose on the derivation path.
|
||||||
|
|
||||||
Wide support for these path templates will increase interoperability and flexibility of solutions,
|
Wide support for these path templates will increase interoperability and flexibility of solutions,
|
||||||
and will allow vendors and individual developers to easily define their own custom restrictions.
|
and will allow vendors and individual developers to easily define their own custom restrictions.
|
||||||
|
@ -89,7 +89,7 @@ installation of malicious or incorrect profiles, though.
|
||||||
|
|
||||||
==Specification==
|
==Specification==
|
||||||
|
|
||||||
The format for the template was chosen to make it easy to read, convenient and visually unambigous.
|
The format for the template was chosen to make it easy to read, convenient and visually unambiguous.
|
||||||
|
|
||||||
Template starts with optional prefix <code>m/</code>, and then one or more sections delimited by the slash character (<code>/</code>).
|
Template starts with optional prefix <code>m/</code>, and then one or more sections delimited by the slash character (<code>/</code>).
|
||||||
|
|
||||||
|
|
|
@ -354,7 +354,7 @@ Instead every different language has it own word list (or word lists) and each c
|
||||||
We would need to encode the choice of word list in our share's meta-data, which takes up even more room, and is difficult to specify due to the ever-evolving choice of word lists.
|
We would need to encode the choice of word list in our share's meta-data, which takes up even more room, and is difficult to specify due to the ever-evolving choice of word lists.
|
||||||
|
|
||||||
Alternatively we could standardize on the choice of the English word list, something that is nearly a de facto standard, and simply be incompatible with BIP-0039 wallets of other languages.
|
Alternatively we could standardize on the choice of the English word list, something that is nearly a de facto standard, and simply be incompatible with BIP-0039 wallets of other languages.
|
||||||
Such a choice also risks users of BIP-0039 recovering their entropy from their language, encoding it in in Codex32 and then failing to recover their wallet because the English word lists has replaced their language's word list.
|
Such a choice also risks users of BIP-0039 recovering their entropy from their language, encoding it in Codex32 and then failing to recover their wallet because the English word lists has replaced their language's word list.
|
||||||
|
|
||||||
The main advantage of this alternative approach would be that wallets could give users an option switch between backing up their entropy as a BIP-0039 mnemonic and in Codex32 format, but again, only if their language choice happens to be the English word list.
|
The main advantage of this alternative approach would be that wallets could give users an option switch between backing up their entropy as a BIP-0039 mnemonic and in Codex32 format, but again, only if their language choice happens to be the English word list.
|
||||||
In practice, we do not expect users in switch back and forth between backup formats, and instead just generate a fresh master seed using Codex32.
|
In practice, we do not expect users in switch back and forth between backup formats, and instead just generate a fresh master seed using Codex32.
|
||||||
|
|
120
bip-0094.mediawiki
Normal file
120
bip-0094.mediawiki
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 94
|
||||||
|
Layer: Applications
|
||||||
|
Title: Testnet 4
|
||||||
|
Author: Fabian Jahr <fjahr@protonmail.com>
|
||||||
|
Comments-Summary: No comments yet.
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0094
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2024-05-27
|
||||||
|
License: CC0-1.0
|
||||||
|
Post-History: https://gnusha.org/pi/bitcoindev/CADL_X_eXjbRFROuJU0b336vPVy5Q2RJvhcx64NSNPH-3fDCUfw@mail.gmail.com/
|
||||||
|
https://gnusha.org/pi/bitcoindev/a6e3VPsXJf9p3gt_FmNF_Up-wrFuNMKTN30-xCSDHBKXzXnSpVflIZIj2NQ8Wos4PhQCzI2mWEMvIms_FAEs7rQdL15MpC_Phmu_fnR9iTg=@protonmail.com/
|
||||||
|
https://github.com/bitcoin/bitcoin/pull/29775
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
== Abstract ==
|
||||||
|
|
||||||
|
A new test network with the goal to replace Testnet 3. This network comes with small but important improvements of the consensus rules, that should make it impractical to attack the network using only CPU mining.
|
||||||
|
|
||||||
|
== Motivation ==
|
||||||
|
|
||||||
|
Quoting the original mailing list post from Jameson Lopp<ref>https://gnusha.org/pi/bitcoindev/CADL_X_eXjbRFROuJU0b336vPVy5Q2RJvhcx64NSNPH-3fDCUfw@mail.gmail.com/</ref>:
|
||||||
|
|
||||||
|
<blockquote><poem>
|
||||||
|
Testnet3 has been running for 13 years. It's on block 2.5 million something and the block reward is down to ~0.014 TBTC, so mining is not doing a great job at distributing testnet coins anymore.
|
||||||
|
|
||||||
|
The reason the block height is insanely high is due to a rather amusing edge case bug that causes the difficulty to regularly get reset to 1, which causes a bit of havoc. If you want a deep dive into the quirk: https://blog.lopp.net/the-block-storms-of-bitcoins-testnet/
|
||||||
|
|
||||||
|
Testnet3 is being actively used for scammy airdrops; those of us who tend to be generous with our testnet coins are getting hounded by non-developers chasing cheap gains.
|
||||||
|
|
||||||
|
As a result, TBTC is being actively bought and sold; one could argue that the fundamental principle of testnet coins having no value has been broken.
|
||||||
|
</poem></blockquote>
|
||||||
|
|
||||||
|
Since then the issue with block storms has been further demonstrated on Testnet 3 when three years' worth of blocks were mined in a few weeks while rendering the network practically unusable at the same time.
|
||||||
|
|
||||||
|
== Specification ==
|
||||||
|
|
||||||
|
Consensus of Testnet 4 follows the same rules as mainnet with the exception of the three rules detailed below. Additionally all soft forks that are active on mainnet as of May 2024 are enforced from genesis.
|
||||||
|
|
||||||
|
=== 20-minute Exception ===
|
||||||
|
|
||||||
|
This rule was already previously implemented and active in Testnet 3<ref>https://github.com/bitcoin/bitcoin/pull/686</ref>.
|
||||||
|
|
||||||
|
A block with a timestamp that is more than 20 minutes past the timestamp of the previous block must have a minimum difficulty of 1 (the network's minimum difficulty) instead of whatever the actual difficulty level currently is. This applies to all blocks in a difficulty period except for the first block. This means the blocks must change their <code>nBits</code> field from the actual difficulty level to the minimum difficulty value <code>0x1d00ffff</code>.
|
||||||
|
|
||||||
|
This rule also led to the block storms<ref>https://blog.lopp.net/the-block-storms-of-bitcoins-testnet/</ref> which the following rule seeks to fix.
|
||||||
|
|
||||||
|
=== Block Storm Fix ===
|
||||||
|
|
||||||
|
The work required for a new difficulty period is calculated as multiplication factor to the difficulty of the previous period (but no less than 1/4th and no more than 4x), depending on the duration of the previous difficulty period. On Mainnet and Testnet 3, this factor is applied to the difficulty value of the last block.
|
||||||
|
|
||||||
|
Block storms happen organically whenever the 20-minute exception is applied to a difficulty period’s last block, causing the block to be mined at a difficulty of 1. The difficulty adjustment rules then limit the subsequent period’s difficulty to a value between 1 (the minimum) and 4. Blocks will be generated rapidly in the subsequent low-difficulty periods while the difficulty climbs back to an adequate range. An arbitrarily large number of blocks can be generated quickly by repeatedly using the 20-minute exception on every last block of difficulty periods. The block storm is then bounded only by miner hash rate, the need for last blocks to have a timestamp 20 minutes after the second to last block, the Median-Time-Past nTime rule, and the requirement that blocks can't be more than 2 hours in the future. Overall a sustained attack would eventually be limited to a maximum cadence of six blocks per second.
|
||||||
|
|
||||||
|
A block storm does not require a time warp attack, but one can be used to amplify<ref>A perpetual block storm attack with entire difficulty periods being authored in less than 3.5 days that resets the difficulty to the minimum in the last block of every difficulty period would adjust to a new actual difficulty of 4 every period. An attacker that additionally leverages a time warp attack would start their attack by holding back timestamps until the latest block’s timestamp is at least two weeks in the past, and then limiting their block rate to six blocks per second, incrementing the timestamp on every sixth block. Only on the last block they would use the current time, which both resets the difficulty to one per the 20-minute exception and would result in a difficulty adjustment keeping the difficulty at the minimum due to the elapsed time exceeding the target. This would allow lower the difficulty for all blocks to difficulty 1 instead of difficulty 4</ref> it.
|
||||||
|
|
||||||
|
The mitigation consists of no longer applying the adjustment factor to the last block of the previous difficulty period. Instead, the first block of the difficulty period is used as the base.
|
||||||
|
|
||||||
|
The first block must contain the actual difficulty of the network and can therefore be used as the base for the calculation of the new difficulty level. Note that the first block in new difficulty period does not allow usage of the 20-minute exception (this is prior behavior). This means that in each difficulty period the first block should always have the actual difficulty even if all other blocks were mined with the 20-minute exception.
|
||||||
|
|
||||||
|
=== Time Warp Fix ===
|
||||||
|
|
||||||
|
In addition to a time warp attack potentially exacerbating the perpetual block storm attack, a time warp attack provides an alternative way to increase the block production rate even if the unintended reset of the actual difficulty due to the 20-minute exception was mitigated.
|
||||||
|
|
||||||
|
To protect against the time warp attack, the following rule proposed as part of The Great Consensus Cleanup<ref>https://github.com/TheBlueMatt/bips/blob/cleanup-softfork/bip-XXXX.mediawiki</ref> is enforced: "The nTime field of each block whose height, mod 2016, is 0 must be greater than or equal to the nTime field of the immediately prior block minus 600. For the avoidance of doubt, such blocks must still comply with existing Median-Time-Past nTime restrictions."
|
||||||
|
|
||||||
|
== Rationale ==
|
||||||
|
|
||||||
|
The applied changes were the result of discussions on the mailing list and the PR. The selected changes try to strike a balance between minimal changes to the network (keeping it as close to mainnet as possible) while making it more robust against attackers that try to disrupt the network. Several alternative designs were considered:
|
||||||
|
|
||||||
|
* For the block storm fix an alternative fix could have been to prevent the last block in a difficulty period from applying the existing difficulty exception. Both solutions were deemed acceptable and there was no clear preference among reviewers.
|
||||||
|
* Removal of the 20-minute exception was discussed but dismissed since several reviewers insisted that it was a useful feature allowing non-standard transactions to be mined with just a CPU. The 20-minute exception also allows CPU users to move the chain forward (except on the first block that needs to be mined at actual difficulty) in case a large amount of hash power suddenly leaves the network. This would allow the chain to recover to a normal difficulty level faster if left stranded at high difficulty.
|
||||||
|
* Increase of minimum difficulty was discussed but dismissed as it would categorically prevent participation in the network using a CPU miner (utilizing the 20-minute exception).
|
||||||
|
* Increase of the delay in the 20-minute exception was suggested but did not receive significant support.
|
||||||
|
* Re-enabling <code>acceptnonstdtxn</code> in bitcoin core by default was dismissed as it had led to confusion among layer-2s that had used testnet for transaction propagation tests and expected it to behave similar to mainnet.
|
||||||
|
* Motivating miners to re-org min difficulty blocks was suggested, but was considered out of scope for this BIP, since adoption of such a mining policy remains available after Testnet 4 is deployed. As 20-minute exception blocks only contribute work corresponding to difficulty one to the chaintip, and actual difficulty blocks should have a difficulty magnitudes higher, a block mined at actual difficulty could easily replace even multiple 20-minute exception blocks.
|
||||||
|
* Persisting the real difficulty in the version field was suggested to robustly prevent exploits of the 20-minute exception while allowing it to be used on any block, but did not receive a sufficient level of support to justify the more invasive change.
|
||||||
|
|
||||||
|
One known downside of the chosen approach is that if the difficulty is gradually raised by a miner with significant hash rate, and this miner disappears, then each difficulty adjustment period requires one block at the actual difficulty.
|
||||||
|
|
||||||
|
This would cause the network to stall once per difficulty adjustment period until the real difficulty is adjusted downwards enough for the remaining hash rate to find this block in reasonable time.
|
||||||
|
|
||||||
|
== Network Parameters ==
|
||||||
|
|
||||||
|
=== Consensus Rules ===
|
||||||
|
|
||||||
|
All consensus rules active on mainnet at the time of this proposal are enforced from block 1, the newest of these rules being the Taproot softfork.
|
||||||
|
|
||||||
|
=== Genesis Block ===
|
||||||
|
|
||||||
|
* Message: <code>03/May/2024 000000000000000000001ebd58c244970b3aa9d783bb001011fbe8ea8e98e00e</code>
|
||||||
|
* Pubkey: <code>000000000000000000000000000000000000000000000000000000000000000000</code>
|
||||||
|
* Time stamp: 1714777860
|
||||||
|
* Nonce: 393743547
|
||||||
|
* Difficulty: <code>0x1d00ffff</code>
|
||||||
|
* Version: 1
|
||||||
|
|
||||||
|
The resulting genesis block hash is <code>00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043</code>, and the block hex is <code>0100000000000000000000000000000000000000000000000000000000000000000000004e7b2b9128fe0291db0693af2ae418b767e657cd407e80cb1434221eaea7a07a046f3566ffff001dbb0c78170101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5504ffff001d01044c4c30332f4d61792f323032342030303030303030303030303030303030303030303165626435386332343439373062336161396437383362623030313031316662653865613865393865303065ffffffff0100f2052a010000002321000000000000000000000000000000000000000000000000000000000000000000ac00000000</code>.
|
||||||
|
|
||||||
|
=== Message Start ===
|
||||||
|
|
||||||
|
The message start is defined as <code>0x1c163f28</code>. These four bytes were randomly generated and have no special meaning.
|
||||||
|
|
||||||
|
== Backwards Compatibility ==
|
||||||
|
|
||||||
|
The rules used by Testnet 4 are backwards compatible to the rules of Testnet 3. Existing software that implements support for Testnet 3 would only require addition of the network parameters (magic number, genesis block, etc.) to be able to follow Testnet 4.
|
||||||
|
|
||||||
|
However, implementations that only implement Testnet 3’s rules would accept a chain that violates Testnet 4’s rules and are therefore susceptible to being forked off. It is recommended that any implementations check blocks in regard to all the new rules of Testnet 4 and reject blocks that fail to comply.
|
||||||
|
|
||||||
|
== Reference implementation ==
|
||||||
|
|
||||||
|
Pull request at https://github.com/bitcoin/bitcoin/pull/29775
|
||||||
|
|
||||||
|
== References ==
|
||||||
|
|
||||||
|
<references/>
|
||||||
|
|
||||||
|
== Copyright ==
|
||||||
|
|
||||||
|
This document is licensed under the Creative Commons CC0 1.0 Universal license.
|
|
@ -63,7 +63,7 @@ Nodes with single children are not allowed.
|
||||||
|
|
||||||
The ''double-SHA256'' cryptographic hash function takes an arbitrary-length data as input and produces a 32-byte hash by running the data through the SHA-256 hash function as specified in FIPS 180-4[3], and then running the same hash function again on the 32-byte result, as a protection against length-extension attacks.
|
The ''double-SHA256'' cryptographic hash function takes an arbitrary-length data as input and produces a 32-byte hash by running the data through the SHA-256 hash function as specified in FIPS 180-4[3], and then running the same hash function again on the 32-byte result, as a protection against length-extension attacks.
|
||||||
|
|
||||||
The ''fast-SHA256'' cryptographic hash function takes two 32-byte hash values, concatenates these to produce a 64-byte buffer, and applies a single run of the SHA-256 hash function with a custom 'initialization vector' (IV) and without message paddding.
|
The ''fast-SHA256'' cryptographic hash function takes two 32-byte hash values, concatenates these to produce a 64-byte buffer, and applies a single run of the SHA-256 hash function with a custom 'initialization vector' (IV) and without message padding.
|
||||||
The result is a 32-byte 'midstate' which is the combined hash value and the label of the inner node.
|
The result is a 32-byte 'midstate' which is the combined hash value and the label of the inner node.
|
||||||
The changed IV protects against path-length extension attacks (grinding to interpret a hash as both an inner node and a leaf).
|
The changed IV protects against path-length extension attacks (grinding to interpret a hash as both an inner node and a leaf).
|
||||||
fast-SHA256 is only defined for two 32-byte inputs.
|
fast-SHA256 is only defined for two 32-byte inputs.
|
||||||
|
@ -241,16 +241,16 @@ Disallowing a node with two SKIP branches eliminates what would otherwise be a s
|
||||||
|
|
||||||
The number of hashing operations required to verify a proof is one less than the number of hashes (SKIP and VERIFY combined),
|
The number of hashing operations required to verify a proof is one less than the number of hashes (SKIP and VERIFY combined),
|
||||||
and is exactly equal to the number of inner nodes serialized as the beginning of the proof as N.
|
and is exactly equal to the number of inner nodes serialized as the beginning of the proof as N.
|
||||||
The variable-length integer encoding has the property that serialized integers, sorted lexigraphically, will also be sorted numerically.
|
The variable-length integer encoding has the property that serialized integers, sorted lexicographically, will also be sorted numerically.
|
||||||
Since the first serialized item is the number of inner nodes, sorting proofs lexigraphically has the effect of sorting the proofs by the amount of work required to verify.
|
Since the first serialized item is the number of inner nodes, sorting proofs lexicographically has the effect of sorting the proofs by the amount of work required to verify.
|
||||||
|
|
||||||
The number of hashes required as input for verification of a proof is N+1 minus the number of SKIP hashes,
|
The number of hashes required as input for verification of a proof is N+1 minus the number of SKIP hashes,
|
||||||
and can be quickly calculated without parsing the tree structure.
|
and can be quickly calculated without parsing the tree structure.
|
||||||
|
|
||||||
The coding and packing rules for the serialized tree structure were also chosen to make lexigraphical comparison useful (or at least not meaningless).
|
The coding and packing rules for the serialized tree structure were also chosen to make lexicographical comparison useful (or at least not meaningless).
|
||||||
If we consider a fully-expanded tree (no SKIP hashes, all VERIFY) to be encoding a list of elements in the order traversed depth-first from left-to-right,
|
If we consider a fully-expanded tree (no SKIP hashes, all VERIFY) to be encoding a list of elements in the order traversed depth-first from left-to-right,
|
||||||
then we can extract proofs for subsets of the list by SKIP'ing the hashes of missing values and recursively pruning any resulting SKIP,SKIP nodes.
|
then we can extract proofs for subsets of the list by SKIP'ing the hashes of missing values and recursively pruning any resulting SKIP,SKIP nodes.
|
||||||
Lexigraphically comparing the resulting serialized tree structures is the same as lexigraphically comparing lists of indices from the original list verified by the derived proof.
|
Lexicographically comparing the resulting serialized tree structures is the same as lexicographically comparing lists of indices from the original list verified by the derived proof.
|
||||||
|
|
||||||
Because the number of inner nodes and the number of SKIP hashes is extractible from the tree structure,
|
Because the number of inner nodes and the number of SKIP hashes is extractible from the tree structure,
|
||||||
both variable-length integers in the proof are redundant and could have been omitted.
|
both variable-length integers in the proof are redundant and could have been omitted.
|
||||||
|
|
|
@ -111,7 +111,7 @@ The advantages of the current proposal are:
|
||||||
* If different parties in a contract do not want to expose their scripts to each other, they may provide only <code>H(Subscript)</code> and keep the <code>Subscript</code> private until redemption.
|
* If different parties in a contract do not want to expose their scripts to each other, they may provide only <code>H(Subscript)</code> and keep the <code>Subscript</code> private until redemption.
|
||||||
* If they are willing to share the actual scripts, they may combine them into one <code>Subscript</code> for each branch, saving some <code>nOpCount</code> and a few bytes of witness space.
|
* If they are willing to share the actual scripts, they may combine them into one <code>Subscript</code> for each branch, saving some <code>nOpCount</code> and a few bytes of witness space.
|
||||||
|
|
||||||
The are some disadvantages, but only when the redemption condition is very complicated:
|
There are some disadvantages, but only when the redemption condition is very complicated:
|
||||||
* It may require more branches than a general MAST design (as shown in the previous example) and take more witness space in redemption
|
* It may require more branches than a general MAST design (as shown in the previous example) and take more witness space in redemption
|
||||||
* Creation and storage of the MAST structure may take more time and space. However, such additional costs affect only the related parties in the contract but not any other Bitcoin users.
|
* Creation and storage of the MAST structure may take more time and space. However, such additional costs affect only the related parties in the contract but not any other Bitcoin users.
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ What if ParamBlockHash has leading zeros? Should this be prevented?
|
||||||
|
|
||||||
* If leading zeros are included, they should be compared to the actual block hash. (If they were truncated, fewer bytes would be compared.)
|
* If leading zeros are included, they should be compared to the actual block hash. (If they were truncated, fewer bytes would be compared.)
|
||||||
* It is unlikely that the leading zeros will ever be necessary for sufficient precision, so the additional space is not a concern.
|
* It is unlikely that the leading zeros will ever be necessary for sufficient precision, so the additional space is not a concern.
|
||||||
* Since all block hashes are in principle shorter than than 29 bytes, ParamBlockHash may not be larger than 28 bytes.
|
* Since all block hashes are in principle shorter than 29 bytes, ParamBlockHash may not be larger than 28 bytes.
|
||||||
|
|
||||||
Why is it safe to allow checking blocks as recently as the immediate previous block?
|
Why is it safe to allow checking blocks as recently as the immediate previous block?
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ This includes execution pathways or policy conditions which end up not being nee
|
||||||
Not only is it inefficient to require this unnecessary information to be present on the blockchain, albeit in the witness, it also impacts privacy and fungibility as some unused script policies may be identifying.
|
Not only is it inefficient to require this unnecessary information to be present on the blockchain, albeit in the witness, it also impacts privacy and fungibility as some unused script policies may be identifying.
|
||||||
Using a Merkle hash tree to commit to the policy options, and then only forcing revelation of the policy used at redemption minimizes this information leakage.
|
Using a Merkle hash tree to commit to the policy options, and then only forcing revelation of the policy used at redemption minimizes this information leakage.
|
||||||
|
|
||||||
Using Merkle hash trees to commit to policy allows for considerably more complex contracts than would would otherwise be possible, due to various built-in script size and runtime limitations.
|
Using Merkle hash trees to commit to policy allows for considerably more complex contracts than would otherwise be possible, due to various built-in script size and runtime limitations.
|
||||||
With Merkle commitments to policy these size and runtime limitations constrain the complexity of any one policy that can be used rather than the sum of all possible policies.
|
With Merkle commitments to policy these size and runtime limitations constrain the complexity of any one policy that can be used rather than the sum of all possible policies.
|
||||||
|
|
||||||
==Rationale==
|
==Rationale==
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
Layer: Consensus (soft fork)
|
Layer: Consensus (soft fork)
|
||||||
Title: CHECKTEMPLATEVERIFY
|
Title: CHECKTEMPLATEVERIFY
|
||||||
Author: Jeremy Rubin <j@rubin.io>
|
Author: Jeremy Rubin <j@rubin.io>
|
||||||
James O'Beirne <vaults@au92.org>
|
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0119
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0119
|
||||||
Status: Draft
|
Status: Draft
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
|
@ -193,7 +192,7 @@ Deployment could be done via BIP 9 VersionBits deployed through Speedy Trial.
|
||||||
The Bitcoin Core reference implementation includes the below parameters,
|
The Bitcoin Core reference implementation includes the below parameters,
|
||||||
configured to match Speedy Trial, as that is the current activation mechanism
|
configured to match Speedy Trial, as that is the current activation mechanism
|
||||||
implemented in Bitcoin Core. Should another method become favored by the wider
|
implemented in Bitcoin Core. Should another method become favored by the wider
|
||||||
Bitcoin comminity, that might be used instead.
|
Bitcoin community, that might be used instead.
|
||||||
|
|
||||||
The start time and bit in the implementation are currently set to bit 5 and
|
The start time and bit in the implementation are currently set to bit 5 and
|
||||||
NEVER_ACTIVE/NO_TIMEOUT, but this is subject to change while the BIP is a draft.
|
NEVER_ACTIVE/NO_TIMEOUT, but this is subject to change while the BIP is a draft.
|
||||||
|
@ -314,7 +313,7 @@ We treat the number of inputs as a `uint32_t` because Bitcoin's consensus decodi
|
||||||
to `MAX_SIZE=33554432` and that is larger than `uint16_t` and smaller than `uint32_t`. 32 bits is also
|
to `MAX_SIZE=33554432` and that is larger than `uint16_t` and smaller than `uint32_t`. 32 bits is also
|
||||||
friendly for manipulation using Bitcoin's current math opcodes, should `OP_CAT` be added. Note that
|
friendly for manipulation using Bitcoin's current math opcodes, should `OP_CAT` be added. Note that
|
||||||
the max inputs in a block is further restricted by the block size to around 25,000, which would fit
|
the max inputs in a block is further restricted by the block size to around 25,000, which would fit
|
||||||
into a `uint16_t`, but that is an uneccessary abstraction leak.
|
into a `uint16_t`, but that is an unnecessary abstraction leak.
|
||||||
|
|
||||||
=====Committing to the Sequences Hash=====
|
=====Committing to the Sequences Hash=====
|
||||||
|
|
||||||
|
@ -362,7 +361,7 @@ scripts cannot be spent at the same index, which implies that they cannot be spe
|
||||||
This makes it safer to design wallet vault contracts without half-spend vulnerabilities.
|
This makes it safer to design wallet vault contracts without half-spend vulnerabilities.
|
||||||
|
|
||||||
Committing to the current index doesn't prevent one from expressing a CHECKTEMPLATEVERIFY which can
|
Committing to the current index doesn't prevent one from expressing a CHECKTEMPLATEVERIFY which can
|
||||||
be spent at multiple indicies. In current script, the CHECKTEMPLATEVERIFY operation can be wrapped
|
be spent at multiple indices. In current script, the CHECKTEMPLATEVERIFY operation can be wrapped
|
||||||
in an OP_IF for each index (or Tapscript branches in the future). If OP_CAT or OP_SHA256STREAM are
|
in an OP_IF for each index (or Tapscript branches in the future). If OP_CAT or OP_SHA256STREAM are
|
||||||
added to Bitcoin, the index may simply be passed in by the witness before hashing.
|
added to Bitcoin, the index may simply be passed in by the witness before hashing.
|
||||||
|
|
||||||
|
@ -392,7 +391,7 @@ transaction preimages.
|
||||||
=====Using Non-Tagged Hashes=====
|
=====Using Non-Tagged Hashes=====
|
||||||
|
|
||||||
The Taproot/Schnorr BIPs use Tagged Hashes
|
The Taproot/Schnorr BIPs use Tagged Hashes
|
||||||
(`SHA256(SHA256(tag)||SHA256(tag)||msg)`) to prevent taproot leafs, branches,
|
(`SHA256(SHA256(tag)||SHA256(tag)||msg)`) to prevent taproot leaves, branches,
|
||||||
tweaks, and signatures from overlapping in a way that might introduce a security
|
tweaks, and signatures from overlapping in a way that might introduce a security
|
||||||
[vulnerability https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-June/016091.html].
|
[vulnerability https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-June/016091.html].
|
||||||
|
|
||||||
|
@ -476,7 +475,7 @@ An example of a script that could experience an DoS issue without caching is:
|
||||||
|
|
||||||
<H> CTV CTV CTV... CTV
|
<H> CTV CTV CTV... CTV
|
||||||
|
|
||||||
Such a script would cause the intepreter to compute hashes (supposing N CTV's) over O(N*T) data.
|
Such a script would cause the interpreter to compute hashes (supposing N CTV's) over O(N*T) data.
|
||||||
If the scriptSigs non-nullity is not cached, then the O(T) transaction could be scanned over O(N)
|
If the scriptSigs non-nullity is not cached, then the O(T) transaction could be scanned over O(N)
|
||||||
times as well (although cheaper than hashing, still a DoS). As such, CTV caches hashes and computations
|
times as well (although cheaper than hashing, still a DoS). As such, CTV caches hashes and computations
|
||||||
over all variable length fields in a transaction.
|
over all variable length fields in a transaction.
|
||||||
|
@ -494,7 +493,7 @@ The preimage argument passed to CHECKTEMPLATEVERIFY may be unknown or otherwise
|
||||||
However, requiring knowledge that an address is spendable from is incompatible with sender's ability
|
However, requiring knowledge that an address is spendable from is incompatible with sender's ability
|
||||||
to spend to any address (especially, OP_RETURN). If a sender needs to know the template can be spent
|
to spend to any address (especially, OP_RETURN). If a sender needs to know the template can be spent
|
||||||
from before sending, they may request a signature of an provably non-transaction challenge string
|
from before sending, they may request a signature of an provably non-transaction challenge string
|
||||||
from the leafs of the CHECKTEMPLATEVERIFY tree.
|
from the leaves of the CHECKTEMPLATEVERIFY tree.
|
||||||
|
|
||||||
====Forwarding Addresses====
|
====Forwarding Addresses====
|
||||||
|
|
||||||
|
@ -504,7 +503,7 @@ For example, a exchange's hot wallet might use an address which can automaticall
|
||||||
storage address after a relative timeout.
|
storage address after a relative timeout.
|
||||||
|
|
||||||
The issue is that reusing addresses in this way can lead to loss of funds.
|
The issue is that reusing addresses in this way can lead to loss of funds.
|
||||||
Suppose one creates an template address which forwards 1 BTC to cold storage.
|
Suppose one creates a template address which forwards 1 BTC to cold storage.
|
||||||
Creating an output to this address with less than 1 BTC will be frozen permanently.
|
Creating an output to this address with less than 1 BTC will be frozen permanently.
|
||||||
Paying more than 1 BTC will lead to the funds in excess of 1BTC to be paid as a large miner fee.
|
Paying more than 1 BTC will lead to the funds in excess of 1BTC to be paid as a large miner fee.
|
||||||
CHECKTEMPLATEVERIFY could commit to the exact amount of bitcoin provided by the inputs/amount of fee
|
CHECKTEMPLATEVERIFY could commit to the exact amount of bitcoin provided by the inputs/amount of fee
|
||||||
|
@ -616,7 +615,7 @@ sponsors might be considered.
|
||||||
|
|
||||||
An opcode which verifies the exact amount that is being spent in the
|
An opcode which verifies the exact amount that is being spent in the
|
||||||
transaction, the amount paid as fees, or made available in a given output could
|
transaction, the amount paid as fees, or made available in a given output could
|
||||||
be used to make safer OP_CHECKTEMPLATEVERIFY addressses. For instance, if the
|
be used to make safer OP_CHECKTEMPLATEVERIFY addresses. For instance, if the
|
||||||
OP_CHECKTEMPLATEVERIFY program P expects exactly S satoshis, sending S-1
|
OP_CHECKTEMPLATEVERIFY program P expects exactly S satoshis, sending S-1
|
||||||
satoshis would result in a frozen UTXO and sending S+n satoshis would result in
|
satoshis would result in a frozen UTXO and sending S+n satoshis would result in
|
||||||
n satoshis being paid to fee. A range check could restrict the program to only
|
n satoshis being paid to fee. A range check could restrict the program to only
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Peter Todd <pete@petertodd.org>
|
Peter Todd <pete@petertodd.org>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0125
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0125
|
||||||
Status: Proposed
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2015-12-04
|
Created: 2015-12-04
|
||||||
License: PD
|
License: PD
|
||||||
|
@ -151,8 +151,8 @@ of full-RBF.
|
||||||
There are no known problematic interactions between opt-in full-RBF and
|
There are no known problematic interactions between opt-in full-RBF and
|
||||||
other uses of nSequence. Specifically, opt-in full-RBF is compatible
|
other uses of nSequence. Specifically, opt-in full-RBF is compatible
|
||||||
with consensus-enforced locktime as provided in the Bitcoin 0.1
|
with consensus-enforced locktime as provided in the Bitcoin 0.1
|
||||||
implementation, draft BIP68 (Relative lock-time using consensus-enforced
|
implementation, BIP68 (Relative lock-time using consensus-enforced
|
||||||
sequence numbers), and draft BIP112 (CHECKSEQUENCEVERIFY).
|
sequence numbers), and BIP112 (CHECKSEQUENCEVERIFY).
|
||||||
|
|
||||||
==Deployment==
|
==Deployment==
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Suhas Daftuar <sdaftuar@chaincode.com>
|
Author: Suhas Daftuar <sdaftuar@chaincode.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0130
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0130
|
||||||
Status: Proposed
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2015-05-08
|
Created: 2015-05-08
|
||||||
License: PD
|
License: PD
|
||||||
|
|
|
@ -170,7 +170,7 @@ A given deployment SHALL remain in the DEFINED state until it either passes the
|
||||||
starttime (and becomes STARTED) or the timeout time (and becomes FAILED).
|
starttime (and becomes STARTED) or the timeout time (and becomes FAILED).
|
||||||
|
|
||||||
Once a deployment has STARTED, the signal for that deployment SHALL be tallied
|
Once a deployment has STARTED, the signal for that deployment SHALL be tallied
|
||||||
over the the past windowsize blocks whenever a new block is received on that
|
over the past windowsize blocks whenever a new block is received on that
|
||||||
chain.
|
chain.
|
||||||
|
|
||||||
A transition from the STARTED state to the LOCKED_IN state SHALL only occur
|
A transition from the STARTED state to the LOCKED_IN state SHALL only occur
|
||||||
|
@ -183,7 +183,7 @@ when all of these are true:
|
||||||
A similar height synchronization precondition SHALL exist for the transition from
|
A similar height synchronization precondition SHALL exist for the transition from
|
||||||
LOCKED_IN to ACTIVE.
|
LOCKED_IN to ACTIVE.
|
||||||
These synchronization conditions are expressed by the "mod(height, windowsize) = 0"
|
These synchronization conditions are expressed by the "mod(height, windowsize) = 0"
|
||||||
clauses in the diagram, and have been been added so that backward compatibility
|
clauses in the diagram, and have been added so that backward compatibility
|
||||||
with BIP9's use of the 2016-block re-targeting periods can be configured for
|
with BIP9's use of the 2016-block re-targeting periods can be configured for
|
||||||
existing deployments (see above 'Optional full backward compatibility' section).
|
existing deployments (see above 'Optional full backward compatibility' section).
|
||||||
|
|
||||||
|
@ -261,7 +261,7 @@ proposal, although a conventional fallow period of 3 months is RECOMMENDED.
|
||||||
Due to the constraints set by BIP 34, BIP 66 and BIP 65, there are only
|
Due to the constraints set by BIP 34, BIP 66 and BIP 65, there are only
|
||||||
0x7FFFFFFB possible nVersion values available. This limits to at most 30
|
0x7FFFFFFB possible nVersion values available. This limits to at most 30
|
||||||
independent deployments.
|
independent deployments.
|
||||||
By restricting the top 3 bits to 001 we we are left with 29 out of those for
|
By restricting the top 3 bits to 001 we are left with 29 out of those for
|
||||||
the purposes of this proposal, and support two future upgrades for different
|
the purposes of this proposal, and support two future upgrades for different
|
||||||
mechanisms (top bits 010 and 011).
|
mechanisms (top bits 010 and 011).
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
This document describes a signature format for signing messages with Bitcoin private keys.
|
This document describes a signature format for signing messages with Bitcoin private keys.
|
||||||
|
|
||||||
The specification is intended to describe the standard for signatures of messages that can be signed and verfied between different clients that exist in the field today. Note: that a new signature format has been defined which has a number of advantages over this BIP, but to be backwards compatible with existing implementations this BIP will be useful. See BIP 322 [1] for full details on the new signature scheme.
|
The specification is intended to describe the standard for signatures of messages that can be signed and verified between different clients that exist in the field today. Note: that a new signature format has been defined which has a number of advantages over this BIP, but to be backwards compatible with existing implementations this BIP will be useful. See BIP 322 [1] for full details on the new signature scheme.
|
||||||
|
|
||||||
One of the key problems in this area is that there are several different types of Bitcoin addresses and without introducing specific standards it is unclear which type of address format is being used. See [2]. This BIP will attempt to address these issues and define a clear and concise format for Bitcoin signatures.
|
One of the key problems in this area is that there are several different types of Bitcoin addresses and without introducing specific standards it is unclear which type of address format is being used. See [2]. This BIP will attempt to address these issues and define a clear and concise format for Bitcoin signatures.
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ This is the standard ''m-of-n'' script defined in [https://github.com/bitcoin/bi
|
||||||
The existing <code>OP_CHECKMULTISIG</code> and <code>OP_CHECKMULTISIGVERIFY</code> have a bug<ref>[[https://bitcoin.org/en/developer-guide#multisig|Developer Documentation - Multisig]]</ref> that pops one argument too many from the stack. This bug is not reproduced in the implementation of OP_CHECKSIGEX, so the canonical solution of pushing a dummy value onto the stack is not necessary.
|
The existing <code>OP_CHECKMULTISIG</code> and <code>OP_CHECKMULTISIGVERIFY</code> have a bug<ref>[[https://bitcoin.org/en/developer-guide#multisig|Developer Documentation - Multisig]]</ref> that pops one argument too many from the stack. This bug is not reproduced in the implementation of OP_CHECKSIGEX, so the canonical solution of pushing a dummy value onto the stack is not necessary.
|
||||||
|
|
||||||
The normalization is achieved by normalizing the transaction before computing the signaturehash, i.e., the hash that is signed.
|
The normalization is achieved by normalizing the transaction before computing the signaturehash, i.e., the hash that is signed.
|
||||||
The transaction must be normalized by replacing all transaction IDs in the inputs by their normalized variants and stripping the signature scripts. The normalized transction IDs are computed as described in the previous section. This normalization step is performed both when creating the signatures as well as when checking the signatures.
|
The transaction must be normalized by replacing all transaction IDs in the inputs by their normalized variants and stripping the signature scripts. The normalized transaction IDs are computed as described in the previous section. This normalization step is performed both when creating the signatures as well as when checking the signatures.
|
||||||
|
|
||||||
=== Tracking Normalized Transaction IDs ===
|
=== Tracking Normalized Transaction IDs ===
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ Refer to the reference implementation, reproduced below, for the precise algorit
|
||||||
ss << hashSequence;
|
ss << hashSequence;
|
||||||
// The input being signed (replacing the scriptSig with scriptCode + amount)
|
// The input being signed (replacing the scriptSig with scriptCode + amount)
|
||||||
// The prevout may already be contained in hashPrevout, and the nSequence
|
// The prevout may already be contained in hashPrevout, and the nSequence
|
||||||
// may already be contain in hashSequence.
|
// may already be contained in hashSequence.
|
||||||
ss << txTo.vin[nIn].prevout;
|
ss << txTo.vin[nIn].prevout;
|
||||||
ss << static_cast<const CScriptBase&>(scriptCode);
|
ss << static_cast<const CScriptBase&>(scriptCode);
|
||||||
ss << amount;
|
ss << amount;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Jonas Schnelli <dev@jonasschnelli.ch>
|
Author: Jonas Schnelli <dev@jonasschnelli.ch>
|
||||||
Comments-Summary: Discouraged for implementation (one person)
|
Comments-Summary: Discouraged for implementation (one person)
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0150
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0150
|
||||||
Status: Draft
|
Status: Deferred
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2016-03-23
|
Created: 2016-03-23
|
||||||
License: PD
|
License: PD
|
||||||
|
|
|
@ -209,7 +209,7 @@ There are several design goals for the Short ID calculation:
|
||||||
* '''Space''' cmpctblock messages are never optional in this protocol, and contain a short ID for each non-prefilled transaction in the block. Thus, the size of short IDs is directly proportional to the maximum bandwidth savings possible.
|
* '''Space''' cmpctblock messages are never optional in this protocol, and contain a short ID for each non-prefilled transaction in the block. Thus, the size of short IDs is directly proportional to the maximum bandwidth savings possible.
|
||||||
* '''Collision resistance''' It should be hard for network participants to create transactions that cause collisions. If an attacker were able to cause such collisions, filling mempools (and, thus, blocks) with them would cause poor network propagation of new (or non-attacker, in the case of a miner) blocks.
|
* '''Collision resistance''' It should be hard for network participants to create transactions that cause collisions. If an attacker were able to cause such collisions, filling mempools (and, thus, blocks) with them would cause poor network propagation of new (or non-attacker, in the case of a miner) blocks.
|
||||||
|
|
||||||
SipHash is a secure, fast, and simple 64-bit MAC designed for network traffic authentication and collision-resistant hash tables. We truncate the output from SipHash-2-4 to 48 bits (see next section) in order to minimize space. The resulting 48-bit hash is certainly not large enough to avoid intentionally created individual collisons, but by using the block hash as a key to SipHash, an attacker cannot predict what keys will be used once their transactions are actually included in a relayed block. We mix in a per-connection 64-bit nonce to obtain independent short IDs on every connection, so that even block creators cannot control where collisions occur, and random collisions only ever affect a small number of connections at any given time. The mixing is done using SHA256(block_header || nonce), which is slow compared to SipHash, but only done once per block. It also adds the ability for nodes to choose the nonce in a better than random way to minimize collisions, though that is not necessary for correct behaviour. Conversely, nodes can also abuse this ability to increase their ability to introduce collisions in the blocks they relay themselves. However, they can already cause more problems by simply refusing to relay blocks. That is inevitable, and this design only seeks to prevent network-wide misbehavior.
|
SipHash is a secure, fast, and simple 64-bit MAC designed for network traffic authentication and collision-resistant hash tables. We truncate the output from SipHash-2-4 to 48 bits (see next section) in order to minimize space. The resulting 48-bit hash is certainly not large enough to avoid intentionally created individual collisions, but by using the block hash as a key to SipHash, an attacker cannot predict what keys will be used once their transactions are actually included in a relayed block. We mix in a per-connection 64-bit nonce to obtain independent short IDs on every connection, so that even block creators cannot control where collisions occur, and random collisions only ever affect a small number of connections at any given time. The mixing is done using SHA256(block_header || nonce), which is slow compared to SipHash, but only done once per block. It also adds the ability for nodes to choose the nonce in a better than random way to minimize collisions, though that is not necessary for correct behaviour. Conversely, nodes can also abuse this ability to increase their ability to introduce collisions in the blocks they relay themselves. However, they can already cause more problems by simply refusing to relay blocks. That is inevitable, and this design only seeks to prevent network-wide misbehavior.
|
||||||
|
|
||||||
====Random collision probability====
|
====Random collision probability====
|
||||||
|
|
||||||
|
|
|
@ -396,7 +396,7 @@ Once the client has downloaded and verified all filter headers needed, ''and''
|
||||||
no outbound peers have sent conflicting headers, the client can download the
|
no outbound peers have sent conflicting headers, the client can download the
|
||||||
actual block filters it needs. The client MAY backfill filter headers before the
|
actual block filters it needs. The client MAY backfill filter headers before the
|
||||||
first verified one at this point if it only downloaded them starting at a later
|
first verified one at this point if it only downloaded them starting at a later
|
||||||
point. Clients SHOULD persist the verified filter headers for last 100 blocks in
|
point. Clients SHOULD persist the verified filter headers for the last 100 blocks in
|
||||||
the chain (or whatever finality depth is desired), to compare against headers
|
the chain (or whatever finality depth is desired), to compare against headers
|
||||||
received from new peers after restart. They MAY store more filter headers to
|
received from new peers after restart. They MAY store more filter headers to
|
||||||
avoid redownloading them if a rescan is later necessary.
|
avoid redownloading them if a rescan is later necessary.
|
||||||
|
|
|
@ -82,7 +82,7 @@ one is able to select both Parameters independently, then more optimal values
|
||||||
can be
|
can be
|
||||||
selected<ref>https://gist.github.com/sipa/576d5f09c3b86c3b1b75598d799fc845</ref>.
|
selected<ref>https://gist.github.com/sipa/576d5f09c3b86c3b1b75598d799fc845</ref>.
|
||||||
Set membership queries against the hash outputs will have a false positive rate
|
Set membership queries against the hash outputs will have a false positive rate
|
||||||
of <code>M</code>. To avoid integer overflow, the number of items <code>N</code>
|
of <code>1 / M</code>. To avoid integer overflow, the number of items <code>N</code>
|
||||||
MUST be <2^32 and <code>M</code> MUST be <2^32.
|
MUST be <2^32 and <code>M</code> MUST be <2^32.
|
||||||
|
|
||||||
The items are first passed through the pseudorandom function ''SipHash'', which
|
The items are first passed through the pseudorandom function ''SipHash'', which
|
||||||
|
@ -186,7 +186,7 @@ golomb_decode(stream, P: uint) -> uint64:
|
||||||
A GCS is constructed from four parameters:
|
A GCS is constructed from four parameters:
|
||||||
* <code>L</code>, a vector of <code>N</code> raw items
|
* <code>L</code>, a vector of <code>N</code> raw items
|
||||||
* <code>P</code>, the bit parameter of the Golomb-Rice coding
|
* <code>P</code>, the bit parameter of the Golomb-Rice coding
|
||||||
* <code>M</code>, the target false positive rate
|
* <code>M</code>, the inverse of the target false positive rate
|
||||||
* <code>k</code>, the 128-bit key used to randomize the SipHash outputs
|
* <code>k</code>, the 128-bit key used to randomize the SipHash outputs
|
||||||
|
|
||||||
The result is a byte vector with a minimum size of <code>N * (P + 1)</code>
|
The result is a byte vector with a minimum size of <code>N * (P + 1)</code>
|
||||||
|
@ -344,7 +344,7 @@ Light client: [https://github.com/lightninglabs/neutrino]
|
||||||
|
|
||||||
Full-node indexing: https://github.com/Roasbeef/btcd/tree/segwit-cbf
|
Full-node indexing: https://github.com/Roasbeef/btcd/tree/segwit-cbf
|
||||||
|
|
||||||
Golomb-Rice Coded sets: https://github.com/btcsuite/btcutil/blob/master/gcs
|
Golomb-Rice Coded sets: https://github.com/btcsuite/btcd/tree/master/btcutil/gcs
|
||||||
|
|
||||||
== Appendix A: Alternatives ==
|
== Appendix A: Alternatives ==
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ var (
|
||||||
{49291, "Tx pays to empty output script"},
|
{49291, "Tx pays to empty output script"},
|
||||||
{180480, "Tx spends from empty output script"},
|
{180480, "Tx spends from empty output script"},
|
||||||
{926485, "Duplicate pushdata 913bcc2be49cb534c20474c4dee1e9c4c317e7eb"},
|
{926485, "Duplicate pushdata 913bcc2be49cb534c20474c4dee1e9c4c317e7eb"},
|
||||||
{987876, "Coinbase tx has unparseable output script"},
|
{987876, "Coinbase tx has unparsable output script"},
|
||||||
{1263442, "Includes witness data"},
|
{1263442, "Includes witness data"},
|
||||||
{1414221, "Empty data"},
|
{1414221, "Empty data"},
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll now ensure that we've constructed the same filter as
|
// We'll now ensure that we've constructed the same filter as
|
||||||
// the chain server we're fetching blocks form.
|
// the chain server we're fetching blocks from.
|
||||||
filter, err := client.GetCFilter(
|
filter, err := client.GetCFilter(
|
||||||
blockHash, wire.GCSFilterRegular,
|
blockHash, wire.GCSFilterRegular,
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Jonas Schnelli <dev@jonasschnelli.ch>
|
Author: Jonas Schnelli <dev@jonasschnelli.ch>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0159
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0159
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2017-05-11
|
Created: 2017-05-11
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -171,6 +171,28 @@ The currently defined global types are as follows:
|
||||||
| 2
|
| 2
|
||||||
| [[bip-0370.mediawiki|370]]
|
| [[bip-0370.mediawiki|370]]
|
||||||
|-
|
|-
|
||||||
|
| Silent Payment Global ECDH Share
|
||||||
|
| <tt>PSBT_GLOBAL_SP_ECDH_SHARE = 0x07</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this ECDH share is for.
|
||||||
|
| <tt><33 byte share></tt>
|
||||||
|
| An ECDH share for a scan key. The ECDH shared is computed with ''a * B_scan'', where ''a'' is the sum of all private keys of all eligible inputs, and ''B_scan'' is the scan key of a recipient.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
| [[bip-0375.mediawiki|375]]
|
||||||
|
|-
|
||||||
|
| Silent Payment Global DLEQ Proof
|
||||||
|
| <tt>PSBT_GLOBAL_SP_DLEQ = 0x08</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this proof covers.
|
||||||
|
| <tt><64-byte proof></tt>
|
||||||
|
| A BIP374 DLEQ proof computed for the matching ECDH share.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
| [[bip-0375.mediawiki|375]]
|
||||||
|
|-
|
||||||
| PSBT Version Number
|
| PSBT Version Number
|
||||||
| <tt>PSBT_GLOBAL_VERSION = 0xFB</tt>
|
| <tt>PSBT_GLOBAL_VERSION = 0xFB</tt>
|
||||||
| None
|
| None
|
||||||
|
@ -483,6 +505,74 @@ The currently defined per-input types are defined as follows:
|
||||||
| 0, 2
|
| 0, 2
|
||||||
| [[bip-0371.mediawiki|371]]
|
| [[bip-0371.mediawiki|371]]
|
||||||
|-
|
|-
|
||||||
|
| MuSig2 Participant Public Keys
|
||||||
|
| <tt>PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a</tt>
|
||||||
|
| <33 byte plain aggregate pubkey>
|
||||||
|
| The MuSig2 aggregate plain public key from the <tt>KeyAgg</tt> algorithm. This key may or may not
|
||||||
|
be in the script directly (as x-only). It may instead be a parent public key from which the public keys in the
|
||||||
|
script were derived.
|
||||||
|
| <tt><33 byte compressed pubkey>*</tt>
|
||||||
|
| A list of the compressed public keys of the participants in the MuSig2 aggregate key in the order
|
||||||
|
required for aggregation. If sorting was done, then the keys must be in the sorted order.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| 0, 2
|
||||||
|
| [[bip-0373.mediawiki|373]]
|
||||||
|
|-
|
||||||
|
| MuSig2 Public Nonce
|
||||||
|
| <tt>PSBT_IN_MUSIG2_PUB_NONCE = 0x1b</tt>
|
||||||
|
| <tt><33 byte compressed pubkey> <33 byte plain pubkey> <32 byte hash or omitted></tt>
|
||||||
|
| The compressed public key of the participant providing this nonce, followed by the plain public
|
||||||
|
key the participant is providing the nonce for, followed by the BIP 341 tapleaf hash of
|
||||||
|
the Taproot leaf script that will be signed. If the aggregate key is the taproot internal key or the
|
||||||
|
taproot output key, then the tapleaf hash must be omitted. The plain public key must be
|
||||||
|
the key found in the script and not the aggregate public key that it was derived from, if it was
|
||||||
|
derived from an aggregate key.
|
||||||
|
| <tt><66 byte public nonce></tt>
|
||||||
|
| The public nonce produced by the <tt>NonceGen</tt> algorithm.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| 0, 2
|
||||||
|
| [[bip-0373.mediawiki|373]]
|
||||||
|
|-
|
||||||
|
| MuSig2 Participant Partial Signature
|
||||||
|
| <tt>PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c</tt>
|
||||||
|
| <tt><33 byte compressed pubkey> <33 byte plain pubkey> <32 byte hash or omitted></tt>
|
||||||
|
| The compressed public key of the participant providing this partial signature, followed by the
|
||||||
|
plain public key the participant is providing the signature for, followed by the BIP 341 tapleaf hash
|
||||||
|
of the Taproot leaf script that will be signed. If the aggregate key is the taproot internal key or
|
||||||
|
the taproot output key, then the tapleaf hash must be omitted. Note that the plain public key must
|
||||||
|
be the key found in the script and not the aggregate public key that it was derived from, if it was
|
||||||
|
derived from an aggregate key.
|
||||||
|
| <tt><32 byte partial signature></tt>
|
||||||
|
| The partial signature produced by the <tt>Sign</tt> algorithm.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| 0, 2
|
||||||
|
| [[bip-0373.mediawiki|373]]
|
||||||
|
|-
|
||||||
|
| Silent Payment Input ECDH Share
|
||||||
|
| <tt>PSBT_IN_SP_ECDH_SHARE = 0x1d</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this ECDH share is for.
|
||||||
|
| <tt><33 byte share></tt>
|
||||||
|
| An ECDH share for a scan key. The ECDH shared is computed with ''a * B_scan'', where ''a'' is the private key of the corresponding prevout public key, and ''B_scan'' is the scan key of a recipient.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
| [[bip-0375.mediawiki|375]]
|
||||||
|
|-
|
||||||
|
| Silent Payment Input DLEQ Proof
|
||||||
|
| <tt>PSBT_IN_SP_DLEQ = 0x1e</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this proof covers.
|
||||||
|
| <tt><64-byte proof></tt>
|
||||||
|
| A BIP374 DLEQ proof computed for the matching ECDH share.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
| [[bip-0375.mediawiki|375]]
|
||||||
|
|-
|
||||||
| Proprietary Use Type
|
| Proprietary Use Type
|
||||||
| <tt>PSBT_IN_PROPRIETARY = 0xFC</tt>
|
| <tt>PSBT_IN_PROPRIETARY = 0xFC</tt>
|
||||||
| <tt><compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata></tt>
|
| <tt><compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata></tt>
|
||||||
|
@ -560,11 +650,11 @@ determine which outputs are change outputs and verify that the change is returni
|
||||||
| None
|
| None
|
||||||
| No key data
|
| No key data
|
||||||
| <tt><bytes script></tt>
|
| <tt><bytes script></tt>
|
||||||
| The script for this output, also known as the scriptPubKey. Must be omitted in PSBTv0. Must be provided in PSBTv2.
|
| The script for this output, also known as the scriptPubKey. Must be omitted in PSBTv0. Must be provided in PSBTv2 if not sending to a BIP352 silent payment address, otherwise may be omitted.
|
||||||
| 2
|
|
|
||||||
| 0
|
| 0
|
||||||
| 2
|
| 2
|
||||||
| [[bip-0370.mediawiki|370]]
|
| [[bip-0370.mediawiki|370]], [[bip-0375.mediawiki|375]]
|
||||||
|-
|
|-
|
||||||
| Taproot Internal Key
|
| Taproot Internal Key
|
||||||
| <tt>PSBT_OUT_TAP_INTERNAL_KEY = 0x05</tt>
|
| <tt>PSBT_OUT_TAP_INTERNAL_KEY = 0x05</tt>
|
||||||
|
@ -599,6 +689,54 @@ determine which outputs are change outputs and verify that the change is returni
|
||||||
| 0, 2
|
| 0, 2
|
||||||
| [[bip-0371.mediawiki|371]]
|
| [[bip-0371.mediawiki|371]]
|
||||||
|-
|
|-
|
||||||
|
| MuSig2 Participant Public Keys
|
||||||
|
| <tt>PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08</tt>
|
||||||
|
| <33 byte plain aggregate pubkey>
|
||||||
|
| The MuSig2 aggregate plain public key from the <tt>KeyAgg</tt> algorithm. This key may or may not
|
||||||
|
be in the script directly. It may instead be a parent public key from which the public keys in the
|
||||||
|
script were derived.
|
||||||
|
| <tt><33 byte compressed pubkey>*</tt>
|
||||||
|
| A list of the compressed public keys of the participants in the MuSig2 aggregate key in the order
|
||||||
|
required for aggregation. If sorting was done, then the keys must be in the sorted order.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| 0, 2
|
||||||
|
| [[bip-0373.mediawiki|373]]
|
||||||
|
|-
|
||||||
|
| Silent Payment Data
|
||||||
|
| <tt>PSBT_OUT_SP_V0_INFO = 0x09</tt>
|
||||||
|
| None
|
||||||
|
| No key data
|
||||||
|
| <tt><33 byte scan key> <33 byte spend key></tt>
|
||||||
|
| The scan and spend public keys from the silent payments address.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
| [[bip-0375.mediawiki|375]]
|
||||||
|
|-
|
||||||
|
| Silent Payment Label
|
||||||
|
| <tt>PSBT_OUT_SP_V0_LABEL = 0x10</tt>
|
||||||
|
| None
|
||||||
|
| No key data
|
||||||
|
| <tt><32-bit little endian uint label></tt>
|
||||||
|
| The label to use to compute the spend key of the silent payments address to verify change.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
| [[bip-0375.mediawiki|375]]
|
||||||
|
|-
|
||||||
|
| BIP 353 DNSSEC proof
|
||||||
|
| <tt>PSBT_OUT_DNSSEC_PROOF = 0x35</tt>
|
||||||
|
| None
|
||||||
|
| No key data
|
||||||
|
| <tt><1-byte-length-prefixed BIP 353 human-readable name><RFC 9102-formatted AuthenticationChain DNSSEC Proof></tt>
|
||||||
|
| A BIP 353 human-readable name (without the ₿ prefix), prefixed by a 1-byte length.
|
||||||
|
Followed by an [[https://www.rfc-editor.org/rfc/rfc9102.html#name-dnssec-authentication-chain|RFC 9102 DNSSEC <tt>AuthenticationChain</tt>]] (i.e. a series of DNS Resource Records in no particular order) providing a DNSSEC proof to a BIP 353 DNS TXT record.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| 0, 2
|
||||||
|
| [[bip-0353.mediawiki|353]]
|
||||||
|
|-
|
||||||
| Proprietary Use Type
|
| Proprietary Use Type
|
||||||
| <tt>PSBT_OUT_PROPRIETARY = 0xFC</tt>
|
| <tt>PSBT_OUT_PROPRIETARY = 0xFC</tt>
|
||||||
| <tt><compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata></tt>
|
| <tt><compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata></tt>
|
||||||
|
|
|
@ -15,31 +15,28 @@
|
||||||
|
|
||||||
==Abstract==
|
==Abstract==
|
||||||
|
|
||||||
In Bip300, txns are not signed via cryptographic key. Instead, they are "signed" by hashpower, over time. Like a big multisig, 13150-of-26300, where each block is a new "signature".
|
BIP-300 enables a new type of L2, where "withdrawals" (the L2-to-L1 txns) are governed by proof-of-work -- instead of a federation or fixed set of pubkeys.
|
||||||
|
|
||||||
Bip300 emphasizes slow, transparent, auditable transactions which are easy for honest users to get right and very hard for dishonest users to abuse. The chief design goal for Bip300 is ''partitioning'' -- users may safely ignore Bip300 txns if they want to (or Bip300 entirely).
|
BIP-300 emphasizes slow, transparent, and auditable withdrawals that are easy for honest users to get right and hard for dishonest miners to abuse. The main design goal for BIP-300 is ''partitioning'' -- users can ignore BIP-300 txns if they wish; it makes no difference to L1 if the user validates all, some, or none of them. The second design goal is ''security'' -- users of the L2 should feel confident that, [https://www.drivechain.info/blog/fees/ if the L2 network is paying a lot of fees], then miners will want to keep it around, and the withdrawals will therefore be processed accurately.
|
||||||
|
|
||||||
See [http://www.drivechain.info/ this site] for more information.
|
Once BIP-300 has established a "bridge" between L1 and these L2s, users can swap coins in and out instantly, only using BIP-300 for final settlement. This setup allows Bitcoin to process all the transactions in the world, of any shape or size, regardless of blocksize, node software, tech stack, or decentralization level -- all without altering L1 at all.
|
||||||
|
|
||||||
|
|
||||||
==Motivation==
|
==Motivation==
|
||||||
|
|
||||||
|
BIP-300 allows us to achieve [https://www.truthcoin.info/blog/zside-meltcast/ strong privacy], [https://www.truthcoin.info/blog/thunder/ planetary scale], and [https://www.truthcoin.info/blog/all-world-txns/ hundreds of billions of dollars in annual mining revenues], all with a [https://www.drivechain.info/blog/fees/ security model] that is [https://x.com/Truthcoin/status/1701959339508965405 much stronger than] that of the [https://www.truthcoin.info/blog/ln-blackpill/ Lightning Network].
|
||||||
|
|
||||||
As Reid Hoffman [https://blockstream.com/2015/01/13/en-reid-hoffman-on-the-future-of-the-bitcoin-ecosystem/ wrote in 2014]: "Sidechains allow developers to add features and functionality to the Bitcoin universe without actually modifying the Bitcoin Core code...Consequently, innovation can occur faster, in more flexible and distributed ways, without losing the synergies of a common platform with a single currency."
|
The original motivation stretches back to Reid Hoffman, who [https://blockstream.com/2015/01/13/en-reid-hoffman-on-the-future-of-the-bitcoin-ecosystem/ wrote in 2014]: "Sidechains allow developers to add features and functionality to the Bitcoin universe without actually modifying the Bitcoin Core code...Consequently, innovation can occur faster, in more flexible and distributed ways, without losing the synergies of a common platform with a single currency."
|
||||||
|
|
||||||
Today, coins such as Namecoin, Monero, ZCash, and Sia, offer features that Bitcoiners cannot access -- not without selling their BTC to invest in a rival monetary unit. According to [https://coinmarketcap.com/charts/#dominance-percentage coinmarketcap.com], there is now more value *outside* the BTC protocol than within it. According to [https://cryptofees.info/ cryptofees.info], 15x more txn fees are paid outside the BTC protocol, than within it.
|
See [http://www.drivechain.info/ drivechain.info] for more information.
|
||||||
|
|
||||||
Software improvements to Bitcoin rely on developer consensus -- BTC will pass on a good idea if it is even slightly controversial. Development is slow: we are now averaging one major feature every 5 years.
|
|
||||||
|
|
||||||
Sidechains allow for competitive "benevolent dictators" to create a new sidechain at any time. These dictators are accountable only to their users, and (crucially) they are protected from rival dictators. Users can move their BTC among these different pieces of software, as *they* see fit.
|
|
||||||
|
|
||||||
BTC can copy every useful technology, as soon as it is invented; scamcoins lose their justification and become obsolete; and the community can be pro-creativity, knowing that Layer1 is protected from harmful changes.
|
|
||||||
|
|
||||||
==Specification==
|
==Specification==
|
||||||
|
|
||||||
===Overview===
|
===Overview===
|
||||||
|
|
||||||
Bip300 allows for six new blockchain messages (these have consensus significance):
|
BIP-300 consists of six new blockchain messages:
|
||||||
|
|
||||||
* M1. "Propose New Sidechain"
|
* M1. "Propose New Sidechain"
|
||||||
* M2. "ACK Proposal"
|
* M2. "ACK Proposal"
|
||||||
|
@ -48,14 +45,15 @@ Bip300 allows for six new blockchain messages (these have consensus significance
|
||||||
* M5. Deposit -- a transfer of BTC from-main-to-side
|
* M5. Deposit -- a transfer of BTC from-main-to-side
|
||||||
* M6. Withdrawal -- a transfer of BTC from-side-to-main
|
* M6. Withdrawal -- a transfer of BTC from-side-to-main
|
||||||
|
|
||||||
Nodes organize those messages into two caches:
|
|
||||||
|
|
||||||
* D1. "The Sidechain List", which tracks the 256 Hashrate Escrows (Escrows are slots that a sidechain can live in).
|
Nodes organize this data into [https://github.com/LayerTwo-Labs/bip300301_enforcer/blob/master/src/bip300.rs#L79-L96 a few caches], mainly these two:
|
||||||
* D2. "The Withdrawal List", which tracks the withdrawal-Bundles (coins leaving a Sidechain).
|
|
||||||
|
* D1. "The Sidechain List"
|
||||||
|
* D2. "The Withdrawal List"
|
||||||
|
|
||||||
==== D1 (The Sidechain List) ====
|
==== D1 (The Sidechain List) ====
|
||||||
|
|
||||||
D1 is a list of active sidechains. D1 is updated via M1 and M2.
|
D1 is a list of active sidechains. D1 is populated via M1 and M2. Fields #9 and #10 are updated via M5 and M6.
|
||||||
|
|
||||||
{| class="wikitable"
|
{| class="wikitable"
|
||||||
|- style="font-weight:bold; text-align:center; vertical-align:middle;"
|
|- style="font-weight:bold; text-align:center; vertical-align:middle;"
|
||||||
|
@ -87,12 +85,12 @@ D1 is a list of active sidechains. D1 is updated via M1 and M2.
|
||||||
| 5
|
| 5
|
||||||
| Hash1 - tarball hash
|
| Hash1 - tarball hash
|
||||||
| uint256
|
| uint256
|
||||||
| Intended as the sha256 hash of the tar.gz of the canonical sidechain software. (This is not enforced anywhere by Bip300, and is for human purposes only.)
|
| Intended as the sha256 hash of the tar.gz of the canonical sidechain software. (This is not enforced by BIP-300, and is for human purposes only.)
|
||||||
|- style="vertical-align:middle;"
|
|- style="vertical-align:middle;"
|
||||||
| 6
|
| 6
|
||||||
| Hash2 - git commit hash
|
| Hash2 - git commit hash
|
||||||
| uint160
|
| uint160
|
||||||
| Intended as the git commit hash of the canonical sidechain node software. (This is not enforced anywhere by Bip300, and is for human purposes only.)
|
| Intended as the git commit hash of the canonical sidechain node software. (This is not enforced by BIP-300, and is for human purposes only.)
|
||||||
|-
|
|-
|
||||||
| 7
|
| 7
|
||||||
| Active
|
| Active
|
||||||
|
@ -118,17 +116,17 @@ D1 is a list of active sidechains. D1 is updated via M1 and M2.
|
||||||
|
|
||||||
==== D2 (The Withdrawal List) ====
|
==== D2 (The Withdrawal List) ====
|
||||||
|
|
||||||
D2 lists withdrawal-attempts. If these attempts succeed, they will pay coins "from" a Bip300-locked UTXO, to new UTXOs controlled by the withdrawing-user. Each attempt pays out many users, so we call these withdrawal-attempts "Bundles".
|
Withdrawals are transactions that remove coins "from" L2 (i.e., from the BIP-300 locked UTXO), and place them back on L1. Each BIP-300 withdrawal can pay out up to 6,000 withdrawals, and only one withdrawal can succeed at a time (per L2). Therefore, since all L2 users share the same large withdrawal-event, on L1 we call these withdrawals "bundles".
|
||||||
|
|
||||||
D2 is driven by M3, M4, M5, and M6. Those messages enforce the following principles:
|
D2 is driven by M3, M4, M5, and M6. Those messages enforce the following principles:
|
||||||
|
|
||||||
# The Bundles have a canonical order (first come first serve).
|
# The database has a canonical order (first come first serve).
|
||||||
# From one block to the next, every "Blocks Remaining" field decreases by 1.
|
# From one block to the next, every "Blocks Remaining" field decreases by 1.
|
||||||
# When "Blocks Remaining" reaches zero the Bundle is removed.
|
# When "Blocks Remaining" reaches zero, the bundle is removed.
|
||||||
# From one block to the next, the value in "ACKs" may either increase or decrease, by a maximum of 1 (see M4).
|
# From one block to the next, the value in "ACKs" may either increase or decrease, by a maximum of 1 (see M4).
|
||||||
# If a Bundle's "ACKs" reach 13150 or greater, it "succeeds" and its corresponding M6 message can be included in a block.
|
# If a bundle's "ACKs" reach 13150 or greater, it "succeeds" and its corresponding M6 message can be included in a block.
|
||||||
# If the M6 of a Bundle is paid out, it is also removed.
|
# If the M6 of a bundle is paid out, it is also removed.
|
||||||
# If a Bundle cannot possibly succeed ( 13150 - "ACKs" > "Blocks Remaining" ), it is removed immediately.
|
# If a bundle cannot possibly succeed ( 13150 - "ACKs" > "Blocks Remaining" ), it is removed immediately.
|
||||||
|
|
||||||
|
|
||||||
{| class="wikitable"
|
{| class="wikitable"
|
||||||
|
@ -145,7 +143,7 @@ D2 is driven by M3, M4, M5, and M6. Those messages enforce the following princip
|
||||||
| 2
|
| 2
|
||||||
| Bundle Hash
|
| Bundle Hash
|
||||||
| uint256
|
| uint256
|
||||||
| A withdrawal attempt. Specifically, it is a "blinded transaction id" (ie, the double-Sha256 of a txn that has had two fields zeroed out, see M6) of a txn which could withdraw funds from a sidechain.
|
| A withdrawal attempt. Specifically, it is a "blinded transaction id" (i.e., the double-Sha256 of a txn that has had two fields zeroed out, see M6) of a txn which could withdraw funds from a sidechain.
|
||||||
|-
|
|-
|
||||||
| 3
|
| 3
|
||||||
| Work Score (ACKs)
|
| Work Score (ACKs)
|
||||||
|
@ -158,20 +156,12 @@ D2 is driven by M3, M4, M5, and M6. Those messages enforce the following princip
|
||||||
| How long this bundle has left to live (measured in blocks). Starts at 26,300 and counts down.
|
| How long this bundle has left to live (measured in blocks). Starts at 26,300 and counts down.
|
||||||
|}
|
|}
|
||||||
|
|
||||||
D1, with all 256 slots active, reaches a maximum size of: 256 * ( 1 (map index) + 36 (outpoint) + 8 (amount) ) = 11,520 bytes.
|
|
||||||
|
|
||||||
D2, under normal conditions, would reach a size of: (38 bytes per withdrawal * 256 sidechains) = 9,728 bytes.
|
|
||||||
|
|
||||||
It is possible to spam D2. A miner can add the max M3s (256) every block, forever. This costs 9,728 on-chain bytes per block, an opportunity cost of about 43 txns. It results in no benefit to the miner whatsoever. D2 will eventually hit a ceiling at 124.5568 MB. (By comparison, the Bitcoin UTXO set is about 7,000 MB.) When the attacker stops, D2 will eventually shrink back down to 9,728 bytes.
|
|
||||||
|
|
||||||
|
|
||||||
=== The Six New Bip300 Messages ===
|
|
||||||
|
|
||||||
First, how are new sidechains created?
|
=== M1 -- Propose Sidechain ===
|
||||||
|
|
||||||
They are first proposed (with M1), and later acked (with M2). This process resembles Bip9 soft fork activation.
|
New sidechains are proposed with M1, and ACKed with M2.
|
||||||
|
|
||||||
==== M1 -- Propose Sidechain ====
|
|
||||||
|
|
||||||
M1 is a coinbase OP Return output containing the following:
|
M1 is a coinbase OP Return output containing the following:
|
||||||
|
|
||||||
|
@ -197,7 +187,7 @@ Otherwise:
|
||||||
* A new entry is added to D1, whose initial Activation Status is (age=0, fails=0).
|
* A new entry is added to D1, whose initial Activation Status is (age=0, fails=0).
|
||||||
|
|
||||||
|
|
||||||
==== M2 -- ACK Sidechain Proposal ====
|
=== M2 -- ACK Sidechain Proposal ===
|
||||||
|
|
||||||
M2 is a coinbase OP Return output containing the following:
|
M2 is a coinbase OP Return output containing the following:
|
||||||
|
|
||||||
|
@ -219,46 +209,32 @@ Otherwise:
|
||||||
|
|
||||||
A sidechain fails to activate if:
|
A sidechain fails to activate if:
|
||||||
|
|
||||||
* If the slot is unused: during the next 2016 blocks, it accumulates 201 fails. (Ie, 90% threshold).
|
* If the slot is unused: during the next 2016 blocks, it accumulates 1008 fails (i.e., 50% hashrate threshold).
|
||||||
* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails. (Ie, 50% threshold).
|
* If the slot is in use: during the next 26,300 blocks, it accumulates 13,150 fails (i.e., 50% hashrate threshold).
|
||||||
|
|
||||||
( Thus we can overwrite a used sidechain slot. Bip300 sidechains are already vulnerable to one catastrophe per 13150 blocks (the invalid withdrawal) so this slot-overwrite option does not change the security assumptions. )
|
( Thus we can overwrite a used sidechain slot. BIP-300 sidechains are already vulnerable to one catastrophe per 13150 blocks (the invalid withdrawal), so this slot-overwrite option does not change the security assumptions. )
|
||||||
|
|
||||||
Otherwise, the sidechain activates (Active is set to TRUE).
|
Otherwise, the sidechain activates (Active is set to TRUE).
|
||||||
|
|
||||||
In the block in which the sidechain activates, the coinbase MUST include at least one 0-valued OP_DRIVECHAIN output. This output becomes the initial CTIP for the sidechain.
|
|
||||||
|
=== Withdrawing in Bundles ===
|
||||||
|
|
||||||
|
Sidechain withdrawals take the form of "bundles" -- named because they "bundle up" many individual withdrawal-requests into a single rare L1 transaction.
|
||||||
|
|
||||||
|
On the L2 side, individual withdrawal requests are periodically combined into a single CoinJoin-like withdrawal bundle. This bundle is hashed [https://github.com/LayerTwo-Labs/bip300301_messages/blob/master/src/lib.rs#L374C1-L419C2 in a particular way] (on both L2 and L1) -- this "blinded hash" commits to its own L1 fee, but (notably) it does not commit to its first tx-input (in that way, it is like [https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki BIP-118]).
|
||||||
|
|
||||||
|
This hash is what L1 miners will slowly ACK over 3-6 months, not the M6 itself (nor any sidechain data, of course).
|
||||||
|
|
||||||
|
A bundle will either pay all its withdrawals out (via M6), or fail (and pay nothing out for anyone).
|
||||||
|
|
||||||
|
|
||||||
|
=== M3 -- Propose Bundle ===
|
||||||
==== Notes on Withdrawing Coins ====
|
|
||||||
|
|
||||||
Bip300 withdrawals ("M6") are very significant.
|
|
||||||
|
|
||||||
For an M6 to be valid, it must be first "prepped" by one M3 and then 13,150+ M4s. M3 and M4 are about "Bundles".
|
|
||||||
|
|
||||||
===== What are Bundles? =====
|
|
||||||
|
|
||||||
Sidechain withdrawals take the form of "Bundles" -- named because they "bundle up" many individual withdrawal-requests into a single rare layer1 transaction.
|
|
||||||
|
|
||||||
Sidechain full nodes aggregate the withdrawal-requests into a big set. The sidechain calculates what M6 would have to look like, to pay all of these withdrawal-requests out. Finally, the sidechain calculates what the hash of this M6 would be. This 32-byte hash identifies the Bundle.
|
|
||||||
|
|
||||||
This 32-byte hash is what miners will be slowly ACKing over 3-6 months, not the M6 itself (nor any sidechain data, of course).
|
|
||||||
|
|
||||||
A bundle either pays all its withdrawals out (via M6), or else it fails (and pays nothing out).
|
|
||||||
|
|
||||||
===== Bundle Hash = Blinded TxID of M6 =====
|
|
||||||
|
|
||||||
The Bundle hash is static as it is being ACKed. Unfortunately, the M6 TxID will be constantly changing -- as users deposit to the sidechain, the input to M6 will change.
|
|
||||||
|
|
||||||
To solve this problem, we do something conceptually similar to AnyPrevOut (BIP 118). We define a "blinded TxID" as a way of hashing a txn, in which some bytes are first overwritten with zeros. These are: the first input and the first output. Via the former, a sidechain can accept deposits, even if we are acking a TxID that spends from it later. Via the latter, we can force all of the non-withdrawn coins to be returned to the sidechain (even if we don't yet know how many coins this will be).
|
|
||||||
|
|
||||||
==== M3 -- Propose Bundle ====
|
|
||||||
|
|
||||||
M3 is a coinbase OP Return output containing the following:
|
M3 is a coinbase OP Return output containing the following:
|
||||||
|
|
||||||
1-byte - OP_RETURN (0x6a)
|
1-byte - OP_RETURN (0x6a)
|
||||||
4-byte - Commitment header (0xD45AA943)
|
4-byte - Commitment header (0xD45AA943)
|
||||||
32-byte - The Bundle hash, to populate a new D2 entry
|
32-byte - The bundle hash, to populate a new D2 entry
|
||||||
1-byte - nSidechain (the slot number)
|
1-byte - nSidechain (the slot number)
|
||||||
|
|
||||||
M3 is ignored if it does not parse, or if it is for a sidechain that doesn't exist.
|
M3 is ignored if it does not parse, or if it is for a sidechain that doesn't exist.
|
||||||
|
@ -272,9 +248,10 @@ M3 is invalid if:
|
||||||
|
|
||||||
Otherwise: M3 adds an entry to D2, with initial ACK score = 1 and initial Blocks Remaining = 26,299. (Merely being added to D2, does count as your first upvote.)
|
Otherwise: M3 adds an entry to D2, with initial ACK score = 1 and initial Blocks Remaining = 26,299. (Merely being added to D2, does count as your first upvote.)
|
||||||
|
|
||||||
Once a Bundle is in D2, how can we give it enough ACKs to make it valid?
|
|
||||||
|
|
||||||
==== M4 -- ACK Bundle(s) ====
|
=== M4 -- ACK Bundle(s) ===
|
||||||
|
|
||||||
|
Once a bundle is in D2, how can we give it enough ACKs to make it valid?
|
||||||
|
|
||||||
M4 is a coinbase OP Return output containing the following:
|
M4 is a coinbase OP Return output containing the following:
|
||||||
|
|
||||||
|
@ -283,35 +260,25 @@ M4 is a coinbase OP Return output containing the following:
|
||||||
1-byte - Version
|
1-byte - Version
|
||||||
n-byte - The "upvote vector" -- describes which bundle-choice is "upvoted", for each sidechain.
|
n-byte - The "upvote vector" -- describes which bundle-choice is "upvoted", for each sidechain.
|
||||||
|
|
||||||
The upvote vector will code "abstain" as 0xFF (or 0xFFFF); it will code "alarm" as 0xFE (or 0xFFFE). Otherwise it simply indicates which withdrawal-bundle in the list, is the one to be "upvoted".
|
|
||||||
|
|
||||||
For example: if there are two sidechains, and we wish to upvote the 7th bundle on sidechain #1 plus the 4th bundle on sidechain #2, then the upvote vector would be { 07, 04 }. And M4 would be [0x6A,D77D1776,00,0006,0003].
|
|
||||||
|
|
||||||
The version number allows us to shrink the upvote vector in many cases.
|
|
||||||
Version 0x00 omits the upvote vector entirely (ie, 6 bytes for the whole M4) and sets this block's M4 equal to the previous block's M4.
|
|
||||||
Version 0x01 uses one byte per sidechain, and can be used while all ACKed withdrawals have an index under 256 (ie, 99.99%+ of the time).
|
|
||||||
Version 0x02 uses a full two bytes per sidechain (each encoded in little endian), but it always works no matter how many withdrawal proposals exist.
|
|
||||||
Version 0x03 omits the upvote vector, and instead upvotes only those withdrawals that are leading their rivals by at least 50 votes.
|
|
||||||
|
|
||||||
If a sidechain has no pending bundles, then it is skipped over when M4 is created and parsed.
|
|
||||||
|
|
||||||
For example, an upvote vector of { 2 , N/A, 1 } would be represented as [0x6A,D77D1776,01,01,00]. It means: "upvote the second bundle in sidechain #1; and the first bundle in sidechain #3" (iff sidechains #2 has no bundles proposed).
|
|
||||||
|
|
||||||
An upvote vector of { N/A, N/A, 4 } would be [0x6A,D77D1776,01,03].
|
|
||||||
|
|
||||||
|
|
||||||
The M4 message will be invalid (and invalidate the block), if:
|
The M4 message will be invalid (and invalidate the block), if:
|
||||||
|
|
||||||
* It tries to upvote a Bundle that doesn't exist. (For example, trying to upvote the 7th bundle on sidechain #2, when sidechain #2 has only three bundles.)
|
* It tries to upvote a bundle that doesn't exist. (For example, trying to upvote the 7th bundle on sidechain #2, when sidechain #2 has only three bundles.)
|
||||||
* There are no Bundles at all, from any sidechain.
|
* There are no bundles at all, from any sidechain.
|
||||||
|
|
||||||
If M4 is NOT present in a block, then it is treated as "abstain".
|
If M4 is NOT present in a block, then it is treated as an "abstain" for all sidechains.
|
||||||
|
|
||||||
If M4 is present and valid: each withdrawal-bundle that is ACKed, will gain one upvote.
|
If M4 is present and valid: each withdrawal-bundle that is ACKed, will gain one upvote.
|
||||||
|
|
||||||
Important: Within a sidechain-group, upvoting one Bundle ("+1") automatically downvotes ("-1") all other Bundles in that group. However, the minimum ACK-counter is zero. While only one Bundle can be upvoted at once; the whole group can all be unchanged at once ("abstain"), and they can all be downvoted at once ("alarm").
|
Each sidechain always has two "virtual bundles" -- an "abstain" bundle (0xFF), and an "alarm" bundle (0xFE). Abstain leaves the ACK count unchanged, and alarm reduces all ACK counts of all bundles by 1.
|
||||||
|
|
||||||
For example:
|
Any bundle which fails to receive a vote, is downvoted (and loses 1 ACK). If a sidechain has no pending bundles, then it is skipped over when M4 is created and parsed.
|
||||||
|
|
||||||
|
|
||||||
|
==== Examples ====
|
||||||
|
|
||||||
|
To upvote the 7th bundle on sidechain #1, and upvote the 4th bundle on sidechain #2, the upvote vector would be { 07, 04 }. And M4 would be [0x6A,D77D1776,00,0006,0003].
|
||||||
|
|
||||||
|
If block 900,000 has D2 of...
|
||||||
|
|
||||||
{| class="wikitable"
|
{| class="wikitable"
|
||||||
|-
|
|-
|
||||||
|
@ -347,7 +314,7 @@ For example:
|
||||||
|}
|
|}
|
||||||
|
|
||||||
|
|
||||||
...in block 900,000 could become...
|
...and then D2 wants to become:
|
||||||
|
|
||||||
|
|
||||||
{| class="wikitable"
|
{| class="wikitable"
|
||||||
|
@ -383,18 +350,29 @@ For example:
|
||||||
| 22,559
|
| 22,559
|
||||||
|}
|
|}
|
||||||
|
|
||||||
...if M4 were [0x6A,D77D1776,00,0000,0001].
|
... then M4 would have been [0x6A,D77D1776,00,0000,0001].
|
||||||
|
|
||||||
|
==== Saving Space ====
|
||||||
|
|
||||||
|
The version number allows us to shrink the upvote vector in many cases.
|
||||||
|
Version 0x00 omits the upvote vector entirely (i.e., 6 bytes for the whole M4) and sets this block's M4 equal to the previous block's M4.
|
||||||
|
Version 0x01 uses 1 byte per sidechain, and can be used while all ACKed withdrawals have an index <256 (i.e., 99.99%+ of the time).
|
||||||
|
Version 0x02 uses 2 bytes per sidechain, but it always works, even in astronomically unlikely cases (such as when >1 sidechains have >256 bundle candidates).
|
||||||
|
Version 0x03 omits the upvote vector, and instead upvotes only those withdrawals that are leading their rivals by at least 50 votes.
|
||||||
|
|
||||||
|
For example, an upvote vector of { 2 , N/A, 1 } would be represented as [0x6A,D77D1776,01,01,00]. It means: "upvote the second bundle in sidechain #1; and the first bundle in sidechain #3" (iff sidechains #2 has no bundles proposed).
|
||||||
|
|
||||||
|
An upvote vector of { N/A, N/A, 4 } would be [0x6A,D77D1776,01,03].
|
||||||
|
|
||||||
|
|
||||||
Finally, we describe Deposits and Withdrawals.
|
|
||||||
|
|
||||||
==== M5 -- Deposit BTC to Sidechain ====
|
=== M5 -- Deposit BTC (from L1 to L2) ===
|
||||||
|
|
||||||
Each sidechain stores all its BTC in one UTXO, called the "CTIP".
|
Finally, we describe Deposits (M5) and Withdrawals (M6). These are not coinbase outputs, they are txns on L1.
|
||||||
|
|
||||||
By definition, an M5 is a transaction which spends the CTIP and '''increases''' the quantity of coins. An M6 is a transaction which spends the CTIP and '''decreases''' the quantity of coins in the CTIP. See [https://github.com/LayerTwo-Labs/mainchain/blob/391ab390adaa19f92871d769f8e120ca62c1cf14/src/validation.cpp#L688-L801 here].
|
We call a transaction "M5" if it spends from the escrow output and '''increases''' the quantity of coins. Conversely, we call a transaction "M6" if it spends from the escrow output and '''decreases''' the quantity of coins. See [https://github.com/LayerTwo-Labs/bip300301_enforcer/blob/master/src/bip300.rs#L462C1-L462C47 here].
|
||||||
|
|
||||||
Every time a deposit/withdrawal is made, the old CTIP is spent and a new one is created. (Deposits/Withdrawals never cause UTXO bloat.) At all times, the CTIP of each sidechain is cached in D1 (above).
|
Every time a deposit/withdrawal is made, the old UTXO is spent and a single new UTXO is created. (Deposits/Withdrawals never cause UTXO bloat.) At all times, the specific treasury UTXO ("CTIP") of each sidechain is cached in D1 (above).
|
||||||
|
|
||||||
Every M5 is valid, as long as:
|
Every M5 is valid, as long as:
|
||||||
|
|
||||||
|
@ -402,19 +380,18 @@ Every M5 is valid, as long as:
|
||||||
* The new CTIP has '''more''' coins in it, than before.
|
* The new CTIP has '''more''' coins in it, than before.
|
||||||
|
|
||||||
|
|
||||||
==== M6 -- Withdraw BTC from a Sidechain ====
|
=== M6 -- Withdraw BTC (from L2 to L1) ===
|
||||||
|
|
||||||
We come, finally, to the critical matter: where users can take their money *out* of the sidechain.
|
|
||||||
|
|
||||||
M6 is invalid if:
|
M6 is invalid if:
|
||||||
|
|
||||||
* The blinded hash of M6 does NOT match one of the approved Bundle-hashes. (In other words: M6 must first be approved by 13,150 upvotes.)
|
* The blinded hash of M6 does NOT match one of the approved bundle-hashes. (In other words: M6 must first be approved by 13,150 upvotes.)
|
||||||
* The first output of M6 is NOT an OP_DRIVECHAIN. (This OP_DRIVECHAIN becomes the new CTIP. In other words: all non-withdrawn coins are paid back to the sidechain.)
|
* The first output of M6 is NOT an OP_DRIVECHAIN. (This OP_DRIVECHAIN becomes the new CTIP. In other words: all non-withdrawn coins are paid back to the sidechain.)
|
||||||
* The second output is NOT a zero-value OP_RETURN script of exactly 10 bytes, of which 8 bytes are a serialized Bitcoin amount.
|
* The second output is NOT a zero-value OP_RETURN script of exactly 10 bytes, of which 8 bytes are a serialized Bitcoin amount.
|
||||||
* The txn fee of M6 is NOT exactly equal to the amount of the previous bullet point.
|
* The txn fee of M6 is NOT exactly equal to the amount of the previous bullet point.
|
||||||
* There are additional OP_DRIVECHAIN outputs after the first one.
|
* There are additional OP_DRIVECHAIN outputs after the first one.
|
||||||
|
|
||||||
Else, M6 is valid.
|
Else, M6 is valid -- and the funds are withdrawn.
|
||||||
|
|
||||||
(The point of the latter two bullet points, is to allow the bundle hash to cover the L1 transaction fee.)
|
(The point of the latter two bullet points, is to allow the bundle hash to cover the L1 transaction fee.)
|
||||||
|
|
||||||
|
@ -429,7 +406,9 @@ without it, sidechain numbers 0 and 128 would cause the legacy script interprete
|
||||||
|
|
||||||
If an OP_DRIVECHAIN input is spent, the additional rules for M5 or M6 (see above) must be enforced.
|
If an OP_DRIVECHAIN input is spent, the additional rules for M5 or M6 (see above) must be enforced.
|
||||||
|
|
||||||
====Weight adjustments====
|
<!--
|
||||||
|
|
||||||
|
Weight adjustments
|
||||||
|
|
||||||
To account for the additional drivechain checks, each message adds to the block's weight:
|
To account for the additional drivechain checks, each message adds to the block's weight:
|
||||||
|
|
||||||
|
@ -449,7 +428,7 @@ To account for the additional drivechain checks, each message adds to the block'
|
||||||
| M6 || 352
|
| M6 || 352
|
||||||
|}
|
|}
|
||||||
|
|
||||||
<!--
|
|
||||||
get: 168 WU for 1 byte
|
get: 168 WU for 1 byte
|
||||||
delete: free?
|
delete: free?
|
||||||
create: 168 WU for 33 bytes
|
create: 168 WU for 33 bytes
|
||||||
|
@ -476,35 +455,25 @@ M5/M6 OP_DRIVECHAIN spends require 2 additional input lookups
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==Backward compatibility==
|
==Backward compatibility==
|
||||||
|
|
||||||
As a soft fork, older software will continue to operate without modification. Non-upgraded nodes will see a number of phenomena that they don't understand -- coinbase txns with non-txn data, value accumulating in anyone-can-spend UTXOs for months at a time, and then random amounts leaving these UTXOs in single, infrequent bursts. However, these phenomena don't affect them, or the validity of the money that they receive.
|
This soft fork can be deployed without modifying Bitcoin Core at all (i.e., via [https://bip300cusf.com/ CUSF]).
|
||||||
|
|
||||||
( As a nice bonus, note that the sidechains themselves inherit a resistance to hard forks. The only way to guarantee that all different sidechain-nodes will always report the same Bundle, is to upgrade sidechains via soft forks of themselves. )
|
|
||||||
|
|
||||||
|
|
||||||
==Deployment==
|
==Deployment==
|
||||||
|
|
||||||
This BIP will be deployed via UASF-style block height activation. Block height TBD.
|
This BIP deploys when/if >51% hashrate runs [https://github.com/LayerTwo-Labs/bip300301_enforcer/ the enforcer client].
|
||||||
|
|
||||||
|
Ideally, a critical mass of users would also run the enforcer client -- this would strongly dissuade miners from ever de-activating it.
|
||||||
|
|
||||||
|
|
||||||
==Reference Implementation==
|
==Reference Implementation==
|
||||||
|
|
||||||
See: https://github.com/drivechain-project/mainchain
|
The enforcer is [https://github.com/LayerTwo-Labs/bip300301_enforcer/ here].
|
||||||
|
|
||||||
Also, for interest, see an example sidechain here: https://github.com/drivechain-project/sidechains/tree/testchain
|
Also, several example L2s are [https://releases.drivechain.info/ here].
|
||||||
|
|
||||||
|
|
||||||
==References==
|
|
||||||
|
|
||||||
https://github.com/drivechain-project/mainchain
|
|
||||||
https://github.com/drivechain-project/sidechains/tree/testchain
|
|
||||||
See http://www.drivechain.info/literature/index.html
|
|
||||||
|
|
||||||
|
|
||||||
==Credits==
|
|
||||||
|
|
||||||
Thanks to everyone who contributed to the discussion, especially: Luke Dashjr, ZmnSCPxj, Adam Back, Peter Todd, Dan Anderson, Sergio Demian Lerner, Chris Stewart, Matt Corallo, Sjors Provoost, Tier Nolan, Erik Aronesty, Jason Dreyzehner, Joe Miyamoto, Ben Goldhaber.
|
|
||||||
|
|
||||||
|
|
||||||
==Copyright==
|
==Copyright==
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
|
|
||||||
==Abstract==
|
==Abstract==
|
||||||
|
|
||||||
Blind Merged Mining (BMM) allows miners to mine a Sidechain/Altcoin, without running its node software (ie, without "looking" at it, hence "blind").
|
Blind Merged Mining (BMM) allows SHA-256d miners to collect transaction fee revenue from other blockchains, without running any new software (i.e., without "looking" at those alt-chains, hence "blind").
|
||||||
|
|
||||||
Instead, a separate sidechain user runs their node and constructs the block, paying himself the transaction fees. He then uses an equivalent amount of money to "buy" the right to find this block, from the conventional layer1 Sha256d miners.
|
Instead, this block-assembly work is done by alt-chain users. They choose the alt-chain block, and what txns go in it, the fees etc. Simultaneously, these users "bid" on L1 to win the right to be the sole creator of the alt-chain block. BIP-301 ensures that L1 miners only accept one bid (per 10 minutes, per L2 category), instead of taking all of them (which is what they would ordinarily do).
|
||||||
|
|
||||||
|
|
||||||
==Motivation==
|
==Motivation==
|
||||||
|
@ -32,9 +32,9 @@ However, traditional MM has two drawbacks:
|
||||||
|
|
||||||
==Notation and Example==
|
==Notation and Example==
|
||||||
|
|
||||||
Note: We use notation side:\* and main:\* in front of otherwise-ambiguous words (such as "block", "node", or "chain"), to sort the mainchain version from its sidechain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
|
We use notation side:\* and main:\* in front of otherwise ambiguous words (such as "block", "node", or "chain"), to distinguish the mainchain version from its sidechain/alt-chain counterpart. We name all sidechain users "Simon", and name all mainchain miners "Mary".
|
||||||
|
|
||||||
Example: imagine that a sidechain block contains 20,000 txns, each paying a $0.10 fee; therefore, the block is worth $2000 of fee-revenue. As usual: the sidechain's coinbase txn will pay this $2000 to someone (in this case, "Simon"). Under Bip301, Simon does no hashing, but instead makes one layer1 txn paying $1999 to the layer1 miners ("Mary").
|
Furthermore, here is an example of BIP-301 in use. Imagine that a side:block contains 20,000 txns, each paying a $0.10 fee; therefore, the side:block is worth $2000 of fee revenue. In BIP-301, the sidechain's coinbase txn will pay this $2000 to "Simon". Simon does no hashing, but instead makes one L1 txn paying $1999 to the L1 miners ("Mary"). Thus, Mary ends up with all of the fee revenue, even though she didn't do anything on the sidechain.
|
||||||
|
|
||||||
|
|
||||||
{| class="wikitable"
|
{| class="wikitable"
|
||||||
|
@ -71,50 +71,34 @@ Example: imagine that a sidechain block contains 20,000 txns, each paying a $0.1
|
||||||
|}
|
|}
|
||||||
|
|
||||||
|
|
||||||
Bip301 makes this specialization-of-labor trustless on layer1. If Mary takes Simon's money, then she must let Simon control the side:block.
|
BIP-301 makes this specialization-of-labor trustless on L1. If Mary takes Simon's money, then she must let Simon control the side:block.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==Specification==
|
==Specification==
|
||||||
|
|
||||||
|
Each candidate for next side:block is defined by its unique side:blockhash "h*". (Even if the entire rest of the L2 block is identical, different Simons will have different side:coinbases and therefore different h*.)
|
||||||
|
|
||||||
Bip300 consists of two messages: "BMM Accept" and "BMM Request". These govern something called "h*".
|
BIP-301 consists of two messages: "BMM Accept" and "BMM Request".
|
||||||
|
|
||||||
So we will discuss:
|
# "BMM Accept" -- A coinbase output in L1, which contains h*. Mary can only choose one h* to endorse.
|
||||||
|
# "BMM Request" -- A transaction where Simon offers to pay Mary, if (and only if) Mary's L1 block contains Simon's h*.
|
||||||
|
|
||||||
# h* -- The sidechain's hashMerkleRoot, and why it matters.
|
As a miner, Mary controls the main:coinbase, so she may select any h*. Her selection determines which side:block is "found" -- and which associated BMM Request she can collect.
|
||||||
# "BMM Accept" -- How h* enters a main:coinbase. When Mary "accepts" a BMM Request, Mary is ''endorsing a side:block''.
|
|
||||||
# "BMM Request" -- Simon offering money to Mary, if (and only if) she will Endorse a specific h*. When Simon broadcasts a BMM Request, Simon is ''attempting a side:block''.
|
|
||||||
|
|
||||||
|
|
||||||
=== h* ===
|
|
||||||
|
|
||||||
h* ("h star") is the sidechain's Merkle Root hash.
|
|
||||||
|
|
||||||
In Bip301, a sidechain's coinbase txn acts as a header (it contains the hash of the previous side:block, and previous main:block). Thus, the MerkleRoot contains everything important.
|
|
||||||
|
|
||||||
Note: in Bip301 sidechains, "headers" and "block hashes" do not have significant consensus meaning and are in the design mainly to help with IBD. (In the mainchain, in contrast, headers and block hashes determine the difficulty adjustments and cumulative PoW.)
|
|
||||||
|
|
||||||
<img src="bip-0301/sidechain-headers.png?raw=true" align="middle"></img>
|
|
||||||
|
|
||||||
|
|
||||||
Above: h* is located in the main:coinbase. h* contains all side:txns, including the side:coinbase. The side:coinbase contains many "header-like" fields, such as the hash of the previous side:block.
|
|
||||||
|
|
||||||
Mary controls the main:coinbase, so she may select any h*. Her selection will determine which side:block is "found".
|
|
||||||
|
|
||||||
|
|
||||||
=== BMM Accept ===
|
=== BMM Accept ===
|
||||||
|
|
||||||
To "Accept" the BMM proposal (and to accept Simon's money), Mary must endorse Simon's block.
|
To "Accept" a BMM proposal (endorsing Simon's side:block, and allowing Mary to accept Simon's money later in the block), Mary places an OP_RETURN output into the main:coinbase as follows:
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
For each side:block Mary wishes to endorse, Mary places the following into a main:coinbase OP_RETURN:
|
|
||||||
1-byte - OP_RETURN (0x6a)
|
1-byte - OP_RETURN (0x6a)
|
||||||
4-bytes - Message header (0xD1617368)
|
4-bytes - Message header (0xD1617368)
|
||||||
|
1-byte - Sidechain number (0-255)
|
||||||
32-bytes - h* (obtained from Simon)
|
32-bytes - h* (obtained from Simon)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
[https://github.com/drivechain-project/mainchain/blob/8901d469975752d799b6a7a61d4e00a9a124028f/src/validation.cpp#L3530-L3572 Code details here].
|
[https://github.com/LayerTwo-Labs/bip300301_messages/blob/master/src/lib.rs#L252-L264 Code details here].
|
||||||
|
|
||||||
If these OP_RETURN outputs are not present, then no Requests were accepted. (And, Mary would get no money from Requests.)
|
If these OP_RETURN outputs are not present, then no Requests were accepted. (And, Mary would get no money from Requests.)
|
||||||
|
|
||||||
|
@ -124,66 +108,51 @@ When Simon and Mary are different people, Simon will need to use BMM Requests.
|
||||||
|
|
||||||
=== BMM Request ===
|
=== BMM Request ===
|
||||||
|
|
||||||
Simon will use BMM Requests to buy the right to find a sidechain block, from Mary.
|
Simon will use BMM Requests to buy the "right" to find a sidechain block, from Mary.
|
||||||
|
|
||||||
|
For each side:block that Simon wants to attempt, he broadcasts a txn containing the following as an OP_RETURN:
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
For each side:block that Simon wants to attempt, he broadcasts a txn containing the following:
|
1-byte - OP_RETURN (0x6a)
|
||||||
3-bytes - Message header (0x00bf00)
|
3-bytes - Message header (0x00bf00)
|
||||||
32-bytes - h* (side:MerkleRoot)
|
1-byte - Sidechain number (0-255)
|
||||||
1-byte - nSidechain (sidechain ID number)
|
32-bytes - h* (obtained from L2 node)
|
||||||
4-bytes - prevMainHeaderBytes (the last four bytes of the previous main:block)
|
32-bytes - prevMainBlock (the blockhash of the previous main:block)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
We make use of the [https://github.com/drivechain-project/mainchain/blob/8901d469975752d799b6a7a61d4e00a9a124028f/src/primitives/transaction.h#L224-L331 extended serialization format]. (SegWit used ESF to position scriptWitness data within txns; we use it here to position the five fields above.)
|
h* is chosen by Simon to correspond to the side:block he wants to mine on the alt-chain. nSidechain is the number assigned to the sidechain/alt-chain when it was created.
|
||||||
|
|
||||||
|
|
||||||
The Message header identifies this txn as a BMM transaction. h* is chosen by Simon to correspond to his side:block. nSidechain is the number assigned to the sidechain when it was created. preSideBlockRef allows Simon to build on any preexisting side:block (allowing him to bypass one or more invalid blocks, details below). prevMainHeaderBytes are the last four bytes of the previous main:block (details below).
|
|
||||||
|
|
||||||
This txn is invalid if it fails any of the following checks:
|
This txn is invalid if it fails any of the following checks:
|
||||||
|
|
||||||
# Each "BMM Request", must match one corresponding "BMM Accept" (previous section).
|
# Each "BMM Request", must match one corresponding "BMM Accept" (previous section).
|
||||||
# Only one BMM Request is allowed in each main:block, per sidechain. In other words, if 700 users broadcast BMM Requests for sidechain #4, then the main:miner singles out one BMM Request to include.
|
# Only one BMM Request is allowed in each main:block, per nSidechain. In other words, if 700 users broadcast BMM Requests for sidechain #4, then the main:miner must single out one BMM_Request_4 to include in this L1 block.
|
||||||
# The 4-bytes of prevMainHeaderBytes must match the last four bytes of the previous main:blockheader. Thus, Simon's txns are only valid for the current block, in the block history that he knows about (and therefore, the current sidechain history that he knows about).
|
# The 32-bytes of prevMainBlock must match the previous main:blockhash. Thus, Simon's txns are only valid for the current block, in the block history that he knows about.
|
||||||
|
|
||||||
|
|
||||||
Most BMM Request txns will never make it into a block. Simon will make many BMM Requests, but only one will be accepted. Since only one BMM Request can become a bona fide transaction, Simon may feel comfortable making multiple offers all day long. This means Mary has many offers to choose from, and can choose the one which pays her the most.
|
Most BMM Request txns will never make it into a block. Simon will make many BMM Requests, but only one will be accepted. Since only one BMM Request can enter the L1 block, Simon may feel comfortable making multiple offers all day long. This means Mary has many offers to choose from, and can choose the one that pays her the most.
|
||||||
|
|
||||||
This BIP allows BMM Requests to take place over Lightning. One method is [https://www.drivechain.info/media/bmm-note/bmm-lightning/ here]. (BMM Accepts cannot be over LN, since they reside in main:coinbase txns.)
|
This BIP allows BMM Requests to take place over Lightning. One method is [https://www.drivechain.info/media/bmm-note/bmm-lightning/ here]. (BMM Accepts cannot be over LN, since they reside in main:coinbase txns.)
|
||||||
|
|
||||||
|
|
||||||
==Backward compatibility==
|
==Backward compatibility==
|
||||||
|
|
||||||
As a soft fork, older software will continue to operate without modification. To enforce BMM trustlessly, nodes must watch "pairs" of transactions, and subject them to extra rules. Non-upgraded nodes will notice that this activity is present in the blockchain, but they will not understand any of it.
|
This soft fork can be deployed without modifying Bitcoin Core at all (ie, via [https://bip300cusf.com/ CUSF]).
|
||||||
|
|
||||||
Much like P2SH or a new OP Code, these old users can never be directly affected by the fork, as they will have no expectations of receiving payments of this kind. (As a matter of fact, the only people receiving BTC here, all happen to be miners. So there is less reason than ever to expect compatibility problems.)
|
|
||||||
|
|
||||||
As with all previous soft forks, non-upgraded users are indirectly affected, in that they are no longer performing full validation.
|
|
||||||
|
|
||||||
|
|
||||||
==Deployment==
|
==Deployment==
|
||||||
|
|
||||||
This BIP will be deployed via UASF-style block height activation. Block height TBD.
|
This BIP deploys when/if >51% hashrate runs [https://github.com/LayerTwo-Labs/bip300301_enforcer/ the enforcer client].
|
||||||
|
|
||||||
|
Ideally, a critical mass of users would also run the enforcer client -- this would strongly dissuade miners from ever de-activating it.
|
||||||
|
|
||||||
|
|
||||||
==Reference Implementation==
|
==Reference Implementation==
|
||||||
|
|
||||||
See: https://github.com/drivechain-project/mainchain
|
The enforcer is [https://github.com/LayerTwo-Labs/bip300301_enforcer/ here].
|
||||||
|
|
||||||
Also, for interest, see an example sidechain here: https://github.com/drivechain-project/sidechains/tree/testchain
|
Also, several example L2s using BIP-301 are [https://releases.drivechain.info/ here].
|
||||||
|
|
||||||
|
|
||||||
==References==
|
|
||||||
|
|
||||||
* http://www.drivechain.info/literature/index.html
|
|
||||||
* http://www.truthcoin.info/blog/blind-merged-mining/
|
|
||||||
* http://www.truthcoin.info/images/bmm-outline.txt
|
|
||||||
|
|
||||||
|
|
||||||
==Thanks==
|
|
||||||
|
|
||||||
Thanks to everyone who contributed to the discussion, especially: ZmnSCPxj, Adam Back, Peter Todd, Dan Anderson, Sergio Demian Lerner, Matt Corallo, Sjors Provoost, Tier Nolan, Erik Aronesty, Jason Dreyzehner, Joe Miyamoto, Chris Stewart, Ben Goldhaber.
|
|
||||||
|
|
||||||
|
|
||||||
==Copyright==
|
==Copyright==
|
||||||
|
|
||||||
This BIP is licensed under the BSD 2-clause license.
|
This BIP is licensed under the BSD 2-clause license.
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Images used as reference in the documentation.
|
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
Binary file not shown.
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
Before Width: | Height: | Size: 262 KiB |
|
@ -174,8 +174,8 @@ Given below parameters:
|
||||||
|
|
||||||
Produce signatures:
|
Produce signatures:
|
||||||
|
|
||||||
* Message = "" (empty string): <code>AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=</code>
|
* Message = "" (empty string): <code>AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=</code> or <code>AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy</code>
|
||||||
* Message = "Hello World": <code>AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=</code>
|
* Message = "Hello World": <code>AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=</code> or <code>AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy</code>
|
||||||
|
|
||||||
=== Transaction Hashes ===
|
=== Transaction Hashes ===
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
Jonas Schnelli <dev@jonasschnelli.ch>
|
Jonas Schnelli <dev@jonasschnelli.ch>
|
||||||
Pieter Wuille <bitcoin-dev@wuille.net>
|
Pieter Wuille <bitcoin-dev@wuille.net>
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0324
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0324
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2019-03-08
|
Created: 2019-03-08
|
||||||
License: BSD-3-Clause
|
License: BSD-3-Clause
|
||||||
|
@ -384,7 +384,7 @@ def complete_handshake(peer, initiating, decoy_content_lengths=[]):
|
||||||
if received_garbage[-16:] == peer.recv_garbage_terminator:
|
if received_garbage[-16:] == peer.recv_garbage_terminator:
|
||||||
# Receive, decode, and ignore version packet.
|
# Receive, decode, and ignore version packet.
|
||||||
# This includes skipping decoys and authenticating the received garbage.
|
# This includes skipping decoys and authenticating the received garbage.
|
||||||
v2_receive_packet(peer, aad=received_garbage)
|
v2_receive_packet(peer, aad=received_garbage[:-16])
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
received_garbage += recv(peer, 1)
|
received_garbage += recv(peer, 1)
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
u,x,case0_t,case1_t,case2_t,case3_t,case4_t,case5_t,case6_t,case7_t
|
|
||||||
08da7c45cb204377e7e42249cda5713fa865116ddbb4cb5a1949b2e5b438a6ab,e087b707dabf2796b03b2fb4f976c3f2f5abb36110d00ef656432117f2c93f0a,,,,,,,,
|
|
||||||
0a6361b3a802f55cd5ae06101c88a1e216320fe11cc0cfe1d791eed08a1200fd,a0223bc98997647daf4d520129bdb66e4937a00d1533af1fa29645fb96fb5bb5,60a3ed14bd9df0bfb89ada9372a7b5790b123a66bf130f5788237e8cd5225de4,9c4ee4629f10220fda49532d0c859a539dec5148eefc78bf48d93d2828027a9c,fc5e72f042fd1792cbf88728a374a2cc1e03e1f9ec8813fa3692e497cfa7d5e6,cb39fac005f26dc0a383ea64cb9b3b0b26767f20232cae4486f32904df4f04e3,9f5c12eb42620f404765256c8d584a86f4edc59940ecf0a877dc81722add9e4b,63b11b9d60efddf025b6acd2f37a65ac6213aeb711038740b726c2d6d7fd8193,03a18d0fbd02e86d340778d75c8b5d33e1fc1e061377ec05c96d1b6730582649,34c6053ffa0d923f5c7c159b3464c4f4d98980dfdcd351bb790cd6fa20b0f74c
|
|
||||||
102b51b9765a56a3e899f7cf0ee38e5251f9c503b357b330a49183eb7b155604,102b51b9765a56a3e899f7cf0ee38e5251f9c503b357b330a49183eb7b155604,bdb5bd58ca96eae36147a6c55bc2bef2cee55a757ee193cb619edc8d3590f90a,bda953c1da02059350e740b83f59149628e0be50c24ac8dc6908a2225931b4a0,,,424a42a73569151c9eb8593aa43d410d311aa58a811e6c349e612371ca6f0325,4256ac3e25fdfa6caf18bf47c0a6eb69d71f41af3db5372396f75ddca6ce478f,,
|
|
||||||
2921a11f25dadaa24aa79a548e4e81508c2e5e56af2d833d65e2bcce448ce2f5,3a70c472406b83d9f1c4398b8ecef786499bc44a3b30c34ac30f2d8a418bffa3,b9c76c21d3fabb948fa0326bf9e999068e9eed56ee4e76cb81558aa26969c56c,ef7dd84338732a0cac3a8995f3bacf9b2896582b8d3317ed508e5d9a5a3447af,,,463893de2c05446b705fcd94061666f9716112a911b189347eaa755c969636c3,108227bcc78cd5f353c5766a0c453064d769a7d472cce812af71a264a5cbb480,,
|
|
||||||
33b67cb5385ceddad93d0ee960679041613bed34b8b4a5e6362fe7539ba2d3ce,0105c74958a165e016502eeb87835195505d89714c95272b6fa88fe6c60b33ac,,,069e1b3b155c6da989b9b6a8735bba3c5c1049dcf01fe4474772244db89cf9ca,c77b10bca540e95ee66c1f57ab6297787849a89b2b883116e700593e3c0fe66d,,,f961e4c4eaa39256764649578ca445c3a3efb6230fe01bb8b88ddbb147630265,3884ef435abf16a11993e0a8549d688787b65764d477cee918ffa6c0c3f015c2
|
|
||||||
3a898eecdae167231275338e9a79153cbe53f7bf99943eeb72ee64e57bb58699,41ffd7362aaa7b90fe03936deeebe9afafd9c18967122d8f972db2c050d4f07b,60abf7ed2a7ffd3d2ac242a782331ea663d55ca157af994e5e964e9c79a0db40,3c3c39dc37753ab9160dfbc2e0596c3a5114784690caa1836e12036814453da3,adcd3f100de60723f127278998c591fbf081af8e0a77f2a9090bed67d8aa2aa3,,9f540812d58002c2d53dbd587dcce1599c2aa35ea85066b1a169b162865f20ef,c3c3c623c88ac546e9f2043d1fa693c5aeeb87b96f355e7c91edfc96ebbabe8c,5232c0eff219f8dc0ed8d876673a6e040f7e5071f5880d56f6f412972755d18c,
|
|
||||||
46e04d129d7b45d054469ce34e24069a1426b3e34f1b68a3d1bff1e070aee192,c6ce9611bd908c16eba5c599e5219de2d18d82c96aafb0180b23ee315513618f,,,,,,,,
|
|
||||||
47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254,13964717dbc998964d7c19ec3d9981fe1d4a9a80845552a98fb9352898532844,,,,,,,,
|
|
||||||
4cab73ce2a7e6220975001c8a354143267a3c1ce8bf7692313e654481e616a93,9114cf2edd3b53dbb6581290a5cca532db38b4e9ceeacc9b0437a0e49bf97211,903b600ed648d4ddc48f0f628829c8992c88fab44b692413fb8b3d783854f9a2,2952afe39557606d08c311345788a5071413580917207c86ea7cb829cf2f2c6d,05f414320d0c4004cff10f798c3fda6c4fc335b5a2db940993b3d78147a25c18,48e2531c7e3ec99f807210d6c5330114b4f04d7345535ca5a6e6abf478bdb723,6fc49ff129b72b223b70f09d77d63766d377054bb496dbec0474c286c7ab028d,d6ad501c6aa89f92f73ceecba8775af8ebeca7f6e8df8379158347d530d0cfc2,fa0bebcdf2f3bffb300ef08673c02593b03cca4a5d246bf66c4c287db85da017,b71dace381c136607f8def293accfeeb4b0fb28cbaaca35a5919540a8742450c
|
|
||||||
5aeca385d8b781825b07bbec7c858b7170426c88088935850bc13dd6402368a5,a5135c7a27487e7da4f84413837a748e8fbd9377f776ca7af43ec228bfdc938a,8da4f71fb2700758f623d73c24ac91747da43f2302fce16c8d438a769c63495f,6b8f345fc0a25a76455541ddbf2791ff4b943c98b16db2b6eb6cea94a6b19afb,,,725b08e04d8ff8a709dc28c3db536e8b825bc0dcfd031e9372bc7588639cb2d0,9470cba03f5da589baaabe2240d86e00b46bc3674e924d491493156a594e6134,,
|
|
||||||
707bf0b938f307b5c222e670598b865d5e1f8a8003df82c7abbf7c9f8fa4d720,8f840f46c70cf84a3ddd198fa67479a2a1e0757ffc207d385440835f705b250f,,,eab90fb459bace62d3ce8fbd69c9f1039f0627d0e93e2f42bffd87889cb236a4,157c26578b226c66daf8edfa56f7560f1131f41d1685175e6d76cc95b4f89f10,,,1546f04ba645319d2c31704296360efc60f9d82f16c1d0bd40027876634dc58b,ea83d9a874dd939925071205a908a9f0eece0be2e97ae8a1928933694b075d1f
|
|
||||||
766caa663e1025b9accd7ededd24fbc8193180e028eedae2f41d6bb0b1d36468,22825ee826f8b76c27220e43c79c884a8518bc20f4978cc15f83f9c48346a314,,,8fe95c178da66d1dd249ea6a4dc614a6d46d79c83cbc4beafee518090263e48a,7b044cb756eb207226db302ba05e164781c2f5161dccd72607282cb9ad86a282,,,7016a3e8725992e22db61595b239eb592b928637c343b415011ae7f5fd9c17a5,84fbb348a914df8dd924cfd45fa1e9b87e3d0ae9e23328d9f8d7d345527959ad
|
|
||||||
78a23af8da46b1b37e8767921a2d3f528fdc8eca37cea8aea775fd2b283d3776,73d5f35d96f3ce1ef5802ead8edc10787700c593b5e0ddcc3bfb2720b9d36de3,8465ad20bd0f2b4a2d37106769af46288a109bc10b527c3b033c930c0e4b1025,1b7f03bd2c915bb736622aec85601bcabec89268c98945e19a0de4126ed62524,,,7b9a52df42f0d4b5d2c8ef989650b9d775ef643ef4ad83c4fcc36cf2f1b4ec0a,e480fc42d36ea448c99dd5137a9fe43541376d973676ba1e65f21bec9129d70b,,
|
|
||||||
78b4be1f9eeef9da65c393e4385f67edd142709b400ca7d900bd952e0c3cf727,089329e17a58a91e71ffe6ddd851e8a352e85a29fcc289b34a3bfdeaf958fe91,,,6008d703955b38da0166bd975ad3535af3b701b2efdf653fc5e7e6eb6afff0a3,,,,9ff728fc6aa4c725fe994268a52caca50c48fe4d10209ac03a18191395000b8c,
|
|
||||||
7a2a7c0a81d1bd595dff09b918f8ecb5b5e8493654a4f83496956ed8eb017674,85d583f57e2e42a6a200f646e707134a4a17b6c9ab5b07cb696a912614fe85bb,,,,,,,,
|
|
||||||
913da1f8df6f8fd47593840d533ba0458cc9873996bf310460abb495b34c232a,a7803f8e02b70718443a06db502c67925640e936b3fa46dd2ed6b8f7c80fa329,67d916ba2cc154464d87ff4e0cfe3bb816b22a961831c2daf62597a8b0681e87,a4b84520f8853e5482ee7689732ed7dd7da59945d26edeee0bf5f55d3507192f,,,9826e945d33eabb9b27800b1f301c447e94dd569e7ce3d2509da68564f97dda8,5b47badf077ac1ab7d1189768cd12822825a66ba2d912111f40a0aa1caf8e300,,
|
|
||||||
96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7,7684ab3b1a43e20a97a7b5520e5b5347841a7d95984fd76b2478a2b710f1a2ce,,,,,,,,
|
|
||||||
99be5efb88ca2013bd8e4eb035fd42d5245468fe9afa70d8ba9c1c419a48c4e8,08ee83ae5c7af0c9b2341e595fe347537272d94f2fe9f10b9a8f913279fc6230,,,,,,,,
|
|
||||||
9b4fb24edd6d1d8830e272398263cdbf026b97392cc35387b991dc0248a628f9,80e81d40a50b53712a8dac5f468b0903c05219544a56af70aa152ebf17887701,,,6e94af5a32ac100c5230f1e119c538742b7051934b02f3850522cff26bd32d97,e9bd309fbf041342311be3d5bab0b9d16c9f80c6640eb47e311d3178c2adc75d,,,916b50a5cd53eff3adcf0e1ee63ac78bd48fae6cb4fd0c7afadd300c942cce98,1642cf6040fbecbdcee41c2a454f462e93607f399bf14b81cee2ce863d5234d2
|
|
||||||
9def996cb1ea87e596b6cadccca3839a352e99d9ce07e635cdb239f38ca294f8,294850a665ab014a0e75eb4b52ee66dd8a8d2b5e453074e58afacb5e019ee90a,b1a29367b95e1996f7e393fb389e7ace812d4135f6ddcdcd77467fc000dfca8c,a340aabc95b4000e3043ba6139178c450046c985fbf09676c440bc6430ddaa5b,4c4cd400d0be335dd651370c5565c2b742a298016212a8605187b3c0751a811e,d90fa208bbb5f3f6e16c5a42b419188ec1951c1eb358f04741b7b48df9e55f79,4e5d6c9846a1e669081c6c04c76185317ed2beca0922323288b9803eff2031a3,5cbf55436a4bfff1cfbc459ec6e873baffb9367a040f69893bbf439acf2251d4,b3b32bff2f41cca229aec8f3aa9a3d48bd5d67fe9ded579fae784c3e8ae57b11,26f05df7444a0c091e93a5bd4be6e7713e6ae3e14ca70fb8be484b71061a9cb6
|
|
||||||
a2c4aed1cf757cd9a509734a267ffc7b1166b55f4c8f9c3e3550c56e743328fc,a2c4aed1cf757cd9a509734a267ffc7b1166b55f4c8f9c3e3550c56e743328fc,,,,,,,,
|
|
||||||
a8e437abf9c0e74dc6d51eabf2d261a00e785c7e21efeac1f322b610273ba066,5a64cce4be767964e7dba23e78e30149326c539353b647e0d5d7cc361943b13b,,,6f73bdd6b748790b5f788935ca02aee3b9e560c4ba6caf47d716fbde1dd6e92c,b1ff705694188e672f58c6a05eeecc379dd1b60fd3cb9f19fcb02b1d9cab4bc5,,,908c422948b786f4a08776ca35fd511c461a9f3b459350b828e90420e2291303,4e008fa96be77198d0a7395fa11133c8622e49f02c3460e6034fd4e16354b06a
|
|
||||||
bf60e4349cace6bce0d552e8d783428db66d0d649bd9e430a3627e2ee14ac839,409f1bcb635319431f2aad17287cbd724992f29b64261bcf5c9d81d01eb533f6,,,,,,,,
|
|
||||||
c0ba8a33ac67f44abff5984dfbb6f56c46b880ac2b86e1f23e7fa9c402c53ae7,4767c4cab0d08133980a8e66c3f93a055c8ae62f89a92f8dcfa47607cee0bc57,4c21052f5ffccadb4f707aa1cba828ef384d7861af1690c59d638dfee9f368e7,dbcc8fe22896478161452d44688a6b138050a4d0964470c175a521dcecc5519a,,,b3defad0a0033524b08f855e3457d710c7b2879e50e96f3a629c7200160c9348,2433701dd769b87e9ebad2bb977594ec7faf5b2f69bb8f3e8a5ade22133aaa95,,
|
|
||||||
cbe2268747c9c8072c7f9926f2288f270637dc55bb9d14d3368361d5e47d25be,0e4e25736b614910c4984843e606b1e229def08bfd672ab61e2707cde8248c6d,,,c30567184201fac8e1cb9e776d921e17d28cdb7333223abd1c8f860a16393df1,,,,3cfa98e7bdfe05371e346188926de1e82d73248cccddc542e37079f4e9c6be3e,
|
|
||||||
ceb827ad3d3884fd4d50ae6099d6d50c09a21e72ebd309708e8b69d93df19e55,a6a0c8c94462f16f1b92502c3d5f9d1618f12ffa756227d5b19b01b9373cd940,,,,,,,,
|
|
||||||
d57e9d4f5842134f140032eaf38b5333638e8c4b145fcf86a23d48d3e9acc0f8,2a8162b0a7bdecb0ebffcd150c74accc9c7173b4eba030795dc2b72b16533b37,349a9a592d2c56e5378ae869d646043fc09ffb8fe5fd9debd83a11274da08892,9875f58028cc991cafab9fb1183b350bc1d8d5ce5723813cc2b8434ed1a2100f,,,cb6565a6d2d3a91ac875179629b9fbc03f6004701a02621427c5eed7b25f739d,678a0a7fd73366e35054604ee7c4caf43e272a31a8dc7ec33d47bcb02e5dec20,,
|
|
||||||
d94e7f1e9bb1f8a9b90996ba12c461b84956f0e7f230145cc594c2f80b067aa0,b4f4632803cff65c013a566748cd3386d58cd3a28f5b4721056cbe9d278a67a4,,,fad51eda7d418ee2785df9f3788ac9152576312177fc0fd83c65036750581620,749259382784be63f86cc927a5defa6aa8cecb98e38d68f6b7a7e958303c94ad,,,052ae12582be711d87a2060c877536eada89cede8803f027c39afc97afa7e60f,8b6da6c7d87b419c079336d85a210595573134671c729709485816a6cfc36782
|
|
||||||
e545d395bb3fd971f91bf9a2b6722831df704efae6c1aa9da0989ed0970b77bb,760486143a1d512da5219d3e5febc7c5c9990d21ca7a501ed23f86c91ddee4cf,,,090892960a84c69967fe5a5d014d3ca19173e4cb72a908586fbce9d1e531a265,42a47f65d00ff2004faa98865ee8ed4f8a9a5ddc9f75042d728de335664bb546,,,f6f76d69f57b39669801a5a2feb2c35e6e8c1b348d56f7a79043162d1ace59ca,bd5b809a2ff00dffb0556779a11712b07565a223608afbd28d721cc999b446e9
|
|
||||||
e9f86cefcfd61558fe75da7d4ea48a6c82d93191c6d49579aab49f99e543dcad,5db7371325a7bb83b030691b2d87cd9f199f43d91e302568391ac48181b7cea6,,,,,,,,
|
|
||||||
eec4121f2a07b61aba16414812aa9afc39ab0a136360a5ace2240dc19b0464eb,0b623c5296c13218a1eb24e79d00b04bf15788f6c2f7ec100a4a16f1473124a2,,,,,,,,
|
|
||||||
f566cc6fccc657365c0197accf3a7d6f80f85209ff666ff774f4dcbc524aa842,0a9933903339a8c9a3fe685330c582907f07adf6009990088b0b2342adb553ed,3ab8dc4ecbc0441c685436ac0d76f16393769c353be6092bd6ec4ce094106bd8,3bd189b4ef3d1baa5610f2b14cb4a2b377eb171511e6f36ef6a05a2c7c52e368,1594764c6296402aadd123675d81f3505d35f2a52c52881568eadb7b675b53f0,c64fbf71138e66de8ce0abdf3b6f51d151ca8e1037ab5b979e62b2faa15be81c,c54723b1343fbbe397abc953f2890e9c6c8963cac419f6d42913b31e6bef9057,c42e764b10c2e455a9ef0d4eb34b5d4c8814e8eaee190c91095fa5d283ad18c7,ea6b89b39d69bfd5522edc98a27e0cafa2ca0d5ad3ad77ea9715248398a4a83f,39b0408eec719921731f5420c490ae2eae3571efc854a468619d4d045ea41413
|
|
|
|
@ -4,7 +4,7 @@
|
||||||
Author: Jonas Nick <jonasd.nick@gmail.com>
|
Author: Jonas Nick <jonasd.nick@gmail.com>
|
||||||
Tim Ruffing <crypto@timruffing.de>
|
Tim Ruffing <crypto@timruffing.de>
|
||||||
Elliott Jin <elliott.jin@gmail.com>
|
Elliott Jin <elliott.jin@gmail.com>
|
||||||
Status: Draft
|
Status: Active
|
||||||
License: BSD-3-Clause
|
License: BSD-3-Clause
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2022-03-22
|
Created: 2022-03-22
|
||||||
|
@ -190,9 +190,6 @@ The aggregate public key can be ''tweaked'', which modifies the key as defined i
|
||||||
In order to apply a tweak, the KeyAgg Context output by ''KeyAgg'' is provided to the ''ApplyTweak'' algorithm with the ''is_xonly_t'' argument set to false for plain tweaking and true for X-only tweaking.
|
In order to apply a tweak, the KeyAgg Context output by ''KeyAgg'' is provided to the ''ApplyTweak'' algorithm with the ''is_xonly_t'' argument set to false for plain tweaking and true for X-only tweaking.
|
||||||
The resulting KeyAgg Context can be used to apply another tweak with ''ApplyTweak'' or obtain the aggregate public key with ''GetXonlyPubkey'' or ''GetPlainPubkey''.
|
The resulting KeyAgg Context can be used to apply another tweak with ''ApplyTweak'' or obtain the aggregate public key with ''GetXonlyPubkey'' or ''GetPlainPubkey''.
|
||||||
|
|
||||||
In addition to individual public keys, the ''KeyAgg'' algorithm accepts tweaks, which modify the aggregate public key as defined in the [[#tweaking-definition|Tweaking Definition]] subsection.
|
|
||||||
For example, if ''KeyAgg'' is run with ''v = 2'', ''is_xonly_t<sub>1</sub> = false'', ''is_xonly_t<sub>2</sub> = true'', then the aggregate key is first plain tweaked with ''tweak<sub>1</sub>'' and then X-only tweaked with ''tweak<sub>2</sub>''.
|
|
||||||
|
|
||||||
The purpose of supporting tweaking is to ensure compatibility with existing uses of tweaking, i.e., that the result of signing is a valid signature for the tweaked public key.
|
The purpose of supporting tweaking is to ensure compatibility with existing uses of tweaking, i.e., that the result of signing is a valid signature for the tweaked public key.
|
||||||
The MuSig2 algorithms take arbitrary tweaks as input but accepting arbitrary tweaks may negatively affect the security of the scheme.<ref>It is an open question whether allowing arbitrary tweaks from an adversary affects the unforgeability of MuSig2.</ref>
|
The MuSig2 algorithms take arbitrary tweaks as input but accepting arbitrary tweaks may negatively affect the security of the scheme.<ref>It is an open question whether allowing arbitrary tweaks from an adversary affects the unforgeability of MuSig2.</ref>
|
||||||
Instead, signers should obtain the tweaks according to other specifications.
|
Instead, signers should obtain the tweaks according to other specifications.
|
||||||
|
@ -554,7 +551,7 @@ influence whether ''sk<sub>1</sub>'' or ''sk<sub>2</sub>'' is provided to ''Sign
|
||||||
This degree of freedom may allow the adversary to perform a generalized birthday attack and thereby forge a signature
|
This degree of freedom may allow the adversary to perform a generalized birthday attack and thereby forge a signature
|
||||||
(see [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-October/021000.html bitcoin-dev mailing list post] and [https://github.com/jonasnick/musig2-tweaking writeup] for details).
|
(see [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-October/021000.html bitcoin-dev mailing list post] and [https://github.com/jonasnick/musig2-tweaking writeup] for details).
|
||||||
|
|
||||||
Checking ''pk'' against ''InvidualPubkey(sk)'' is a simple way to ensure
|
Checking ''pk'' against ''IndividualPubkey(sk)'' is a simple way to ensure
|
||||||
that the secret key provided to ''Sign'' is fully determined already when ''NonceGen'' is invoked.
|
that the secret key provided to ''Sign'' is fully determined already when ''NonceGen'' is invoked.
|
||||||
This removes the adversary's ability to influence the secret key after having seen the ''pubnonce''
|
This removes the adversary's ability to influence the secret key after having seen the ''pubnonce''
|
||||||
and thus rules out the attack.<ref>Ensuring that the secret key provided to ''Sign'' is fully determined already when ''NonceGen'' is invoked is a simple policy to rule out the attack,
|
and thus rules out the attack.<ref>Ensuring that the secret key provided to ''Sign'' is fully determined already when ''NonceGen'' is invoked is a simple policy to rule out the attack,
|
||||||
|
@ -622,7 +619,7 @@ Algorithm ''DeterministicSign(sk, aggothernonce, pk<sub>1..u</sub>, tweak<sub>1.
|
||||||
* Let ''keyagg_ctx<sub>0</sub> = KeyAgg(pk<sub>1..u</sub>)''; fail if that fails
|
* Let ''keyagg_ctx<sub>0</sub> = KeyAgg(pk<sub>1..u</sub>)''; fail if that fails
|
||||||
* For ''i = 1 .. v'':
|
* For ''i = 1 .. v'':
|
||||||
** Let ''keyagg_ctx<sub>i</sub> = ApplyTweak(keyagg_ctx<sub>i-1</sub>, tweak<sub>i</sub>, is_xonly_t<sub>i</sub>)''; fail if that fails
|
** Let ''keyagg_ctx<sub>i</sub> = ApplyTweak(keyagg_ctx<sub>i-1</sub>, tweak<sub>i</sub>, is_xonly_t<sub>i</sub>)''; fail if that fails
|
||||||
* Let ''aggpk = GetPubkey(keyagg_ctx<sub>v</sub>)''
|
* Let ''aggpk = GetXonlyPubkey(keyagg_ctx<sub>v</sub>)''
|
||||||
* Let ''k<sub>i</sub> = int(hash<sub>MuSig/deterministic/nonce</sub>(sk' || aggothernonce || aggpk || bytes(8, len(m)) || m || bytes(1, i - 1))) mod n'' for ''i = 1,2''
|
* Let ''k<sub>i</sub> = int(hash<sub>MuSig/deterministic/nonce</sub>(sk' || aggothernonce || aggpk || bytes(8, len(m)) || m || bytes(1, i - 1))) mod n'' for ''i = 1,2''
|
||||||
* Fail if ''k<sub>1</sub> = 0'' or ''k<sub>2</sub> = 0''
|
* Fail if ''k<sub>1</sub> = 0'' or ''k<sub>2</sub> = 0''
|
||||||
* Let ''R<sub>⁎,1</sub> = k<sub>1</sub>⋅G, R<sub>⁎,2</sub> = k<sub>2</sub>⋅G''
|
* Let ''R<sub>⁎,1</sub> = k<sub>1</sub>⋅G, R<sub>⁎,2</sub> = k<sub>2</sub>⋅G''
|
||||||
|
@ -785,6 +782,10 @@ An exception to this rule is <code>MAJOR</code> version zero (0.y.z) which is fo
|
||||||
The <code>MINOR</code> version is incremented whenever the inputs or the output of an algorithm changes in a backward-compatible way or new backward-compatible functionality is added.
|
The <code>MINOR</code> version is incremented whenever the inputs or the output of an algorithm changes in a backward-compatible way or new backward-compatible functionality is added.
|
||||||
The <code>PATCH</code> version is incremented for other changes that are noteworthy (bug fixes, test vectors, important clarifications, etc.).
|
The <code>PATCH</code> version is incremented for other changes that are noteworthy (bug fixes, test vectors, important clarifications, etc.).
|
||||||
|
|
||||||
|
* '''1.0.2''' (2024-07-22):
|
||||||
|
** Fix minor bug in the specification of ''DeterministicSign'' and add small improvement to a ''PartialSigAgg'' test vector.
|
||||||
|
* '''1.0.1''' (2024-05-14):
|
||||||
|
** Fix minor issue in ''PartialSigVerify'' vectors.
|
||||||
* '''1.0.0''' (2023-03-26):
|
* '''1.0.0''' (2023-03-26):
|
||||||
** Number 327 was assigned to this BIP.
|
** Number 327 was assigned to this BIP.
|
||||||
* '''1.0.0-rc.4''' (2023-03-02):
|
* '''1.0.0-rc.4''' (2023-03-02):
|
||||||
|
@ -826,4 +827,4 @@ The <code>PATCH</code> version is incremented for other changes that are notewor
|
||||||
|
|
||||||
== Acknowledgements ==
|
== Acknowledgements ==
|
||||||
|
|
||||||
We thank Brandon Black, Riccardo Casatta, Lloyd Fournier, Russell O'Connor, and Pieter Wuille for their contributions to this document.
|
We thank Brandon Black, Riccardo Casatta, Sivaram Dhakshinamoorthy, Lloyd Fournier, Russell O'Connor, and Pieter Wuille for their contributions to this document.
|
||||||
|
|
|
@ -153,7 +153,8 @@ def sig_agg_vectors():
|
||||||
"psig_indices": [7, 8],
|
"psig_indices": [7, 8],
|
||||||
"error": {
|
"error": {
|
||||||
"type": "invalid_contribution",
|
"type": "invalid_contribution",
|
||||||
"signer": 1
|
"signer": 1,
|
||||||
|
"contrib": "psig",
|
||||||
},
|
},
|
||||||
"comment": "Partial signature is invalid because it exceeds group size"
|
"comment": "Partial signature is invalid because it exceeds group size"
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,7 +317,7 @@ SessionContext = NamedTuple('SessionContext', [('aggnonce', bytes),
|
||||||
('is_xonly', List[bool]),
|
('is_xonly', List[bool]),
|
||||||
('msg', bytes)])
|
('msg', bytes)])
|
||||||
|
|
||||||
def key_agg_and_tweak(pubkeys: List[PlainPk], tweaks: List[bytes], is_xonly: List[bool]):
|
def key_agg_and_tweak(pubkeys: List[PlainPk], tweaks: List[bytes], is_xonly: List[bool]) -> KeyAggContext:
|
||||||
if len(tweaks) != len(is_xonly):
|
if len(tweaks) != len(is_xonly):
|
||||||
raise ValueError('The `tweaks` and `is_xonly` arrays must have the same length.')
|
raise ValueError('The `tweaks` and `is_xonly` arrays must have the same length.')
|
||||||
keyagg_ctx = key_agg(pubkeys)
|
keyagg_ctx = key_agg(pubkeys)
|
||||||
|
@ -440,8 +440,6 @@ def partial_sig_verify_internal(psig: bytes, pubnonce: bytes, pk: bytes, session
|
||||||
Re_s_ = point_add(R_s1, point_mul(R_s2, b))
|
Re_s_ = point_add(R_s1, point_mul(R_s2, b))
|
||||||
Re_s = Re_s_ if has_even_y(R) else point_negate(Re_s_)
|
Re_s = Re_s_ if has_even_y(R) else point_negate(Re_s_)
|
||||||
P = cpoint(pk)
|
P = cpoint(pk)
|
||||||
if P is None:
|
|
||||||
return False
|
|
||||||
a = get_session_key_agg_coeff(session_ctx, P)
|
a = get_session_key_agg_coeff(session_ctx, P)
|
||||||
g = 1 if has_even_y(Q) else n - 1
|
g = 1 if has_even_y(Q) else n - 1
|
||||||
g_ = g * gacc % n
|
g_ = g * gacc % n
|
||||||
|
@ -523,7 +521,7 @@ def test_key_agg_vectors() -> None:
|
||||||
|
|
||||||
assert get_xonly_pk(key_agg(pubkeys)) == expected
|
assert get_xonly_pk(key_agg(pubkeys)) == expected
|
||||||
|
|
||||||
for i, test_case in enumerate(error_test_cases):
|
for test_case in error_test_cases:
|
||||||
exception, except_fn = get_error_details(test_case)
|
exception, except_fn = get_error_details(test_case)
|
||||||
|
|
||||||
pubkeys = [X[i] for i in test_case["key_indices"]]
|
pubkeys = [X[i] for i in test_case["key_indices"]]
|
||||||
|
@ -572,7 +570,7 @@ def test_nonce_agg_vectors() -> None:
|
||||||
expected = bytes.fromhex(test_case["expected"])
|
expected = bytes.fromhex(test_case["expected"])
|
||||||
assert nonce_agg(pubnonces) == expected
|
assert nonce_agg(pubnonces) == expected
|
||||||
|
|
||||||
for i, test_case in enumerate(error_test_cases):
|
for test_case in error_test_cases:
|
||||||
exception, except_fn = get_error_details(test_case)
|
exception, except_fn = get_error_details(test_case)
|
||||||
pubnonces = [pnonce[i] for i in test_case["pnonce_indices"]]
|
pubnonces = [pnonce[i] for i in test_case["pnonce_indices"]]
|
||||||
assert_raises(exception, lambda: nonce_agg(pubnonces), except_fn)
|
assert_raises(exception, lambda: nonce_agg(pubnonces), except_fn)
|
||||||
|
@ -599,6 +597,9 @@ def test_sign_verify_vectors() -> None:
|
||||||
aggnonces = fromhex_all(test_data["aggnonces"])
|
aggnonces = fromhex_all(test_data["aggnonces"])
|
||||||
# The aggregate of the first three elements of pnonce is at index 0
|
# The aggregate of the first three elements of pnonce is at index 0
|
||||||
assert (aggnonces[0] == nonce_agg([pnonce[0], pnonce[1], pnonce[2]]))
|
assert (aggnonces[0] == nonce_agg([pnonce[0], pnonce[1], pnonce[2]]))
|
||||||
|
# The aggregate of the first and fourth elements of pnonce is at index 1,
|
||||||
|
# which is the infinity point encoded as a zeroed 33-byte array
|
||||||
|
assert (aggnonces[1] == nonce_agg([pnonce[0], pnonce[3]]))
|
||||||
|
|
||||||
msgs = fromhex_all(test_data["msgs"])
|
msgs = fromhex_all(test_data["msgs"])
|
||||||
|
|
||||||
|
@ -626,7 +627,7 @@ def test_sign_verify_vectors() -> None:
|
||||||
assert sign(secnonce_tmp, sk, session_ctx) == expected
|
assert sign(secnonce_tmp, sk, session_ctx) == expected
|
||||||
assert partial_sig_verify(expected, pubnonces, pubkeys, [], [], msg, signer_index)
|
assert partial_sig_verify(expected, pubnonces, pubkeys, [], [], msg, signer_index)
|
||||||
|
|
||||||
for i, test_case in enumerate(sign_error_test_cases):
|
for test_case in sign_error_test_cases:
|
||||||
exception, except_fn = get_error_details(test_case)
|
exception, except_fn = get_error_details(test_case)
|
||||||
|
|
||||||
pubkeys = [X[i] for i in test_case["key_indices"]]
|
pubkeys = [X[i] for i in test_case["key_indices"]]
|
||||||
|
@ -646,7 +647,7 @@ def test_sign_verify_vectors() -> None:
|
||||||
|
|
||||||
assert not partial_sig_verify(sig, pubnonces, pubkeys, [], [], msg, signer_index)
|
assert not partial_sig_verify(sig, pubnonces, pubkeys, [], [], msg, signer_index)
|
||||||
|
|
||||||
for i, test_case in enumerate(verify_error_test_cases):
|
for test_case in verify_error_test_cases:
|
||||||
exception, except_fn = get_error_details(test_case)
|
exception, except_fn = get_error_details(test_case)
|
||||||
|
|
||||||
sig = bytes.fromhex(test_case["sig"])
|
sig = bytes.fromhex(test_case["sig"])
|
||||||
|
@ -702,7 +703,7 @@ def test_tweak_vectors() -> None:
|
||||||
assert sign(secnonce_tmp, sk, session_ctx) == expected
|
assert sign(secnonce_tmp, sk, session_ctx) == expected
|
||||||
assert partial_sig_verify(expected, pubnonces, pubkeys, tweaks, is_xonly, msg, signer_index)
|
assert partial_sig_verify(expected, pubnonces, pubkeys, tweaks, is_xonly, msg, signer_index)
|
||||||
|
|
||||||
for i, test_case in enumerate(error_test_cases):
|
for test_case in error_test_cases:
|
||||||
exception, except_fn = get_error_details(test_case)
|
exception, except_fn = get_error_details(test_case)
|
||||||
|
|
||||||
pubkeys = [X[i] for i in test_case["key_indices"]]
|
pubkeys = [X[i] for i in test_case["key_indices"]]
|
||||||
|
@ -747,7 +748,7 @@ def test_det_sign_vectors() -> None:
|
||||||
session_ctx = SessionContext(aggnonce, pubkeys, tweaks, is_xonly, msg)
|
session_ctx = SessionContext(aggnonce, pubkeys, tweaks, is_xonly, msg)
|
||||||
assert partial_sig_verify_internal(psig, pubnonce, pubkeys[signer_index], session_ctx)
|
assert partial_sig_verify_internal(psig, pubnonce, pubkeys[signer_index], session_ctx)
|
||||||
|
|
||||||
for i, test_case in enumerate(error_test_cases):
|
for test_case in error_test_cases:
|
||||||
exception, except_fn = get_error_details(test_case)
|
exception, except_fn = get_error_details(test_case)
|
||||||
|
|
||||||
pubkeys = [X[i] for i in test_case["key_indices"]]
|
pubkeys = [X[i] for i in test_case["key_indices"]]
|
||||||
|
@ -796,7 +797,7 @@ def test_sig_agg_vectors() -> None:
|
||||||
aggpk = get_xonly_pk(key_agg_and_tweak(pubkeys, tweaks, is_xonly))
|
aggpk = get_xonly_pk(key_agg_and_tweak(pubkeys, tweaks, is_xonly))
|
||||||
assert schnorr_verify(msg, aggpk, sig)
|
assert schnorr_verify(msg, aggpk, sig)
|
||||||
|
|
||||||
for i, test_case in enumerate(error_test_cases):
|
for test_case in error_test_cases:
|
||||||
exception, except_fn = get_error_details(test_case)
|
exception, except_fn = get_error_details(test_case)
|
||||||
|
|
||||||
pubnonces = [pnonce[i] for i in test_case["nonce_indices"]]
|
pubnonces = [pnonce[i] for i in test_case["nonce_indices"]]
|
||||||
|
|
|
@ -143,7 +143,8 @@
|
||||||
],
|
],
|
||||||
"error": {
|
"error": {
|
||||||
"type": "invalid_contribution",
|
"type": "invalid_contribution",
|
||||||
"signer": 1
|
"signer": 1,
|
||||||
|
"contrib": "psig"
|
||||||
},
|
},
|
||||||
"comment": "Partial signature is invalid because it exceeds group size"
|
"comment": "Partial signature is invalid because it exceeds group size"
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@
|
||||||
],
|
],
|
||||||
"verify_fail_test_cases": [
|
"verify_fail_test_cases": [
|
||||||
{
|
{
|
||||||
"sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406",
|
"sig": "FED54434AD4CFE953FC527DC6A5E5BE8F6234907B7C187559557CE87A0541C46",
|
||||||
"key_indices": [0, 1, 2],
|
"key_indices": [0, 1, 2],
|
||||||
"nonce_indices": [0, 1, 2],
|
"nonce_indices": [0, 1, 2],
|
||||||
"msg_index": 0,
|
"msg_index": 0,
|
||||||
|
@ -165,7 +165,7 @@
|
||||||
"comment": "Wrong signature (which is equal to the negation of valid signature)"
|
"comment": "Wrong signature (which is equal to the negation of valid signature)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
"sig": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB",
|
||||||
"key_indices": [0, 1, 2],
|
"key_indices": [0, 1, 2],
|
||||||
"nonce_indices": [0, 1, 2],
|
"nonce_indices": [0, 1, 2],
|
||||||
"msg_index": 0,
|
"msg_index": 0,
|
||||||
|
@ -183,7 +183,7 @@
|
||||||
],
|
],
|
||||||
"verify_error_test_cases": [
|
"verify_error_test_cases": [
|
||||||
{
|
{
|
||||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
"sig": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB",
|
||||||
"key_indices": [0, 1, 2],
|
"key_indices": [0, 1, 2],
|
||||||
"nonce_indices": [4, 1, 2],
|
"nonce_indices": [4, 1, 2],
|
||||||
"msg_index": 0,
|
"msg_index": 0,
|
||||||
|
@ -196,7 +196,7 @@
|
||||||
"comment": "Invalid pubnonce"
|
"comment": "Invalid pubnonce"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
"sig": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB",
|
||||||
"key_indices": [3, 1, 2],
|
"key_indices": [3, 1, 2],
|
||||||
"nonce_indices": [0, 1, 2],
|
"nonce_indices": [0, 1, 2],
|
||||||
"msg_index": 0,
|
"msg_index": 0,
|
||||||
|
|
80
bip-0328.mediawiki
Normal file
80
bip-0328.mediawiki
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 328
|
||||||
|
Layer: Applications
|
||||||
|
Title: Derivation Scheme for MuSig2 Aggregate Keys
|
||||||
|
Author: Ava Chow <me@achow101.com>
|
||||||
|
Comments-Summary: No comments yet.
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0328
|
||||||
|
Status: Draft
|
||||||
|
Type: Informational
|
||||||
|
Created: 2024-01-15
|
||||||
|
License: CC0-1.0
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
==Abstract==
|
||||||
|
|
||||||
|
This document specifies how BIP 32 extended public keys can be constructed from a BIP 327 MuSig2
|
||||||
|
aggregate public key and how such keys should be used for key derivation.
|
||||||
|
|
||||||
|
==Copyright==
|
||||||
|
|
||||||
|
This BIP is licensed under the Creative Commons CC0 1.0 Universal license.
|
||||||
|
|
||||||
|
==Motivation==
|
||||||
|
|
||||||
|
Multiple signers can create a single aggregate public key with MuSig2 that is indistinguishable
|
||||||
|
from a random public key. The cosigners need a method for generating additional aggregate pubkeys
|
||||||
|
to follow the best practice of using a new address for every payment.
|
||||||
|
|
||||||
|
The obvious method is for the cosigners to generate multiple public keys and produce a
|
||||||
|
new aggregate pubkey every time one is needed. This is similar to how multisig using Bitcoin script
|
||||||
|
works where all of the cosigners share their extended public keys and do derivation to produce
|
||||||
|
the multisig script. The same could be done with MuSig2 and instead of producing a multisig script,
|
||||||
|
the result would be a MuSig2 aggregate pubkey.
|
||||||
|
|
||||||
|
However, it is much simpler to be able to derive from a single extended public key instead of having
|
||||||
|
to derive from many extended public keys and aggregate them. As MuSig2 produces a normal looking
|
||||||
|
public key, the aggregate public can be used in this way. This reduces the storage and computation
|
||||||
|
requirements for generating new aggregate pubkeys.
|
||||||
|
|
||||||
|
==Specification==
|
||||||
|
|
||||||
|
A synthetic xpub can be created from a BIP 327 MuSig2 plain aggregate public key by setting
|
||||||
|
the depth to 0, the child number to 0, and attaching a chaincode with the byte string
|
||||||
|
<tt>868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965</tt><ref>'''Where does this
|
||||||
|
constant chaincode come from?''' It is the SHA256 of the text <tt>MuSig2MuSig2MuSig2</tt></ref>.
|
||||||
|
This fixed chaincode should be used by all such synthetic xpubs following this specification.
|
||||||
|
Unhardened child public keys can be derived from the synthetic xpub as with any other xpub. Since
|
||||||
|
the aggregate public key is all that is necessary to produce the synthetic xpub, any aggregate
|
||||||
|
public key that will be used in this way shares the same privacy concerns as typical xpubs.
|
||||||
|
|
||||||
|
Furthermore, as there is no aggregate private key, only unhardened derivation from the aggregate
|
||||||
|
public key is possible.
|
||||||
|
|
||||||
|
When signing, all signers must compute the tweaks used in the BIP 32 derivation for the child key
|
||||||
|
being signed for. The I<sub>L</sub> value computed in ''CKDpub'' is the tweak used at each
|
||||||
|
derivation step. These are provided in the session context, each with a tweak mode of plain
|
||||||
|
(''is_xonly_t = false''). When the ''Sign'' algorithm is used, the tweaks will be applied to the
|
||||||
|
partial signatures.
|
||||||
|
|
||||||
|
==Test Vectors==
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
==Backwards Compatibility==
|
||||||
|
|
||||||
|
Once a synthetic xpub is created, it is fully backwards compatible with BIP 32 - only unhardened
|
||||||
|
derivation can be done, and the signers will be able to produce a signature for any derived children.
|
||||||
|
|
||||||
|
==Rationale==
|
||||||
|
|
||||||
|
<references/>
|
||||||
|
|
||||||
|
==Reference Implementation==
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
==Acknowledgements==
|
||||||
|
|
||||||
|
Thanks to Pieter Wuille, Andrew Poelstra, Sanket Kanjalkar, Salvatore Ingala, and all others who
|
||||||
|
participated in discussions on this topic.
|
301
bip-0337.mediawiki
Normal file
301
bip-0337.mediawiki
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 337
|
||||||
|
Layer: API/RPC
|
||||||
|
Title: Compressed Transactions
|
||||||
|
Author: Tom Briar <tombriar11@protonmail.com>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0337
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2024-02-01
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Post-History: https://github.com/bitcoin/bitcoin/pull/29134
|
||||||
|
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-August/021924.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
== Introduction ==
|
||||||
|
|
||||||
|
=== Abstract ===
|
||||||
|
This document proposes a serialization scheme for compressing Bitcoin transactions. The compressed Bitcoin transactions can reach a serialized size of less than 50% of the original serialized transaction. One method for compressing involves reducing the transaction outpoints in a potentially lossy way. Therefore, it is an optional path for compression. Compressing the outpoints is necessary for compressed transactions to reach less than 70% of the original size.
|
||||||
|
|
||||||
|
=== Motivation ===
|
||||||
|
Typical Bitcoin transactions usually contain a large amount of white space and padding due to specific fields that are often one of a minimal number of possibilities. We can use this fact and a few similar methods to create an encoding for 90% of Bitcoin transactions that are roughly 25-50% smaller.
|
||||||
|
|
||||||
|
There exists a working-in-progress app that allows the use of steganography to encode data in images to be passed around via various social media groups. When used in conjunction with this compression scheme and an elligator squared encryption, this would allow for a very secure and private form of broadcasting bitcoin transactions.
|
||||||
|
|
||||||
|
=== Rationale ===
|
||||||
|
|
||||||
|
The four main methods to achieve a lower transaction size are:
|
||||||
|
|
||||||
|
1. Packing transaction metadata before it and each of its inputs and outputs to determine the following data structure.
|
||||||
|
|
||||||
|
2. Replacing 32-bit numeric values with either variable-length integers (VarInts) or compact integers (CompactSizes).
|
||||||
|
|
||||||
|
3. Using compressed signatures and public key recovery upon decompression.
|
||||||
|
|
||||||
|
4. Replacing the 36-byte Outpoint txid/vout pair with a block height and index.
|
||||||
|
|
||||||
|
|
||||||
|
=== Backwards Compatibility ===
|
||||||
|
|
||||||
|
There are no concerns with backwards compatibility.
|
||||||
|
|
||||||
|
=== Specification ===
|
||||||
|
|
||||||
|
==== Primitives ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| CompactSize || 1-5 Bytes || For 0-253, encode the value directly in one byte. For 254-65535, encode 254 followed by two little-endian bytes. For 65536-(2^32-1), encode 255 followed by four little-endian bytes.
|
||||||
|
|-
|
||||||
|
| CompactSize Flag || 2 Bits || 1, 2, or 3 indicate literal values. 0 indicates that a CompactSize encoding of the value will follow.
|
||||||
|
|-
|
||||||
|
| VarInt || 1+ Bytes || 7-bit little-endian encoding, with each 7-bit word encoded in a byte. The highest bit of each byte is one if more bytes follow, and 0 for the last byte.
|
||||||
|
|-
|
||||||
|
| VLP-Bytestream || 2+ Bytes || A VarInt Length Prefixed Bytestream. It uses the prefixed VarInt to determine the length of the following byte stream.
|
||||||
|
|}
|
||||||
|
|
||||||
|
==== General Schema ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Transaction metadata || 1 Bytes || Information on the structure of the transaction. See [[#transaction-metadata|Transaction Metadata]]
|
||||||
|
|-
|
||||||
|
| Version || 0-5 Bytes || If present according to the metadata field, a CompactSize encoding of the transaction version.
|
||||||
|
|-
|
||||||
|
| Input Count || 0-5 Bytes || If present according to the metadata field, a CompactSize encoding of the transaction input count.
|
||||||
|
|-
|
||||||
|
| Output Count || 0-5 Bytes || If present according to the metadata field, a CompactSize encoding of the transaction output count.
|
||||||
|
|-
|
||||||
|
| LockTime || 0-5 Bytes || If present according to the metadata field, a CompactSize encoding of the transaction LockTime.
|
||||||
|
|-
|
||||||
|
| Minimum Blockheight || 1-5 Bytes || If present according to the metadata field, a VarInt encoding of the minimum block height for transaction compressed inputs and LockTime.
|
||||||
|
|-
|
||||||
|
| Input Metadata+Output Metadata || 1+ Bytes || An encoding containing the metadata for all the inputs followed by all the outputs of the transaction. For each input, see [[#input-metadata|Input Metadata]], and for each output, see [[#output-metadata|Output Metadata]].
|
||||||
|
|-
|
||||||
|
| Input Data || 66+ Bytes || See [[#input-data|Input Data]].
|
||||||
|
|-
|
||||||
|
| Output Data || 3+ Bytes || See [[#output-data|Output Data]].
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="transaction-metadata"></span>
|
||||||
|
==== Transaction Metadata ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Version || 2 Bits || A CompactSize flag for the transaction version.
|
||||||
|
|-
|
||||||
|
| Input Count || 2 Bits || A CompactSize flag for the transaction input count.
|
||||||
|
|-
|
||||||
|
| Output Count || 2 Bits || A CompactSize flag for the transaction output count.
|
||||||
|
|-
|
||||||
|
| LockTime || 1 Bit || A boolean to indicate if the transaction has a LockTime.
|
||||||
|
|-
|
||||||
|
| Minimum Blockheight || 1 Bit || A boolean to indicate if the transaction minimum block height is greater than zero.
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="input-metadata"></span>
|
||||||
|
==== Input Metadata ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Compressed Signature || 1 Bit || A Boolean do determine if this input's signature is compressed. The signature is only compressed for P2TR on a key spend and for P2SH when it is a wrapped P2SH-WPKH.
|
||||||
|
|-
|
||||||
|
| Standard Hash || 1 Bit || A Boolean to determine if this input's signature hash type is standard (0x00 for Taproot, 0x01 for Legacy/Segwit).
|
||||||
|
|-
|
||||||
|
| Standard Sequence || 2 Bits || A CompactSize flag for this input's sequence. Encode literal values as follows: 1 = 0x00000000, 2 = 0xFFFFFFFE, 3 = 0xFFFFFFFF.
|
||||||
|
|-
|
||||||
|
| Compressed OutPoint || 1 bit || A Boolean to determine if the input's outpoint is compressed.
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="output-metadata"></span>
|
||||||
|
==== Output Metadata ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Encoded Script Type || 3 Bits || [[#script-type-encoding|Encoded Script Type]].
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="script-type-encoding"></span>
|
||||||
|
==== Script Type Encoding ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Script Type !! Value
|
||||||
|
|-
|
||||||
|
| Uncompressed Custom Script || 0b000
|
||||||
|
|-
|
||||||
|
| Uncompressed P2PK || 0b001
|
||||||
|
|-
|
||||||
|
| Compressed P2PK || 0b010
|
||||||
|
|-
|
||||||
|
| P2PKH || 0b011
|
||||||
|
|-
|
||||||
|
| P2SH || 0b100
|
||||||
|
|-
|
||||||
|
| P2WPKH || 0b101
|
||||||
|
|-
|
||||||
|
| P2WSH || 0b110
|
||||||
|
|-
|
||||||
|
| P2TR || 0b111
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="input-data"></span>
|
||||||
|
==== Input Data ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Outpoint || 2-37 Bytes || The Outpoint Txid/Vout are determined to be compressed or otherwise by the "Compressed Outpoint" Boolean in the input metadata. For each compressed outpoint see [[#compressed-outpoint|Compressed Outpoint]]. For each uncompressed signature see [[#uncompressed-outpoint|Uncompressed Outpoint]].
|
||||||
|
|-
|
||||||
|
| Signature || 64+ Bytes || The Signature is determined to be compressed or otherwise by the output script of the previous transaction. For each compressed signature see [[#compressed-signature|Compressed Signature]]. For each uncompressed signature see [[#uncompressed-signature|Uncompressed Signature]].
|
||||||
|
|-
|
||||||
|
| Sequence || 0-5 Bytes || If present due to a non-standard sequence, a VarInt encoding of the sequence.
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="compressed-outpoint"></span>
|
||||||
|
==== Compressed Outpoint ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Txid Block Height || 1-5 Bytes || A VarInt containing the offset from Minimum Blockheight for this Txid.
|
||||||
|
|-
|
||||||
|
| Txid Block Index || 1-5 Bytes || A VarInt containing the flattened index from the Txid block height for the Vout.
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="uncompressed-outpoint"></span>
|
||||||
|
==== Uncompressed Outpoint ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Txid || 32 Bytes || Contains the 32 Byte Txid.
|
||||||
|
|-
|
||||||
|
| Vout || 1-5 Bytes || A CompactSize Containing the Vout of the Txid.
|
||||||
|
|}
|
||||||
|
|
||||||
|
|
||||||
|
<span id="compressed-signature"></span>
|
||||||
|
==== Compressed Signature ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Signature || 64 Bytes || Contains the 64 Byte signature.
|
||||||
|
|-
|
||||||
|
| Pubkey Hash || 0-20 Bytes || If input is P2SH-P2WPKH contains the 20 byte hash of the public key.
|
||||||
|
|-
|
||||||
|
| Hash Type || 0-1 Bytes || An Optional Byte containing the Hash Type if it was non-standard.
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="uncompressed-signature"></span>
|
||||||
|
==== Uncompressed Signature ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Signature || 2+ Bytes || A VLP-Bytestream containing the signature.
|
||||||
|
|}
|
||||||
|
|
||||||
|
<span id="output-data"></span>
|
||||||
|
==== Output Data ====
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Name !! Width !! Description
|
||||||
|
|-
|
||||||
|
| Output Script || 2+ Bytes || A VLP-Bytestream containing the output script.
|
||||||
|
|-
|
||||||
|
| Amount || 1-9 Bytes || A VarInt containing the output amount.
|
||||||
|
|}
|
||||||
|
|
||||||
|
==== Ideal Transaction ====
|
||||||
|
|
||||||
|
The compression scheme was designed to be optimal for a "typical" transaction, spending a few close-in-age inputs and having one or two outputs. Here are size
|
||||||
|
values for such a transaction, which demonstrate the effectiveness of the compression.
|
||||||
|
|
||||||
|
{| class="wikitable" style="margin:auto"
|
||||||
|
|-
|
||||||
|
! Field !! Requirements !! Savings Up To
|
||||||
|
|-
|
||||||
|
| Version || Less than four || 30 Bits
|
||||||
|
|-
|
||||||
|
| Input Count || Less than four || 30 Bits
|
||||||
|
|-
|
||||||
|
| Output Count || Less than four || 30 Bits
|
||||||
|
|-
|
||||||
|
| LockTime || 0 || 30 Bits
|
||||||
|
|-
|
||||||
|
| Input Sequence || 0x00, 0xFFFFFFFE, or 0xFFFFFFFF || 62 Bits For Each Input
|
||||||
|
|-
|
||||||
|
| Input Txid || Compressed Outpoint || 23 - 31 Bytes For Each Input
|
||||||
|
|-
|
||||||
|
| Input Vout || Compressed Outpoint || (-1) - 3 Bytes For Each Input
|
||||||
|
|-
|
||||||
|
| Input Signature || Non-custom Script Signing || 40 - 72 Bytes For Each Legacy Input
|
||||||
|
|-
|
||||||
|
| Input Hash Type || 0x00 for Taproot, 0x01 for Legacy || 7 Bits For Each Input
|
||||||
|
|-
|
||||||
|
| Output Script || Non-custom Scripts || 2 - 5 Bytes For Each Output
|
||||||
|
|-
|
||||||
|
| Output Amount || No Restrictions || (-1) - 7 Bytes For Each Output
|
||||||
|
|}
|
||||||
|
|
||||||
|
=== Reference Implementation ===
|
||||||
|
|
||||||
|
This reference implementation adds two new RPC endpoints, compressrawtransaction and decompressrawtransaction. The first accepts a raw hex-encoded transaction and returns a compact hex-encoded transaction; also included in the output is a list of warnings to help ensure there are no unexpected uncompressed values. The second accepts a compact hex transaction and returns the uncompressed raw hex-encoded transaction.
|
||||||
|
|
||||||
|
https://github.com/bitcoin/bitcoin/pull/29134
|
||||||
|
|
||||||
|
=== Test Vectors ===
|
||||||
|
|
||||||
|
==== Taproot ====
|
||||||
|
|
||||||
|
===== Uncompressed =====
|
||||||
|
<code>020000000001017ad1d0cc314504ec06f1b5c786c50cf3cda30bd5be88cf08ead571b0ce7481fb0000000000fdffffff0188130000000000001600142da377ed4978fefa043a58489912f8e28e16226201408ce65b3170d3fbc68e3b6980650514dc53565f915d14351f83050ff50c8609495b7aa96271c3c99cdac1a92b1b45e77a4a870251fc1673596793adf2494565e500000000</code>
|
||||||
|
|
||||||
|
===== Compressed =====
|
||||||
|
<code>96b1ec7f968001b0218ce65b3170d3fbc68e3b6980650514dc53565f915d14351f83050ff50c8609495b7aa96271c3c99cdac1a92b1b45e77a4a870251fc1673596793adf2494565e58efefefe7d2da377ed4978fefa043a58489912f8e28e162262a608</code>
|
||||||
|
|
||||||
|
==== P2WPKH ====
|
||||||
|
|
||||||
|
===== Uncompressed =====
|
||||||
|
<code>0200000000010144bcf05ab48b8789268a7ca07133241ad654c0739ac7165015b2d669eadb10ea0000000000fdffffff0188130000000000001600142da377ed4978fefa043a58489912f8e28e16226202473044022043ab639a98dfbc704f16a35bf25b8b72acb4cb928fd772285f1fcf63725caa85022001c9ff354504e7024708bce61f30370c8db13da8170cef4e8e4c4cdad0f71bfe0121030072484c24705512bfb1f7f866d95f808d81d343e552bc418113e1b9a1da0eb400000000</code>
|
||||||
|
|
||||||
|
===== Compressed =====
|
||||||
|
<code>96b1ec71968001932643ab639a98dfbc704f16a35bf25b8b72acb4cb928fd772285f1fcf63725caa8501c9ff354504e7024708bce61f30370c8db13da8170cef4e8e4c4cdad0f71bfe8efefefe7d2da377ed4978fefa043a58489912f8e28e162262a608</code>
|
||||||
|
|
||||||
|
==== P2SH-P2WPKH ====
|
||||||
|
|
||||||
|
===== Uncompressed =====
|
||||||
|
<code>0200000000010192fb2e4332b43dc9a73febba67f3b7d97ba890673cb08efde2911330f77bbdfc00000000171600147a1979232206857167b401fdac1ffbf33f8204fffdffffff0188130000000000001600142da377ed4978fefa043a58489912f8e28e16226202473044022041eb682e63c25b85a5a400b11d41cf4b9c25f309090a5f3e0b69dc15426da90402205644ddc3d5179bab49cce4bf69ebfaeab1afa34331c1a0a70be2927d2836b0e8012103c483f1b1bd24dd23b3255a68d87ef9281f9d080fd707032ccb81c1cc56c5b00200000000</code>
|
||||||
|
|
||||||
|
===== Compressed =====
|
||||||
|
<code>96b1ec7c9e8001981641eb682e63c25b85a5a400b11d41cf4b9c25f309090a5f3e0b69dc15426da9045644ddc3d5179bab49cce4bf69ebfaeab1afa34331c1a0a70be2927d2836b0e87a1979232206857167b401fdac1ffbf33f8204ff8efefefe7d2da377ed4978fefa043a58489912f8e28e162262a608</code>
|
||||||
|
|
||||||
|
==== P2PKH ====
|
||||||
|
|
||||||
|
===== Uncompressed =====
|
||||||
|
<code>02000000015f5be26862482fe2fcc900f06ef26ee256fb205bc4773e5a402d0c1b88b82043000000006a473044022031a20f5d9212023b510599c9d53d082f8e07faaa2d51482e078f8e398cb50d770220635abd99220ad713a081c4f20b83cb3f491ed8bd032cb151a3521ed144164d9c0121027977f1b6357cead2df0a0a19570088a1eb9115468b2dfa01439493807d8f1294fdffffff0188130000000000001600142da377ed4978fefa043a58489912f8e28e16226200000000</code>
|
||||||
|
|
||||||
|
===== Compressed =====
|
||||||
|
<code>96b1ec7c968001981431a20f5d9212023b510599c9d53d082f8e07faaa2d51482e078f8e398cb50d77635abd99220ad713a081c4f20b83cb3f491ed8bd032cb151a3521ed144164d9c8efefefe7d2da377ed4978fefa043a58489912f8e28e162262a608</code>
|
||||||
|
|
||||||
|
|
||||||
|
== Acknowledgements ==
|
||||||
|
Thank you to Andrew Poelstra, who helped invent and develop the ideas in the proposal and the code for reference implementation.
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Suhas Daftuar <sdaftuar@chaincode.com>
|
Author: Suhas Daftuar <sdaftuar@chaincode.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0338
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0338
|
||||||
Status: Draft
|
Status: Withdrawn
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2020-09-03
|
Created: 2020-09-03
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Suhas Daftuar <sdaftuar@chaincode.com>
|
Author: Suhas Daftuar <sdaftuar@chaincode.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0339
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0339
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2020-02-03
|
Created: 2020-02-03
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -62,7 +62,7 @@ Since we would like to avoid the fragility that comes with short hashes, the ''e
|
||||||
|
|
||||||
'''Key prefixing''' Using the verification rule above directly makes Schnorr signatures vulnerable to "related-key attacks" in which a third party can convert a signature ''(R, s)'' for public key ''P'' into a signature ''(R, s + a⋅hash(R || m))'' for public key ''P + a⋅G'' and the same message ''m'', for any given additive tweak ''a'' to the signing key. This would render signatures insecure when keys are generated using [[bip-0032.mediawiki#public-parent-key--public-child-key|BIP32's unhardened derivation]] and other methods that rely on additive tweaks to existing keys such as Taproot.
|
'''Key prefixing''' Using the verification rule above directly makes Schnorr signatures vulnerable to "related-key attacks" in which a third party can convert a signature ''(R, s)'' for public key ''P'' into a signature ''(R, s + a⋅hash(R || m))'' for public key ''P + a⋅G'' and the same message ''m'', for any given additive tweak ''a'' to the signing key. This would render signatures insecure when keys are generated using [[bip-0032.mediawiki#public-parent-key--public-child-key|BIP32's unhardened derivation]] and other methods that rely on additive tweaks to existing keys such as Taproot.
|
||||||
|
|
||||||
To protect against these attacks, we choose ''key prefixed''<ref>A limitation of committing to the public key (rather than to a short hash of it, or not at all) is that it removes the ability for public key recovery or verifying signatures against a short public key hash. These constructions are generally incompatible with batch verification.</ref> Schnorr signatures which means that the public key is prefixed to the message in the challenge hash input. This changes the equation to ''s⋅G = R + hash(R || P || m)⋅P''. [https://eprint.iacr.org/2015/1135.pdf It can be shown] that key prefixing protects against related-key attacks with additive tweaks. In general, key prefixing increases robustness in multi-user settings, e.g., it seems to be a requirement for proving the MuSig multisignature scheme secure (see Applications below).
|
To protect against these attacks, we choose ''key prefixed''<ref>A limitation of committing to the public key (rather than to a short hash of it, or not at all) is that it removes the ability for public key recovery or verifying signatures against a short public key hash. These constructions are generally incompatible with batch verification.</ref> Schnorr signatures which means that the public key is prefixed to the message in the challenge hash input. This changes the equation to ''s⋅G = R + hash(R || P || m)⋅P''. [https://eprint.iacr.org/2015/1135.pdf It can be shown] that key prefixing protects against related-key attacks with additive tweaks. In general, key prefixing increases robustness in multi-user settings, e.g., it seems to be a requirement for proving multiparty signing protocols (such as MuSig, MuSig2, and FROST) secure (see Applications below).
|
||||||
|
|
||||||
We note that key prefixing is not strictly necessary for transaction signatures as used in Bitcoin currently, because signed transactions indirectly commit to the public keys already, i.e., ''m'' contains a commitment to ''pk''. However, this indirect commitment should not be relied upon because it may change with proposals such as SIGHASH_NOINPUT ([[bip-0118.mediawiki|BIP118]]), and would render the signature scheme unsuitable for other purposes than signing transactions, e.g., [https://bitcoin.org/en/developer-reference#signmessage signing ordinary messages].
|
We note that key prefixing is not strictly necessary for transaction signatures as used in Bitcoin currently, because signed transactions indirectly commit to the public keys already, i.e., ''m'' contains a commitment to ''pk''. However, this indirect commitment should not be relied upon because it may change with proposals such as SIGHASH_NOINPUT ([[bip-0118.mediawiki|BIP118]]), and would render the signature scheme unsuitable for other purposes than signing transactions, e.g., [https://bitcoin.org/en/developer-reference#signmessage signing ordinary messages].
|
||||||
|
|
||||||
|
@ -161,14 +161,14 @@ The auxiliary random data should be set to fresh randomness generated at signing
|
||||||
|
|
||||||
==== Alternative Signing ====
|
==== Alternative Signing ====
|
||||||
|
|
||||||
It should be noted that various alternative signing algorithms can be used to produce equally valid signatures. The 32-byte ''rand'' value may be generated in other ways, producing a different but still valid signature (in other words, this is not a ''unique'' signature scheme). '''No matter which method is used to generate the ''rand'' value, the value must be a fresh uniformly random 32-byte string which is not even partially predictable for the attacker.''' For nonces without randomness this implies that the same inputs must not be presented in another context. This can be most reliably accomplished by not reusing the same private key across different signing schemes. For example, if the ''rand'' value was computed as per RFC6979 and the same secret key is used in deterministic ECDSA with RFC6979, the signatures can leak the secret key through nonce reuse.
|
It should be noted that various alternative signing algorithms can be used to produce equally valid signatures. The 32-byte ''rand'' value may be generated in other ways, producing a different but still valid signature (in other words, this is not a ''unique'' signature scheme). '''No matter which method is used to generate the ''rand'' value, the value must be a fresh uniformly random 32-byte string which is not even partially predictable for the attacker.''' For nonces without randomness, this implies that the same inputs must not be presented in another context. This can be most reliably accomplished by not reusing the same private key across different signing schemes. For example, if the ''rand'' value was computed as per RFC6979 and the same secret key is used in deterministic ECDSA with RFC6979, the signatures can leak the secret key through nonce reuse.
|
||||||
|
|
||||||
'''Nonce exfiltration protection''' It is possible to strengthen the nonce generation algorithm using a second device. In this case, the second device contributes randomness which the actual signer provably incorporates into its nonce. This prevents certain attacks where the signer device is compromised and intentionally tries to leak the secret key through its nonce selection.
|
'''Nonce exfiltration protection''' It is possible to strengthen the nonce generation algorithm using a second device. In this case, the second device contributes randomness which the actual signer provably incorporates into its nonce. This prevents certain attacks where the signer's device is compromised and intentionally tries to leak the secret key through its nonce selection.
|
||||||
|
|
||||||
'''Multisignatures''' This signature scheme is compatible with various types of multisignature and threshold schemes such as [https://eprint.iacr.org/2018/068 MuSig], where a single public key requires holders of multiple secret keys to participate in signing (see Applications below).
|
'''Multisignatures''' This signature scheme is compatible with various types of multisignature and threshold schemes such as [https://eprint.iacr.org/2020/1261.pdf MuSig2], where a single public key requires holders of multiple secret keys to participate in signing (see Applications below).
|
||||||
'''It is important to note that multisignature signing schemes in general are insecure with the ''rand'' generation from the default signing algorithm above (or any other deterministic method).'''
|
'''It is important to note that multisignature signing schemes in general are insecure with the ''rand'' generation from the default signing algorithm above (or any other deterministic method).'''
|
||||||
|
|
||||||
'''Precomputed public key data''' For many uses the compressed 33-byte encoding of the public key corresponding to the secret key may already be known, making it easy to evaluate ''has_even_y(P)'' and ''bytes(P)''. As such, having signers supply this directly may be more efficient than recalculating the public key from the secret key. However, if this optimization is used and additionally the signature verification at the end of the signing algorithm is dropped for increased efficiency, signers must ensure the public key is correctly calculated and not taken from untrusted sources.
|
'''Precomputed public key data''' For many uses, the compressed 33-byte encoding of the public key corresponding to the secret key may already be known, making it easy to evaluate ''has_even_y(P)'' and ''bytes(P)''. As such, having signers supply this directly may be more efficient than recalculating the public key from the secret key. However, if this optimization is used and additionally the signature verification at the end of the signing algorithm is dropped for increased efficiency, signers must ensure the public key is correctly calculated and not taken from untrusted sources.
|
||||||
|
|
||||||
==== Verification ====
|
==== Verification ====
|
||||||
|
|
||||||
|
@ -264,9 +264,9 @@ While recent academic papers claim that they are also possible with ECDSA, conse
|
||||||
|
|
||||||
=== Multisignatures and Threshold Signatures ===
|
=== Multisignatures and Threshold Signatures ===
|
||||||
|
|
||||||
By means of an interactive scheme such as [https://eprint.iacr.org/2018/068 MuSig], participants can aggregate their public keys into a single public key which they can jointly sign for. This allows ''n''-of-''n'' multisignatures which, from a verifier's perspective, are no different from ordinary signatures, giving improved privacy and efficiency versus ''CHECKMULTISIG'' or other means.
|
By means of an interactive scheme such as [https://eprint.iacr.org/2020/1261.pdf MuSig2] ([[bip-0327.mediawiki|BIP327]]), participants can aggregate their public keys into a single public key which they can jointly sign for. This allows ''n''-of-''n'' multisignatures which, from a verifier's perspective, are no different from ordinary signatures, giving improved privacy and efficiency versus ''CHECKMULTISIG'' or other means.
|
||||||
|
|
||||||
Moreover, Schnorr signatures are compatible with [https://web.archive.org/web/20031003232851/http://www.research.ibm.com/security/dkg.ps distributed key generation], which enables interactive threshold signatures schemes, e.g., the schemes described by [http://cacr.uwaterloo.ca/techreports/2001/corr2001-13.ps Stinson and Strobl (2001)] or [https://web.archive.org/web/20060911151529/http://theory.lcs.mit.edu/~stasio/Papers/gjkr03.pdf Gennaro, Jarecki and Krawczyk (2003)]. These protocols make it possible to realize ''k''-of-''n'' threshold signatures, which ensure that any subset of size ''k'' of the set of ''n'' signers can sign but no subset of size less than ''k'' can produce a valid Schnorr signature. However, the practicality of the existing schemes is limited: most schemes in the literature have been proven secure only for the case ''k-1 < n/2'', are not secure when used concurrently in multiple sessions, or require a reliable broadcast mechanism to be secure. Further research is necessary to improve this situation.
|
Moreover, Schnorr signatures are compatible with [https://en.wikipedia.org/wiki/Distributed_key_generation distributed key generation], which enables interactive threshold signatures schemes, e.g., the schemes by [http://cacr.uwaterloo.ca/techreports/2001/corr2001-13.ps Stinson and Strobl (2001)], by [https://link.springer.com/content/pdf/10.1007/s00145-006-0347-3.pdf Gennaro, Jarecki, Krawczyk, and Rabin (2007)], or the [https://eprint.iacr.org/2020/852.pdf FROST] scheme including its variants such as [https://eprint.iacr.org/2023/899.pdf FROST3]. These protocols make it possible to realize ''k''-of-''n'' threshold signatures, which ensure that any subset of size ''k'' of the set of ''n'' signers can sign but no subset of size less than ''k'' can produce a valid Schnorr signature.
|
||||||
|
|
||||||
=== Adaptor Signatures ===
|
=== Adaptor Signatures ===
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ Adaptor signatures, beyond the efficiency and privacy benefits of encoding scrip
|
||||||
|
|
||||||
=== Blind Signatures ===
|
=== Blind Signatures ===
|
||||||
|
|
||||||
A blind signature protocol is an interactive protocol that enables a signer to sign a message at the behest of another party without learning any information about the signed message or the signature. Schnorr signatures admit a very [http://publikationen.ub.uni-frankfurt.de/files/4292/schnorr.blind_sigs_attack.2001.pdf simple blind signature scheme] which is however insecure because it's vulnerable to [https://www.iacr.org/archive/crypto2002/24420288/24420288.pdf Wagner's attack]. A known mitigation is to let the signer abort a signing session with a certain probability, and the resulting scheme can be [https://eprint.iacr.org/2019/877 proven secure under non-standard cryptographic assumptions].
|
A blind signature protocol is an interactive protocol that enables a signer to sign a message at the behest of another party without learning any information about the signed message or the signature. Schnorr signatures admit a very [http://publikationen.ub.uni-frankfurt.de/files/4292/schnorr.blind_sigs_attack.2001.pdf simple blind signature scheme] which is however insecure because it's vulnerable to [https://www.iacr.org/archive/crypto2002/24420288/24420288.pdf Wagner's attack]. Known mitigations are to let the signer abort a signing session with a certain probability, which can be [https://eprint.iacr.org/2019/877 proven secure under non-standard cryptographic assumptions], or [https://eprint.iacr.org/2022/1676.pdf to use zero-knowledge proofs].
|
||||||
|
|
||||||
Blind Schnorr signatures could for example be used in [https://github.com/ElementsProject/scriptless-scripts/blob/master/md/partially-blind-swap.md Partially Blind Atomic Swaps], a construction to enable transferring of coins, mediated by an untrusted escrow agent, without connecting the transactors in the public blockchain transaction graph.
|
Blind Schnorr signatures could for example be used in [https://github.com/ElementsProject/scriptless-scripts/blob/master/md/partially-blind-swap.md Partially Blind Atomic Swaps], a construction to enable transferring of coins, mediated by an untrusted escrow agent, without connecting the transactors in the public blockchain transaction graph.
|
||||||
|
|
||||||
|
@ -293,6 +293,7 @@ To help implementors understand updates to this BIP, we keep a list of substanti
|
||||||
|
|
||||||
* 2022-08: Fix function signature of lift_x in reference code
|
* 2022-08: Fix function signature of lift_x in reference code
|
||||||
* 2023-04: Allow messages of arbitrary size
|
* 2023-04: Allow messages of arbitrary size
|
||||||
|
* 2024-05: Update "Applications" section with more recent references
|
||||||
|
|
||||||
== Footnotes ==
|
== Footnotes ==
|
||||||
|
|
||||||
|
|
|
@ -24,17 +24,17 @@ def vector0():
|
||||||
assert(y(P) % 2 == 0)
|
assert(y(P) % 2 == 0)
|
||||||
|
|
||||||
# For historical reasons (pubkey tiebreaker was squareness and not evenness)
|
# For historical reasons (pubkey tiebreaker was squareness and not evenness)
|
||||||
# we should have at least one test vector where the the point reconstructed
|
# we should have at least one test vector where the point reconstructed
|
||||||
# from the public key has a square and one where it has a non-square Y
|
# from the public key has a square and one where it has a non-square Y
|
||||||
# coordinate. In this one Y is non-square.
|
# coordinate. In this one Y is non-square.
|
||||||
pubkey_point = lift_x(pubkey)
|
pubkey_point = lift_x(int_from_bytes(pubkey))
|
||||||
assert(not has_square_y(pubkey_point))
|
assert(not has_square_y(pubkey_point))
|
||||||
|
|
||||||
# For historical reasons (R tiebreaker was squareness and not evenness)
|
# For historical reasons (R tiebreaker was squareness and not evenness)
|
||||||
# we should have at least one test vector where the the point reconstructed
|
# we should have at least one test vector where the point reconstructed
|
||||||
# from the R.x coordinate has a square and one where it has a non-square Y
|
# from the R.x coordinate has a square and one where it has a non-square Y
|
||||||
# coordinate. In this one Y is non-square.
|
# coordinate. In this one Y is non-square.
|
||||||
R = lift_x(sig[0:32])
|
R = lift_x(int_from_bytes(sig[0:32]))
|
||||||
assert(not has_square_y(R))
|
assert(not has_square_y(R))
|
||||||
|
|
||||||
return (seckey, pubkey, aux_rand, msg, sig, "TRUE", None)
|
return (seckey, pubkey, aux_rand, msg, sig, "TRUE", None)
|
||||||
|
@ -47,7 +47,7 @@ def vector1():
|
||||||
sig = schnorr_sign(msg, seckey, aux_rand)
|
sig = schnorr_sign(msg, seckey, aux_rand)
|
||||||
|
|
||||||
# The point reconstructed from the R.x coordinate has a square Y coordinate.
|
# The point reconstructed from the R.x coordinate has a square Y coordinate.
|
||||||
R = lift_x(sig[0:32])
|
R = lift_x(int_from_bytes(sig[0:32]))
|
||||||
assert(has_square_y(R))
|
assert(has_square_y(R))
|
||||||
|
|
||||||
return (seckey, pubkey_gen(seckey), aux_rand, msg, sig, "TRUE", None)
|
return (seckey, pubkey_gen(seckey), aux_rand, msg, sig, "TRUE", None)
|
||||||
|
@ -60,12 +60,12 @@ def vector2():
|
||||||
|
|
||||||
# The point reconstructed from the public key has a square Y coordinate.
|
# The point reconstructed from the public key has a square Y coordinate.
|
||||||
pubkey = pubkey_gen(seckey)
|
pubkey = pubkey_gen(seckey)
|
||||||
pubkey_point = lift_x(pubkey)
|
pubkey_point = lift_x(int_from_bytes(pubkey))
|
||||||
assert(has_square_y(pubkey_point))
|
assert(has_square_y(pubkey_point))
|
||||||
|
|
||||||
# This signature vector would not verify if the implementer checked the
|
# This signature vector would not verify if the implementer checked the
|
||||||
# evenness of the X coordinate of R instead of the Y coordinate.
|
# evenness of the X coordinate of R instead of the Y coordinate.
|
||||||
R = lift_x(sig[0:32])
|
R = lift_x(int_from_bytes(sig[0:32]))
|
||||||
assert(R[0] % 2 == 1)
|
assert(R[0] % 2 == 1)
|
||||||
|
|
||||||
return (seckey, pubkey, aux_rand, msg, sig, "TRUE", None)
|
return (seckey, pubkey, aux_rand, msg, sig, "TRUE", None)
|
||||||
|
@ -99,7 +99,7 @@ def insecure_schnorr_sign_fixed_nonce(msg, seckey0, k):
|
||||||
e = int_from_bytes(tagged_hash("BIP0340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n
|
e = int_from_bytes(tagged_hash("BIP0340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n
|
||||||
return bytes_from_point(R) + bytes_from_int((k + e * seckey) % n)
|
return bytes_from_point(R) + bytes_from_int((k + e * seckey) % n)
|
||||||
|
|
||||||
# Creates a singature with a small x(R) by using k = -1/2
|
# Creates a signature with a small x(R) by using k = -1/2
|
||||||
def vector4():
|
def vector4():
|
||||||
one_half = n - 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0
|
one_half = n - 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0
|
||||||
seckey = bytes_from_int(0x763758E5CBEEDEE4F7D3FC86F531C36578933228998226672F13C4F0EBE855EB)
|
seckey = bytes_from_int(0x763758E5CBEEDEE4F7D3FC86F531C36578933228998226672F13C4F0EBE855EB)
|
||||||
|
@ -119,8 +119,9 @@ def vector5():
|
||||||
msg = default_msg
|
msg = default_msg
|
||||||
sig = schnorr_sign(msg, seckey, default_aux_rand)
|
sig = schnorr_sign(msg, seckey, default_aux_rand)
|
||||||
|
|
||||||
pubkey = bytes_from_int(0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34)
|
pubkey_int = 0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34
|
||||||
assert(lift_x(pubkey) is None)
|
pubkey = bytes_from_int(pubkey_int)
|
||||||
|
assert(lift_x(pubkey_int) is None)
|
||||||
|
|
||||||
return (None, pubkey, None, msg, sig, "FALSE", "public key not on the curve")
|
return (None, pubkey, None, msg, sig, "FALSE", "public key not on the curve")
|
||||||
|
|
||||||
|
@ -197,9 +198,9 @@ def vector11():
|
||||||
sig = schnorr_sign(msg, seckey, default_aux_rand)
|
sig = schnorr_sign(msg, seckey, default_aux_rand)
|
||||||
|
|
||||||
# Replace R's X coordinate with an X coordinate that's not on the curve
|
# Replace R's X coordinate with an X coordinate that's not on the curve
|
||||||
x_not_on_curve = bytes_from_int(0x4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D)
|
x_not_on_curve = 0x4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D
|
||||||
assert(lift_x(x_not_on_curve) is None)
|
assert(lift_x(x_not_on_curve) is None)
|
||||||
sig = x_not_on_curve + sig[32:64]
|
sig = bytes_from_int(x_not_on_curve) + sig[32:64]
|
||||||
|
|
||||||
return (None, pubkey_gen(seckey), None, msg, sig, "FALSE", "sig[0:32] is not an X coordinate on the curve")
|
return (None, pubkey_gen(seckey), None, msg, sig, "FALSE", "sig[0:32] is not an X coordinate on the curve")
|
||||||
|
|
||||||
|
@ -242,10 +243,10 @@ def vector14():
|
||||||
sig = schnorr_sign(msg, seckey, default_aux_rand)
|
sig = schnorr_sign(msg, seckey, default_aux_rand)
|
||||||
pubkey_int = p + 1
|
pubkey_int = p + 1
|
||||||
pubkey = bytes_from_int(pubkey_int)
|
pubkey = bytes_from_int(pubkey_int)
|
||||||
assert(lift_x(pubkey) is None)
|
assert(lift_x(pubkey_int) is None)
|
||||||
# If an implementation would reduce a given public key modulo p then the
|
# If an implementation would reduce a given public key modulo p then the
|
||||||
# pubkey would be valid
|
# pubkey would be valid
|
||||||
assert(lift_x(bytes_from_int(pubkey_int % p)) is not None)
|
assert(lift_x(pubkey_int % p) is not None)
|
||||||
|
|
||||||
return (None, pubkey, None, msg, sig, "FALSE", "public key is not a valid X coordinate because it exceeds the field size")
|
return (None, pubkey, None, msg, sig, "FALSE", "public key is not a valid X coordinate because it exceeds the field size")
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ As a result we choose this combination of technologies:
|
||||||
* '''Taproot''' on top of that lets us merge the traditionally separate pay-to-pubkey and pay-to-scripthash policies, making all outputs spendable by either a key or (optionally) a script, and indistinguishable from each other. As long as the key-based spending path is used for spending, it is not revealed whether a script path was permitted as well, resulting in space savings and an increase in scripting privacy at spending time.
|
* '''Taproot''' on top of that lets us merge the traditionally separate pay-to-pubkey and pay-to-scripthash policies, making all outputs spendable by either a key or (optionally) a script, and indistinguishable from each other. As long as the key-based spending path is used for spending, it is not revealed whether a script path was permitted as well, resulting in space savings and an increase in scripting privacy at spending time.
|
||||||
* Taproot's advantages become apparent under the assumption that most applications involve outputs that could be spent by all parties agreeing. That's where '''Schnorr''' signatures come in, as they permit [https://eprint.iacr.org/2018/068 key aggregation]: a public key can be constructed from multiple participant public keys, and which requires cooperation between all participants to sign for. Such multi-party public keys and signatures are indistinguishable from their single-party equivalents. This means that with taproot most applications can use the key-based spending path, which is both efficient and private. This can be generalized to arbitrary M-of-N policies, as Schnorr signatures support threshold signing, at the cost of more complex setup protocols.
|
* Taproot's advantages become apparent under the assumption that most applications involve outputs that could be spent by all parties agreeing. That's where '''Schnorr''' signatures come in, as they permit [https://eprint.iacr.org/2018/068 key aggregation]: a public key can be constructed from multiple participant public keys, and which requires cooperation between all participants to sign for. Such multi-party public keys and signatures are indistinguishable from their single-party equivalents. This means that with taproot most applications can use the key-based spending path, which is both efficient and private. This can be generalized to arbitrary M-of-N policies, as Schnorr signatures support threshold signing, at the cost of more complex setup protocols.
|
||||||
* As Schnorr signatures also permit '''batch validation''', allowing multiple signatures to be validated together more efficiently than validating each one independently, we make sure all parts of the design are compatible with this.
|
* As Schnorr signatures also permit '''batch validation''', allowing multiple signatures to be validated together more efficiently than validating each one independently, we make sure all parts of the design are compatible with this.
|
||||||
* Where unused bits appear as a result of the above changes, they are reserved for mechanisms for '''future extensions'''. As a result, every script in the Merkle tree has an associated version such that new script versions can be introduced with a soft fork while remaining compatible with BIP 341. Additionally, future soft forks can make use of the currently unused <code>annex</code> in the witness (see [[bip-0341.mediawiki#Rationale|BIP341]]).
|
* Where unused bits appear as a result of the above changes, they are reserved for mechanisms for '''future extensions'''. As a result, every script in the Merkle tree has an associated version such that new script versions can be introduced with a soft fork while remaining compatible with BIP 341. Additionally, future soft forks can make use of the currently unused <code>annex</code> in the witness (see [[bip-0341.mediawiki#rationale|Rationale]]).
|
||||||
* While the core semantics of the '''signature hashing algorithm''' are not changed, a number of improvements are included in this proposal. The new signature hashing algorithm fixes the verification capabilities of offline signing devices by including amount and scriptPubKey in the signature message, avoids unnecessary hashing, uses '''tagged hashes''' and defines a default sighash byte.
|
* While the core semantics of the '''signature hashing algorithm''' are not changed, a number of improvements are included in this proposal. The new signature hashing algorithm fixes the verification capabilities of offline signing devices by including amount and scriptPubKey in the signature message, avoids unnecessary hashing, uses '''tagged hashes''' and defines a default sighash byte.
|
||||||
* The '''public key is directly included in the output''' in contrast to typical earlier constructions which store a hash of the public key or script in the output. This has the same cost for senders and is more space efficient overall if the key-based spending path is taken. <ref>'''Why is the public key directly included in the output?''' While typical earlier constructions store a hash of a script or a public key in the output, this is rather wasteful when a public key is always involved. To guarantee batch verifiability, the public key must be known to every verifier, and thus only revealing its hash as an output would imply adding an additional 32 bytes to the witness. Furthermore, to maintain [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2016-January/012198.html 128-bit collision security] for outputs, a 256-bit hash would be required anyway, which is comparable in size (and thus in cost for senders) to revealing the public key directly. While the usage of public key hashes is often said to protect against ECDLP breaks or quantum computers, this protection is very weak at best: transactions are not protected while being confirmed, and a very [https://twitter.com/pwuille/status/1108097835365339136 large portion] of the currency's supply is not under such protection regardless. Actual resistance to such systems can be introduced by relying on different cryptographic assumptions, but this proposal focuses on improvements that do not change the security model.</ref>
|
* The '''public key is directly included in the output''' in contrast to typical earlier constructions which store a hash of the public key or script in the output. This has the same cost for senders and is more space efficient overall if the key-based spending path is taken. <ref>'''Why is the public key directly included in the output?''' While typical earlier constructions store a hash of a script or a public key in the output, this is rather wasteful when a public key is always involved. To guarantee batch verifiability, the public key must be known to every verifier, and thus only revealing its hash as an output would imply adding an additional 32 bytes to the witness. Furthermore, to maintain [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2016-January/012198.html 128-bit collision security] for outputs, a 256-bit hash would be required anyway, which is comparable in size (and thus in cost for senders) to revealing the public key directly. While the usage of public key hashes is often said to protect against ECDLP breaks or quantum computers, this protection is very weak at best: transactions are not protected while being confirmed, and a very [https://twitter.com/pwuille/status/1108097835365339136 large portion] of the currency's supply is not under such protection regardless. Actual resistance to such systems can be introduced by relying on different cryptographic assumptions, but this proposal focuses on improvements that do not change the security model.</ref>
|
||||||
|
|
||||||
|
@ -136,6 +136,8 @@ In summary, the semantics of the [[bip-0143.mediawiki|BIP143]] sighash types rem
|
||||||
|
|
||||||
==== Taproot key path spending signature validation ====
|
==== Taproot key path spending signature validation ====
|
||||||
|
|
||||||
|
A Taproot signature is a 64-byte Schnorr signature, as defined in [[bip-0340.mediawiki|BIP340]], with the sighash byte appended in the usual Bitcoin fashion. This sighash byte is optional. If omitted, the resulting signatures are 64 bytes, and a SIGHASH_DEFAULT mode is implied.
|
||||||
|
|
||||||
To validate a signature ''sig'' with public key ''q'':
|
To validate a signature ''sig'' with public key ''q'':
|
||||||
* If the ''sig'' is 64 bytes long, return ''Verify(q, hash<sub>TapSighash</sub>(0x00 || SigMsg(0x00, 0)), sig)''<ref>'''Why is the input to ''hash<sub>TapSighash</sub>'' prefixed with 0x00?''' This prefix is called the sighash epoch, and allows reusing the ''hash<sub>TapSighash</sub>'' tagged hash in future signature algorithms that make invasive changes to how hashing is performed (as opposed to the ''ext_flag'' mechanism that is used for incremental extensions). An alternative is having them use a different tag, but supporting a growing number of tags may become undesirable.</ref>, where ''Verify'' is defined in [[bip-0340.mediawiki#design|BIP340]].
|
* If the ''sig'' is 64 bytes long, return ''Verify(q, hash<sub>TapSighash</sub>(0x00 || SigMsg(0x00, 0)), sig)''<ref>'''Why is the input to ''hash<sub>TapSighash</sub>'' prefixed with 0x00?''' This prefix is called the sighash epoch, and allows reusing the ''hash<sub>TapSighash</sub>'' tagged hash in future signature algorithms that make invasive changes to how hashing is performed (as opposed to the ''ext_flag'' mechanism that is used for incremental extensions). An alternative is having them use a different tag, but supporting a growing number of tags may become undesirable.</ref>, where ''Verify'' is defined in [[bip-0340.mediawiki#design|BIP340]].
|
||||||
* If the ''sig'' is 65 bytes long, return ''sig[64] ≠ 0x00<ref>'''Why can the <code>hash_type</code> not be <code>0x00</code> in 65-byte signatures?''' Permitting that would enable malleating (by third parties, including miners) 64-byte signatures into 65-byte ones, resulting in a different `wtxid` and a different fee rate than the creator intended.</ref> and Verify(q, hash<sub>TapSighash</sub>(0x00 || SigMsg(sig[64], 0)), sig[0:64])''.
|
* If the ''sig'' is 65 bytes long, return ''sig[64] ≠ 0x00<ref>'''Why can the <code>hash_type</code> not be <code>0x00</code> in 65-byte signatures?''' Permitting that would enable malleating (by third parties, including miners) 64-byte signatures into 65-byte ones, resulting in a different `wtxid` and a different fee rate than the creator intended.</ref> and Verify(q, hash<sub>TapSighash</sub>(0x00 || SigMsg(sig[64], 0)), sig[0:64])''.
|
||||||
|
|
|
@ -4,13 +4,12 @@
|
||||||
Title: OP_VAULT
|
Title: OP_VAULT
|
||||||
Author: James O'Beirne <vaults@au92.org>
|
Author: James O'Beirne <vaults@au92.org>
|
||||||
Greg Sanders <gsanders87@gmail.com>
|
Greg Sanders <gsanders87@gmail.com>
|
||||||
Anthony Towns <aj@erisian.com.au>
|
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0345
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0345
|
||||||
Status: Draft
|
Status: Draft
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2023-02-03
|
Created: 2023-02-03
|
||||||
License: BSD-3-Clause
|
License: BSD-3-Clause
|
||||||
Post-History: 2023-01-09: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-January/021318.html [bitcoin-dev] OP_VAULT announcment
|
Post-History: 2023-01-09: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-January/021318.html [bitcoin-dev] OP_VAULT announcement
|
||||||
2023-03-01: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-March/021510.html [bitcoin-dev] BIP for OP_VAULT
|
2023-03-01: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-March/021510.html [bitcoin-dev] BIP for OP_VAULT
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
@ -290,7 +289,7 @@ If none of the conditions fail, a single true value (<code>0x01</code>) is left
|
||||||
=== <code>OP_VAULT_RECOVER</code> evaluation ===
|
=== <code>OP_VAULT_RECOVER</code> evaluation ===
|
||||||
|
|
||||||
When evaluating <code>OP_VAULT_RECOVER</code> (<code>OP_SUCCESS188</code>,
|
When evaluating <code>OP_VAULT_RECOVER</code> (<code>OP_SUCCESS188</code>,
|
||||||
<code>0xbb</code>), the expected format of the stack, shown top to bottom, is:
|
<code>0xbc</code>), the expected format of the stack, shown top to bottom, is:
|
||||||
|
|
||||||
<source>
|
<source>
|
||||||
<recovery-sPK-hash>
|
<recovery-sPK-hash>
|
||||||
|
|
113
bip-0347.mediawiki
Normal file
113
bip-0347.mediawiki
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 347
|
||||||
|
Layer: Consensus (soft fork)
|
||||||
|
Title: OP_CAT in Tapscript
|
||||||
|
Author: Ethan Heilman <ethan.r.heilman@gmail.com>
|
||||||
|
Armin Sabouri <arminsdev@gmail.com>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0347
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2023-12-11
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Post-History: 2023-10-21: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-October/022049.html [bitcoin-dev] Proposed BIP for OP_CAT
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
==Abstract==
|
||||||
|
|
||||||
|
This BIP introduces OP_CAT as a tapscript opcode which allows the concatenation of two values on the stack. OP_CAT would be activated via a soft fork by redefining the opcode OP_SUCCESS126 (126 in decimal and 0x7e in hexadecimal). This is the same opcode value used by the original OP_CAT.
|
||||||
|
|
||||||
|
== Copyright ==
|
||||||
|
|
||||||
|
This document is licensed under the 3-clause BSD license.
|
||||||
|
|
||||||
|
==Specification==
|
||||||
|
|
||||||
|
When evaluated, the OP_CAT instruction:
|
||||||
|
# Pops the top two values off the stack,
|
||||||
|
# concatenates the popped values together in stack order,
|
||||||
|
# and then pushes the concatenated value on the top of the stack.
|
||||||
|
|
||||||
|
Given the stack ''<nowiki>[x1, x2]</nowiki>'', where ''x2'' is at the top of the stack, OP_CAT will push ''x1 || x2'' onto the stack. By ''||'' we denote concatenation. OP_CAT fails if there are fewer than two values on the stack or if a concatenated value would have a combined size greater than the maximum script element size of 520 bytes.
|
||||||
|
|
||||||
|
This opcode would be activated via a soft fork by redefining the tapscript opcode OP_SUCCESS126 (126 in decimal and 0x7e in hexadecimal) to OP_CAT.
|
||||||
|
|
||||||
|
==Motivation==
|
||||||
|
|
||||||
|
Bitcoin Tapscript lacks a general purpose way of combining objects on the stack, restricting the expressiveness and power of Tapscript. This prevents, among many other things, the ability to construct and evaluate merkle trees and other hashed data structures in Tapscript. OP_CAT, by adding a general purpose way to concatenate stack values, would overcome this limitation and greatly increase the functionality of Tapscript.
|
||||||
|
|
||||||
|
OP_CAT aims to expand the toolbox of the tapscript developer with a simple, modular, and useful opcode in the spirit of Unix <ref>R. Pike and B. Kernighan, "Program design in the UNIX environment", 1983, https://harmful.cat-v.org/cat-v/unix_prog_design.pdf</ref>. To demonstrate the usefulness of OP_CAT below we provide a non-exhaustive list of some usecases that OP_CAT would enable:
|
||||||
|
|
||||||
|
* Bitstream, a protocol for the atomic swap (fair exchange) of bitcoins for decryption keys, that enables decentralized file hosting systems paid in Bitcoin. While such swaps are currently possible on Bitcoin without OP_CAT, they require the use of complex and computationally expensive Verifiable Computation cryptographic techniques. OP_CAT would remove this requirement on Verifiable Computation, making such protocols far more practical to build in Bitcoin. <ref>R. Linus, "BitStream: Decentralized File Hosting Incentivised via Bitcoin Payments", 2023, https://robinlinus.com/bitstream.pdf</ref>
|
||||||
|
* Tree signatures provide a multisignature script whose size can be logarithmic in the number of public keys and can encode spend conditions beyond n-of-m. For instance a transaction less than 1KB in size could support tree signatures with up to 4,294,967,296 public keys. This also enables generalized logical spend conditions. <ref> P. Wuille, "Multisig on steroids using tree signatures", 2015, https://blog.blockstream.com/en-treesignatures/</ref>
|
||||||
|
* Post-Quantum Lamport signatures in Bitcoin transactions. Lamport signatures merely require the ability to hash and concatenate values on the stack. <ref>J. Rubin, "[bitcoin-dev] OP_CAT Makes Bitcoin Quantum Secure [was CheckSigFromStack for Arithmetic Values]", 2021, https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-July/019233.html</ref> It has been proposed that if ECDSA is broken or a powerful computer was on the horizon, there might be an effort to protect ownership of bitcoins by allowing people to mark their taproot outputs as "script-path only" and then move their coins into such outputs with a leaf in the script tree requiring a Lamport signature. It is an open question if a tapscript commitment would preserve the quantum resistance of Lamport signatures. Beyond this question, the use of Lamport Signatures in taproot outputs is unlikely to be quantum resistant even if the script spend-path is made quantum resistant. This is because taproot outputs can also be spent with a key. An attacker with a sufficiently powerful quantum computer could bypass the taproot script spend-path by finding the discrete log of the taproot output and thus spending the output using the key spend-path. The use of "Nothing Up My Sleeve" (NUMS) points as described in [[bip-0341.mediawiki|BIP341]] to disable the key spend-path does not disable the key spend-path against a quantum attacker as NUMS relies on the hardness of finding discrete logs. We are not aware of any mechanism which could disable the key spend-path in a taproot output without a softfork change to taproot.
|
||||||
|
* Non-equivocation contracts <ref>T. Ruffing, A. Kate, D. Schröder, "Liar, Liar, Coins on Fire: Penalizing Equivocation by Loss of Bitcoins", 2015, https://web.archive.org/web/20221023121048/https://publications.cispa.saarland/565/1/penalizing.pdf</ref> in tapscript provide a mechanism to punish equivocation/double spending in Bitcoin payment channels. OP_CAT enables this by enforcing rules on the spending transaction's nonce. The capability is a useful building block for payment channels and other Bitcoin protocols.
|
||||||
|
* Vaults <ref>M. Moser, I. Eyal, and E. G. Sirer, Bitcoin Covenants, http://fc16.ifca.ai/bitcoin/papers/MES16.pdf</ref> which are a specialized covenant that allows a user to block a malicious party who has compromised the user's secret key from stealing the funds in that output. As shown in <ref>A. Poelstra, "CAT and Schnorr Tricks II", 2021, https://www.wpsoftware.net/andrew/blog/cat-and-schnorr-tricks-ii.html</ref> OP_CAT is sufficient to build vaults in Bitcoin.
|
||||||
|
* Replicating CheckSigFromStack <ref>A. Poelstra, "CAT and Schnorr Tricks I", 2021, https://www.wpsoftware.net/andrew/blog/cat-and-schnorr-tricks-i.html</ref> which would allow the creation of simple covenants and other advanced contracts without having to presign spending transactions, possibly reducing complexity and the amount of data that needs to be stored. Originally shown to work with Schnorr signatures, this result has been extended to ECDSA signatures <ref>R. Linus, "Covenants with CAT and ECDSA", 2023, https://gist.github.com/RobinLinus/9a69f5552be94d13170ec79bf34d5e85#file-covenants_cat_ecdsa-md</ref>.
|
||||||
|
|
||||||
|
OP_CAT was available in early versions of Bitcoin.
|
||||||
|
In 2010, a single commit disabled OP_CAT, along with another 15 opcodes.
|
||||||
|
Folklore states that OP_CAT was removed in this commit because it enabled the construction of a script whose evaluation could have memory usage exponential in the size of the script.
|
||||||
|
For example, a script that pushed a 1-byte value on the stack and then repeated the opcodes OP_DUP, OP_CAT 40 times would result in a stack element whose size was greater than 1 terabyte assuming no maximum stack element size. As Bitcoin at that time had a maximum stack element size of 5000 bytes, the effect of this expansion was limited to 5000 bytes.
|
||||||
|
This is no longer an issue because tapscript enforces a maximum stack element size of 520 bytes.
|
||||||
|
|
||||||
|
|
||||||
|
==Rationale==
|
||||||
|
|
||||||
|
Our decision to reenable OP_CAT by redefining a tapscript OP_SUCCESSx opcode to OP_CAT was motivated to leverage the tapscript softfork opcode upgrade path introduced in [[bip-0342.mediawiki|BIP342]].
|
||||||
|
|
||||||
|
We specifically choose to use OP_SUCCESS126 rather than another OP_SUCCESSx as OP_SUCCESS126 uses the same opcode value (126 in decimal and 0x7e in hexadecimal) that was used for OP_CAT prior to it being disabled in Bitcoin. This removes a potential source of confusion that would exist if we had a opcode value different from the one used in the original OP_CAT opcode.
|
||||||
|
|
||||||
|
While the OP_SUCCESSx opcode upgrade path could enable us to increase the stack element size while reenabling OP_CAT, we wanted to separate the decision to change the stack element size limit from the decision to reenable OP_CAT. This BIP takes no position in favor or against increasing the stack element size limit.
|
||||||
|
|
||||||
|
==Backwards Compatibility==
|
||||||
|
|
||||||
|
OP_CAT usage in a non-tapscript script will continue to trigger the SCRIPT_ERR_DISABLED_OPCODE. The only change would be to OP_CAT usage in tapscript. This change to tapscript would be activated as a soft fork that redefines an OP_SUCCESSx opcode (OP_SUCCESS126) to OP_CAT.
|
||||||
|
|
||||||
|
==Reference implementation==
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
case OP_CAT:
|
||||||
|
{
|
||||||
|
if (stack.size() < 2)
|
||||||
|
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||||
|
valtype& vch1 = stacktop(-2);
|
||||||
|
valtype& vch2 = stacktop(-1);
|
||||||
|
if (vch1.size() + vch2.size() > MAX_SCRIPT_ELEMENT_SIZE)
|
||||||
|
return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
|
||||||
|
vch1.insert(vch1.end(), vch2.begin(), vch2.end());
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
|
The value of <code>MAX_SCRIPT_ELEMENT_SIZE</code> is 520.
|
||||||
|
|
||||||
|
This implementation is inspired by the original implementation of [https://github.com/bitcoin/bitcoin/blob/01cd2fdaf3ac6071304ceb80fb7436ac02b1059e/script.cpp#L381-L393 OP_CAT as it existed in the Bitcoin codebase] prior to the commit "misc changes" 4bd188c<ref>S. Nakamoto, "misc changes", Aug 25 2010, https://github.com/bitcoin/bitcoin/commit/4bd188c4383d6e614e18f79dc337fbabe8464c82#diff-27496895958ca30c47bbb873299a2ad7a7ea1003a9faa96b317250e3b7aa1fefR94</ref> which disabled it:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
case OP_CAT:
|
||||||
|
{
|
||||||
|
// (x1 x2 -- out)
|
||||||
|
if (stack.size() < 2)
|
||||||
|
return false;
|
||||||
|
valtype& vch1 = stacktop(-2);
|
||||||
|
valtype& vch2 = stacktop(-1);
|
||||||
|
vch1.insert(vch1.end(), vch2.begin(), vch2.end());
|
||||||
|
stack.pop_back();
|
||||||
|
if (stacktop(-1).size() > 5000)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
An alternative implementation of OP_CAT can be found in Elements <ref>Roose S., Elements Project, "Re-enable several disabled opcodes", 2019, https://github.com/ElementsProject/elements/commit/13e1103abe3e328c5a4e2039b51a546f8be6c60a#diff-a0337ffd7259e8c7c9a7786d6dbd420c80abfa1afdb34ebae3261109d9ae3c19R740-R759</ref>.
|
||||||
|
|
||||||
|
==References==
|
||||||
|
|
||||||
|
<references/>
|
||||||
|
|
||||||
|
==Acknowledgements==
|
||||||
|
|
||||||
|
We wish to acknowledge Dan Gould for encouraging and helping review this effort. We also want to thank Madars Virza, Jeremy Rubin, Andrew Poelstra, Bob Summerwill,
|
||||||
|
Tim Ruffing and Johan T. Halseth for their feedback, review and helpful comments.
|
142
bip-0348.md
Normal file
142
bip-0348.md
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 348
|
||||||
|
Layer: Consensus (soft fork)
|
||||||
|
Title: CHECKSIGFROMSTACK
|
||||||
|
Author: Brandon Black <freedom@reardencode.com>
|
||||||
|
Jeremy Rubin <j@rubin.io>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0348
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2024-11-26
|
||||||
|
License: BSD-3-Clause
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Abstract
|
||||||
|
|
||||||
|
This BIP describes a new opcode for the purpose of checking cryptographic
|
||||||
|
signatures in bitcoin scripts against data from the stack.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
When verifying taproot script spends having leaf version 0xc0 (as defined in
|
||||||
|
[BIP 342]), we propose `OP_CHECKSIGFROMSTACK` to replace `OP_SUCCESS204`
|
||||||
|
(0xcc).
|
||||||
|
|
||||||
|
`OP_CHECKSIGFROMSTACK` has semantics similar to `OP_CHECKSIG`, as specified
|
||||||
|
below. Briefly, it pops 3 elements from the stack: a 32-byte public key, a
|
||||||
|
message, and a signature. If the signature is valid for that public key and
|
||||||
|
message, 1 is pushed to the stack. If the signature is the empty vector, 0 is
|
||||||
|
pushed to the stack, and otherwise script execution fails.
|
||||||
|
|
||||||
|
Only 32-byte keys are constrained. Similar to [BIP 341] unknown key types, for
|
||||||
|
other key lengths no signature verification is performed and it is considered
|
||||||
|
successful.
|
||||||
|
|
||||||
|
## Specification
|
||||||
|
|
||||||
|
* If fewer than 3 elements are on the stack, the script MUST fail and terminate immediately.
|
||||||
|
* The public key (top element), message (second to top element), and signature (third from top element) are read from the stack.
|
||||||
|
* The top three elements are popped from the stack.
|
||||||
|
* If the public key size is zero, the script MUST fail and terminate immediately.
|
||||||
|
* If the public key size is 32 bytes, it is considered to be a public key as described in [BIP 340]:
|
||||||
|
* If the signature is not the empty vector, the signature is validated against the public key and message according to [BIP 340]. Validation failure in this case immediately terminates script execution with failure.
|
||||||
|
* If the public key size is not zero and not 32 bytes; the public key is of an unknown public key type. Signature verification for unknown public key types succeeds as if signature verification for a known public key type had succeeded.
|
||||||
|
* If the script did not fail and terminate before this step, regardless of the public key type:
|
||||||
|
* If the signature is the empty vector: An empty vector is pushed onto the stack, and execution continues with the next opcode.
|
||||||
|
* If the signature is not the empty vector:
|
||||||
|
* The opcode is counted towards the sigops budget as described in [BIP 342].
|
||||||
|
* A 1-byte value 0x01 is pushed onto the stack.
|
||||||
|
|
||||||
|
## Design Considerations
|
||||||
|
|
||||||
|
1. Message hashing: [BIP 340] is compatible with any size of message and does not require it to be a securely hashed input, so the message is not hashed prior to [BIP 340] verification.
|
||||||
|
2. Lack of verify semantics: Adding a single opcode for this purpose keeps the implementation and design simple. An earlier draft had a verify variant as a NOP upgrade, and if this functionality is later brought to legacy scripts, that would be a good time to add a verify variant.
|
||||||
|
3. Add/multisig: No concession is made to `OP_CHECKMULTISIG` or `OP_CHECKSIGADD` semantics with `OP_CHECKSIGFROMSTACK`. In Tapscript, add semantics can be implemented with 1 additional vByte per key (`OP_TOALTSTACK OP_CHECKSIGFROMSTACK OP_FROMALTSTACK OP_ADD`).
|
||||||
|
4. Splitting R/S on the stack: Implementing split/separate signatures is left as an exercise for other bitcoin upgrades, such as [BIP 347] (`OP_CAT`).
|
||||||
|
5. APO-style ([BIP 118]) Taproot internal key: Rather than introducing an additional key type in this change, we suggest implementing `OP_INTERNALKEY` ([BIP 349]) or separately introducing that key type for all Tapscript signature checking operations in a separate change.
|
||||||
|
|
||||||
|
## Resource Limits
|
||||||
|
|
||||||
|
These opcodes are treated identically to other signature checking opcodes and
|
||||||
|
count against the sigops and budget.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
### LN Symmetry
|
||||||
|
|
||||||
|
When combined with [BIP 119] (`OP_CHECKTEMPLATEVERIFY`/CTV),
|
||||||
|
`OP_CHECKSIGFROMSTACK` (CSFS) can be used to implement Lightning Symmetry
|
||||||
|
channels. The construction `OP_CHECKTEMPLATEVERIFY <pubkey>
|
||||||
|
OP_CHECKSIGFROMSTACK` with a spend stack containing the CTV hash and a
|
||||||
|
signature for it is logically equivalent to `<bip118_pubkey> OP_CHECKSIG` and
|
||||||
|
a signature over `SIGHASH_ALL|SIGHASH_ANYPREVOUTANYSCRIPT`. The
|
||||||
|
`OP_CHECKSIGFROMSTACK` construction is 8 vBytes larger.
|
||||||
|
|
||||||
|
Summary of alternatives:
|
||||||
|
* CTV+CSFS is the minimal functionality needed for Lightning Symmetry but requires the use of an `OP_RETURN` for data availability
|
||||||
|
* APO is the original design for Lightning Symmetry and uses the taproot annex for data availability.
|
||||||
|
* LNHANCE (CTV+CSFS+IKEY+PC) is the most efficient and direct way currently designed to implement Lightning Symmetry.
|
||||||
|
|
||||||
|
### Delegation
|
||||||
|
|
||||||
|
Using a script like:
|
||||||
|
`<pubkey> SWAP IF 2 PICK SWAP CSFS VERIFY ENDIF CHECKSIG`
|
||||||
|
either direct verification or delegation can be achieved by the following
|
||||||
|
unlock stacks: `<sig> 0` or `<dsig> <dpubkey> <sig> 1`
|
||||||
|
|
||||||
|
### Advanced delegation when combined with [OP_PAIRCOMMIT] or OP_CAT
|
||||||
|
|
||||||
|
Using a script like:
|
||||||
|
`CLTV OVER PAIRCOMMIT TOALT CHECKSIGVERIFY FROMALT <pubkey> CSFS`
|
||||||
|
or:
|
||||||
|
`CLTV SHA256 OVER CAT TOALT CHECKSIGVERIFY FROMALT <pubkey> CSFS`
|
||||||
|
with the unlock stack:
|
||||||
|
`<sig> <delegate_sig> <delegate_pubkey> <locktime>`
|
||||||
|
|
||||||
|
Delegates to a public key after a lock time, enabling delegation to various
|
||||||
|
keys after various associated times.
|
||||||
|
|
||||||
|
## Reference Implementation
|
||||||
|
|
||||||
|
A reference implementation is provided here:
|
||||||
|
|
||||||
|
https://github.com/bitcoin/bitcoin/pull/29270
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
|
||||||
|
By constraining the behavior of an OP_SUCCESS opcode,
|
||||||
|
deployment of the BIP can be done in a backwards compatible, soft-fork manner.
|
||||||
|
If anyone were to rely on the OP_SUCCESS behavior of
|
||||||
|
`OP_SUCCESS204`, `OP_CHECKSIGFROMSTACK` would invalidate
|
||||||
|
their spend.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Reference implementation was made with reference to the implementation in
|
||||||
|
Elements and started by moonsettler.
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
This document is licensed under the 3-clause BSD license.
|
||||||
|
|
||||||
|
[BIP 119]: https://github.com/bitcoin/bips/blob/master/bip-0119.mediawiki
|
||||||
|
|
||||||
|
[BIP 118]: https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki
|
||||||
|
|
||||||
|
[BIP 340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
||||||
|
|
||||||
|
[BIP 341]: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
|
||||||
|
|
||||||
|
[BIP 342]: https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki
|
||||||
|
|
||||||
|
[BIP 349]: https://github.com/bitcoin/bips/blob/master/bip-0349.md
|
||||||
|
|
||||||
|
[BIP 347]: https://github.com/bitcoin/bips/blob/master/bip-0347.mediawiki
|
||||||
|
|
||||||
|
[OP_PAIRCOMMIT]: https://github.com/bitcoin/bips/pull/1699
|
||||||
|
|
||||||
|
[mailing list]: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-July/019192.html
|
95
bip-0349.md
Normal file
95
bip-0349.md
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 349
|
||||||
|
Layer: Consensus (soft fork)
|
||||||
|
Title: OP_INTERNALKEY
|
||||||
|
Author: Brandon Black <freedom@reardencode.com>
|
||||||
|
Jeremy Rubin <j@rubin.io>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0349
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2024-11-14
|
||||||
|
License: BSD-3-Clause
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Abstract
|
||||||
|
|
||||||
|
This BIP describes a new tapscript opcode (`OP_INTERNALKEY`) which
|
||||||
|
pushes the _taproot internal key_ to the stack.
|
||||||
|
|
||||||
|
## Specification
|
||||||
|
|
||||||
|
When verifying taproot script path spends having leaf version `0xc0` (as
|
||||||
|
defined in [BIP 342]), `OP_INTERNALKEY` replaces `OP_SUCCESS203` (0xcb).
|
||||||
|
`OP_INTERNALKEY` pushes the 32-byte x-only representation of the _taproot
|
||||||
|
internal key_ (referred to as _p_), as defined in [BIP 341], to the stack.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
### Key spend with additional conditions
|
||||||
|
|
||||||
|
When building taproot outputs, especially those secured by an aggregate key
|
||||||
|
representing more than one signer, the parties may wish to collaborate on
|
||||||
|
signing with the _taproot internal key_, but only with additional script
|
||||||
|
restrictions. In this case, `OP_INTERNALKEY` saves 8 vBytes.
|
||||||
|
|
||||||
|
### Mitigated control block overhead for scripts using hash locks
|
||||||
|
|
||||||
|
In cases where key path spending is not desired, the internal key may be set to
|
||||||
|
a NUMS point whose bytes would otherwise be required in a tapscript. This could
|
||||||
|
be used with any hash locked transaction, for example, to save 8 vBytes.
|
||||||
|
|
||||||
|
Note: The internal key must be the X coordinate of a point on the SECP256K1
|
||||||
|
curve, so any such hash must be checked and modified until it is such an X
|
||||||
|
coordinate. This will typically take approximately 2 attempts.
|
||||||
|
|
||||||
|
### Re-Keying with Merkle Root Preservation
|
||||||
|
|
||||||
|
Consider a program such `CTV <X> CSFS <S+1> CLTV`. Such fragments are useful for LN-Symmetry applications.
|
||||||
|
|
||||||
|
Such a program would be embedded within a Taproot script path, such as `TR(X, {CTV <X> CSFS <S+1> CLTV})`.
|
||||||
|
|
||||||
|
Were the internal key to be updated from `X` to `Y`, the resulting program would be: `TR(Y, {CTV <X> CSFS <S+1> CLTV})`.
|
||||||
|
|
||||||
|
The key in the leaf and the key-path would be mismatched. Were `OP_INTERNALKEY` to be used,
|
||||||
|
the leaf would automatically re-key.
|
||||||
|
E.g., `TR(X, {CTV OP_INTERNALKEY CSFS <S+1> CLTV})` is equivalent to `TR(X, {CTV <X> CSFS <S+1> CLTV})`
|
||||||
|
and `TR(Y, {CTV OP_INTERNALKEY CSFS <S+1> CLTV})` is equivalent to `TR(Y, {CTV <Y> CSFS <S+1> CLTV})`.
|
||||||
|
|
||||||
|
While this particular example is contrived, the general technique of using `OP_INTERNALKEY`
|
||||||
|
as updatable across an entire script tree is a helpful covenant primitive when it is desirable to
|
||||||
|
invalidate signatures from prior states. For example, the theoretical `OP_TAPLEAFUPDATEVERIFY` opcode
|
||||||
|
modifies the internal key directly to remove or add a participant, and `OP_INTERNALKEY` would ensure
|
||||||
|
that the tweaked key is used from all script paths where desired.
|
||||||
|
|
||||||
|
## Reference Implementation
|
||||||
|
|
||||||
|
A reference implementation is provided here:
|
||||||
|
|
||||||
|
https://github.com/bitcoin/bitcoin/pull/29269
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
|
||||||
|
By constraining the behavior of an OP_SUCCESS opcode, deployment of the BIP
|
||||||
|
can be done in a backwards compatible, soft-fork manner. If anyone were to
|
||||||
|
rely on the OP_SUCCESS behavior of `OP_SUCCESS203`, `OP_INTERNALKEY` would
|
||||||
|
invalidate their spend.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
The concept for INTERNALKEY first arose in a [discussion](https://gnusha.org/bitcoin-wizards/2022-01-05.log) between Russell O'Connor
|
||||||
|
and Jeremy Rubin in Bitcoin Wizards IRC, inspired by BIP-0118's key punning technique
|
||||||
|
for the internal key. It was later
|
||||||
|
drafted into this BIP by Brandon Black.
|
||||||
|
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
This document is licensed under the 3-clause BSD license.
|
||||||
|
|
||||||
|
[BIP 341]: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
|
||||||
|
|
||||||
|
[BIP 342]: https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki
|
|
@ -217,7 +217,7 @@ their invalidity.
|
||||||
|
|
||||||
Checksums are used to detect errors introduced into data during transfer. A hash function-based checksum such as Base58Check detects any type of error uniformly, but not all classes of errors are equally likely to occur in practice. Bech32 prioritizes detection of substitution errors, but improving detection of one error class inevitably worsens detection of other error classes. During the design of Bech32, it was assumed that other simple error patterns beside substitutions would have a similar detection rate as in a hash function-based design, and detection would only be worse for complex, impractical errors. The discovered insertion weakness shows that this is not the case.
|
Checksums are used to detect errors introduced into data during transfer. A hash function-based checksum such as Base58Check detects any type of error uniformly, but not all classes of errors are equally likely to occur in practice. Bech32 prioritizes detection of substitution errors, but improving detection of one error class inevitably worsens detection of other error classes. During the design of Bech32, it was assumed that other simple error patterns beside substitutions would have a similar detection rate as in a hash function-based design, and detection would only be worse for complex, impractical errors. The discovered insertion weakness shows that this is not the case.
|
||||||
|
|
||||||
For Bech32m, we aim to retain Bech32's guarantees for substitution errors, but make sure that other common errors don't perform worse than a hash function-based checksum would. To make sure the new standard is easy to implement, we restrict the design space to only amending the final constant that is xored in, as it was [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-December/017521.html observed] that that is sufficient to mitigate the 'q' insertion issue while retaining the intended substitution error detection. In what follows, we explain how the new constant ''0x2bc830a3'' was chosen.
|
For Bech32m, we aim to retain Bech32's guarantees for substitution errors, but make sure that other common errors don't perform worse than a hash function-based checksum would. To make sure the new standard is easy to implement, we restrict the design space to only amending the final constant that is xored in, as it was [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-December/017521.html observed] that is sufficient to mitigate the 'q' insertion issue while retaining the intended substitution error detection. In what follows, we explain how the new constant ''0x2bc830a3'' was chosen.
|
||||||
|
|
||||||
===Error patterns & detection probability===
|
===Error patterns & detection probability===
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ A recipient that wishes to receive funds privately has several options. Each has
|
||||||
|
|
||||||
* The BIP uses a notification mechanism that relies on publicly known per-recipient notification addresses. If Alice wants to send funds to Bob, she has to use the same notification address that everyone else uses to notify Bob. If Alice is not careful with coin selection, i.e. ensuring that her notification UTXO is not linked to her, she will publicly expose herself as someone who is trying to send funds to Bob and their relationship becomes permanently visible on the blockchain.
|
* The BIP uses a notification mechanism that relies on publicly known per-recipient notification addresses. If Alice wants to send funds to Bob, she has to use the same notification address that everyone else uses to notify Bob. If Alice is not careful with coin selection, i.e. ensuring that her notification UTXO is not linked to her, she will publicly expose herself as someone who is trying to send funds to Bob and their relationship becomes permanently visible on the blockchain.
|
||||||
|
|
||||||
* The BIP does not say anything about address types. Receiving wallets therefore have to watch all address types that can be created from a single public key. Even then, a sender could send to a script that a receipient cannot spend from.
|
* The BIP does not say anything about address types. Receiving wallets therefore have to watch all address types that can be created from a single public key. Even then, a sender could send to a script that a recipient cannot spend from.
|
||||||
|
|
||||||
==Method==
|
==Method==
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ Notifications are performed by publishing transactions that contain a 40-byte <c
|
||||||
* ''search_key'' equals "PP" and is a static ASCII-encoded string (2 bytes)
|
* ''search_key'' equals "PP" and is a static ASCII-encoded string (2 bytes)
|
||||||
* ''notification_code'' is ''H(n<sub>x</sub> * P)[0..4]'' (4 bytes)
|
* ''notification_code'' is ''H(n<sub>x</sub> * P)[0..4]'' (4 bytes)
|
||||||
* ''N<sub>x</sub>'' is the unique public key a sender is using for a particular recipient (33 bytes)
|
* ''N<sub>x</sub>'' is the unique public key a sender is using for a particular recipient (33 bytes)
|
||||||
* ''address_type'' is the '''ordinal''' value of a single address type that a sender wants to send to (1 byte). This must be selected from the recepient's accepted address types.
|
* ''address_type'' is the '''ordinal''' value of a single address type that a sender wants to send to (1 byte). This must be selected from the recipient's accepted address types.
|
||||||
|
|
||||||
When Alice wants to notify Bob that he will receive future payments from her, she performs the following procedure:
|
When Alice wants to notify Bob that he will receive future payments from her, she performs the following procedure:
|
||||||
|
|
||||||
|
|
498
bip-0352.mediawiki
Normal file
498
bip-0352.mediawiki
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 352
|
||||||
|
Layer: Applications
|
||||||
|
Title: Silent Payments
|
||||||
|
Author: josibake <josibake@protonmail.com>
|
||||||
|
Ruben Somsen <rsomsen@gmail.com>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0352
|
||||||
|
Status: Proposed
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2023-03-09
|
||||||
|
License: BSD-2-Clause
|
||||||
|
Post-History: 2022-03-13: https://gist.github.com/RubenSomsen/c43b79517e7cb701ebf77eec6dbb46b8 [gist] Original proposal
|
||||||
|
2022-03-28: https://gnusha.org/pi/bitcoindev/CAPv7TjbXm953U2h+-12MfJ24YqOM5Kcq77_xFTjVK+R2nf-nYg@mail.gmail.com/ [bitcoin-dev] Silent Payments – Non-interactive private payments with no on-chain overhead
|
||||||
|
2022-10-11: https://gnusha.org/pi/bitcoindev/P_21MLHGJicZ-hkbC4DGu86c5BtNKiH8spY4TOw5FJsfimdi_6VyHzU_y-s1mZsOcC2FA3EW_6w6W5qfV9dRK_7AvTAxDlwVfU-yhWZPEuo=@protonmail.com/ [bitcoin-dev] Silent Payment v4 (coinjoin support added)
|
||||||
|
2023-08-04: https://gnusha.org/pi/bitcoindev/ZM03twumu88V2NFH@petertodd.org/ [bitcoin-dev] BIP-352 Silent Payments addresses should have an expiration time
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
== Introduction ==
|
||||||
|
|
||||||
|
=== Abstract ===
|
||||||
|
|
||||||
|
This document specifies a protocol for static payment addresses in Bitcoin without on-chain linkability of payments or a need for on-chain notifications.
|
||||||
|
|
||||||
|
=== Copyright ===
|
||||||
|
|
||||||
|
This BIP is licensed under the BSD 2-clause license.
|
||||||
|
|
||||||
|
=== Motivation ===
|
||||||
|
|
||||||
|
Using a new address for each Bitcoin transaction is a crucial aspect of maintaining privacy. This often requires a secure interaction between sender and receiver, so that the receiver can hand out a fresh address, a batch of fresh addresses, or a method for the sender to generate addresses on-demand, such as an xpub.
|
||||||
|
|
||||||
|
However, interaction is often infeasible and in many cases undesirable. To solve for this, various protocols have been proposed which use a static payment address and notifications sent via the blockchain<ref name="out_of_band_notifications">'''Why not use out-of-band notifications''' Out-of-band notifications (e.g. using something other than the Bitcoin blockchain) have been proposed as a way of addressing the privacy and cost concerns of using the Bitcoin blockchain as a messaging layer. This, however, simply moves the privacy and cost concerns somewhere else and increases the risk of losing money due to a notification not being reliably delivered, or even censored, and makes this notification data critical for backup to recover funds.</ref>. These protocols eliminate the need for interaction, but at the expense of increased costs for one-time payments and a noticeable footprint in the blockchain, potentially revealing metadata about the sender and receiver. Notification schemes also allow the receiver to link all payments from the same sender, compromising sender privacy.
|
||||||
|
|
||||||
|
This proposal aims to address the limitations of these current approaches by presenting a solution that eliminates the need for interaction, eliminates the need for notifications, and protects both sender and receiver privacy. These benefits come at the cost of requiring wallets to scan the blockchain in order to detect payments. This added requirement is generally feasible for full nodes but poses a challenge for light clients. While it is possible today to implement a privacy-preserving light client at the cost of increased bandwidth, light client support is considered an area of open research (see [[#appendix-a-light-client-support|Appendix A: Light Client Support]]).
|
||||||
|
|
||||||
|
The design keeps collaborative transactions such as CoinJoins and inputs with MuSig and FROST keys in mind, but it is recommended that the keys of all inputs of a transaction belong to the same entity as there is no formal proof that the protocol is secure in a collaborative setting.
|
||||||
|
|
||||||
|
== Goals ==
|
||||||
|
|
||||||
|
We aim to present a protocol which satisfies the following properties:
|
||||||
|
|
||||||
|
* No increase in the size or cost of transactions
|
||||||
|
* Resulting transactions blend in with other bitcoin transactions and can't be distinguished
|
||||||
|
* Transactions can't be linked to a silent payment address by an outside observer
|
||||||
|
* No sender-receiver interaction required
|
||||||
|
* No linking of multiple payments to the same sender
|
||||||
|
* Each silent payment goes to a unique address, avoiding accidental address reuse
|
||||||
|
* Supports payment labeling
|
||||||
|
* Uses existing seed phrase or descriptor methods for backup and recovery
|
||||||
|
* Separates scanning and spending responsibilities
|
||||||
|
* Compatible with other spending protocols, such as CoinJoin
|
||||||
|
* Light client/SPV wallet support
|
||||||
|
* Protocol is upgradeable
|
||||||
|
|
||||||
|
== Overview ==
|
||||||
|
|
||||||
|
We first present an informal overview of the protocol. In what follows, uppercase letters represent public keys, lowercase letters represent private keys, ''||'' refers to byte concatenation, ''·'' refers to elliptic curve scalar multiplication, ''G'' represents the generator point for secp256k1, and ''n'' represents the curve order for secp256k1. Each section of the overview is incomplete on its own and is meant to build on the previous section in order to introduce and briefly explain each aspect of the protocol. For the full protocol specification, see [[#specification|Specification]].
|
||||||
|
|
||||||
|
''' Simple case '''
|
||||||
|
|
||||||
|
Bob publishes a public key ''B'' as a silent payment address. Alice discovers Bob's silent payment address, selects a UTXO with private key ''a'', public key ''A'' and creates a destination output ''P'' for Bob in the following manner:
|
||||||
|
|
||||||
|
* Let ''P = B + hash(a·B)·G''
|
||||||
|
* Encode ''P'' as a [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot output
|
||||||
|
|
||||||
|
Since ''a·B == b·A'' ([https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman Elliptic-curve Diffie–Hellman]), Bob scans with his private key ''b'' by collecting the input public keys for each transaction with at least one unspent taproot output and performing the ECDH calculation until ''P'' is found (i.e. calculating ''P = B + hash(b·A)·G'' and seeing that ''P'' is present in the transaction outputs).
|
||||||
|
|
||||||
|
''' Creating more than one output '''
|
||||||
|
|
||||||
|
In order to allow Alice to create more than one output for Bob<ref name="why_more_than_one_output">'''Why allow for more than one output?''' Allowing Alice to break her payment to Bob into multiple amounts opens up a number of privacy improving techniques for Alice, making the transaction look like a CoinJoin or better hiding the change amount by splitting both the payment and change outputs into multiple amounts. It also allows for Alice and Carol to both have their own unique output paying Bob in the event they are in a collaborative transaction and both paying Bob's silent payment address.</ref>, we include an integer in the following manner:
|
||||||
|
|
||||||
|
* Let ''k = 0''
|
||||||
|
* Let ''P<sub>0</sub> = B + hash(a·B || k)·G''
|
||||||
|
* For additional outputs:
|
||||||
|
** Increment ''k'' by one (''k++'')
|
||||||
|
** Let ''P<sub>i</sub> = B + hash(a·B || k)·G''
|
||||||
|
|
||||||
|
Bob detects this output the same as before by searching for ''P<sub>0</sub> = B + hash(b·A || 0)·G''. Once he detects the first output, he must:
|
||||||
|
|
||||||
|
* Check for ''P<sub>1</sub> = B + hash(b·A || 1)·G''
|
||||||
|
* If ''P<sub>1</sub>'' is not found, stop
|
||||||
|
* If ''P<sub>1</sub>'' is found, continue to check for ''P<sub>2</sub>'' and so on until an additional output is not found
|
||||||
|
|
||||||
|
Since Bob will only perform these subsequent checks after a transaction with at least one output paying him is found, the increase to his overall scanning requirement is negligible. It should also be noted that the order in which these outputs appear in the transaction does not affect the outcome.
|
||||||
|
|
||||||
|
''' Preventing address reuse '''
|
||||||
|
|
||||||
|
If Alice were to use a different UTXO from the same public key ''A'' for a subsequent payment to Bob, she would end up deriving the same destinations ''P<sub>i</sub>''. To prevent this, Alice should include an input hash in the following manner:
|
||||||
|
|
||||||
|
* Let ''input_hash = hash(outpoint || A)''<ref name="why_include_A">'''Why include A in the input hash calculation?''' By committing to A in input hash, this ensures that the sender cannot maliciously choose a private key ''a′'' in a subsequent transaction where ''a′ = input_hash·a / input_hash′'', which would force address reuse in the protocol.</ref>
|
||||||
|
* Let ''P<sub>0</sub> = B + hash(input_hash·a·B || 0)·G''
|
||||||
|
|
||||||
|
Bob must calculate the same ''input_hash'' when scanning.
|
||||||
|
|
||||||
|
''' Using all inputs '''
|
||||||
|
|
||||||
|
In our simplified example we have been referring to Alice's transactions as having only one input ''A'', but in reality a Bitcoin transaction can have many inputs. Instead of requiring Alice to pick a particular input and requiring Bob to check each input separately, we can instead require Alice to perform the tweak with the sum of the input public keys<ref name="other_inputs">'''What about inputs without public keys?''' Inputs without public keys can still be spent in the transaction but are simply ignored in the silent payments protocol.</ref>. This significantly reduces Bob's scanning requirement, makes light client support more feasible<ref name="using_all_inputs">'''How does using all inputs help light clients?''' If Alice uses a random input for the tweak, Bob necessarily has to have access to and check all transaction inputs, which requires performing an ECC multiplication per input. If instead Alice performs the tweak with the sum of the input public keys, Bob only needs the summed 33 byte public key per transaction and only does one ECC multiplication per transaction. Bob can then use BIP158 block filters to determine if any of the outputs exist in a block and thus avoids downloading transactions which don't belong to him. It is still an open question as to how Bob can source the 33 bytes per transaction in a trustless manner, see [[#appendix-a-light-client-support|Appendix A: Light Client Support]] for more details.</ref>, and protects Alice's privacy in collaborative transaction protocols such as CoinJoin<ref name=""all_inputs_and_coinjoin">'''Why does using all inputs matter for CoinJoin?''' If Alice uses a random input to create the output for Bob, this necessarily reveals to Bob which input Alice has control of. If Alice is paying Bob as part of a CoinJoin, this would reveal which input belongs to her, degrading the anonymity set of the CoinJoin and giving Bob more information about Alice. If instead all inputs are used, Bob has no way of knowing which input(s) belong to Alice. This comes at the cost of increased complexity as the CoinJoin participants now need to coordinate to create the silent payment output and would need to use [https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406 Blind Diffie–Hellman] to prevent the other participants from learning who Alice is paying. Note it is currently not recommended to use this protocol for CoinJoins due to a lack of a formal security proof.</ref>.
|
||||||
|
|
||||||
|
Alice performs the tweak with the sum of her input private keys in the following manner:
|
||||||
|
|
||||||
|
* Let ''a = a<sub>1</sub> + a<sub>2</sub> + ... + a<sub>n</sub>''
|
||||||
|
* Let ''input_hash = hash(outpoint<sub>L</sub> || (a·G))'', where ''outpoint<sub>L</sub>'' is the smallest outpoint lexicographically<ref name="why_smallest_outpoint">'''Why use the lexicographically smallest outpoint for the hash?''' Recall that the purpose of including the input hash is so that the sender and receiver can both come up with a deterministic nonce that ensures that a unique address is generated each time, even when reusing the same scriptPubKey as an input. Choosing the smallest outpoint lexicographically satisfies this requirement, while also ensuring that the generated output is not dependent on the final ordering of inputs in the transaction. Using a single outpoint also works well with memory constrained devices (such as hardware signing devices) as it does not require the device to have the entire transaction in memory in order to generate the silent payment output.</ref>
|
||||||
|
* Let ''P<sub>0</sub> = B + hash(input_hash·a·B || 0)·G''
|
||||||
|
|
||||||
|
''' Spend and Scan Key '''
|
||||||
|
|
||||||
|
Since Bob needs his private key ''b'' to check for incoming payments, this requires ''b'' to be exposed to an online device. To minimize the risks involved, Bob can instead publish an address of the form ''(B<sub>scan</sub>, B<sub>spend</sub>)''. This allows Bob to keep ''b<sub>spend</sub>'' in offline cold storage and perform the scanning with the public key ''B<sub>spend</sub>'' and private key ''b<sub>scan</sub>''. Alice performs the tweak using both of Bob's public keys in the following manner:
|
||||||
|
|
||||||
|
* Let ''P<sub>0</sub> = B<sub>spend</sub> + hash(input_hash·a·B<sub>scan</sub> || 0)·G''
|
||||||
|
|
||||||
|
Bob detects this payment by calculating ''P<sub>0</sub> = B<sub>spend</sub> + hash(input_hash·b<sub>scan</sub>·A || 0)·G'' with his online device and can spend from his cold storage signing device using ''(b<sub>spend</sub> + hash(input_hash·b<sub>scan</sub>·A || 0)) mod n'' as the private key.
|
||||||
|
|
||||||
|
''' Labels '''
|
||||||
|
|
||||||
|
For a single silent payment address of the form ''(B<sub>scan</sub>, B<sub>spend</sub>)'', Bob may wish to differentiate incoming payments. Naively, Bob could publish multiple silent payment addresses, but this would require him to scan for each one, which becomes prohibitively expensive. Instead, Bob can label his spend public key ''B<sub>spend</sub>'' with an integer ''m'' in the following way:
|
||||||
|
|
||||||
|
* Let ''B<sub>m</sub> = B<sub>spend</sub> + hash(b<sub>scan</sub> || m)·G'' where m is an incrementable integer starting from 1
|
||||||
|
* Publish ''(B<sub>scan</sub>, B<sub>1</sub>)'', ''(B<sub>scan</sub>, B<sub>2</sub>)'' etc.
|
||||||
|
|
||||||
|
Alice performs the tweak as before using one of the published ''(B<sub>scan</sub>, B<sub>m</sub>)'' pairs. Bob detects the labeled payment in the following manner:
|
||||||
|
|
||||||
|
* Let ''P<sub>0</sub> = B<sub>spend</sub> + hash(input_hash·b<sub>scan</sub>·A || 0)·G''
|
||||||
|
* Subtract ''P<sub>0</sub>'' from each of the transaction outputs and check if the remainder matches any of the labels (''hash(b<sub>scan</sub> || 1)·G'', ''hash(b<sub>scan</sub> || 2)·G'' etc.) that the wallet has previously used
|
||||||
|
|
||||||
|
It is important to note that an outside observer can easily deduce that each published ''(B<sub>scan</sub>, B<sub>m</sub>)'' pair is owned by the same entity as each published address will have ''B<sub>scan</sub>'' in common. As such, labels are not meant as a way for Bob to manage separate identities, but rather a way for Bob to determine the source of an incoming payment.
|
||||||
|
|
||||||
|
''' Labels for change '''
|
||||||
|
|
||||||
|
Bob can also use labels for managing his own change outputs. We reserve ''m = 0'' for this use case. This gives Bob an alternative to using BIP32 for managing change, while still allowing him to know which of his unspent outputs were change when recovering his wallet from the master key. It is important that the wallet never hands out the label with ''m = 0'' in order to ensure nobody else can create payments that are wrongly labeled as change.
|
||||||
|
|
||||||
|
While the use of labels is optional, every receiving silent payments wallet should at least scan for the change label when recovering from backup in order to ensure maximum cross-compatibility.
|
||||||
|
|
||||||
|
== Specification ==
|
||||||
|
|
||||||
|
We use the following functions and conventions:
|
||||||
|
|
||||||
|
* ''outpoint'' (36 bytes): the <code>COutPoint</code> of an input (32-byte txid, least significant byte first || 4-byte vout, least significant byte first)<ref name="why_little_endian">'''Why are outpoints little-endian?''' Despite using big endian throughout the rest of the BIP, outpoints are sorted and hashed matching their transaction serialization, which is little-endian. This allows a wallet to parse a serialized transaction for use in silent payments without needing to re-order the bytes when computing the input hash. Note: despite outpoints being stored and serialized as little-endian, the transaction hash (txid) is always displayed as big-endian.</ref>
|
||||||
|
* ser<sub>32</sub>(i): serializes a 32-bit unsigned integer ''i'' as a 4-byte sequence, most significant byte first.
|
||||||
|
* ser<sub>256</sub>(p): serializes the integer p as a 32-byte sequence, most significant byte first.
|
||||||
|
* ser<sub>P</sub>(P): serializes the coordinate pair P = (x,y) as a byte sequence using SEC1's compressed form: (0x02 or 0x03) || ser<sub>256</sub>(x), where the header byte depends on the parity of the omitted Y coordinate.
|
||||||
|
|
||||||
|
For everything not defined above, we use the notation from [https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#specification BIP340]. This includes the ''hash<sub>tag</sub>(x)'' notation to refer to ''SHA256(SHA256(tag) || SHA256(tag) || x)''.
|
||||||
|
|
||||||
|
=== Versions ===
|
||||||
|
|
||||||
|
This document defines version 0 (''sp1q''). Version is communicated through the address in the same way as bech32 addresses (see [https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]. Future upgrades to silent payments will require a new version. As much as possible, future upgrades should support receiving from older wallets (e.g. a silent payments v0 wallet can send to both v0 and v1 addresses). Any changes that break compatibility with older silent payment versions should be a new BIP.
|
||||||
|
|
||||||
|
Future silent payments versions will use the following scheme:
|
||||||
|
|
||||||
|
{| class="wikitable"
|
||||||
|
|-
|
||||||
|
!
|
||||||
|
!0
|
||||||
|
!1
|
||||||
|
!2
|
||||||
|
!3
|
||||||
|
!4
|
||||||
|
!5
|
||||||
|
!6
|
||||||
|
!7
|
||||||
|
!Compatibility
|
||||||
|
|-
|
||||||
|
!+0
|
||||||
|
|q||p||z||r||y||9||x||8||rowspan="4" | backwards compatible
|
||||||
|
|-
|
||||||
|
!+8
|
||||||
|
|g||f||2||t||v||d||w||0
|
||||||
|
|-
|
||||||
|
!+16
|
||||||
|
|s||3||j||n||5||4||k||h
|
||||||
|
|-
|
||||||
|
!+24
|
||||||
|
|c||e||6||m||u||a||7|| -
|
||||||
|
|}
|
||||||
|
|
||||||
|
''v31'' (l) is reserved for a backwards incompatible change, if needed. For silent payments v0:
|
||||||
|
|
||||||
|
* If the receiver's silent payment address version is:
|
||||||
|
** ''v0'': check that the data part is exactly 66-bytes. Otherwise, fail
|
||||||
|
** ''v1'' through ''v30'': read the first 66-bytes of the data part and discard the remaining bytes
|
||||||
|
** ''v31'': fail
|
||||||
|
* Receiver addresses are always [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot outputs<ref name="why_taproot">'''Why only taproot outputs?''' Providing too much optionality for the protocol makes it difficult to implement and can be at odds with the goal of providing the best privacy. Limiting to taproot outputs helps simplify the implementation significantly while also putting users in the best eventual anonymity set.</ref>
|
||||||
|
* The sender should sign with one of the sighash flags ''DEFAULT'', ''ALL'', ''SINGLE'', ''NONE'' (''ANYONECANPAY'' is unsafe). It is strongly recommended implementations use ''SIGHASH_ALL'' (''SIGHASH_DEFAULT'' for taproot inputs) when possible<ref name="why_not_sighash_anyonecanpay">'''Why is it unsafe to use ''SIGHASH_ANYONECANPAY''?''' Since the output address for the receiver is derived from the sum of the [[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]] public keys, the inputs must not change once the sender has signed the transaction. If the inputs are allowed to change after the fact, the receiver will not be able to calculate the shared secret needed to find and spend the output. It is currently an open question on how a future version of silent payments could be made to work with new sighash flags such as ''SIGHASH_GROUP'' and ''SIGHASH_ANYPREVOUT''.</ref>
|
||||||
|
* Inputs used to derive the shared secret are from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list
|
||||||
|
|
||||||
|
=== Scanning silent payment eligible transactions ===
|
||||||
|
|
||||||
|
For silent payments v0 a transaction MUST be scanned if and only if all of the following are true:
|
||||||
|
|
||||||
|
* The transaction contains at least one BIP341 taproot output (note: spent transactions optionally can be skipped by only considering transactions with at least one unspent taproot output)
|
||||||
|
* The transaction has at least one input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list
|
||||||
|
* The transaction does not spend an output with SegWit version > 1<ref name="skip_txs_with_unknown_prevouts">'''Why skip transactions that spend SegWit version > 1?''' Skipping transactions that spend unknown output scripts allows us to have a clean upgrade path for silent payments by avoiding the need to scan the same transaction multiple times with different rule sets. If a new SegWit version is added in the future and silent payments v1 is released with support, we would want to avoid having to first scan the transaction with the silent payment v0 rules and then again with the silent payment v1 rules. Note: this restriction only applies to the inputs of a transaction.</ref>
|
||||||
|
|
||||||
|
=== Address encoding ===
|
||||||
|
|
||||||
|
A silent payment address is constructed in the following manner:
|
||||||
|
|
||||||
|
* Let ''B<sub>scan</sub>, b<sub>scan</sub> = Receiver's scan public key and corresponding private key''
|
||||||
|
* Let ''B<sub>spend</sub>, b<sub>spend</sub> = Receiver's spend public key and corresponding private key''
|
||||||
|
* Let ''B<sub>m</sub> = B<sub>spend</sub> + hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))·G'', where ''hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))·G'' is an optional integer tweak for labeling
|
||||||
|
** If no label is applied then ''B<sub>m</sub> = B<sub>spend</sub>''
|
||||||
|
* The final address is a [https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki Bech32m] encoding of:
|
||||||
|
** The human-readable part "sp" for mainnet, "tsp" for testnets (e.g. signet, testnet)
|
||||||
|
** The data-part values:
|
||||||
|
*** The character "q", to represent a silent payment address of version 0
|
||||||
|
*** The 66-byte concatenation of the receiver's public keys, ''ser<sub>P</sub>(B<sub>scan</sub>) || ser<sub>P</sub>(B<sub>m</sub>)''
|
||||||
|
|
||||||
|
Note: [https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki BIP173] imposes a 90 character limit for Bech32 segwit addresses and limits versions to 0 through 16, whereas a silent payment address requires ''at least'' 117 characters<ref name="why_117_chars"> ''' Why do silent payment addresses need at least 117 characters?''' A silent payment address is a bech32m encoding comprised of the following parts:
|
||||||
|
|
||||||
|
|
||||||
|
* HRP [2-3 characters]
|
||||||
|
* separator [1 character]
|
||||||
|
* version [1-2 characters]
|
||||||
|
* payload, 66 bytes concatenated pubkeys [ceil(66*8/5) = 106 characters]
|
||||||
|
* checksum [6 characters]
|
||||||
|
|
||||||
|
|
||||||
|
For a silent payments v0 address, this results in a 117-character address when using a 3-character HRP. Future versions of silent payment addresses may add to the payload, which is why a 1023-character limit is suggested.</ref> and allows versions up to 31. Additionally, since higher versions may add to the data field, it is recommended implementations use a limit of 1023 characters (see [https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#checksum-design BIP173: Checksum design] for more details).
|
||||||
|
|
||||||
|
=== Inputs For Shared Secret Derivation ===
|
||||||
|
|
||||||
|
While any UTXO with known output scripts can be used to fund the transaction, the sender and receiver MUST use inputs from the following list when deriving the shared secret:
|
||||||
|
|
||||||
|
* ''P2TR''
|
||||||
|
* ''P2WPKH''
|
||||||
|
* ''P2SH-P2WPKH''
|
||||||
|
* ''P2PKH''
|
||||||
|
|
||||||
|
Inputs with conditional branches or multiple public keys (e.g. ''CHECKMULTISIG'') are excluded from shared secret derivation as this introduces malleability and would allow a sender to re-sign with a different set of public keys after the silent payment output has been derived. This is not a concern when the sender controls all of the inputs, but is an issue for CoinJoins and other collaborative protocols, where a malicious participant can participate in deriving the silent payment address with one set of keys and then re-broadcast the transaction with signatures for a different set of public keys. P2TR can have hidden conditional branches (script path), but we work around this by using only the output public key.
|
||||||
|
|
||||||
|
For all of the output types listed, only X-only and compressed public keys are permitted<ref name="why_only_compressed_public_keys">''' Why only compressed public keys ''' Uncompressed and hybrid public keys are less common than compressed keys and generally considered to be a bad idea due to their blockspace inefficiency. Additionally, [https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#restrictions-on-public-key-type BIP143] recommends restricting P2WPKH inputs to compressed keys as a default policy.</ref>.
|
||||||
|
|
||||||
|
''' P2TR '''
|
||||||
|
|
||||||
|
'' Keypath spend ''
|
||||||
|
|
||||||
|
witness: <signature>
|
||||||
|
scriptSig: (empty)
|
||||||
|
scriptPubKey: 1 <32-byte-x-only-key>
|
||||||
|
(0x5120{32-byte-x-only-key})
|
||||||
|
|
||||||
|
The sender uses the private key corresponding to the taproot output key (i.e. the tweaked private key). This can be a single private key or an aggregate key (e.g. taproot outputs using MuSig or FROST)<ref name="musig_frost_support">'''Are key aggregation techniques like FROST and MuSig supported?''' While we do not recommend it due to lack of a security proof (except if all participants are trusted or are the same entity), any taproot output able to do a key path theoretically is supported. Any offline key aggregation technique can be used, such as FROST or MuSig. This would require participants to perform the ECDH step collaboratively e.g. ''ECDH = a<sub>1</sub>·B<sub>scan</sub> + a<sub>2</sub>·B<sub>scan</sub> + ... + a<sub>t</sub>·B<sub>scan</sub>'' and ''P = B<sub>spend</sub> + hash(input_hash·ECDH || 0)·G''. Additionally, it may be necessary for the participants to provide a DLEQ proof to ensure they are not acting maliciously.</ref>. The receiver obtains the public key from the ''scriptPubKey'' (i.e. the taproot output key).
|
||||||
|
|
||||||
|
'' Script path spend ''
|
||||||
|
|
||||||
|
witness: <optional witness items> <leaf script> <control block>
|
||||||
|
scriptSig: (empty)
|
||||||
|
scriptPubKey: 1 <32-byte-x-only-key>
|
||||||
|
(0x5120{32-byte-x-only-key})
|
||||||
|
|
||||||
|
Same as a keypath spend, the sender MUST use the private key corresponding to the taproot output key. If this key is not available, the output cannot be included as an input to the transaction. Same as a keypath spend, the receiver obtains the public key from the ''scriptPubKey'' (i.e. the taproot output key)<ref name="why_always_output_pubkey">''' Why not skip all taproot script path spends? ''' This causes malleability issues for CoinJoins. If the silent payments protocol skipped taproot script path spends, this would allow an attacker to join a CoinJoin round, participate in deriving the silent payment address using the tweaked private key for a key path spend, and then broadcast their own version of the transaction using the script path spend. If the receiver were to only consider key path spends, they would skip the attacker's script path spend input when deriving the shared secret and not be able to find the funds. Additionally, there may be scenarios where the sender can perform ECDH with the key path private key but spends the output using the script path.</ref>.
|
||||||
|
|
||||||
|
The one exception is script path spends that use NUMS point ''H'' as their internal key (where ''H'' is constructed by taking the hash of the standard uncompressed encoding of the secp256k1 base point ''G'' as X coordinate, see [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs BIP341: Constructing and spending Taproot outputs] for more details), in which case the input will be skipped for the purposes of shared secret derivation<ref name="why_ignore_h">'''Why skip outputs with H as the internal taproot key?''' If use cases get popularized where the taproot key path cannot be used, these outputs can still be included without getting in the way of making a silent payment, provided they specifically use H as their internal taproot key.</ref>. The receiver determines whether or not to skip the input by checking in the control block if the taproot internal key is equal to ''H''.
|
||||||
|
|
||||||
|
''' P2WPKH '''
|
||||||
|
|
||||||
|
witness: <signature> <33-byte-compressed-key>
|
||||||
|
scriptSig: (empty)
|
||||||
|
scriptPubKey: 0 <20-byte-key-hash>
|
||||||
|
(0x0014{20-byte-key-hash})
|
||||||
|
|
||||||
|
The sender performs the tweak using the private key for the output and the receiver obtains the public key as the last witness item.
|
||||||
|
|
||||||
|
''' P2SH-P2WPKH '''
|
||||||
|
|
||||||
|
witness: <signature> <33-byte-compressed-key>
|
||||||
|
scriptSig: <0 <20-byte-key-hash>>
|
||||||
|
(0x160014{20-byte-key-hash})
|
||||||
|
scriptPubKey: HASH160 <20-byte-script-hash> EQUAL
|
||||||
|
(0xA914{20-byte-script-hash}87)
|
||||||
|
|
||||||
|
The sender performs the tweak using the private key for the nested ''P2WPKH'' output and the receiver obtains the public key as the last witness item.
|
||||||
|
|
||||||
|
''' P2PKH '''
|
||||||
|
|
||||||
|
scriptSig: <signature> <33-byte-compressed-key>
|
||||||
|
scriptPubKey: OP_DUP HASH160 <20-byte-key-hash> OP_EQUALVERIFY OP_CHECKSIG
|
||||||
|
(0x76A914{20-byte-key-hash}88AC)
|
||||||
|
|
||||||
|
The receiver obtains the public key from the ''scriptSig''. The receiver MUST parse the ''scriptSig'' for the public key, even if the ''scriptSig'' does not match the template specified (e.g. <code><dummy> OP_DROP <Signature> <Public Key></code>). This is to address the [https://en.bitcoin.it/wiki/Transaction_malleability third-party malleability of ''P2PKH'' ''scriptSigs''].
|
||||||
|
|
||||||
|
=== Sender ===
|
||||||
|
|
||||||
|
==== Selecting inputs ====
|
||||||
|
|
||||||
|
The sending wallet performs coin selection as usual with the following restrictions:
|
||||||
|
|
||||||
|
* At least one input MUST be from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list
|
||||||
|
* Exclude inputs with SegWit version > 1 (see ''[[#scanning-silent-payment-eligible-transactions|Scanning silent payment eligible transactions]]'')
|
||||||
|
* For each taproot output spent the sending wallet MUST have access to the private key corresponding to the taproot output key, unless ''H'' is used as the internal public key
|
||||||
|
|
||||||
|
==== Creating outputs ====
|
||||||
|
|
||||||
|
After the inputs have been selected, the sender can create one or more outputs for one or more silent payment addresses in the following manner:
|
||||||
|
|
||||||
|
* Collect the private keys for each input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list
|
||||||
|
* For each private key ''a<sub>i</sub>'' corresponding to a [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot output, check that the private key produces a point with an even Y coordinate and negate the private key if not<ref name="why_negate_taproot_private_keys">'''Why do taproot private keys need to be checked?''' Recall from [https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki BIP340] that each X-only public key has two corresponding private keys, ''d'' and ''n - d''. To maintain parity between sender and receiver, it is necessary to use the private key corresponding to the even Y coordinate when performing the ECDH step since the receiver will assume the even Y coordinate when summing the taproot X-only public keys.</ref>
|
||||||
|
* Let ''a = a<sub>1</sub> + a<sub>2</sub> + ... + a<sub>n</sub>'', where each ''a<sub>i</sub>'' has been negated if necessary
|
||||||
|
** If ''a = 0'', fail
|
||||||
|
* Let ''input_hash = hash<sub>BIP0352/Inputs</sub>(outpoint<sub>L</sub> || A)'', where ''outpoint<sub>L</sub>'' is the smallest ''outpoint'' lexicographically used in the transaction<ref name="why_smallest_outpoint"></ref> and ''A = a·G''
|
||||||
|
* Group receiver silent payment addresses by ''B<sub>scan</sub>'' (e.g. each group consists of one ''B<sub>scan</sub>'' and one or more ''B<sub>m</sub>'')
|
||||||
|
* For each group:
|
||||||
|
** Let ''ecdh_shared_secret = input_hash·a·B<sub>scan</sub>''
|
||||||
|
** Let ''k = 0''
|
||||||
|
** For each ''B<sub>m</sub>'' in the group:
|
||||||
|
*** Let ''t<sub>k</sub> = hash<sub>BIP0352/SharedSecret</sub>(ser<sub>P</sub>(ecdh_shared_secret) || ser<sub>32</sub>(k))''
|
||||||
|
**** If ''t<sub>k</sub>'' is not valid tweak, i.e., if ''t<sub>k</sub> = 0'' or ''t<sub>k</sub>'' is larger or equal to the secp256k1 group order, fail
|
||||||
|
*** Let ''P<sub>mn</sub> = B<sub>m</sub> + t<sub>k</sub>·G''
|
||||||
|
*** Encode ''P<sub>mn</sub>'' as a [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot output
|
||||||
|
*** Optionally, repeat with k++ to create additional outputs for the current ''B<sub>m</sub>''
|
||||||
|
*** If no additional outputs are required, continue to the next ''B<sub>m</sub>'' with ''k++''<ref name="why_not_the_same_tn">''' Why not re-use ''t<sub>k</sub>'' when paying different labels to the same receiver?''' If paying the same entity but to two separate labeled addresses in the same transaction without incrementing ''k'', an outside observer could subtract the two output values and observe that this value is the same as the difference between two published silent payment addresses and learn who the recipient is.</ref>
|
||||||
|
** Optionally, if the sending wallet implements receiving silent payments, it can create change outputs by sending to its own silent payment address using label ''m = 0'', following the steps above
|
||||||
|
|
||||||
|
=== Receiver ===
|
||||||
|
|
||||||
|
==== Key Derivation ====
|
||||||
|
|
||||||
|
Two keys are needed to create a silent payments address: the spend key and the scan key. To ensure compatibility, wallets MAY use BIP32 derivation with the following derivation paths for the spend and scan key. When using BIP32 derivation, wallet software MUST use hardened derivation<ref name="bip32_derivation">'''Why use BIP32 hardened derivation?''' Using BIP32 derivation allows users to add silent payments to an existing master seed. It also ensures that a user's silent payment funds are recoverable in any BIP32/BIP43 compatible wallet. Using hardened derivation ensures that it is safe to export the scan private key without exposing the master key or spend private key.</ref> for both the spend and scan key.
|
||||||
|
|
||||||
|
A scan and spend key pair using BIP32 derivation are defined (taking inspiration from [https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]) in the following manner:
|
||||||
|
|
||||||
|
scan_private_key: m / purpose' / coin_type' / account' / 1' / 0
|
||||||
|
spend_private_key: m / purpose' / coin_type' / account' / 0' / 0
|
||||||
|
|
||||||
|
<code>purpose</code> is a constant set to ''352'' following the BIP43 recommendation. Refer to [https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki BIP43] and [https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44] for more details.
|
||||||
|
|
||||||
|
==== Scanning ====
|
||||||
|
|
||||||
|
If each of the checks in ''[[#scanning-silent-payment-eligible-transactions|Scanning silent payment eligible transactions]]'' passes, the receiving wallet must:
|
||||||
|
|
||||||
|
* Let ''A = A<sub>1</sub> + A<sub>2</sub> + ... + A<sub>n</sub>'', where each ''A<sub>i</sub>'' is the public key of an input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list
|
||||||
|
** If ''A'' is the point at infinity, skip the transaction
|
||||||
|
* Let ''input_hash = hash<sub>BIP0352/Inputs</sub>(outpoint<sub>L</sub> || A)'', where ''outpoint<sub>L</sub>'' is the smallest ''outpoint'' lexicographically used in the transaction<ref name="why_smallest_outpoint"></ref>
|
||||||
|
* Let ''ecdh_shared_secret = input_hash·b<sub>scan</sub>·A''
|
||||||
|
* Check for outputs:
|
||||||
|
** Let ''outputs_to_check'' be the taproot output keys from all taproot outputs in the transaction (spent and unspent).
|
||||||
|
** Starting with ''k = 0'':
|
||||||
|
*** Let ''t<sub>k</sub> = hash<sub>BIP0352/SharedSecret</sub>(ser<sub>P</sub>(ecdh_shared_secret) || ser<sub>32</sub>(k))''
|
||||||
|
**** If ''t<sub>k</sub>'' is not valid tweak, i.e., if ''t<sub>k</sub> = 0'' or ''t<sub>k</sub>'' is larger or equal to the secp256k1 group order, fail
|
||||||
|
*** Compute ''P<sub>k</sub> = B<sub>spend</sub> + t<sub>k</sub>·G''
|
||||||
|
*** For each ''output'' in ''outputs_to_check'':
|
||||||
|
**** If ''P<sub>k</sub>'' equals ''output'':
|
||||||
|
***** Add ''P<sub>k</sub>'' to the wallet
|
||||||
|
***** Remove ''output'' from ''outputs_to_check'' and rescan ''outputs_to_check'' with ''k++''
|
||||||
|
**** Else, check for labels (always check for the change label, i.e. ''hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))'' where ''m = 0'')<ref name="precompute_labels">''' Why precompute labels?''' Precomputing the labels is not strictly necessary: a wallet could track the max number of labels it has used (call it ''M'') and scan for labels by adding ''hash(b<sub>scan</sub> || m)·G'' to ''P<sub>0</sub>'' for each label ''m'' up to ''M'' and comparing to the transaction outputs. This is more performant than precomputing the labels and checking via subtraction in cases where the number of eligible outputs exceeds the number of labels in use. In practice this will mainly apply to users that choose never to use labels, or users that use a single label for generating silent payment change outputs. If using a large number of labels, the wallet would need to add all possible labels to each output. This ends up being ''n·M'' additions, where ''n'' is the number of outputs in the transaction and ''M'' is the number of labels in the wallet. By precomputing the labels, the wallet only needs to compute ''hash(b<sub>scan</sub> || m)·G'' once when creating the labeled address and can determine if a label was used via a lookup, rather than adding each label to each output.</ref>:
|
||||||
|
***** Compute ''label = output - P<sub>k</sub>''
|
||||||
|
***** Check if ''label'' exists in the list of labels used by the wallet
|
||||||
|
***** If a match is found:
|
||||||
|
****** Add ''P<sub>k</sub> + label'' to the wallet
|
||||||
|
****** Remove ''output'' from ''outputs_to_check'' and rescan ''outputs_to_check'' with ''k++''
|
||||||
|
***** If a label is not found, negate ''output'' and check a second time<ref name="negate_output">''' Why negate the output?''' Unfortunately taproot outputs are X-only, meaning we don't know what the correct Y coordinate is. This causes this specific calculation to fail 50% of the time, so we need to repeat it with the other Y coordinate by negating the output.</ref>
|
||||||
|
*** If no matches are found, stop
|
||||||
|
|
||||||
|
==== Spending ====
|
||||||
|
|
||||||
|
Recall that a silent payment output is of the form ''B<sub>spend</sub> + t<sub>k</sub>·G + hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))·G'', where ''hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))·G'' is an optional label. To spend a silent payment output:
|
||||||
|
|
||||||
|
* Let ''d = (b<sub>spend</sub> + t<sub>k</sub> + hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))) mod n'', where ''hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))'' is the optional label
|
||||||
|
* Spend the [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] output with the private key ''d''
|
||||||
|
|
||||||
|
==== Backup and Recovery ====
|
||||||
|
|
||||||
|
Since each silent payment output address is derived independently, regular backups are recommended. When recovering from a backup, the wallet will need to scan since the last backup to detect new payments.
|
||||||
|
|
||||||
|
If using a seed/seed phrase only style backup, the user can recover the wallet's unspent outputs from the UTXO set (i.e. only scanning transactions with at least one unspent taproot output) and can recover the full wallet history by scanning the blockchain starting from the wallet birthday. If a wallet uses labels, this information SHOULD be included in the backup. If the user does not know whether labels were used, it is strongly recommended they always precompute and check a large number of labels (e.g. 100k labels) to use when re-scanning. This ensures that the wallet can recover all funds from only a seed/seed phrase backup. The change label should simply always be scanned for, even when no other labels were used. This ensures the use of a change label is not critical for backups and maximizes cross-compatibility.
|
||||||
|
|
||||||
|
== Backward Compatibility ==
|
||||||
|
|
||||||
|
Silent payments introduces a new address format and protocol for sending and as such is not compatible with older wallet software or wallets which have not implemented the silent payments protocol.
|
||||||
|
|
||||||
|
== Test Vectors ==
|
||||||
|
|
||||||
|
A [[bip-0352/send_and_receive_test_vectors.json|collection of test vectors in JSON format]] are provided, along with a [[bip-0352/reference.py|python reference implementation]]. Each test vector consists of a sending test case and corresponding receiving test case. This is to allow sending and receiving to be implemented separately. To ensure determinism while testing, sort the array of ''B<sub>m</sub>'' by amount (see the [[bip-0352/reference.py|reference implementation]]). Test cases use the following schema:
|
||||||
|
|
||||||
|
''' test_case '''
|
||||||
|
|
||||||
|
{
|
||||||
|
"comment": "Comment describing the behavior being tested",
|
||||||
|
"sending": [<array of sender test objects>],
|
||||||
|
"receiving": [<array of recipient test objects>],
|
||||||
|
}
|
||||||
|
|
||||||
|
''' sender '''
|
||||||
|
|
||||||
|
{
|
||||||
|
"given": {
|
||||||
|
"vin": [<array of vin objects with an added field for the private key. These objects are structured to match the `vin` output field from `getrawtransaction verbosity=2`>],
|
||||||
|
"recipients": [<array of strings, where each string is a bech32m encoding representing a silent payment address>]
|
||||||
|
},
|
||||||
|
"expected": {
|
||||||
|
"outputs": [<array of strings, where each string is a hex encoding of 32-byte X-only public key; contains all possible output sets, test must match a subset of size `n_outputs`>],
|
||||||
|
"n_outputs": <integer for the exact number of expected outputs>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
''' recipient '''
|
||||||
|
|
||||||
|
{
|
||||||
|
"given": {
|
||||||
|
"vin": [<array of vin objects. These objects are structured to match the `vin` output field from `getrawtransaction verbosity=2`>],
|
||||||
|
"key_material": {
|
||||||
|
"scan_priv_key": <hex encoded scan private key>,
|
||||||
|
"spend_priv_key": <hex encoded spend private key>,
|
||||||
|
}
|
||||||
|
"labels": [<array of ints, representing labels the receiver has used>],
|
||||||
|
},
|
||||||
|
"expected": {
|
||||||
|
"addresses": [<array of bech32m strings, one for the silent payment address and each labeled address (if used)>],
|
||||||
|
"outputs": [<array of outputs with tweak and signature; contains all possible output sets, tester must match a subset of size `n_outputs`>
|
||||||
|
{
|
||||||
|
"priv_key_tweak": <hex encoded private key tweak data>,
|
||||||
|
"pub_key": <hex encoded X-only public key>,
|
||||||
|
"signature": <hex encoded signature for the output (produced with spend_priv_key + priv_key_tweak)>
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"n_outputs": <integer for the exact number of expected outputs>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Wallets should include inputs not in the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list when testing to ensure that only inputs from the list are being used for shared secret derivation. Additionally, receiving wallets should include non-silent payment outputs for themselves in testing to ensure silent payments scanning does not interfere with regular outputs detection.
|
||||||
|
|
||||||
|
=== Functional tests ===
|
||||||
|
|
||||||
|
Below is a list of functional tests which should be included in sending and receiving implementations.
|
||||||
|
|
||||||
|
==== Sending ====
|
||||||
|
|
||||||
|
* Ensure taproot outputs are excluded during coin selection if the sender does not have access to the key path private key (unless using ''H'' as the taproot internal key)
|
||||||
|
* Ensure the silent payment address is re-derived if inputs are added or removed during RBF
|
||||||
|
|
||||||
|
==== Receiving ====
|
||||||
|
|
||||||
|
* Ensure the public key can be extracted from non-standard ''P2PKH'' scriptSigs
|
||||||
|
* Ensure taproot script path spends are included, using the taproot output key (unless ''H'' is used as the taproot internal key)
|
||||||
|
* Ensure the scanner can extract the public key from each of the input types supported (e.g. ''P2WPKH'', ''P2SH-P2WPKH'', etc.)
|
||||||
|
|
||||||
|
== Appendix A: Light Client Support ==
|
||||||
|
|
||||||
|
This section proposes a few ideas for how light clients could support scanning for incoming silent payments (sending is fairly straightforward) in ways that preserve bandwidth and privacy. While this is out of scope for the current BIP, it is included to motivate further research into this topic. In this context, a light client refers to any bitcoin wallet client which does not process blocks and does not have a direct connection to a node which does process blocks (e.g. a full node). Based on this definition, clients that directly connect to a personal electrum server or a bitcoin node are not light clients.
|
||||||
|
|
||||||
|
This distinction makes the problem for light clients more clear: light clients need a way to source the necessary data for performing the tweaks and a way of determining if any of the generated outputs exist in a block.
|
||||||
|
|
||||||
|
=== Tweak Data ===
|
||||||
|
|
||||||
|
Recall that a silent payment eligible transaction follows [[#scanning-silent-payment-eligible-transactions|certain conditions]] and should have at least one unspent taproot output. Full nodes (or any index server backed by a full node, such as electrum server) can build an index which collects all of the eligible public keys for a silent payments eligible transaction, sums them up, multiplies the sum by the ''input_hash'', and serves them to clients. This would be 33 bytes per silent payment eligible transaction.
|
||||||
|
|
||||||
|
For a typical bitcoin block of ~3500 txs, lets assume every transaction is a silent payments eligible transaction. This means a client would need to request ''33 bytes * 3500'' of data per block (roughly 100 kB per block). If a client were to request data for every block, this would amount to ~450 MB per month, assuming 100% taproot usage and all non-dust outputs remain unspent for > 1 month. As of today, these numbers are closer to 7–12 kB per block (30–50 MB per month)<ref name="appendix_data">''' Data for Appendix A ''' These numbers are based on data from January 2023 until July 2024. See [https://github.com/josibake/bitcoin-data-analysis/blob/main/notebooks/silent-payments-light-client-data.ipynb Silent payments light client data] for the full analysis.</ref>.
|
||||||
|
|
||||||
|
=== Transaction cut-through ===
|
||||||
|
|
||||||
|
It is unlikely a light client would need to scan every block and as such can take advantage of transaction cut-through, depending on how often they choose to scan for new blocks. Empirically, ~75% of transactions with at least one non-dust unspent taproot output will have spent all non-dust taproot UTXOs in 150 blocks or less<ref name="appendix_data"></ref>. This means a client that only scans once per day could ''significantly'' cut down on the number of blocks and the number of transactions per block that they need to request by only asking for data on transactions that were created since their last scan and that still have at least one non-dust unspent taproot output as of the current block height. Based on taproot adoption as of July 2024, a light client scanning once every 3 days would use roughly 30 MB per month<ref name="appendix_data">.
|
||||||
|
|
||||||
|
[[File:bip-0352/scan_data_downloader_per_month.png]]
|
||||||
|
|
||||||
|
=== BIP158 ===
|
||||||
|
|
||||||
|
Once a light client has the tweak data for a block, they can determine whether or not an output to them exists in the block using BIP158 block filters. Per BIP158, they would then request the entire block and add the transaction to their wallet, though it maybe be possible to only request the prevout txids and vouts for all transactions with at least one taproot output, along with the scriptPubKeys and amounts. This would allow the client to download the necessary data for constructing a spending transaction, without downloading the entire block. How this affects the security assumptions of BIP158 is an open question.
|
||||||
|
|
||||||
|
=== Out-of-band notifications ===
|
||||||
|
|
||||||
|
Assuming a secure messaging protocol exists, the sender can send an encrypted (using the scan public key of the silent payment address) notification to the receiver with the following information:
|
||||||
|
* The spend public key (communicates the label)
|
||||||
|
* The shared secret portion of the private key (i.e ''hash(ecdh_shared_secret || k)'')
|
||||||
|
* The outpoint and amount (so it's immediately spendable)
|
||||||
|
|
||||||
|
It is important to note that these notifications are not required. At any point, the receiver can fall back to scanning for silent payment transactions if they don't trust the notifications they are receiving, are being spammed with fake notifications, or if they are concerned that they are not receiving notifications.
|
||||||
|
|
||||||
|
A malicious notification could potentially cause the following issues:
|
||||||
|
|
||||||
|
* You did not actually receive money to the stated key
|
||||||
|
** This can be probabilistically resolved by matching the key against the BIP158 block filters and assuming it's not a false positive, or fully resolved by downloading the block
|
||||||
|
* You received money but the outpoint or amount is incorrect, so attempts to spend it will fail or cause you to overpay fees
|
||||||
|
** There doesn't seem to be much motivation for malicious senders to ever do this, but light clients need to take into account that this can occur and should ideally check for it by downloading the block
|
||||||
|
* The private key is correct but it wasn't actually derived using the silent payment protocol, causing recovery from back-up to fail (unsafe - no implementation should ever allow this)
|
||||||
|
** This can be detected by downloading the tweak data of the corresponding block and should be resolved by immediately spending the output
|
||||||
|
|
||||||
|
Wallet designers can choose which tradeoffs they find appropriate. For example, a wallet could check the block filter to at least probabilistically confirm the likely existence of the UTXO, thus efficiently cutting down on spam. The payment could then be marked as unconfirmed until a scan is performed and the existence of the UTXO in accordance to the silent payment specification is verified.
|
||||||
|
|
||||||
|
== Change Log ==
|
||||||
|
|
||||||
|
To help implementers understand updates to this document, we attach a version number that resembles ''semantic versioning'' (<code>MAJOR.MINOR.PATCH</code>).
|
||||||
|
The <code>MAJOR</code> version is incremented if changes to the BIP are introduced that are incompatible with prior versions.
|
||||||
|
The <code>MINOR</code> version is incremented whenever the inputs or the output of an algorithm changes in a backward-compatible way or new backward-compatible functionality is added.
|
||||||
|
The <code>PATCH</code> version is incremented for other changes that are noteworthy (bug fixes, test vectors, important clarifications, etc.).
|
||||||
|
|
||||||
|
* '''1.0.1''' (2024-06-22):
|
||||||
|
** Add steps to fail if private key sum is zero (for sender) or public key sum is point at infinity (for receiver), add corresponding test vectors.
|
||||||
|
* '''1.0.0''' (2024-05-08):
|
||||||
|
** Initial version, merged as BIP-352.
|
||||||
|
|
||||||
|
== Acknowledgements ==
|
||||||
|
|
||||||
|
This document is the result of many discussions and contains contributions by a number of people. The authors wish to thank all those who provided valuable feedback and reviews, including the participants of the [https://gist.github.com/RubenSomsen/21c477c90c942acf45f8e8f5c1ad4fae BIP47 Prague discussion], the [https://github.com/josibake/silent-payments-workshop Advancing Bitcoin silent payments Workshop], and [https://btctranscripts.com/bitcoin-core-dev-tech/2023-04/2023-04-26-silent-payments/ coredev]. The authors would like to also thank [https://github.com/w0xlt w0xlt] for writing the initial implementation of silent payments.
|
||||||
|
|
||||||
|
== Rationale and References ==
|
||||||
|
<references/>
|
||||||
|
|
135
bip-0352/bech32m.py
Normal file
135
bip-0352/bech32m.py
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
# Copyright (c) 2017, 2020 Pieter Wuille
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Reference implementation for Bech32/Bech32m and segwit addresses."""
|
||||||
|
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class Encoding(Enum):
|
||||||
|
"""Enumeration type to list the various supported encodings."""
|
||||||
|
BECH32 = 1
|
||||||
|
BECH32M = 2
|
||||||
|
|
||||||
|
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||||
|
BECH32M_CONST = 0x2bc830a3
|
||||||
|
|
||||||
|
def bech32_polymod(values):
|
||||||
|
"""Internal function that computes the Bech32 checksum."""
|
||||||
|
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
|
||||||
|
chk = 1
|
||||||
|
for value in values:
|
||||||
|
top = chk >> 25
|
||||||
|
chk = (chk & 0x1ffffff) << 5 ^ value
|
||||||
|
for i in range(5):
|
||||||
|
chk ^= generator[i] if ((top >> i) & 1) else 0
|
||||||
|
return chk
|
||||||
|
|
||||||
|
|
||||||
|
def bech32_hrp_expand(hrp):
|
||||||
|
"""Expand the HRP into values for checksum computation."""
|
||||||
|
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
|
||||||
|
|
||||||
|
|
||||||
|
def bech32_verify_checksum(hrp, data):
|
||||||
|
"""Verify a checksum given HRP and converted data characters."""
|
||||||
|
const = bech32_polymod(bech32_hrp_expand(hrp) + data)
|
||||||
|
if const == 1:
|
||||||
|
return Encoding.BECH32
|
||||||
|
if const == BECH32M_CONST:
|
||||||
|
return Encoding.BECH32M
|
||||||
|
return None
|
||||||
|
|
||||||
|
def bech32_create_checksum(hrp, data, spec):
|
||||||
|
"""Compute the checksum values given HRP and data."""
|
||||||
|
values = bech32_hrp_expand(hrp) + data
|
||||||
|
const = BECH32M_CONST if spec == Encoding.BECH32M else 1
|
||||||
|
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
|
||||||
|
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
|
||||||
|
|
||||||
|
|
||||||
|
def bech32_encode(hrp, data, spec):
|
||||||
|
"""Compute a Bech32 string given HRP and data values."""
|
||||||
|
combined = data + bech32_create_checksum(hrp, data, spec)
|
||||||
|
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
|
||||||
|
|
||||||
|
def bech32_decode(bech):
|
||||||
|
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
|
||||||
|
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
|
||||||
|
(bech.lower() != bech and bech.upper() != bech)):
|
||||||
|
return (None, None, None)
|
||||||
|
bech = bech.lower()
|
||||||
|
pos = bech.rfind('1')
|
||||||
|
|
||||||
|
# remove the requirement that bech32m be less than 90 chars
|
||||||
|
if pos < 1 or pos + 7 > len(bech):
|
||||||
|
return (None, None, None)
|
||||||
|
if not all(x in CHARSET for x in bech[pos+1:]):
|
||||||
|
return (None, None, None)
|
||||||
|
hrp = bech[:pos]
|
||||||
|
data = [CHARSET.find(x) for x in bech[pos+1:]]
|
||||||
|
spec = bech32_verify_checksum(hrp, data)
|
||||||
|
if spec is None:
|
||||||
|
return (None, None, None)
|
||||||
|
return (hrp, data[:-6], spec)
|
||||||
|
|
||||||
|
def convertbits(data, frombits, tobits, pad=True):
|
||||||
|
"""General power-of-2 base conversion."""
|
||||||
|
acc = 0
|
||||||
|
bits = 0
|
||||||
|
ret = []
|
||||||
|
maxv = (1 << tobits) - 1
|
||||||
|
max_acc = (1 << (frombits + tobits - 1)) - 1
|
||||||
|
for value in data:
|
||||||
|
if value < 0 or (value >> frombits):
|
||||||
|
return None
|
||||||
|
acc = ((acc << frombits) | value) & max_acc
|
||||||
|
bits += frombits
|
||||||
|
while bits >= tobits:
|
||||||
|
bits -= tobits
|
||||||
|
ret.append((acc >> bits) & maxv)
|
||||||
|
if pad:
|
||||||
|
if bits:
|
||||||
|
ret.append((acc << (tobits - bits)) & maxv)
|
||||||
|
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
|
||||||
|
return None
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def decode(hrp, addr):
|
||||||
|
"""Decode a segwit address."""
|
||||||
|
hrpgot, data, spec = bech32_decode(addr)
|
||||||
|
if hrpgot != hrp:
|
||||||
|
return (None, None)
|
||||||
|
decoded = convertbits(data[1:], 5, 8, False)
|
||||||
|
if decoded is None or len(decoded) < 2:
|
||||||
|
return (None, None)
|
||||||
|
if data[0] > 16:
|
||||||
|
return (None, None)
|
||||||
|
return (data[0], decoded)
|
||||||
|
|
||||||
|
|
||||||
|
def encode(hrp, witver, witprog):
|
||||||
|
"""Encode a segwit address."""
|
||||||
|
spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
|
||||||
|
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
|
||||||
|
if decode(hrp, ret) == (None, None):
|
||||||
|
return None
|
||||||
|
return ret
|
159
bip-0352/bitcoin_utils.py
Normal file
159
bip-0352/bitcoin_utils.py
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import hashlib
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
from ripemd160 import ripemd160
|
||||||
|
from secp256k1 import ECKey
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
def from_hex(hex_string):
|
||||||
|
"""Deserialize from a hex string representation (e.g. from RPC)"""
|
||||||
|
return BytesIO(bytes.fromhex(hex_string))
|
||||||
|
|
||||||
|
|
||||||
|
def ser_uint32(u: int) -> bytes:
|
||||||
|
return u.to_bytes(4, "big")
|
||||||
|
|
||||||
|
|
||||||
|
def ser_uint256(u):
|
||||||
|
return u.to_bytes(32, 'little')
|
||||||
|
|
||||||
|
|
||||||
|
def deser_uint256(f):
|
||||||
|
return int.from_bytes(f.read(32), 'little')
|
||||||
|
|
||||||
|
|
||||||
|
def deser_txid(txid: str):
|
||||||
|
# recall that txids are serialized little-endian, but displayed big-endian
|
||||||
|
# this means when converting from a human readable hex txid, we need to first
|
||||||
|
# reverse it before deserializing it
|
||||||
|
dixt = "".join(map(str.__add__, txid[-2::-2], txid[-1::-2]))
|
||||||
|
return bytes.fromhex(dixt)
|
||||||
|
|
||||||
|
|
||||||
|
def deser_compact_size(f: BytesIO):
|
||||||
|
view = f.getbuffer()
|
||||||
|
nbytes = view.nbytes;
|
||||||
|
view.release()
|
||||||
|
if (nbytes == 0):
|
||||||
|
return 0 # end of stream
|
||||||
|
|
||||||
|
nit = struct.unpack("<B", f.read(1))[0]
|
||||||
|
if nit == 253:
|
||||||
|
nit = struct.unpack("<H", f.read(2))[0]
|
||||||
|
elif nit == 254:
|
||||||
|
nit = struct.unpack("<I", f.read(4))[0]
|
||||||
|
elif nit == 255:
|
||||||
|
nit = struct.unpack("<Q", f.read(8))[0]
|
||||||
|
return nit
|
||||||
|
|
||||||
|
|
||||||
|
def deser_string(f: BytesIO):
|
||||||
|
nit = deser_compact_size(f)
|
||||||
|
return f.read(nit)
|
||||||
|
|
||||||
|
|
||||||
|
def deser_string_vector(f: BytesIO):
|
||||||
|
nit = deser_compact_size(f)
|
||||||
|
r = []
|
||||||
|
for _ in range(nit):
|
||||||
|
t = deser_string(f)
|
||||||
|
r.append(t)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class COutPoint:
|
||||||
|
__slots__ = ("hash", "n",)
|
||||||
|
|
||||||
|
def __init__(self, hash=b"", n=0,):
|
||||||
|
self.hash = hash
|
||||||
|
self.n = n
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
r = b""
|
||||||
|
r += self.hash
|
||||||
|
r += struct.pack("<I", self.n)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def deserialize(self, f):
|
||||||
|
self.hash = f.read(32)
|
||||||
|
self.n = struct.unpack("<I", f.read(4))[0]
|
||||||
|
|
||||||
|
|
||||||
|
class VinInfo:
|
||||||
|
__slots__ = ("outpoint", "scriptSig", "txinwitness", "prevout", "private_key")
|
||||||
|
|
||||||
|
def __init__(self, outpoint=None, scriptSig=b"", txinwitness=None, prevout=b"", private_key=None):
|
||||||
|
if outpoint is None:
|
||||||
|
self.outpoint = COutPoint()
|
||||||
|
else:
|
||||||
|
self.outpoint = outpoint
|
||||||
|
if txinwitness is None:
|
||||||
|
self.txinwitness = CTxInWitness()
|
||||||
|
else:
|
||||||
|
self.txinwitness = txinwitness
|
||||||
|
if private_key is None:
|
||||||
|
self.private_key = ECKey()
|
||||||
|
else:
|
||||||
|
self.private_key = private_key
|
||||||
|
self.scriptSig = scriptSig
|
||||||
|
self.prevout = prevout
|
||||||
|
|
||||||
|
|
||||||
|
class CScriptWitness:
|
||||||
|
__slots__ = ("stack",)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# stack is a vector of strings
|
||||||
|
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: BytesIO):
|
||||||
|
self.scriptWitness.stack = deser_string_vector(f)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def is_null(self):
|
||||||
|
return self.scriptWitness.is_null()
|
||||||
|
|
||||||
|
|
||||||
|
def hash160(s: Union[bytes, bytearray]) -> bytes:
|
||||||
|
return ripemd160(hashlib.sha256(s).digest())
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
342
bip-0352/reference.py
Executable file
342
bip-0352/reference.py
Executable file
|
@ -0,0 +1,342 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# For running the test vectors, run this script:
|
||||||
|
# ./reference.py send_and_receive_test_vectors.json
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from typing import List, Tuple, Dict, cast
|
||||||
|
from sys import argv, exit
|
||||||
|
from functools import reduce
|
||||||
|
from itertools import permutations
|
||||||
|
|
||||||
|
# local files
|
||||||
|
from bech32m import convertbits, bech32_encode, decode, Encoding
|
||||||
|
from secp256k1 import ECKey, ECPubKey, TaggedHash, NUMS_H
|
||||||
|
from bitcoin_utils import (
|
||||||
|
deser_txid,
|
||||||
|
from_hex,
|
||||||
|
hash160,
|
||||||
|
is_p2pkh,
|
||||||
|
is_p2sh,
|
||||||
|
is_p2wpkh,
|
||||||
|
is_p2tr,
|
||||||
|
ser_uint32,
|
||||||
|
COutPoint,
|
||||||
|
CTxInWitness,
|
||||||
|
VinInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pubkey_from_input(vin: VinInfo) -> ECPubKey:
|
||||||
|
if is_p2pkh(vin.prevout):
|
||||||
|
# skip the first 3 op_codes and grab the 20 byte hash
|
||||||
|
# from the scriptPubKey
|
||||||
|
spk_hash = vin.prevout[3:3 + 20]
|
||||||
|
for i in range(len(vin.scriptSig), 0, -1):
|
||||||
|
if i - 33 >= 0:
|
||||||
|
# starting from the back, we move over the scriptSig with a 33 byte
|
||||||
|
# window (to match a compressed pubkey). we hash this and check if it matches
|
||||||
|
# the 20 byte has from the scriptPubKey. for standard scriptSigs, this will match
|
||||||
|
# right away because the pubkey is the last item in the scriptSig.
|
||||||
|
# if its a non-standard (malleated) scriptSig, we will still find the pubkey if its
|
||||||
|
# a compressed pubkey.
|
||||||
|
#
|
||||||
|
# note: this is an incredibly inefficient implementation, for demonstration purposes only.
|
||||||
|
pubkey_bytes = vin.scriptSig[i - 33:i]
|
||||||
|
pubkey_hash = hash160(pubkey_bytes)
|
||||||
|
if pubkey_hash == spk_hash:
|
||||||
|
pubkey = ECPubKey().set(pubkey_bytes)
|
||||||
|
if (pubkey.valid) & (pubkey.compressed):
|
||||||
|
return pubkey
|
||||||
|
if is_p2sh(vin.prevout):
|
||||||
|
redeem_script = vin.scriptSig[1:]
|
||||||
|
if is_p2wpkh(redeem_script):
|
||||||
|
pubkey = ECPubKey().set(vin.txinwitness.scriptWitness.stack[-1])
|
||||||
|
if (pubkey.valid) & (pubkey.compressed):
|
||||||
|
return pubkey
|
||||||
|
if is_p2wpkh(vin.prevout):
|
||||||
|
txin = vin.txinwitness
|
||||||
|
pubkey = ECPubKey().set(txin.scriptWitness.stack[-1])
|
||||||
|
if (pubkey.valid) & (pubkey.compressed):
|
||||||
|
return pubkey
|
||||||
|
if is_p2tr(vin.prevout):
|
||||||
|
witnessStack = vin.txinwitness.scriptWitness.stack
|
||||||
|
if (len(witnessStack) >= 1):
|
||||||
|
if (len(witnessStack) > 1 and witnessStack[-1][0] == 0x50):
|
||||||
|
# Last item is annex
|
||||||
|
witnessStack.pop()
|
||||||
|
|
||||||
|
if (len(witnessStack) > 1):
|
||||||
|
# Script-path spend
|
||||||
|
control_block = witnessStack[-1]
|
||||||
|
# control block is <control byte> <32 byte internal key> and 0 or more <32 byte hash>
|
||||||
|
internal_key = control_block[1:33]
|
||||||
|
if (internal_key == NUMS_H.to_bytes(32, 'big')):
|
||||||
|
# Skip if NUMS_H
|
||||||
|
return ECPubKey()
|
||||||
|
|
||||||
|
pubkey = ECPubKey().set(vin.prevout[2:])
|
||||||
|
if (pubkey.valid) & (pubkey.compressed):
|
||||||
|
return pubkey
|
||||||
|
|
||||||
|
|
||||||
|
return ECPubKey()
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_hash(outpoints: List[COutPoint], sum_input_pubkeys: ECPubKey) -> bytes:
|
||||||
|
lowest_outpoint = sorted(outpoints, key=lambda outpoint: outpoint.serialize())[0]
|
||||||
|
return TaggedHash("BIP0352/Inputs", lowest_outpoint.serialize() + cast(bytes, sum_input_pubkeys.get_bytes(False)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def encode_silent_payment_address(B_scan: ECPubKey, B_m: ECPubKey, hrp: str = "tsp", version: int = 0) -> str:
|
||||||
|
data = convertbits(cast(bytes, B_scan.get_bytes(False)) + cast(bytes, B_m.get_bytes(False)), 8, 5)
|
||||||
|
return bech32_encode(hrp, [version] + cast(List[int], data), Encoding.BECH32M)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_label(b_scan: ECKey, m: int) -> bytes:
|
||||||
|
return TaggedHash("BIP0352/Label", b_scan.get_bytes() + ser_uint32(m))
|
||||||
|
|
||||||
|
|
||||||
|
def create_labeled_silent_payment_address(b_scan: ECKey, B_spend: ECPubKey, m: int, hrp: str = "tsp", version: int = 0) -> str:
|
||||||
|
G = ECKey().set(1).get_pubkey()
|
||||||
|
B_scan = b_scan.get_pubkey()
|
||||||
|
B_m = B_spend + generate_label(b_scan, m) * G
|
||||||
|
labeled_address = encode_silent_payment_address(B_scan, B_m, hrp, version)
|
||||||
|
|
||||||
|
return labeled_address
|
||||||
|
|
||||||
|
|
||||||
|
def decode_silent_payment_address(address: str, hrp: str = "tsp") -> Tuple[ECPubKey, ECPubKey]:
|
||||||
|
_, data = decode(hrp, address)
|
||||||
|
if data is None:
|
||||||
|
return ECPubKey(), ECPubKey()
|
||||||
|
B_scan = ECPubKey().set(data[:33])
|
||||||
|
B_spend = ECPubKey().set(data[33:])
|
||||||
|
|
||||||
|
return B_scan, B_spend
|
||||||
|
|
||||||
|
|
||||||
|
def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], outpoints: List[COutPoint], recipients: List[str], hrp="tsp") -> List[str]:
|
||||||
|
G = ECKey().set(1).get_pubkey()
|
||||||
|
negated_keys = []
|
||||||
|
for key, is_xonly in input_priv_keys:
|
||||||
|
k = ECKey().set(key.get_bytes())
|
||||||
|
if is_xonly and k.get_pubkey().get_y() % 2 != 0:
|
||||||
|
k.negate()
|
||||||
|
negated_keys.append(k)
|
||||||
|
|
||||||
|
a_sum = sum(negated_keys)
|
||||||
|
if not a_sum.valid:
|
||||||
|
# Input privkeys sum is zero -> fail
|
||||||
|
return []
|
||||||
|
input_hash = get_input_hash(outpoints, a_sum * G)
|
||||||
|
silent_payment_groups: Dict[ECPubKey, List[ECPubKey]] = {}
|
||||||
|
for recipient in recipients:
|
||||||
|
B_scan, B_m = decode_silent_payment_address(recipient, hrp=hrp)
|
||||||
|
if B_scan in silent_payment_groups:
|
||||||
|
silent_payment_groups[B_scan].append(B_m)
|
||||||
|
else:
|
||||||
|
silent_payment_groups[B_scan] = [B_m]
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
for B_scan, B_m_values in silent_payment_groups.items():
|
||||||
|
ecdh_shared_secret = input_hash * a_sum * B_scan
|
||||||
|
k = 0
|
||||||
|
for B_m in B_m_values:
|
||||||
|
t_k = TaggedHash("BIP0352/SharedSecret", ecdh_shared_secret.get_bytes(False) + ser_uint32(k))
|
||||||
|
P_km = B_m + t_k * G
|
||||||
|
outputs.append(P_km.get_bytes().hex())
|
||||||
|
k += 1
|
||||||
|
|
||||||
|
return list(set(outputs))
|
||||||
|
|
||||||
|
|
||||||
|
def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: bytes, outputs_to_check: List[ECPubKey], labels: Dict[str, str] = {}) -> List[Dict[str, str]]:
|
||||||
|
G = ECKey().set(1).get_pubkey()
|
||||||
|
ecdh_shared_secret = input_hash * b_scan * A_sum
|
||||||
|
k = 0
|
||||||
|
wallet = []
|
||||||
|
while True:
|
||||||
|
t_k = TaggedHash("BIP0352/SharedSecret", ecdh_shared_secret.get_bytes(False) + ser_uint32(k))
|
||||||
|
P_k = B_spend + t_k * G
|
||||||
|
for output in outputs_to_check:
|
||||||
|
if P_k == output:
|
||||||
|
wallet.append({"pub_key": P_k.get_bytes().hex(), "priv_key_tweak": t_k.hex()})
|
||||||
|
outputs_to_check.remove(output)
|
||||||
|
k += 1
|
||||||
|
break
|
||||||
|
elif labels:
|
||||||
|
m_G_sub = output - P_k
|
||||||
|
if m_G_sub.get_bytes(False).hex() in labels:
|
||||||
|
P_km = P_k + m_G_sub
|
||||||
|
wallet.append({
|
||||||
|
"pub_key": P_km.get_bytes().hex(),
|
||||||
|
"priv_key_tweak": (ECKey().set(t_k).add(
|
||||||
|
bytes.fromhex(labels[m_G_sub.get_bytes(False).hex()])
|
||||||
|
)).get_bytes().hex(),
|
||||||
|
})
|
||||||
|
outputs_to_check.remove(output)
|
||||||
|
k += 1
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
output.negate()
|
||||||
|
m_G_sub = output - P_k
|
||||||
|
if m_G_sub.get_bytes(False).hex() in labels:
|
||||||
|
P_km = P_k + m_G_sub
|
||||||
|
wallet.append({
|
||||||
|
"pub_key": P_km.get_bytes().hex(),
|
||||||
|
"priv_key_tweak": (ECKey().set(t_k).add(
|
||||||
|
bytes.fromhex(labels[m_G_sub.get_bytes(False).hex()])
|
||||||
|
)).get_bytes().hex(),
|
||||||
|
})
|
||||||
|
outputs_to_check.remove(output)
|
||||||
|
k += 1
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return wallet
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(argv) != 2 or argv[1] in ('-h', '--help'):
|
||||||
|
print("Usage: ./reference.py send_and_receive_test_vectors.json")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
with open(argv[1], "r") as f:
|
||||||
|
test_data = json.loads(f.read())
|
||||||
|
|
||||||
|
# G , needed for generating the labels "database"
|
||||||
|
G = ECKey().set(1).get_pubkey()
|
||||||
|
for case in test_data:
|
||||||
|
print(case["comment"])
|
||||||
|
# Test sending
|
||||||
|
for sending_test in case["sending"]:
|
||||||
|
given = sending_test["given"]
|
||||||
|
expected = sending_test["expected"]
|
||||||
|
|
||||||
|
vins = [
|
||||||
|
VinInfo(
|
||||||
|
outpoint=COutPoint(hash=deser_txid(input["txid"]), n=input["vout"]),
|
||||||
|
scriptSig=bytes.fromhex(input["scriptSig"]),
|
||||||
|
txinwitness=CTxInWitness().deserialize(from_hex(input["txinwitness"])),
|
||||||
|
prevout=bytes.fromhex(input["prevout"]["scriptPubKey"]["hex"]),
|
||||||
|
private_key=ECKey().set(bytes.fromhex(input["private_key"])),
|
||||||
|
)
|
||||||
|
for input in given["vin"]
|
||||||
|
]
|
||||||
|
# Convert the tuples to lists so they can be easily compared to the json list of lists from the given test vectors
|
||||||
|
input_priv_keys = []
|
||||||
|
input_pub_keys = []
|
||||||
|
for vin in vins:
|
||||||
|
pubkey = get_pubkey_from_input(vin)
|
||||||
|
if not pubkey.valid:
|
||||||
|
continue
|
||||||
|
input_priv_keys.append((
|
||||||
|
vin.private_key,
|
||||||
|
is_p2tr(vin.prevout),
|
||||||
|
))
|
||||||
|
input_pub_keys.append(pubkey)
|
||||||
|
|
||||||
|
sending_outputs = []
|
||||||
|
if (len(input_pub_keys) > 0):
|
||||||
|
outpoints = [vin.outpoint for vin in vins]
|
||||||
|
sending_outputs = create_outputs(input_priv_keys, outpoints, given["recipients"], hrp="sp")
|
||||||
|
|
||||||
|
# Note: order doesn't matter for creating/finding the outputs. However, different orderings of the recipient addresses
|
||||||
|
# will produce different generated outputs if sending to multiple silent payment addresses belonging to the
|
||||||
|
# same sender but with different labels. Because of this, expected["outputs"] contains all possible valid output sets,
|
||||||
|
# based on all possible permutations of recipient address orderings. Must match exactly one of the possible output sets.
|
||||||
|
assert(any(set(sending_outputs) == set(lst) for lst in expected["outputs"])), "Sending test failed"
|
||||||
|
else:
|
||||||
|
assert(sending_outputs == expected["outputs"][0] == []), "Sending test failed"
|
||||||
|
|
||||||
|
# Test receiving
|
||||||
|
msg = hashlib.sha256(b"message").digest()
|
||||||
|
aux = hashlib.sha256(b"random auxiliary data").digest()
|
||||||
|
for receiving_test in case["receiving"]:
|
||||||
|
given = receiving_test["given"]
|
||||||
|
expected = receiving_test["expected"]
|
||||||
|
outputs_to_check = [
|
||||||
|
ECPubKey().set(bytes.fromhex(p)) for p in given["outputs"]
|
||||||
|
]
|
||||||
|
vins = [
|
||||||
|
VinInfo(
|
||||||
|
outpoint=COutPoint(hash=deser_txid(input["txid"]), n=input["vout"]),
|
||||||
|
scriptSig=bytes.fromhex(input["scriptSig"]),
|
||||||
|
txinwitness=CTxInWitness().deserialize(from_hex(input["txinwitness"])),
|
||||||
|
prevout=bytes.fromhex(input["prevout"]["scriptPubKey"]["hex"]),
|
||||||
|
)
|
||||||
|
for input in given["vin"]
|
||||||
|
]
|
||||||
|
# Check that the given inputs for the receiving test match what was generated during the sending test
|
||||||
|
receiving_addresses = []
|
||||||
|
b_scan = ECKey().set(bytes.fromhex(given["key_material"]["scan_priv_key"]))
|
||||||
|
b_spend = ECKey().set(
|
||||||
|
bytes.fromhex(given["key_material"]["spend_priv_key"])
|
||||||
|
)
|
||||||
|
B_scan = b_scan.get_pubkey()
|
||||||
|
B_spend = b_spend.get_pubkey()
|
||||||
|
receiving_addresses.append(
|
||||||
|
encode_silent_payment_address(B_scan, B_spend, hrp="sp")
|
||||||
|
)
|
||||||
|
if given["labels"]:
|
||||||
|
for label in given["labels"]:
|
||||||
|
receiving_addresses.append(
|
||||||
|
create_labeled_silent_payment_address(
|
||||||
|
b_scan, B_spend, m=label, hrp="sp"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that the silent payment addresses match for the given BIP32 seed and labels dictionary
|
||||||
|
assert (receiving_addresses == expected["addresses"]), "Receiving addresses don't match"
|
||||||
|
input_pub_keys = []
|
||||||
|
for vin in vins:
|
||||||
|
pubkey = get_pubkey_from_input(vin)
|
||||||
|
if not pubkey.valid:
|
||||||
|
continue
|
||||||
|
input_pub_keys.append(pubkey)
|
||||||
|
|
||||||
|
add_to_wallet = []
|
||||||
|
if (len(input_pub_keys) > 0):
|
||||||
|
A_sum = reduce(lambda x, y: x + y, input_pub_keys)
|
||||||
|
if A_sum.get_bytes() is None:
|
||||||
|
# Input pubkeys sum is point at infinity -> skip tx
|
||||||
|
assert expected["outputs"] == []
|
||||||
|
continue
|
||||||
|
input_hash = get_input_hash([vin.outpoint for vin in vins], A_sum)
|
||||||
|
pre_computed_labels = {
|
||||||
|
(generate_label(b_scan, label) * G).get_bytes(False).hex(): generate_label(b_scan, label).hex()
|
||||||
|
for label in given["labels"]
|
||||||
|
}
|
||||||
|
add_to_wallet = scanning(
|
||||||
|
b_scan=b_scan,
|
||||||
|
B_spend=B_spend,
|
||||||
|
A_sum=A_sum,
|
||||||
|
input_hash=input_hash,
|
||||||
|
outputs_to_check=outputs_to_check,
|
||||||
|
labels=pre_computed_labels,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that the private key is correct for the found output public key
|
||||||
|
for output in add_to_wallet:
|
||||||
|
pub_key = ECPubKey().set(bytes.fromhex(output["pub_key"]))
|
||||||
|
full_private_key = b_spend.add(bytes.fromhex(output["priv_key_tweak"]))
|
||||||
|
if full_private_key.get_pubkey().get_y() % 2 != 0:
|
||||||
|
full_private_key.negate()
|
||||||
|
|
||||||
|
sig = full_private_key.sign_schnorr(msg, aux)
|
||||||
|
assert pub_key.verify_schnorr(sig, msg), f"Invalid signature for {pub_key}"
|
||||||
|
output["signature"] = sig.hex()
|
||||||
|
|
||||||
|
# Note: order doesn't matter for creating/finding the outputs. However, different orderings of the recipient addresses
|
||||||
|
# will produce different generated outputs if sending to multiple silent payment addresses belonging to the
|
||||||
|
# same sender but with different labels. Because of this, expected["outputs"] contains all possible valid output sets,
|
||||||
|
# based on all possible permutations of recipient address orderings. Must match exactly one of the possible found output
|
||||||
|
# sets in expected["outputs"]
|
||||||
|
generated_set = {frozenset(d.items()) for d in add_to_wallet}
|
||||||
|
expected_set = {frozenset(d.items()) for d in expected["outputs"]}
|
||||||
|
assert generated_set == expected_set, "Receive test failed"
|
||||||
|
|
||||||
|
|
||||||
|
print("All tests passed")
|
130
bip-0352/ripemd160.py
Normal file
130
bip-0352/ripemd160.py
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
# Copyright (c) 2021 Pieter Wuille
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Test-only pure Python RIPEMD160 implementation."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# Message schedule indexes for the left path.
|
||||||
|
ML = [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
|
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
|
||||||
|
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
|
||||||
|
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
|
||||||
|
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
|
||||||
|
]
|
||||||
|
|
||||||
|
# Message schedule indexes for the right path.
|
||||||
|
MR = [
|
||||||
|
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
|
||||||
|
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
|
||||||
|
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
|
||||||
|
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
|
||||||
|
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# Rotation counts for the left path.
|
||||||
|
RL = [
|
||||||
|
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
|
||||||
|
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
|
||||||
|
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
|
||||||
|
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
|
||||||
|
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
|
||||||
|
]
|
||||||
|
|
||||||
|
# Rotation counts for the right path.
|
||||||
|
RR = [
|
||||||
|
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
|
||||||
|
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
|
||||||
|
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
|
||||||
|
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
|
||||||
|
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# K constants for the left path.
|
||||||
|
KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]
|
||||||
|
|
||||||
|
# K constants for the right path.
|
||||||
|
KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0]
|
||||||
|
|
||||||
|
|
||||||
|
def fi(x, y, z, i):
|
||||||
|
"""The f1, f2, f3, f4, and f5 functions from the specification."""
|
||||||
|
if i == 0:
|
||||||
|
return x ^ y ^ z
|
||||||
|
elif i == 1:
|
||||||
|
return (x & y) | (~x & z)
|
||||||
|
elif i == 2:
|
||||||
|
return (x | ~y) ^ z
|
||||||
|
elif i == 3:
|
||||||
|
return (x & z) | (y & ~z)
|
||||||
|
elif i == 4:
|
||||||
|
return x ^ (y | ~z)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def rol(x, i):
|
||||||
|
"""Rotate the bottom 32 bits of x left by i bits."""
|
||||||
|
return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff
|
||||||
|
|
||||||
|
|
||||||
|
def compress(h0, h1, h2, h3, h4, block):
|
||||||
|
"""Compress state (h0, h1, h2, h3, h4) with block."""
|
||||||
|
# Left path variables.
|
||||||
|
al, bl, cl, dl, el = h0, h1, h2, h3, h4
|
||||||
|
# Right path variables.
|
||||||
|
ar, br, cr, dr, er = h0, h1, h2, h3, h4
|
||||||
|
# Message variables.
|
||||||
|
x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)]
|
||||||
|
|
||||||
|
# Iterate over the 80 rounds of the compression.
|
||||||
|
for j in range(80):
|
||||||
|
rnd = j >> 4
|
||||||
|
# Perform left side of the transformation.
|
||||||
|
al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el
|
||||||
|
al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl
|
||||||
|
# Perform right side of the transformation.
|
||||||
|
ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er
|
||||||
|
ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr
|
||||||
|
|
||||||
|
# Compose old state, left transform, and right transform into new state.
|
||||||
|
return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr
|
||||||
|
|
||||||
|
|
||||||
|
def ripemd160(data):
|
||||||
|
"""Compute the RIPEMD-160 hash of data."""
|
||||||
|
# Initialize state.
|
||||||
|
state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
|
||||||
|
# Process full 64-byte blocks in the input.
|
||||||
|
for b in range(len(data) >> 6):
|
||||||
|
state = compress(*state, data[64*b:64*(b+1)])
|
||||||
|
# Construct final blocks (with padding and size).
|
||||||
|
pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
|
||||||
|
fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
|
||||||
|
# Process final blocks.
|
||||||
|
for b in range(len(fin) >> 6):
|
||||||
|
state = compress(*state, fin[64*b:64*(b+1)])
|
||||||
|
# Produce output.
|
||||||
|
return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFrameworkKey(unittest.TestCase):
|
||||||
|
def test_ripemd160(self):
|
||||||
|
"""RIPEMD-160 test vectors."""
|
||||||
|
# See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
|
||||||
|
for msg, hexout in [
|
||||||
|
(b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"),
|
||||||
|
(b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"),
|
||||||
|
(b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"),
|
||||||
|
(b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"),
|
||||||
|
(b"abcdefghijklmnopqrstuvwxyz",
|
||||||
|
"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"),
|
||||||
|
(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
||||||
|
"12a053384a9c0c88e405a06c27dcf49ada62eb2b"),
|
||||||
|
(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||||
|
"b0e20b6e3116640286ed3a87a5713079b21f5189"),
|
||||||
|
(b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"),
|
||||||
|
(b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528")
|
||||||
|
]:
|
||||||
|
self.assertEqual(ripemd160(msg).hex(), hexout)
|
BIN
bip-0352/scan_data_downloader_per_month.png
Normal file
BIN
bip-0352/scan_data_downloader_per_month.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 166 KiB |
696
bip-0352/secp256k1.py
Normal file
696
bip-0352/secp256k1.py
Normal file
|
@ -0,0 +1,696 @@
|
||||||
|
# Copyright (c) 2019 Pieter Wuille
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Test-only secp256k1 elliptic curve implementation
|
||||||
|
|
||||||
|
WARNING: This code is slow, uses bad randomness, does not properly protect
|
||||||
|
keys, and is trivially vulnerable to side channel attacks. Do not use for
|
||||||
|
anything but tests."""
|
||||||
|
import random
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
|
||||||
|
def TaggedHash(tag, data):
|
||||||
|
ss = hashlib.sha256(tag.encode('utf-8')).digest()
|
||||||
|
ss += ss
|
||||||
|
ss += data
|
||||||
|
return hashlib.sha256(ss).digest()
|
||||||
|
|
||||||
|
def modinv(a, n):
|
||||||
|
"""Compute the modular inverse of a modulo n
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
|
||||||
|
"""
|
||||||
|
t1, t2 = 0, 1
|
||||||
|
r1, r2 = n, a
|
||||||
|
while r2 != 0:
|
||||||
|
q = r1 // r2
|
||||||
|
t1, t2 = t2, t1 - q * t2
|
||||||
|
r1, r2 = r2, r1 - q * r2
|
||||||
|
if r1 > 1:
|
||||||
|
return None
|
||||||
|
if t1 < 0:
|
||||||
|
t1 += n
|
||||||
|
return t1
|
||||||
|
|
||||||
|
def jacobi_symbol(n, k):
|
||||||
|
"""Compute the Jacobi symbol of n modulo k
|
||||||
|
|
||||||
|
See http://en.wikipedia.org/wiki/Jacobi_symbol
|
||||||
|
|
||||||
|
For our application k is always prime, so this is the same as the Legendre symbol."""
|
||||||
|
assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k"
|
||||||
|
n %= k
|
||||||
|
t = 0
|
||||||
|
while n != 0:
|
||||||
|
while n & 1 == 0:
|
||||||
|
n >>= 1
|
||||||
|
r = k & 7
|
||||||
|
t ^= (r == 3 or r == 5)
|
||||||
|
n, k = k, n
|
||||||
|
t ^= (n & k & 3 == 3)
|
||||||
|
n = n % k
|
||||||
|
if k == 1:
|
||||||
|
return -1 if t else 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def modsqrt(a, p):
|
||||||
|
"""Compute the square root of a modulo p when p % 4 = 3.
|
||||||
|
|
||||||
|
The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm
|
||||||
|
|
||||||
|
Limiting this function to only work for p % 4 = 3 means we don't need to
|
||||||
|
iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd
|
||||||
|
is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4)
|
||||||
|
|
||||||
|
secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4.
|
||||||
|
"""
|
||||||
|
if p % 4 != 3:
|
||||||
|
raise NotImplementedError("modsqrt only implemented for p % 4 = 3")
|
||||||
|
sqrt = pow(a, (p + 1)//4, p)
|
||||||
|
if pow(sqrt, 2, p) == a % p:
|
||||||
|
return sqrt
|
||||||
|
return None
|
||||||
|
|
||||||
|
def int_or_bytes(s):
|
||||||
|
"Convert 32-bytes to int while accepting also int and returning it as is."
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
assert(len(s) == 32)
|
||||||
|
s = int.from_bytes(s, 'big')
|
||||||
|
elif not isinstance(s, int):
|
||||||
|
raise TypeError
|
||||||
|
return s
|
||||||
|
|
||||||
|
class EllipticCurve:
|
||||||
|
def __init__(self, p, a, b):
|
||||||
|
"""Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
|
||||||
|
self.p = p
|
||||||
|
self.a = a % p
|
||||||
|
self.b = b % p
|
||||||
|
|
||||||
|
def affine(self, p1):
|
||||||
|
"""Convert a Jacobian point tuple p1 to affine form, or None if at infinity.
|
||||||
|
|
||||||
|
An affine point is represented as the Jacobian (x, y, 1)"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
if z1 == 0:
|
||||||
|
return None
|
||||||
|
inv = modinv(z1, self.p)
|
||||||
|
inv_2 = (inv**2) % self.p
|
||||||
|
inv_3 = (inv_2 * inv) % self.p
|
||||||
|
return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
|
||||||
|
|
||||||
|
def has_even_y(self, p1):
|
||||||
|
"""Whether the point p1 has an even Y coordinate when expressed in affine coordinates."""
|
||||||
|
return not (p1[2] == 0 or self.affine(p1)[1] & 1)
|
||||||
|
|
||||||
|
def negate(self, p1):
|
||||||
|
"""Negate a Jacobian point tuple p1."""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
return (x1, (self.p - y1) % self.p, z1)
|
||||||
|
|
||||||
|
def on_curve(self, p1):
|
||||||
|
"""Determine whether a Jacobian tuple p is on the curve (and not infinity)"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
z2 = pow(z1, 2, self.p)
|
||||||
|
z4 = pow(z2, 2, self.p)
|
||||||
|
return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0
|
||||||
|
|
||||||
|
def is_x_coord(self, x):
|
||||||
|
"""Test whether x is a valid X coordinate on the curve."""
|
||||||
|
x_3 = pow(x, 3, self.p)
|
||||||
|
return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
|
||||||
|
|
||||||
|
def lift_x(self, x):
|
||||||
|
"""Given an X coordinate on the curve, return a corresponding affine point."""
|
||||||
|
x_3 = pow(x, 3, self.p)
|
||||||
|
v = x_3 + self.a * x + self.b
|
||||||
|
y = modsqrt(v, self.p)
|
||||||
|
if y is None:
|
||||||
|
return None
|
||||||
|
return (x, y, 1)
|
||||||
|
|
||||||
|
def double(self, p1):
|
||||||
|
"""Double a Jacobian tuple p1
|
||||||
|
|
||||||
|
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
if z1 == 0:
|
||||||
|
return (0, 1, 0)
|
||||||
|
y1_2 = (y1**2) % self.p
|
||||||
|
y1_4 = (y1_2**2) % self.p
|
||||||
|
x1_2 = (x1**2) % self.p
|
||||||
|
s = (4*x1*y1_2) % self.p
|
||||||
|
m = 3*x1_2
|
||||||
|
if self.a:
|
||||||
|
m += self.a * pow(z1, 4, self.p)
|
||||||
|
m = m % self.p
|
||||||
|
x2 = (m**2 - 2*s) % self.p
|
||||||
|
y2 = (m*(s - x2) - 8*y1_4) % self.p
|
||||||
|
z2 = (2*y1*z1) % self.p
|
||||||
|
return (x2, y2, z2)
|
||||||
|
|
||||||
|
def add_mixed(self, p1, p2):
|
||||||
|
"""Add a Jacobian tuple p1 and an affine tuple p2
|
||||||
|
|
||||||
|
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
x2, y2, z2 = p2
|
||||||
|
assert(z2 == 1)
|
||||||
|
# Adding to the point at infinity is a no-op
|
||||||
|
if z1 == 0:
|
||||||
|
return p2
|
||||||
|
z1_2 = (z1**2) % self.p
|
||||||
|
z1_3 = (z1_2 * z1) % self.p
|
||||||
|
u2 = (x2 * z1_2) % self.p
|
||||||
|
s2 = (y2 * z1_3) % self.p
|
||||||
|
if x1 == u2:
|
||||||
|
if (y1 != s2):
|
||||||
|
# p1 and p2 are inverses. Return the point at infinity.
|
||||||
|
return (0, 1, 0)
|
||||||
|
# p1 == p2. The formulas below fail when the two points are equal.
|
||||||
|
return self.double(p1)
|
||||||
|
h = u2 - x1
|
||||||
|
r = s2 - y1
|
||||||
|
h_2 = (h**2) % self.p
|
||||||
|
h_3 = (h_2 * h) % self.p
|
||||||
|
u1_h_2 = (x1 * h_2) % self.p
|
||||||
|
x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
|
||||||
|
y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p
|
||||||
|
z3 = (h*z1) % self.p
|
||||||
|
return (x3, y3, z3)
|
||||||
|
|
||||||
|
def add(self, p1, p2):
|
||||||
|
"""Add two Jacobian tuples p1 and p2
|
||||||
|
|
||||||
|
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition"""
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
x2, y2, z2 = p2
|
||||||
|
# Adding the point at infinity is a no-op
|
||||||
|
if z1 == 0:
|
||||||
|
return p2
|
||||||
|
if z2 == 0:
|
||||||
|
return p1
|
||||||
|
# Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1
|
||||||
|
if z1 == 1:
|
||||||
|
return self.add_mixed(p2, p1)
|
||||||
|
if z2 == 1:
|
||||||
|
return self.add_mixed(p1, p2)
|
||||||
|
z1_2 = (z1**2) % self.p
|
||||||
|
z1_3 = (z1_2 * z1) % self.p
|
||||||
|
z2_2 = (z2**2) % self.p
|
||||||
|
z2_3 = (z2_2 * z2) % self.p
|
||||||
|
u1 = (x1 * z2_2) % self.p
|
||||||
|
u2 = (x2 * z1_2) % self.p
|
||||||
|
s1 = (y1 * z2_3) % self.p
|
||||||
|
s2 = (y2 * z1_3) % self.p
|
||||||
|
if u1 == u2:
|
||||||
|
if (s1 != s2):
|
||||||
|
# p1 and p2 are inverses. Return the point at infinity.
|
||||||
|
return (0, 1, 0)
|
||||||
|
# p1 == p2. The formulas below fail when the two points are equal.
|
||||||
|
return self.double(p1)
|
||||||
|
h = u2 - u1
|
||||||
|
r = s2 - s1
|
||||||
|
h_2 = (h**2) % self.p
|
||||||
|
h_3 = (h_2 * h) % self.p
|
||||||
|
u1_h_2 = (u1 * h_2) % self.p
|
||||||
|
x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
|
||||||
|
y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p
|
||||||
|
z3 = (h*z1*z2) % self.p
|
||||||
|
return (x3, y3, z3)
|
||||||
|
|
||||||
|
def mul(self, ps):
|
||||||
|
"""Compute a (multi) point multiplication
|
||||||
|
|
||||||
|
ps is a list of (Jacobian tuple, scalar) pairs.
|
||||||
|
"""
|
||||||
|
r = (0, 1, 0)
|
||||||
|
for i in range(255, -1, -1):
|
||||||
|
r = self.double(r)
|
||||||
|
for (p, n) in ps:
|
||||||
|
if ((n >> i) & 1):
|
||||||
|
r = self.add(r, p)
|
||||||
|
return r
|
||||||
|
|
||||||
|
SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977
|
||||||
|
SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7)
|
||||||
|
SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
|
||||||
|
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||||
|
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
|
||||||
|
NUMS_H = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
|
||||||
|
|
||||||
|
class ECPubKey():
|
||||||
|
"""A secp256k1 public key"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Construct an uninitialized public key"""
|
||||||
|
self.valid = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.get_bytes().hex()
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
assert isinstance(other, ECPubKey)
|
||||||
|
return self.get_bytes() == other.get_bytes()
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.get_bytes())
|
||||||
|
|
||||||
|
def set(self, data):
|
||||||
|
"""Construct a public key from a serialization in compressed or uncompressed DER format or BIP340 format"""
|
||||||
|
if (len(data) == 65 and data[0] == 0x04):
|
||||||
|
p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1)
|
||||||
|
self.valid = SECP256K1.on_curve(p)
|
||||||
|
if self.valid:
|
||||||
|
self.p = p
|
||||||
|
self.compressed = False
|
||||||
|
elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)):
|
||||||
|
x = int.from_bytes(data[1:33], 'big')
|
||||||
|
if SECP256K1.is_x_coord(x):
|
||||||
|
p = SECP256K1.lift_x(x)
|
||||||
|
# if the oddness of the y co-ord isn't correct, find the other
|
||||||
|
# valid y
|
||||||
|
if (p[1] & 1) != (data[0] & 1):
|
||||||
|
p = SECP256K1.negate(p)
|
||||||
|
self.p = p
|
||||||
|
self.valid = True
|
||||||
|
self.compressed = True
|
||||||
|
else:
|
||||||
|
self.valid = False
|
||||||
|
elif (len(data) == 32):
|
||||||
|
x = int.from_bytes(data[0:32], 'big')
|
||||||
|
if SECP256K1.is_x_coord(x):
|
||||||
|
p = SECP256K1.lift_x(x)
|
||||||
|
# if the oddness of the y co-ord isn't correct, find the other
|
||||||
|
# valid y
|
||||||
|
if p[1]%2 != 0:
|
||||||
|
p = SECP256K1.negate(p)
|
||||||
|
self.p = p
|
||||||
|
self.valid = True
|
||||||
|
self.compressed = True
|
||||||
|
else:
|
||||||
|
self.valid = False
|
||||||
|
else:
|
||||||
|
self.valid = False
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_compressed(self):
|
||||||
|
return self.compressed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
return self.valid
|
||||||
|
|
||||||
|
def get_y(self):
|
||||||
|
return SECP256K1.affine(self.p)[1]
|
||||||
|
|
||||||
|
def get_x(self):
|
||||||
|
return SECP256K1.affine(self.p)[0]
|
||||||
|
|
||||||
|
def get_bytes(self, bip340=True):
|
||||||
|
assert(self.valid)
|
||||||
|
p = SECP256K1.affine(self.p)
|
||||||
|
if p is None:
|
||||||
|
return None
|
||||||
|
if bip340:
|
||||||
|
return bytes(p[0].to_bytes(32, 'big'))
|
||||||
|
elif self.compressed:
|
||||||
|
return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big')
|
||||||
|
else:
|
||||||
|
return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big')
|
||||||
|
|
||||||
|
def verify_ecdsa(self, sig, msg, low_s=True):
|
||||||
|
"""Verify a strictly DER-encoded ECDSA signature against this pubkey.
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
|
||||||
|
ECDSA verifier algorithm"""
|
||||||
|
assert(self.valid)
|
||||||
|
|
||||||
|
# Extract r and s from the DER formatted signature. Return false for
|
||||||
|
# any DER encoding errors.
|
||||||
|
if (sig[1] + 2 != len(sig)):
|
||||||
|
return False
|
||||||
|
if (len(sig) < 4):
|
||||||
|
return False
|
||||||
|
if (sig[0] != 0x30):
|
||||||
|
return False
|
||||||
|
if (sig[2] != 0x02):
|
||||||
|
return False
|
||||||
|
rlen = sig[3]
|
||||||
|
if (len(sig) < 6 + rlen):
|
||||||
|
return False
|
||||||
|
if rlen < 1 or rlen > 33:
|
||||||
|
return False
|
||||||
|
if sig[4] >= 0x80:
|
||||||
|
return False
|
||||||
|
if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)):
|
||||||
|
return False
|
||||||
|
r = int.from_bytes(sig[4:4+rlen], 'big')
|
||||||
|
if (sig[4+rlen] != 0x02):
|
||||||
|
return False
|
||||||
|
slen = sig[5+rlen]
|
||||||
|
if slen < 1 or slen > 33:
|
||||||
|
return False
|
||||||
|
if (len(sig) != 6 + rlen + slen):
|
||||||
|
return False
|
||||||
|
if sig[6+rlen] >= 0x80:
|
||||||
|
return False
|
||||||
|
if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)):
|
||||||
|
return False
|
||||||
|
s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big')
|
||||||
|
|
||||||
|
# Verify that r and s are within the group order
|
||||||
|
if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER:
|
||||||
|
return False
|
||||||
|
if low_s and s >= SECP256K1_ORDER_HALF:
|
||||||
|
return False
|
||||||
|
z = int.from_bytes(msg, 'big')
|
||||||
|
|
||||||
|
# Run verifier algorithm on r, s
|
||||||
|
w = modinv(s, SECP256K1_ORDER)
|
||||||
|
u1 = z*w % SECP256K1_ORDER
|
||||||
|
u2 = r*w % SECP256K1_ORDER
|
||||||
|
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)]))
|
||||||
|
if R is None or R[0] != r:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def verify_schnorr(self, sig, msg):
|
||||||
|
assert(len(msg) == 32)
|
||||||
|
assert(len(sig) == 64)
|
||||||
|
assert(self.valid)
|
||||||
|
r = int.from_bytes(sig[0:32], 'big')
|
||||||
|
if r >= SECP256K1_FIELD_SIZE:
|
||||||
|
return False
|
||||||
|
s = int.from_bytes(sig[32:64], 'big')
|
||||||
|
if s >= SECP256K1_ORDER:
|
||||||
|
return False
|
||||||
|
e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + self.get_bytes() + msg), 'big') % SECP256K1_ORDER
|
||||||
|
R = SECP256K1.mul([(SECP256K1_G, s), (self.p, SECP256K1_ORDER - e)])
|
||||||
|
if not SECP256K1.has_even_y(R):
|
||||||
|
return False
|
||||||
|
if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
"""Adds two ECPubKey points."""
|
||||||
|
assert isinstance(other, ECPubKey)
|
||||||
|
assert self.valid
|
||||||
|
assert other.valid
|
||||||
|
ret = ECPubKey()
|
||||||
|
ret.p = SECP256K1.add(other.p, self.p)
|
||||||
|
ret.valid = True
|
||||||
|
ret.compressed = self.compressed
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
"""Allows this ECPubKey to be added to 0 for sum()"""
|
||||||
|
if other == 0:
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return self + other
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
"""Multiplies ECPubKey point with a scalar(int/32bytes/ECKey)."""
|
||||||
|
if isinstance(other, ECKey):
|
||||||
|
assert self.valid
|
||||||
|
assert other.secret is not None
|
||||||
|
multiplier = other.secret
|
||||||
|
else:
|
||||||
|
# int_or_bytes checks that other is `int` or `bytes`
|
||||||
|
multiplier = int_or_bytes(other)
|
||||||
|
|
||||||
|
assert multiplier < SECP256K1_ORDER
|
||||||
|
multiplier = multiplier % SECP256K1_ORDER
|
||||||
|
ret = ECPubKey()
|
||||||
|
ret.p = SECP256K1.mul([(self.p, multiplier)])
|
||||||
|
ret.valid = True
|
||||||
|
ret.compressed = self.compressed
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
"""Multiplies a scalar(int/32bytes/ECKey) with an ECPubKey point"""
|
||||||
|
return self * other
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
"""Subtract one point from another"""
|
||||||
|
assert isinstance(other, ECPubKey)
|
||||||
|
assert self.valid
|
||||||
|
assert other.valid
|
||||||
|
ret = ECPubKey()
|
||||||
|
ret.p = SECP256K1.add(self.p, SECP256K1.negate(other.p))
|
||||||
|
ret.valid = True
|
||||||
|
ret.compressed = self.compressed
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def tweak_add(self, tweak):
|
||||||
|
assert(self.valid)
|
||||||
|
t = int_or_bytes(tweak)
|
||||||
|
if t >= SECP256K1_ORDER:
|
||||||
|
return None
|
||||||
|
tweaked = SECP256K1.affine(SECP256K1.mul([(self.p, 1), (SECP256K1_G, t)]))
|
||||||
|
if tweaked is None:
|
||||||
|
return None
|
||||||
|
ret = ECPubKey()
|
||||||
|
ret.p = tweaked
|
||||||
|
ret.valid = True
|
||||||
|
ret.compressed = self.compressed
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def mul(self, data):
|
||||||
|
"""Multiplies ECPubKey point with scalar data."""
|
||||||
|
assert self.valid
|
||||||
|
other = ECKey()
|
||||||
|
other.set(data, True)
|
||||||
|
return self * other
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
self.p = SECP256K1.affine(SECP256K1.negate(self.p))
|
||||||
|
|
||||||
|
def rfc6979_nonce(key):
|
||||||
|
"""Compute signing nonce using RFC6979."""
|
||||||
|
v = bytes([1] * 32)
|
||||||
|
k = bytes([0] * 32)
|
||||||
|
k = hmac.new(k, v + b"\x00" + key, 'sha256').digest()
|
||||||
|
v = hmac.new(k, v, 'sha256').digest()
|
||||||
|
k = hmac.new(k, v + b"\x01" + key, 'sha256').digest()
|
||||||
|
v = hmac.new(k, v, 'sha256').digest()
|
||||||
|
return hmac.new(k, v, 'sha256').digest()
|
||||||
|
|
||||||
|
class ECKey():
|
||||||
|
"""A secp256k1 private key"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.valid = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.secret)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
assert isinstance(other, ECKey)
|
||||||
|
return self.secret == other.secret
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.secret)
|
||||||
|
|
||||||
|
def set(self, secret, compressed=True):
|
||||||
|
"""Construct a private key object from either 32-bytes or an int secret and a compressed flag."""
|
||||||
|
secret = int_or_bytes(secret)
|
||||||
|
|
||||||
|
self.valid = (secret > 0 and secret < SECP256K1_ORDER)
|
||||||
|
if self.valid:
|
||||||
|
self.secret = secret
|
||||||
|
self.compressed = compressed
|
||||||
|
return self
|
||||||
|
|
||||||
|
def generate(self, compressed=True):
|
||||||
|
"""Generate a random private key (compressed or uncompressed)."""
|
||||||
|
self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_bytes(self):
|
||||||
|
"""Retrieve the 32-byte representation of this key."""
|
||||||
|
assert(self.valid)
|
||||||
|
return self.secret.to_bytes(32, 'big')
|
||||||
|
|
||||||
|
def as_int(self):
|
||||||
|
return self.secret
|
||||||
|
|
||||||
|
def from_int(self, secret, compressed=True):
|
||||||
|
self.valid = (secret > 0 and secret < SECP256K1_ORDER)
|
||||||
|
if self.valid:
|
||||||
|
self.secret = secret
|
||||||
|
self.compressed = compressed
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
"""Add key secrets. Returns compressed key."""
|
||||||
|
assert isinstance(other, ECKey)
|
||||||
|
assert other.secret > 0 and other.secret < SECP256K1_ORDER
|
||||||
|
assert self.valid is True
|
||||||
|
ret_data = ((self.secret + other.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
|
||||||
|
ret = ECKey()
|
||||||
|
ret.set(ret_data, True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
"""Allows this ECKey to be added to 0 for sum()"""
|
||||||
|
if other == 0:
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return self + other
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
"""Subtract key secrets. Returns compressed key."""
|
||||||
|
assert isinstance(other, ECKey)
|
||||||
|
assert other.secret > 0 and other.secret < SECP256K1_ORDER
|
||||||
|
assert self.valid is True
|
||||||
|
ret_data = ((self.secret - other.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
|
||||||
|
ret = ECKey()
|
||||||
|
ret.set(ret_data, True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
"""Multiply a private key by another private key or multiply a public key by a private key. Returns compressed key."""
|
||||||
|
if isinstance(other, ECKey):
|
||||||
|
assert other.secret > 0 and other.secret < SECP256K1_ORDER
|
||||||
|
assert self.valid is True
|
||||||
|
ret_data = ((self.secret * other.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
|
||||||
|
ret = ECKey()
|
||||||
|
ret.set(ret_data, True)
|
||||||
|
return ret
|
||||||
|
elif isinstance(other, ECPubKey):
|
||||||
|
return other * self
|
||||||
|
else:
|
||||||
|
# ECKey().set() checks that other is an `int` or `bytes`
|
||||||
|
assert self.valid
|
||||||
|
second = ECKey().set(other, self.compressed)
|
||||||
|
return self * second
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
return self * other
|
||||||
|
|
||||||
|
def add(self, data):
|
||||||
|
"""Add key to scalar data. Returns compressed key."""
|
||||||
|
other = ECKey()
|
||||||
|
other.set(data, True)
|
||||||
|
return self + other
|
||||||
|
|
||||||
|
def mul(self, data):
|
||||||
|
"""Multiply key secret with scalar data. Returns compressed key."""
|
||||||
|
other = ECKey()
|
||||||
|
other.set(data, True)
|
||||||
|
return self * other
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
"""Negate a private key."""
|
||||||
|
assert self.valid
|
||||||
|
self.secret = SECP256K1_ORDER - self.secret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
return self.valid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_compressed(self):
|
||||||
|
return self.compressed
|
||||||
|
|
||||||
|
def get_pubkey(self):
|
||||||
|
"""Compute an ECPubKey object for this secret key."""
|
||||||
|
assert(self.valid)
|
||||||
|
ret = ECPubKey()
|
||||||
|
p = SECP256K1.mul([(SECP256K1_G, self.secret)])
|
||||||
|
ret.p = p
|
||||||
|
ret.valid = True
|
||||||
|
ret.compressed = self.compressed
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def sign_ecdsa(self, msg, low_s=True, rfc6979=False):
|
||||||
|
"""Construct a DER-encoded ECDSA signature with this key.
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
|
||||||
|
ECDSA signer algorithm."""
|
||||||
|
assert(self.valid)
|
||||||
|
z = int.from_bytes(msg, 'big')
|
||||||
|
# Note: no RFC6979 by default, but a simple random nonce (some tests rely on distinct transactions for the same operation)
|
||||||
|
if rfc6979:
|
||||||
|
k = int.from_bytes(rfc6979_nonce(self.secret.to_bytes(32, 'big') + msg), 'big')
|
||||||
|
else:
|
||||||
|
k = random.randrange(1, SECP256K1_ORDER)
|
||||||
|
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)]))
|
||||||
|
r = R[0] % SECP256K1_ORDER
|
||||||
|
s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER
|
||||||
|
if low_s and s > SECP256K1_ORDER_HALF:
|
||||||
|
s = SECP256K1_ORDER - s
|
||||||
|
# Represent in DER format. The byte representations of r and s have
|
||||||
|
# length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33
|
||||||
|
# bytes).
|
||||||
|
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
|
||||||
|
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
|
||||||
|
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
|
||||||
|
|
||||||
|
def sign_schnorr(self, msg, aux=None):
|
||||||
|
"""Create a Schnorr signature (see BIP340)."""
|
||||||
|
if aux is None:
|
||||||
|
aux = bytes(32)
|
||||||
|
|
||||||
|
assert self.valid
|
||||||
|
assert len(msg) == 32
|
||||||
|
assert len(aux) == 32
|
||||||
|
|
||||||
|
t = (self.secret ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big')
|
||||||
|
kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + self.get_pubkey().get_bytes() + msg), 'big') % SECP256K1_ORDER
|
||||||
|
assert kp != 0
|
||||||
|
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)]))
|
||||||
|
k = kp if SECP256K1.has_even_y(R) else SECP256K1_ORDER - kp
|
||||||
|
e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + self.get_pubkey().get_bytes() + msg), 'big') % SECP256K1_ORDER
|
||||||
|
return R[0].to_bytes(32, 'big') + ((k + e * self.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
|
||||||
|
|
||||||
|
def tweak_add(self, tweak):
|
||||||
|
"""Return a tweaked version of this private key."""
|
||||||
|
assert(self.valid)
|
||||||
|
t = int_or_bytes(tweak)
|
||||||
|
if t >= SECP256K1_ORDER:
|
||||||
|
return None
|
||||||
|
tweaked = (self.secret + t) % SECP256K1_ORDER
|
||||||
|
if tweaked == 0:
|
||||||
|
return None
|
||||||
|
ret = ECKey()
|
||||||
|
ret.set(tweaked.to_bytes(32, 'big'), self.compressed)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def generate_key_pair(secret=None, compressed=True):
|
||||||
|
"""Convenience function to generate a private-public key pair."""
|
||||||
|
d = ECKey()
|
||||||
|
if secret:
|
||||||
|
d.set(secret, compressed)
|
||||||
|
else:
|
||||||
|
d.generate(compressed)
|
||||||
|
|
||||||
|
P = d.get_pubkey()
|
||||||
|
return d, P
|
||||||
|
|
||||||
|
def generate_bip340_key_pair():
|
||||||
|
"""Convenience function to generate a BIP0340 private-public key pair."""
|
||||||
|
d = ECKey()
|
||||||
|
d.generate()
|
||||||
|
P = d.get_pubkey()
|
||||||
|
if P.get_y()%2 != 0:
|
||||||
|
d.negate()
|
||||||
|
P.negate()
|
||||||
|
return d, P
|
||||||
|
|
||||||
|
def generate_schnorr_nonce():
|
||||||
|
"""Generate a random valid BIP340 nonce.
|
||||||
|
|
||||||
|
See https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki.
|
||||||
|
This implementation ensures the y-coordinate of the nonce point is even."""
|
||||||
|
kp = random.randrange(1, SECP256K1_ORDER)
|
||||||
|
assert kp != 0
|
||||||
|
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)]))
|
||||||
|
k = kp if R[1] % 2 == 0 else SECP256K1_ORDER - kp
|
||||||
|
k_key = ECKey()
|
||||||
|
k_key.set(k.to_bytes(32, 'big'), True)
|
||||||
|
return k_key
|
2760
bip-0352/send_and_receive_test_vectors.json
Normal file
2760
bip-0352/send_and_receive_test_vectors.json
Normal file
File diff suppressed because it is too large
Load diff
176
bip-0353.mediawiki
Normal file
176
bip-0353.mediawiki
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 353
|
||||||
|
Layer: Applications
|
||||||
|
Title: DNS Payment Instructions
|
||||||
|
Author: Matt Corallo <bip353@bluematt.me>
|
||||||
|
Bastien Teinturier <bastien@acinq.fr>
|
||||||
|
Comments-Summary: No comments yet.
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0353
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2024-02-10
|
||||||
|
License: CC0-1.0
|
||||||
|
Post-History: 2024-02-13: https://groups.google.com/g/bitcoindev/c/uATaflkYglQ [bitcoin-dev] Mapping Human-Readable Names to Payment Instructions
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
|
==Copyright==
|
||||||
|
|
||||||
|
This BIP is licensed under the CC0-1.0 license.
|
||||||
|
|
||||||
|
==Abstract==
|
||||||
|
This BIP proposes a standard format for encoding [[bip-0021.mediawiki|BIP 21]] URI schemes in DNS TXT records.
|
||||||
|
|
||||||
|
==Motivation==
|
||||||
|
Various Bitcoin and other cryptocurrency applications have developed human-readable names for payment instructions over time, with marketplace adoption signaling strong demand for it from users.
|
||||||
|
|
||||||
|
The DNS provides a standard, global, hierarchical namespace mapping human-readable labels to records of various forms. Using DNSSEC, the DNS provides cryptographic guarantees using a straightforward PKI which follows the hierarchical nature of the DNS, allowing for stateless and even offline validation of DNS records from a single trusted root.
|
||||||
|
|
||||||
|
Further, because DNS queries are generally proxied through ISP-provided or other resolvers, DNS queries usually do not directly expose the queryer's IP address. Further, because of the prevalence of open resolvers, the simplicity of the protocol, and broad availability of DNS recursive resolver implementations, finding a proxy for DNS records is trivial.
|
||||||
|
|
||||||
|
Thus, using TXT records to store Bitcoin payment instructions allows for human-readable Bitcoin payment destinations which can be trivially verified on hardware wallets and which can be resolved relatively privately.
|
||||||
|
|
||||||
|
==Specification==
|
||||||
|
|
||||||
|
=== General rules for handling ===
|
||||||
|
Bitcoin wallets MUST NOT prefer to use DNS-based resolving when methods with explicit public keys or addresses are available. In other words, if a standard Bitcoin address or direct BIP 21 URI is available or would suffice, Bitcoin wallets MUST prefer to use that instead.
|
||||||
|
|
||||||
|
=== Records ===
|
||||||
|
Payment instructions are indexed by both a user and a domain. Instructions for a given `user` and `domain` are stored at `user`.user._bitcoin-payment.`domain` in a single TXT record.
|
||||||
|
|
||||||
|
All payment instructions MUST be DNSSEC-signed.
|
||||||
|
|
||||||
|
Payment instructions MAY resolve through CNAME or DNAME records as long as all such records and the ultimate records pointed to by them are DNSSEC signed.
|
||||||
|
|
||||||
|
User and domain names which are not expressible using standard printable ASCII MUST be encoded using the punycode IDN encoding defined in [[https://datatracker.ietf.org/doc/html/rfc3492|RFC 3492]] and [[https://datatracker.ietf.org/doc/html/rfc5891|RFC 5891]].
|
||||||
|
|
||||||
|
Note that because resolvers are not required to support resolving non-ASCII identifiers, wallets SHOULD avoid using non-ASCII identifiers.
|
||||||
|
|
||||||
|
For payment instructions that have a built-in expiry time (e.g. Lightning BOLT 12 offers), care must be taken to ensure that the DNS records expire prior to the expiry of the payment instructions. Otherwise, senders may have payment instructions cached locally which have expired, preventing payment.
|
||||||
|
|
||||||
|
=== Resolution ===
|
||||||
|
|
||||||
|
Clients resolving Bitcoin payment instructions MUST ignore any TXT records at the same label which do not begin with (ignoring case) "bitcoin:". Resolvers encountering multiple "bitcoin:"-matching TXT records at the same label MUST treat the records as invalid and refuse to use any payment instructions therein.
|
||||||
|
|
||||||
|
Clients resolving Bitcoin payment instructions MUST concatenate all strings in the TXT record before processing the complete URI.<ref>TXT records are defined as "one or more character-strings" in [[https://www.rfc-editor.org/rfc/rfc1035#section-3.3.14|RFC 1035]], and a "character-string" is a single byte (with a max value of 255) followed by that many characters.</ref>
|
||||||
|
|
||||||
|
Clients resolving Bitcoin payment instructions MUST fully validate DNSSEC signatures leading to the DNS root (including any relevant CNAME or DNAME records) and MUST NOT accept DNSSEC signatures which use SHA-1 or RSA with keys shorter than 1024 bits. Resolvers MAY accept SHA-1 DS records.
|
||||||
|
|
||||||
|
Clients resolving Bitcoin payment instructions MUST NOT trust a remote resolver to validate DNSSEC records on their behalf.
|
||||||
|
|
||||||
|
Clients resolving Bitcoin payment instructions MUST support resolving through CNAME or DNAME records.
|
||||||
|
|
||||||
|
Resolvers MAY support resolving non-ASCII user and domain identifiers. Resolvers which do support non-ASCII user and domain identifiers MUST take precautions to prevent homograph attacks and SHOULD consider denying paste functionality when entering non-ASCII identifiers. Wallets which do not take any such precautions MUST instead display non-ASCII user and domain identifiers using their raw punycode. As such, wallets SHOULD NOT create identifiers which are not entirely printable ASCII.
|
||||||
|
|
||||||
|
While clients MAY cache the payment instructions they receive from the DNS, clients MUST NOT cache the payment instructions received from the DNS for longer than the TTL provided by their DNS resolver, and further MUST NOT cache the payment instructions for longer than the lowest initial TTL (which is signed as a part of DNSSEC signatures) received in the full DNSSEC chain leading from the DNS root to the resolved TXT record.
|
||||||
|
|
||||||
|
=== Address Reuse ===
|
||||||
|
|
||||||
|
Payment instructions with on-chain addresses which will be re-used SHOULD be rotated as regularly as possible to reduce address reuse. Such payment instructions SHOULD also use a relatively short DNS TTL to ensure regular rotation takes effect quickly. In cases where this is not practical, payment instructions SHOULD NOT contain on-chain addresses (i.e. the URI path SHOULD be empty).
|
||||||
|
|
||||||
|
Payment instructions which do contain on-chain addresses which will be re-used SHOULD be rotated after any transaction to such an address is confirmed on-chain.
|
||||||
|
|
||||||
|
=== Display ===
|
||||||
|
|
||||||
|
When displaying a verified human-readable name, wallets SHOULD prefix it with ₿, i.e. ₿`user`@`domain`. They SHOULD parse recipient information in both `user`@`domain` and ₿`user`@`domain` forms and resolve such an entry into recipient information using the above record. For the avoidance of doubt, the ₿ is *not* included in the DNS label which is resolved.
|
||||||
|
|
||||||
|
Wallets providing the ability for users to "copy" their address information SHOULD copy the underlying URI directly, rather than the human-readable name. This avoids an additional DNS lookup by the application in which it is pasted. Wallets that nevertheless provide users the ability to copy their human-readable name, MUST include the ₿ prefix (i.e. copy it in the form ₿`user`@`domain`).
|
||||||
|
|
||||||
|
Wallets accepting payment information from external devices (e.g. hardware wallets) SHOULD accept RFC 9102-formatted proofs (as a series of unsorted `AuthenticationChain` records) and, if verification succeeds, SHOULD display the recipient in the form ₿`user`@`domain`.
|
||||||
|
|
||||||
|
=== PSBT types ===
|
||||||
|
|
||||||
|
Wallets accepting payment information from external devices (e.g. hardware wallets) MAY examine the following per-output PSBT fields to fetch RFC 9102-formatted proofs. Wallets creating PSBTs with recipient information derived from human-readable names SHOULD include the following fields.
|
||||||
|
|
||||||
|
When validating the contained proof, clients MUST enforce the inception on all contained RRSigs is no later than the current time and that the expiry of all RRSigs is no earlier than an hour in the past. Clients MAY allow for an expiry up to an hour in the past to allow for delays between PSBT construction and signing only if such a delay is likely to occur in their intended usecase.
|
||||||
|
|
||||||
|
{|
|
||||||
|
! Name
|
||||||
|
! <tt><keytype></tt>
|
||||||
|
! <tt><keydata></tt>
|
||||||
|
! <tt><valuedata></tt>
|
||||||
|
! <tt><valuedata></tt> Description
|
||||||
|
! Versions Requiring Inclusion
|
||||||
|
! Versions Requiring Exclusion
|
||||||
|
! Versions Allowing Inclusion
|
||||||
|
|-
|
||||||
|
| BIP 353 DNSSEC proof
|
||||||
|
| <tt>PSBT_OUT_DNSSEC_PROOF = 0x35</tt>
|
||||||
|
| None
|
||||||
|
| <tt><1-byte-length-prefixed BIP 353 human-readable name without the ₿ prefix><RFC 9102-formatted DNSSEC Proof></tt>
|
||||||
|
| A BIP 353 human-readable name (without the ₿ prefix), prefixed by a 1-byte length.
|
||||||
|
Followed by an [[https://www.rfc-editor.org/rfc/rfc9102.html#name-dnssec-authentication-chain|RFC 9102 DNSSEC <tt>AuthenticationChain</tt>]] (i.e. a series of DNS Resource Records in no particular order) providing a DNSSEC proof to a BIP 353 DNS TXT record.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
| 0, 2
|
||||||
|
|}
|
||||||
|
|
||||||
|
== Rationale ==
|
||||||
|
|
||||||
|
=== Display ===
|
||||||
|
|
||||||
|
There are several ways in which human-readable payment instructions could be displayed in wallets. In order to ensure compatibility with existing human-readable names schemes, @ is used as the separator between the `user` and `domain` parts. However, simply using the @ separator can lead to confusion between email addresses on a given domain and payment instructions on a domain. In order to somewhat reduce the incidence of such confusion, a ₿ prefix is used.
|
||||||
|
|
||||||
|
=== Rotation ===
|
||||||
|
|
||||||
|
On-chain addresses which are re-used (i.e. not including schemes like [[bip-0352.mediawiki|Silent Payments]]) need to be rotated to avoid contributing substantially to address reuse. However, rotating them on a timer or any time a transaction enters the mempool could lead to substantial overhead from excess address generation. Instead, rotating addresses any time a transaction is confirmed on-chain ensures address rotation happens often while bounding the maximum number of addresses needed to one per block, which grows very slowly and will not generate an address set too large to handle while scanning the chain going forward.
|
||||||
|
|
||||||
|
=== Alternatives ===
|
||||||
|
There are many existing schemes to resolve human-readable names to cryptocurrency payment instructions. Sadly, these current schemes suffer from a myriad of drawbacks, including (a) lacking succinct proofs of namespace to public key mappings, (b) revealing sender IP addresses to recipients or other intermediaries as a side-effect of payment, (c) relying on the bloated TLS Certificate Authority infrastructure, or (d) lacking open access, not allowing anyone to create a namespace mapping.
|
||||||
|
|
||||||
|
==== DNS Rather than blockchain-based solutions ====
|
||||||
|
There are many blockchain-based alternatives to the DNS which feature better censorship-resistance and, in many cases, security. However, here we chose to use the standard ICANN-managed DNS namespace as many blockchain-based schemes suffer from (a), above (though in some cases this could be addressed with cryptographic SNARK schemes). Further, because they do not have simple client-side querying ability, many of these schemes use trusted intermediaries which resolve names on behalf of clients. This reintroduces drawbacks (b) and often (c) as well.
|
||||||
|
|
||||||
|
Finally, it is worth noting that none of the blockchain-based alternatives to the DNS have had material adoption outside of their specific silos, and committing Bitcoin wallets to rely on a separate system which doesn't see broad adoption may not be sustainable.
|
||||||
|
|
||||||
|
==== DNS Rather than HTTP-based solutions ====
|
||||||
|
HTTP(s)-based payment instruction resolution protocols suffer from drawbacks (a), (b), and (c), above, and generally shouldn't be considered a serious alternative for payment instruction resolution.
|
||||||
|
|
||||||
|
==== Private DNS Querying ====
|
||||||
|
While public recursive DNS resolvers are very common (e.g. 1.1.1.1, 8.8.8.8, and 9.9.9.9), using such resolvers directly (even after validating DNSSEC signatures) introduces drawback (b), at least in regard to a centralized intermediary. Resolving payment instructions recursively locally using a resolver on the same local network or in the paying application would instead introduce drawback (b) directly to the recipient, which may well be worse.
|
||||||
|
|
||||||
|
For payers not using VPN or other proxying technologies, they generally trust their ISP to not snoop on their DNS requests anyway, so using ISP-provided recursive DNS resolvers is likely the best option.
|
||||||
|
|
||||||
|
However, for the best privacy, payers are encouraged to perform DNS resolution over Tor or another VPN technology.
|
||||||
|
|
||||||
|
Lightning payers should consider utilizing DNS resolution over native onion messages, using the protocol described in [[https://github.com/lightning/blips/blob/master/blip-0032.md|BLIP 32]]
|
||||||
|
|
||||||
|
=== DNS Enumeration ===
|
||||||
|
|
||||||
|
In most cases where payments are accepted from any third-party, user enumeration is practical by simply attempting to send small value payments to a list of possible user names. However, storing all valid users in the DNS directly may make such enumeration marginally more practical. Thus, those wishing to avoid such enumeration should carefully ensure all DNS names return valid payment instructions. Note when doing so that wildcard records are identified as such by the DNSSEC RRSIG labels counter and are differentiable from non-wildcard records.
|
||||||
|
|
||||||
|
== Backwards Compatibility ==
|
||||||
|
|
||||||
|
This work is intended to extend and subsume the existing "Lightning Address" scheme, which maps similar names (without the ₿ prefix) using HTTPS servers to Lightning BOLT 11 payment instructions. Wallets implementing this scheme MAY fall back to existing "Lightning Address" logic if DNS resolution fails but SHOULD NOT do so after this scheme is sufficiently broadly deployed to avoid leaking sender IP address information.
|
||||||
|
|
||||||
|
== Examples ==
|
||||||
|
|
||||||
|
<code>matt@mattcorallo.com</code> resolves to
|
||||||
|
|
||||||
|
<pre>matt.user._bitcoin-payment.mattcorallo.com. 1800 IN TXT "bitcoin:?lno=lno1qsgr30k45jhvkfqxjqheaetac
|
||||||
|
u4guyxvqttftvqu0f5sneckep3lkwdut7mmhhpcyjmlmnjn4hze8ed7pq88xqkxt2dcw5mlxhz644fms82f7k4ymfxs2ehhpjtxw
|
||||||
|
xly0w5k8xdtlvpqyd8xzdq4tq8lgupnueshgydr330lc3j5kdcqh54gt7jdg9n68j4eqqeu7ts8uh0qxamee6ndj37tc6mzgejth
|
||||||
|
vvjqj96p8dz2h" "rsh5z5n27qfk6svjz5pmkh0smq26k0j2j4q36xgq3r5qzet9kuhq4lydpen5mskxgjdvs5faqgv8pmj7cfd7
|
||||||
|
ny84djksqpqk9ky6juc7fpezecxvg7sjx05dckyypnv9tmvfp6tkpehmtaqmvuupetxuzqf4t0azddjdcpasgw6hxuz9g"
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
* Note that `lno` indicates a value containing a lightning BOLT12 offer.
|
||||||
|
* Note that the complete URI is broken into two strings with maximum 255 characters each
|
||||||
|
|
||||||
|
== Reference Implementations ==
|
||||||
|
* A DNSSEC proof generation and validation implementation can be found at https://git.bitcoin.ninja/index.cgi?p=dnssec-prover;a=summary
|
||||||
|
* A lightning-specific name to payment instruction resolver can be found at https://git.bitcoin.ninja/index.cgi?p=lightning-resolver;a=summary
|
||||||
|
* Reference implementations for parsing the URI contents can be found in [[bip-0021.mediawiki|BIP 21]].
|
||||||
|
|
||||||
|
|
||||||
|
== Footnotes ==
|
||||||
|
|
||||||
|
<references />
|
||||||
|
|
||||||
|
== Acknowledgements ==
|
||||||
|
|
||||||
|
Thanks to Rusty Russell for the concrete address rotation suggestion.
|
||||||
|
|
||||||
|
Thanks to the Bitcoin Design Community, and especially Christoph Ono for lots of discussion, analysis, and UX mockups in how human-readable payment instructions should be displayed.
|
||||||
|
|
||||||
|
Thanks to Andrew Kaizer for the suggestion to explicitly restrict cache lifetime to the relevant DNS TTLs.
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Ava Chow <me@achow101.com>
|
Author: Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0370
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0370
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2021-01-14
|
Created: 2021-01-14
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
@ -42,7 +42,7 @@ PSBT Version 2 (PSBTv2) only specifies new fields and field inclusion/exclusion
|
||||||
<tt>PSBT_GLOBAL_UNSIGNED_TX</tt> must be excluded in PSBTv2.
|
<tt>PSBT_GLOBAL_UNSIGNED_TX</tt> must be excluded in PSBTv2.
|
||||||
<tt>PSBT_GLOBAL_VERSION</tt> must be included in PSBTv2 and set to version number 2<ref>'''What happened to version number 1?'''
|
<tt>PSBT_GLOBAL_VERSION</tt> must be included in PSBTv2 and set to version number 2<ref>'''What happened to version number 1?'''
|
||||||
Version number 1 is skipped because PSBT Version 0 has been colloquially referred to as version 1. Originally this BIP was to be
|
Version number 1 is skipped because PSBT Version 0 has been colloquially referred to as version 1. Originally this BIP was to be
|
||||||
version 1, but because it has been colloquially referred to as version 2 during its design phrase, it was decided to change the
|
version 1, but because it has been colloquially referred to as version 2 during its design phase, it was decided to change the
|
||||||
version number to 2 so that there would not be any confusion</ref>.
|
version number to 2 so that there would not be any confusion</ref>.
|
||||||
|
|
||||||
The new global types for PSBT Version 2 are as follows:
|
The new global types for PSBT Version 2 are as follows:
|
||||||
|
@ -224,7 +224,7 @@ Height based locktime is preferred over time based as Bitcoin's unit of time is
|
||||||
|
|
||||||
===Unique Identification===
|
===Unique Identification===
|
||||||
|
|
||||||
PSBTv2s can be uniquely identified by constructing an unsigned transaction given the information provided in the PSBT and computing the transaction ID of that transaction.
|
PSBTv2s can be uniquely identified by constructing an unsigned transaction given the information provided in the PSBT, and computing the transaction ID of that transaction.
|
||||||
Since PSBT_IN_SEQUENCE can be changed by Updaters and Combiners, the sequence number in this unsigned transaction must be set to 0 (not final, nor the sequence in PSBT_IN_SEQUENCE).
|
Since PSBT_IN_SEQUENCE can be changed by Updaters and Combiners, the sequence number in this unsigned transaction must be set to 0 (not final, nor the sequence in PSBT_IN_SEQUENCE).
|
||||||
The lock time in this unsigned transaction must be computed as described previously.
|
The lock time in this unsigned transaction must be computed as described previously.
|
||||||
|
|
||||||
|
@ -252,7 +252,7 @@ When an input or output is added, the corresponding PSBT_GLOBAL_INPUT_COUNT or P
|
||||||
When an input is added, it must have PSBT_IN_PREVIOUS_TXID and PSBT_IN_OUTPUT_INDEX set.
|
When an input is added, it must have PSBT_IN_PREVIOUS_TXID and PSBT_IN_OUTPUT_INDEX set.
|
||||||
When an output is added, it must have PSBT_OUT_VALUE and PSBT_OUT_OUTPUT_SCRIPT set.
|
When an output is added, it must have PSBT_OUT_VALUE and PSBT_OUT_OUTPUT_SCRIPT set.
|
||||||
If the input has a required timelock, Constructors must set the requisite timelock field.
|
If the input has a required timelock, Constructors must set the requisite timelock field.
|
||||||
If the input has a required time based timelock, then PSBT_IN_REQUIRED_TIME_TIMELOCK must be set
|
If the input has a required time based timelock, then PSBT_IN_REQUIRED_TIME_TIMELOCK must be set.
|
||||||
If the input has a required height based timelock, then PSBT_IN_REQUIRED_HEIGHT_TIMELOCK must be set.
|
If the input has a required height based timelock, then PSBT_IN_REQUIRED_HEIGHT_TIMELOCK must be set.
|
||||||
If an input has both types of timelocks, then both may be set.
|
If an input has both types of timelocks, then both may be set.
|
||||||
In some cases, an input that can allow both types, but a particular branch supporting only one type of timelock will be taken, then the type of timelock that will be used can be the only one set.
|
In some cases, an input that can allow both types, but a particular branch supporting only one type of timelock will be taken, then the type of timelock that will be used can be the only one set.
|
||||||
|
@ -497,4 +497,4 @@ The timelock for the following PSBTs cannot be computed:
|
||||||
|
|
||||||
==Reference implementation==
|
==Reference implementation==
|
||||||
|
|
||||||
The reference implementation of the PSBT format is available at https://github.com/achow101/bitcoin/tree/psbt2.
|
The reference implementation of the PSBT format is available in [https://github.com/bitcoin/bitcoin/pull/21283 Bitcoin Core PR 21283].
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
Author: Ava Chow <me@achow101.com>
|
Author: Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0371
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0371
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Created: 2021-06-21
|
Created: 2021-06-21
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
===Abstract===
|
===Abstract===
|
||||||
|
|
||||||
This document proposes additional fields for BIP 174 PSBTv0 and BIP 370 PSBTv2
|
This document proposes additional fields for BIP 174 PSBTv0 and BIP 370 PSBTv2
|
||||||
that allow for pay-to-contract key tweaking data data to be included in a PSBT
|
that allow for pay-to-contract (P2C) key tweaking data to be included in a PSBT
|
||||||
of any version. These will represent an extra-transaction information required
|
of any version. These will represent extra-transaction information required
|
||||||
for the signer to produce valid signatures spending previous outputs.
|
for the signer to produce valid signatures spending previous outputs.
|
||||||
|
|
||||||
===Copyright===
|
===Copyright===
|
||||||
|
@ -27,33 +27,33 @@ This BIP is licensed under the 2-clause BSD license.
|
||||||
|
|
||||||
===Background===
|
===Background===
|
||||||
|
|
||||||
Key tweaking is a procedure for creating a cryptographic commitment to some
|
Key tweaking is a procedure for creating a cryptographic commitment to a
|
||||||
message using elliptic curve properties. The procedure uses the discrete log
|
message using elliptic curve properties. The procedure uses the discrete log
|
||||||
problem (DLP) to commit to an extra-transaction message. This is done by adding
|
problem (DLP) to commit to an extra-transaction message. This is done by adding
|
||||||
to a public key (for which the output owner knows the corresponding private key)
|
to a public key (for which the output owner knows the corresponding private key)
|
||||||
a hash of the message multiplied on the generator point G of the elliptic curve.
|
a hash of the message multiplied on the generator point G of the elliptic curve.
|
||||||
This produces a tweaked public key, containing the commitment. Later, in order
|
This produces a tweaked public key containing the commitment. Later, in order
|
||||||
to spend an output containing P2C commitment, the same commitment should be
|
to spend an output containing the P2C commitment, the same commitment should be
|
||||||
added to the corresponding private key.
|
added to the corresponding private key.
|
||||||
|
|
||||||
This type of commitment was originally proposed as a part of the pay to contract
|
This type of commitment was originally proposed as a part of the pay to contract
|
||||||
concept by Ilja Gerhardt and Timo Hanke in [1] and later used by Eternity Wall
|
concept by Ilja Gerhardt and Timo Hanke in [1] and later used by Eternity Wall
|
||||||
[2] for the same purpose. Since that time multiple different protocols for P2C
|
[2] for the same purpose. Since that time, multiple different protocols for P2C
|
||||||
has been developed, including OpenTimeStamps [3], Elements sidechain P2C tweaks
|
have been developed, including OpenTimeStamps [3], Elements sidechain P2C tweaks
|
||||||
[4] and LNPBP-1 [5], used in for constructing Peter Todd's single-use-seals [6]
|
[4] and LNPBP-1 [5], used for constructing Peter Todd's single-use-seals [6]
|
||||||
in client-side-validation protocols like RGB.
|
in client-side-validation protocols like RGB.
|
||||||
|
|
||||||
===Motivation===
|
===Motivation===
|
||||||
|
|
||||||
P2C outputs can be detected onchain and spent only if the output owner
|
P2C outputs can be detected onchain and spent only if the output owner
|
||||||
not just knows the corresponding original private key, but also is aware about
|
not only knows the corresponding original private key, but also is aware of
|
||||||
P2C tweak applied to the public key. In order to produce a valid signature, the
|
a P2C tweak applied to the public key. In order to produce a valid signature, the
|
||||||
same tweak value must be added (modulo group order) to the original private key
|
same tweak value must be added (modulo group order) to the original private key
|
||||||
by a signer device. This represents a challenge for external signers, which may
|
by a signer device. This represents a challenge for external signers, which may
|
||||||
not have any information about such commitment. This proposal addresses this
|
not have any information about such commitment. This proposal addresses this
|
||||||
issue by adding relevant fields to the PSBT input information.
|
issue by adding relevant fields to the PSBT input information.
|
||||||
|
|
||||||
The proposal abstracts details of specific P2C protocols and provides universal
|
The proposal abstracts details of specific P2C protocols and provides a universal
|
||||||
method for spending previous outputs containing P2C tweaks, applied to the public
|
method for spending previous outputs containing P2C tweaks, applied to the public
|
||||||
key contained within any standard form of the <tt>scriptPubkey</tt>, including
|
key contained within any standard form of the <tt>scriptPubkey</tt>, including
|
||||||
bare scripts and P2PK, P2PKH, P2SH, witness v0 P2WPKH, P2WSH, nested witness v0
|
bare scripts and P2PK, P2PKH, P2SH, witness v0 P2WPKH, P2WSH, nested witness v0
|
||||||
|
@ -67,7 +67,7 @@ P2C-tweaked public keys are already exposed in the
|
||||||
<tt>PSBT_IN_TAP_INTERNAL_KEY</tt> and <tt>PSBT_IN_TAP_LEAF_SCRIPT</tt> fields;
|
<tt>PSBT_IN_TAP_INTERNAL_KEY</tt> and <tt>PSBT_IN_TAP_LEAF_SCRIPT</tt> fields;
|
||||||
the only information signer is needed to recognize which keys it should sign
|
the only information signer is needed to recognize which keys it should sign
|
||||||
with is from which of the original keys they were generated. This is achieved by
|
with is from which of the original keys they were generated. This is achieved by
|
||||||
introducing new `PSBT_IN_P2C_TWEAK` field which has the original key as a field
|
introducing a new `PSBT_IN_P2C_TWEAK` field, which has the original key as a field
|
||||||
key and the tweak as a field value. The signer will recognize the keys which are
|
key and the tweak as a field value. The signer will recognize the keys which are
|
||||||
available to it, apply the tweak to them and see in which scripts it was used --
|
available to it, apply the tweak to them and see in which scripts it was used --
|
||||||
and use this information to apply tweaks for the corresponding private keys and
|
and use this information to apply tweaks for the corresponding private keys and
|
||||||
|
@ -92,16 +92,16 @@ The new per-input type is defined as follows:
|
||||||
| P2C Key Tweak
|
| P2C Key Tweak
|
||||||
| <tt>PSBT_IN_P2C_TWEAK = 0x19</tt>
|
| <tt>PSBT_IN_P2C_TWEAK = 0x19</tt>
|
||||||
| <tt><pubkey></tt>
|
| <tt><pubkey></tt>
|
||||||
| 33 bytes of compact public key serialization specifying to which of keys the
|
| 33 bytes of compact public key serialization specifying to which keys the
|
||||||
P2C tweak may be applied (i.e. this MUST be a value of a public key before the
|
P2C tweak may be applied (i.e. this MUST be a value of a public key before the
|
||||||
tweak is applied). BIP-340 keys are serialized by appending `02`
|
tweak is applied). BIP-340 keys are serialized by appending `02`
|
||||||
byte.<ref>'''Why compressed public keys are not distinguished from BIP-340
|
byte.<ref>'''Why compressed public keys are not distinguished from BIP-340
|
||||||
public keys'''We follow the logic of BIP32 key derivation which does not
|
public keys''' We follow the logic of BIP32 key derivation, which does not
|
||||||
performs that distinguishment. The type of the key is defined by the input type,
|
distinguish them. The type of the key is defined by the input type,
|
||||||
and adding additional PSBT field type will just create the need for handling
|
and adding additional PSBT field types will just create the need for handling
|
||||||
errors when the input type does not match the provided key type.</ref>
|
errors when the input type does not match the provided key type.</ref>
|
||||||
| <tt><tweak></tt>
|
| <tt><tweak></tt>
|
||||||
| The 32 byte value which MUST be added to a private key to produce correct
|
| The 32 byte value which MUST be added to a private key to produce a correct
|
||||||
ECDSA and/or Schnorr signature ("key tweak"). Signers SHOULD remove this field
|
ECDSA and/or Schnorr signature ("key tweak"). Signers SHOULD remove this field
|
||||||
after <tt>PSBT_IN_PARTIAL_SIG</tt> is constructed.
|
after <tt>PSBT_IN_PARTIAL_SIG</tt> is constructed.
|
||||||
|
|
|
|
||||||
|
@ -115,17 +115,17 @@ after <tt>PSBT_IN_PARTIAL_SIG</tt> is constructed.
|
||||||
|
|
||||||
The scope of this proposal is deliberately kept narrow; it addresses
|
The scope of this proposal is deliberately kept narrow; it addresses
|
||||||
only spending of transaction outputs containing P2C tweaks - and does not
|
only spending of transaction outputs containing P2C tweaks - and does not
|
||||||
addresses construction of a new P2C commitments or transactions containing them
|
address construction of new P2C commitments or transactions containing them
|
||||||
in their outputs.<ref>'''Why only spending of P2C tweaked outputs is covered'''
|
in their outputs.<ref>'''Why only spending of P2C tweaked outputs is covered'''
|
||||||
P2C tweaks commit to external data, some of which may represent certain value
|
P2C tweaks commit to external data, some of which may represent certain values
|
||||||
(like in some sidechains, single-use-seal applications like RGB etc). Creation
|
(like in some sidechains, single-use-seal applications like RGB, etc). Creation
|
||||||
of such outputs much allow hardware devices to understand the structure of such
|
of such outputs may allow hardware devices to understand the structure of such
|
||||||
extra-transaction data, which may be in different formats and constantly
|
extra-transaction data, which may be in different formats and constantly
|
||||||
involve. Thus, this should be addresses with a separate standards (or be a
|
evolve. Thus, this should be addressed with separate standards (or be
|
||||||
vendor-based). The current proposal only touches the question of spending an
|
vendor-based). The current proposal only touches the question of spending an
|
||||||
output which contained previously created P2C commitment, which does not creates
|
output that contained a previously created P2C commitment, which does not create
|
||||||
a new commitment and does not provides that kind of risk of extra-blockchain
|
a new commitment and does not provide that kind of risk of extra-blockchain
|
||||||
value loses.</ref>
|
value losses.</ref>
|
||||||
|
|
||||||
|
|
||||||
==Rationale==
|
==Rationale==
|
||||||
|
@ -136,10 +136,10 @@ value loses.</ref>
|
||||||
==Compatibility==
|
==Compatibility==
|
||||||
|
|
||||||
The proposal is compatible with the existing consensus rules and does not
|
The proposal is compatible with the existing consensus rules and does not
|
||||||
require any of their modifications.
|
require any modification to them.
|
||||||
|
|
||||||
The proposed P2C PSBT fields provides sufficient information for creating a
|
The proposed P2C PSBT fields provide sufficient information for creating
|
||||||
valid signatures for spendings of the following output types containing tweaked
|
valid signatures for spending the following output types containing tweaked
|
||||||
public keys:
|
public keys:
|
||||||
- bare scripts,
|
- bare scripts,
|
||||||
- P2PK,
|
- P2PK,
|
||||||
|
@ -149,7 +149,7 @@ public keys:
|
||||||
- nested witness v0 P2WPKH-P2SH and P2WSH-P2SH,
|
- nested witness v0 P2WPKH-P2SH and P2WSH-P2SH,
|
||||||
|
|
||||||
Post-0 witness versions, including taproot outputs and future witness versions,
|
Post-0 witness versions, including taproot outputs and future witness versions,
|
||||||
may not be supported or covered by this BIP and may require addition of new
|
may not be supported or covered by this BIP and may require the addition of new
|
||||||
fields to the PSBT inputs.
|
fields to the PSBT inputs.
|
||||||
|
|
||||||
|
|
||||||
|
@ -160,8 +160,8 @@ WIP
|
||||||
|
|
||||||
==Acknowledgements==
|
==Acknowledgements==
|
||||||
|
|
||||||
Author is grateful to Andrew Poelstra, who provided an initial set of ideas
|
The author is grateful to Andrew Poelstra, who provided an initial set of ideas
|
||||||
and information on his previous work on the topic basing on which this standard
|
and information with his previous work on the topic, on which this standard
|
||||||
was designed.
|
was designed.
|
||||||
|
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ TBD
|
||||||
Timestamping with Bitcoin.
|
Timestamping with Bitcoin.
|
||||||
<https://petertodd.org/2016/opentimestamps-announcement>
|
<https://petertodd.org/2016/opentimestamps-announcement>
|
||||||
[4] Adam Back, Matt Corallo, Luke Dashjr, et al. Enabling Blockchain
|
[4] Adam Back, Matt Corallo, Luke Dashjr, et al. Enabling Blockchain
|
||||||
Innovations with Pegged Sidechains (commit5620e43). Appenxix A.
|
Innovations with Pegged Sidechains (commit5620e43). Appendix A.
|
||||||
<https://blockstream.com/sidechains.pdf>;.
|
<https://blockstream.com/sidechains.pdf>;.
|
||||||
[5] Maxim Orlovsky, Rene Pickhardt, Federico Tenga, et al. Key
|
[5] Maxim Orlovsky, Rene Pickhardt, Federico Tenga, et al. Key
|
||||||
tweaking: collision- resistant elliptic curve-based commitments.
|
tweaking: collision- resistant elliptic curve-based commitments.
|
||||||
|
|
216
bip-0373.mediawiki
Normal file
216
bip-0373.mediawiki
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 373
|
||||||
|
Layer: Applications
|
||||||
|
Title: MuSig2 PSBT Fields
|
||||||
|
Author: Ava Chow <me@achow101.com>
|
||||||
|
Comments-Summary: No comments yet.
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0373
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2024-01-15
|
||||||
|
License: CC0-1.0
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
==Introduction==
|
||||||
|
|
||||||
|
===Abstract===
|
||||||
|
|
||||||
|
This document proposes additional fields for BIP 174 PSBTv0 and BIP 370 PSBTv2 that allow for BIP
|
||||||
|
327 MuSig2 Multi-Signature data to be included in a PSBT of any version. These will be fields for
|
||||||
|
the participants' keys, the public nonces, and the partial signatures produced with MuSig2.
|
||||||
|
|
||||||
|
===Copyright===
|
||||||
|
|
||||||
|
This BIP is licensed under the Creative Commons CC0 1.0 Universal license.
|
||||||
|
|
||||||
|
===Motivation===
|
||||||
|
|
||||||
|
BIP 327 specifies a way to create BIP 340 compatible public keys and signatures using the MuSig2
|
||||||
|
Multi-Signature scheme. The existing PSBT fields are unable to support MuSig2 as it introduces new
|
||||||
|
concepts and additional rounds of communication. Therefore new fields must be defined to allow PSBTs
|
||||||
|
to carry the information necessary to produce a valid signature with MuSig2.
|
||||||
|
|
||||||
|
==Specification==
|
||||||
|
|
||||||
|
The new per-input types are defined as follows:
|
||||||
|
|
||||||
|
{|
|
||||||
|
! Name
|
||||||
|
! <tt><keytype></tt>
|
||||||
|
! <tt><keydata></tt>
|
||||||
|
! <tt><valuedata></tt>
|
||||||
|
! Versions Requiring Inclusion
|
||||||
|
! Versions Requiring Exclusion
|
||||||
|
! Versions Allowing Inclusion
|
||||||
|
|-
|
||||||
|
| rowspan="2"|MuSig2 Participant Public Keys
|
||||||
|
| rowspan="2"|<tt>PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a</tt>
|
||||||
|
| <tt><33 byte aggregate pubkey (compressed)></tt>
|
||||||
|
| <tt><33 byte participant pubkey (compressed)>*</tt>
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"| 0, 2
|
||||||
|
|-
|
||||||
|
| The MuSig2 aggregate public key (compressed) <ref>'''Why the compressed aggregate public key instead of x-only?'''
|
||||||
|
BIP 32 public keys can be derived from a BIP 327 MuSig2 aggregate public key (see: [[bip-0328.mediawiki|BIP 328]]).
|
||||||
|
But since BIP 32 requires public keys to include their evenness byte, BIP 327 MuSig2 aggregate public keys must
|
||||||
|
include their evenness byte as well. Furthermore, PSBT_IN_TAP_BIP32_DERIVATION fields include fingerprints to identify
|
||||||
|
master keys, and these fingerprints require the y-coordinate of the public key, so x-only serialization can't be used.
|
||||||
|
By including the aggregate key as a full public key, signers that are unaware of the MuSig2 outside of the PSBT will
|
||||||
|
still be able to identify which keys are derived from the aggregate key by computing and then comparing the
|
||||||
|
fingerprints. This is necessary for the signer to apply the correct tweaks to their partial signature.</ref> from the
|
||||||
|
<tt>KeyAgg</tt> algorithm. This key may or may not appear (as x-only) in the Taproot output key, the internal key, or
|
||||||
|
in a script. It may instead be a parent public key from which the Taproot output key, internal key, or keys in a script
|
||||||
|
were derived.
|
||||||
|
| A list of the compressed public keys of the participants in the MuSig2 aggregate key in the order
|
||||||
|
required for aggregation. If sorting was done, then the keys must be in the sorted order.
|
||||||
|
|-
|
||||||
|
| rowspan="2"|MuSig2 Public Nonce
|
||||||
|
| rowspan="2"|<tt>PSBT_IN_MUSIG2_PUB_NONCE = 0x1b</tt>
|
||||||
|
| <tt><33 byte participant pubkey (compressed)> <33 byte aggregate pubkey (compressed)> <32 byte hash or omitted></tt>
|
||||||
|
| <tt><66 byte public nonce></tt>
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"| 0, 2
|
||||||
|
|-
|
||||||
|
| The compressed public key of the participant providing this nonce, followed by the compressed aggregate public
|
||||||
|
key the participant is providing the nonce for, followed by the BIP 341 tapleaf hash of
|
||||||
|
the Taproot leaf script that will be signed. If the aggregate key is the Taproot internal key or the
|
||||||
|
Taproot output key, then the tapleaf hash must be omitted. The compressed participant public key must be
|
||||||
|
the Taproot output key or found in a script. It is not the internal key nor the aggregate public key that
|
||||||
|
it was derived from, if it was derived from an aggregate key.
|
||||||
|
| The public nonce produced by the <tt>NonceGen</tt> algorithm.
|
||||||
|
|-
|
||||||
|
| rowspan="2"|MuSig2 Participant Partial Signature
|
||||||
|
| rowspan="2"|<tt>PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c</tt>
|
||||||
|
| <tt><33 byte participant pubkey (compressed)> <33 byte aggregate pubkey (compressed)> <32 byte hash or omitted></tt>
|
||||||
|
| <tt><32 byte partial signature></tt>
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"| 0, 2
|
||||||
|
|-
|
||||||
|
| The compressed public key of the participant providing this partial signature, followed by the
|
||||||
|
compressed public key the participant is providing the signature for, followed by the BIP 341 tapleaf hash
|
||||||
|
of the Taproot leaf script that will be signed. If the aggregate key is the Taproot internal key or
|
||||||
|
the Taproot output key, then the tapleaf hash must be omitted. Note that the compressed participant public key must be
|
||||||
|
the Taproot output key or found in a script. It is not the internal key nor the aggregate public key that
|
||||||
|
it was derived from, if it was derived from an aggregate key.
|
||||||
|
| The partial signature produced by the <tt>Sign</tt> algorithm.
|
||||||
|
|}
|
||||||
|
|
||||||
|
The new per-output types are defined as follows:
|
||||||
|
|
||||||
|
{|
|
||||||
|
! Name
|
||||||
|
! <tt><keytype></tt>
|
||||||
|
! <tt><keydata></tt>
|
||||||
|
! <tt><valuedata></tt>
|
||||||
|
! Versions Requiring Inclusion
|
||||||
|
! Versions Requiring Exclusion
|
||||||
|
! Versions Allowing Inclusion
|
||||||
|
|-
|
||||||
|
| rowspan="2"|MuSig2 Participant Public Keys
|
||||||
|
| rowspan="2"|<tt>PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08</tt>
|
||||||
|
| <tt><33 byte aggregate pubkey (compressed)></tt>
|
||||||
|
| <tt><33 byte participant pubkey (compressed)>*</tt>
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"|
|
||||||
|
| rowspan="2"|0, 2
|
||||||
|
|-
|
||||||
|
| The MuSig2 compressed aggregate public key from the <tt>KeyAgg</tt> algorithm. This key may or may not
|
||||||
|
appear (as x-only) in the Taproot output key, the internal key, or in a script. It may instead be a parent
|
||||||
|
public key from which the Taproot output key, internal key, or keys in a script were derived.
|
||||||
|
| A list of the compressed public keys of the participants in the MuSig2 aggregate key in the order
|
||||||
|
required for aggregation. If sorting was done, then the keys must be in the sorted order.
|
||||||
|
|}
|
||||||
|
|
||||||
|
==Roles==
|
||||||
|
|
||||||
|
===Updater===
|
||||||
|
|
||||||
|
When an updater observes a Taproot output which involves a MuSig2 aggregate public key that it is
|
||||||
|
aware of, it can add a <tt>PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS</tt> field containing the public keys
|
||||||
|
of the participants. This aggregate public key may be directly in the script, the Taproot internal
|
||||||
|
key, the Taproot output key, or a public key from which the key in the script was derived from.
|
||||||
|
|
||||||
|
An aggregate public key that appears directly in the script or internal key may be from the result
|
||||||
|
of deriving child pubkeys from participant xpubs. If the updater has this derivation information, it
|
||||||
|
should also add <tt>PSBT_IN_TAP_BIP32_DERIVATION</tt> for each participant public key.
|
||||||
|
|
||||||
|
If the public key found was derived from an aggregate public key, then all MuSig2 PSBT fields for
|
||||||
|
that public key should contain the aggregate public key rather than the found pubkey itself. The
|
||||||
|
updater should also add <tt>PSBT_IN_TAP_BIP32_DERIVATION</tt> that contains the derivation path used
|
||||||
|
to derive the found pubkey from the aggregate pubkey.
|
||||||
|
Derivation from the aggregate pubkey can be assumed to follow [[bip-0328.mediawiki|BIP 328]]
|
||||||
|
if there is no <tt>PSBT_IN_GLOBAL_XPUB</tt> that specifies the synthetic xpub for the aggregate
|
||||||
|
public key.
|
||||||
|
|
||||||
|
Updaters should add <tt>PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS</tt> and
|
||||||
|
<tt>PSBT_OUT_TAP_BIP32_DERIVATION</tt> similarly to inputs to aid in change detection.
|
||||||
|
|
||||||
|
===Signer===
|
||||||
|
|
||||||
|
To determine whether a signer is a participant in the MuSig2 aggregate key, the signer should first
|
||||||
|
look at all <tt>PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS</tt> and see if any key which it knows the
|
||||||
|
private key for appears as a participant in any aggregate pubkey. Signers should also check whether
|
||||||
|
any of the keys in <tt>PSBT_IN_TAP_BIP32_DERIVATION</tt> belong to it, and if any of those keys
|
||||||
|
appear in as a participant in <tt>PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS</tt>.
|
||||||
|
|
||||||
|
For each aggregate public key that the signer is a participant of that it wants
|
||||||
|
to produce a signature for, if the signer does not find an existing
|
||||||
|
<tt>PSBT_IN_MUSIG2_PUB_NONCE</tt> field for its key, then it should add one using
|
||||||
|
the <tt>NonceGen</tt> algorithm (or one of its variations) to produce a public
|
||||||
|
nonce that is added in a <tt>PSBT_IN_MUSIG2_PUB_NONCE</tt> field. However
|
||||||
|
signers must keep in mind that '''improper nonce usage can compromise private
|
||||||
|
keys.''' Please see BIP 327 for best practices on nonce generation and usage.
|
||||||
|
|
||||||
|
Once all signers have added their <tt>PSBT_IN_MUSIG2_PUB_NONCE</tt> fields, each signer will perform
|
||||||
|
the <tt>NonceAgg</tt> algorithm followed by the <tt>Sign</tt> algorithm in order to produce the
|
||||||
|
partial signature for their key. The result will be added to the PSBT in a
|
||||||
|
<tt>PSBT_IN_MUSIG2_PARTIAL_SIG</tt> field.
|
||||||
|
|
||||||
|
Signers must remember to apply any relevant tweaks such as a tweak that is the result of performing
|
||||||
|
BIP 32 unhardened derivation with the aggregate public key as the parent key.
|
||||||
|
|
||||||
|
If all other signers have provided a <tt>PSBT_IN_MUSIG2_PARTIAL_SIG</tt>, then the final signer may
|
||||||
|
perform the <tt>PartialSigAgg</tt> algorithm and produce a BIP 340 compatible signature that can be
|
||||||
|
placed into a <tt>PSBT_IN_TAP_KEY_SIG</tt> or a <tt>PSBT_IN_TAP_SCRIPT_SIG</tt>.
|
||||||
|
|
||||||
|
===Finalizer===
|
||||||
|
|
||||||
|
A finalizer may perform the same <tt>PartialSigAgg</tt> step as the final signer if it has not
|
||||||
|
already been done.
|
||||||
|
|
||||||
|
Otherwise, the resulting signature is a BIP 340 compatible signature and finalizers should treat it
|
||||||
|
as such.
|
||||||
|
|
||||||
|
==Backwards Compatibility==
|
||||||
|
|
||||||
|
These are simply new fields added to the existing PSBT format. Because PSBT is designed to be
|
||||||
|
extensible, old software will ignore the new fields.
|
||||||
|
|
||||||
|
Reusing <tt>PSBT_IN_TAP_BIP32_DERIVATION</tt> to provide derivation paths for participant public
|
||||||
|
keys may cause software unaware of MuSig2 to produce a signature for that public key. This is still
|
||||||
|
safe. If that public key does not directly appear in the leaf script that was signed, then the
|
||||||
|
signature produced will not be useful and so cannot be replayed. If the public key does directly
|
||||||
|
appear in the leaf script, then the signer will have validated the script as if it did not involve a
|
||||||
|
MuSig2 and will have found it acceptable in order for it to have produced a signature. In either
|
||||||
|
case, producing a signature does not give rise to the possibility of losing funds.
|
||||||
|
|
||||||
|
==Test Vectors==
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
==Rationale==
|
||||||
|
|
||||||
|
<references/>
|
||||||
|
|
||||||
|
==Reference implementation==
|
||||||
|
|
||||||
|
The reference implementation of the PSBT format is available at TBD.
|
||||||
|
|
||||||
|
==Acknowledgements==
|
||||||
|
|
||||||
|
Thanks to Sanket Kanjalkar whose notes on this topic formed the initial basis of this BIP. Also
|
||||||
|
thanks to Pieter Wuille, Jonas Nick, Tim Ruffing, Marko Bencun, Salvatore Ingala, and all others who
|
||||||
|
have participated in discussions about these fields.
|
128
bip-0374.mediawiki
Normal file
128
bip-0374.mediawiki
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 374
|
||||||
|
Layer: Applications
|
||||||
|
Title: Discrete Log Equality Proofs
|
||||||
|
Author: Andrew Toth <andrewstoth@gmail.com>
|
||||||
|
Ruben Somsen <rsomsen@gmail.com>
|
||||||
|
Sebastian Falbesoner <sebastian.falbesoner@gmail.com>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0374
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
License: BSD-2-Clause
|
||||||
|
Created: 2024-12-26
|
||||||
|
Post-History: https://gist.github.com/andrewtoth/df97c3260cc8d12f09d3855ee61322ea
|
||||||
|
https://groups.google.com/g/bitcoindev/c/MezoKV5md7s
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
== Introduction ==
|
||||||
|
|
||||||
|
=== Abstract ===
|
||||||
|
|
||||||
|
This document proposes a standard for 64-byte zero-knowledge ''discrete logarithm equality proofs'' (DLEQ proofs) over an elliptic curve. For given elliptic curve points ''A'', ''B'', ''C'', ''G'', and a scalar ''a'' known only to the prover where ''A = a⋅G'' and ''C = a⋅B'', the prover proves knowledge of ''a'' without revealing anything about ''a''. This can, for instance, be useful in ECDH: if ''A'' and ''B'' are ECDH public keys, and ''C'' is their ECDH shared secret computed as ''C = a⋅B'', the proof establishes that the same secret key ''a'' is used for generating both ''A'' and ''C'' without revealing ''a''.
|
||||||
|
|
||||||
|
=== Copyright ===
|
||||||
|
|
||||||
|
This document is licensed under the 2-clause BSD license.
|
||||||
|
|
||||||
|
=== Motivation ===
|
||||||
|
|
||||||
|
[https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#specification BIP352] requires senders to compute output scripts using ECDH shared secrets from the same secret keys used to sign the inputs. Generating an incorrect signature will produce an invalid transaction that will be rejected by consensus. An incorrectly generated output script can still be consensus-valid, meaning funds may be lost if it gets broadcast.
|
||||||
|
By producing a DLEQ proof for the generated ECDH shared secrets, the signing entity can prove to other entities that the output scripts have been generated correctly without revealing the private keys.
|
||||||
|
|
||||||
|
== Specification ==
|
||||||
|
|
||||||
|
All conventions and notations are used as defined in [https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki#user-content-Notation BIP327].
|
||||||
|
|
||||||
|
=== Description ===
|
||||||
|
|
||||||
|
The basic proof generation uses a random scalar ''k'', the secret ''a'', and the point being proven ''C = a⋅B''.
|
||||||
|
|
||||||
|
* Let ''R<sub>1</sub> = k⋅G''.
|
||||||
|
* Let ''R<sub>2</sub> = k⋅B''.
|
||||||
|
* Let ''e = hash(R<sub>1</sub> || R<sub>2</sub>)''.
|
||||||
|
* Let ''s = (k + e⋅a)''.
|
||||||
|
|
||||||
|
Providing only ''C'', ''e'' and ''s'' as a proof does not reveal ''a'' or ''k''.
|
||||||
|
|
||||||
|
Verifying the proof involves recreating ''R<sub>1</sub>'' and ''R<sub>2</sub>'' with only ''e'' and ''s'' as follows:
|
||||||
|
|
||||||
|
* Let ''R<sub>1</sub> = s⋅G - e⋅A''.
|
||||||
|
* Let ''R<sub>2</sub> = s⋅B - e⋅C''.
|
||||||
|
|
||||||
|
This can be verified by substituting ''s = (k + e⋅a)'':
|
||||||
|
|
||||||
|
* ''s⋅G - e⋅A = (k + e⋅a)⋅G - e⋅A = k⋅G + e⋅(a⋅G) - e⋅A = k⋅G + e⋅A - e⋅A = k⋅G''.
|
||||||
|
* ''s⋅B - e⋅C = (k + e⋅a)⋅B - e⋅C = k⋅B + e⋅(a⋅B) - e⋅C = k⋅B + e⋅C - e⋅C = k⋅B''.
|
||||||
|
|
||||||
|
Thus verifying ''e = hash(R<sub>1</sub> || R<sub>2</sub>)'' proves the discrete logarithm equivalency of ''A'' and ''C''.
|
||||||
|
|
||||||
|
=== DLEQ Proof Generation ===
|
||||||
|
|
||||||
|
The following generates a proof that the result of ''a⋅B'' and the result of ''a⋅G'' are both generated from the same scalar ''a'' without having to reveal ''a''.
|
||||||
|
|
||||||
|
Input:
|
||||||
|
* The secret key ''a'': a 256-bit unsigned integer
|
||||||
|
* The public key ''B'': a point on the curve
|
||||||
|
* Auxiliary random data ''r'': a 32-byte array<ref name="why_include_auxiliary_random_data"> ''' Why include auxiliary random data?''' The auxiliary random data should be set to fresh randomness for each proof. The same rationale and recommendations from [https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#default-signing BIP340] should be applied.</ref>
|
||||||
|
* The generator point ''G'': a point on the curve<ref name="why_include_G"> ''' Why include the generator point G as an input?''' While all other BIPs have used the generator point from secp256k1, passing it as an input here lets this algorithm be used for other curves.</ref>
|
||||||
|
* An optional message ''m'': a 32-byte array<ref name="why_include_a_message"> ''' Why include a message as an input?''' This could be useful for protocols that want to authorize on a compound statement, not just knowledge of a scalar. This allows the protocol to combine knowledge of the scalar and the statement.</ref>
|
||||||
|
|
||||||
|
The algorithm ''GenerateProof(a, B, r, G, m)'' is defined as:
|
||||||
|
* Fail if ''a = 0'' or ''a ≥ n''.
|
||||||
|
* Fail if ''is_infinite(B)''.
|
||||||
|
* Let ''A = a⋅G''.
|
||||||
|
* Let ''C = a⋅B''.
|
||||||
|
* Let ''t'' be the byte-wise xor of ''bytes(32, a)'' and ''hash<sub>BIP0374/aux</sub>(r)''.
|
||||||
|
* Let ''rand = hash<sub>BIP0374/nonce</sub>(t || cbytes(A) || cbytes(C))''.
|
||||||
|
* Let ''k = int(rand) mod n''.
|
||||||
|
* Fail if ''k = 0''.
|
||||||
|
* Let ''R<sub>1</sub> = k⋅G''.
|
||||||
|
* Let ''R<sub>2</sub> = k⋅B''.
|
||||||
|
* Let ''m' = m if m is provided, otherwise an empty byte array''.
|
||||||
|
* Let ''e = int(hash<sub>BIP0374/challenge</sub>(cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>) || m'))''.
|
||||||
|
* Let ''s = (k + e⋅a) mod n''.
|
||||||
|
* Let ''proof = bytes(32, e) || bytes(32, s)''.
|
||||||
|
* If ''VerifyProof(A, B, C, proof)'' (see below) returns failure, abort.
|
||||||
|
* Return the proof ''proof''.
|
||||||
|
|
||||||
|
=== DLEQ Proof Verification ===
|
||||||
|
|
||||||
|
The following verifies the proof generated in the previous section. If the following algorithm succeeds, the points ''A'' and ''C'' were both generated from the same scalar. The former from multiplying by ''G'', and the latter from multiplying by ''B''.
|
||||||
|
|
||||||
|
Input:
|
||||||
|
* The public key of the secret key used in the proof generation ''A'': a point on the curve
|
||||||
|
* The public key used in the proof generation ''B'': a point on the curve
|
||||||
|
* The result of multiplying the secret and public keys used in the proof generation ''C'': a point on the curve
|
||||||
|
* A proof ''proof'': a 64-byte array
|
||||||
|
* The generator point used in the proof generation ''G'': a point on the curve<ref name="why_include_G"> ''' Why include the generator point G as an input?''' While all other BIPs have used the generator point from Secp256k1, passing it as an input here lets this algorithm be used for other curves.</ref>
|
||||||
|
* An optional message ''m'': a 32-byte array<ref name="why_include_a_message"> ''' Why include a message as an input?''' This could be useful for protocols that want to authorize on a compound statement, not just knowledge of a scalar. This allows the protocol to combine knowledge of the scalar and the statement.</ref>
|
||||||
|
|
||||||
|
The algorithm ''VerifyProof(A, B, C, proof, G, m)'' is defined as:
|
||||||
|
* Fail if any of ''is_infinite(A)'', ''is_infinite(B)'', ''is_infinite(C)'', ''is_infinite(G)''
|
||||||
|
* Let ''e = int(proof[0:32])''.
|
||||||
|
* Let ''s = int(proof[32:64])''; fail if ''s ≥ n''.
|
||||||
|
* Let ''R<sub>1</sub> = s⋅G - e⋅A''.
|
||||||
|
* Fail if ''is_infinite(R<sub>1</sub>)''.
|
||||||
|
* Let ''R<sub>2</sub> = s⋅B - e⋅C''.
|
||||||
|
* Fail if ''is_infinite(R<sub>2</sub>)''.
|
||||||
|
* Let ''m' = m if m is provided, otherwise an empty byte array''.
|
||||||
|
* Fail if ''e ≠ int(hash<sub>BIP0374/challenge</sub>(cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R<sub>1</sub>) || cbytes(R<sub>2</sub>) || m'))''.
|
||||||
|
* Return success iff no failure occurred before reaching this point.
|
||||||
|
|
||||||
|
==Backwards Compatibility==
|
||||||
|
|
||||||
|
This proposal is compatible with all older clients.
|
||||||
|
|
||||||
|
== Test Vectors and Reference Code ==
|
||||||
|
|
||||||
|
A reference python implementation is included [https://github.com/bitcoin/bips/blob/master/bip-0374/reference.py here].
|
||||||
|
Test vectors can be generated by running <code>./bip-0374/gen_test_vectors.py</code> which will produce a CSV file of random test vectors for both generating and verifying proofs. These can be run against the reference implementation with <code>./bip-0374/run_test_vectors.py</code>.
|
||||||
|
|
||||||
|
== Footnotes ==
|
||||||
|
|
||||||
|
<references />
|
||||||
|
|
||||||
|
== Acknowledgements ==
|
||||||
|
|
||||||
|
Thanks to josibake, Tim Ruffing, benma, stratospher, waxwing, Yuval Kogman and all others who
|
||||||
|
participated in discussions on this topic.
|
126
bip-0374/gen_test_vectors.py
Executable file
126
bip-0374/gen_test_vectors.py
Executable file
|
@ -0,0 +1,126 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate the BIP-DLEQ test vectors (limited to secp256k1 generator right now)."""
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from reference import (
|
||||||
|
TaggedHash,
|
||||||
|
dleq_generate_proof,
|
||||||
|
dleq_verify_proof,
|
||||||
|
)
|
||||||
|
from secp256k1 import G as GENERATOR, GE
|
||||||
|
|
||||||
|
|
||||||
|
NUM_SUCCESS_TEST_VECTORS = 5
|
||||||
|
DLEQ_TAG_TESTVECTORS_RNG = "BIP0374/testvectors_rng"
|
||||||
|
|
||||||
|
FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv')
|
||||||
|
FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv')
|
||||||
|
|
||||||
|
|
||||||
|
def random_scalar_int(vector_i, purpose):
|
||||||
|
rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
|
||||||
|
return int.from_bytes(rng_out, 'big') % GE.ORDER
|
||||||
|
|
||||||
|
|
||||||
|
def random_bytes(vector_i, purpose):
|
||||||
|
rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
|
||||||
|
return rng_out
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_vector_data(vector_i):
|
||||||
|
g = random_scalar_int(vector_i, "scalar_g")
|
||||||
|
assert g < GE.ORDER
|
||||||
|
assert g > 0
|
||||||
|
G = g * GENERATOR
|
||||||
|
assert not G.infinity
|
||||||
|
a = random_scalar_int(vector_i, "scalar_a")
|
||||||
|
A = a * G
|
||||||
|
b = random_scalar_int(vector_i, "scalar_b")
|
||||||
|
B = b * G
|
||||||
|
C = a * B # shared secret
|
||||||
|
assert C.to_bytes_compressed() == (b * A).to_bytes_compressed()
|
||||||
|
auxrand = random_bytes(vector_i, "auxrand")
|
||||||
|
msg = random_bytes(vector_i, "message")
|
||||||
|
proof = dleq_generate_proof(a, B, auxrand, G=G, m=msg)
|
||||||
|
return (G, a, A, b, B, C, auxrand, msg, proof)
|
||||||
|
|
||||||
|
TEST_VECTOR_DATA = [create_test_vector_data(i) for i in range(NUM_SUCCESS_TEST_VECTORS)]
|
||||||
|
|
||||||
|
|
||||||
|
def gen_all_generate_proof_vectors(f):
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(("index", "point_G", "scalar_a", "point_B", "auxrand_r", "message", "result_proof", "comment"))
|
||||||
|
|
||||||
|
# success cases with random values
|
||||||
|
idx = 0
|
||||||
|
for i in range(NUM_SUCCESS_TEST_VECTORS):
|
||||||
|
G, a, A, b, B, C, auxrand, msg, proof = TEST_VECTOR_DATA[i]
|
||||||
|
assert proof is not None and len(proof) == 64
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), f"{a:064x}", B.to_bytes_compressed().hex(), auxrand.hex(), msg.hex(), proof.hex(), f"Success case {i+1}"))
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
# failure cases: a is not within group order (a=0, a=N)
|
||||||
|
a_invalid = 0
|
||||||
|
assert dleq_generate_proof(a_invalid, B, auxrand, G=G, m=msg) is None
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), f"{a_invalid:064x}", B.to_bytes_compressed().hex(), auxrand.hex(), msg.hex(), "INVALID", f"Failure case (a=0)"))
|
||||||
|
idx += 1
|
||||||
|
a_invalid = GE.ORDER
|
||||||
|
assert dleq_generate_proof(a_invalid, B, auxrand, G=G, m=msg) is None
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), f"{a_invalid:064x}", B.to_bytes_compressed().hex(), auxrand.hex(), msg.hex(), "INVALID", f"Failure case (a=N [group order])"))
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
# failure case: B is point at infinity
|
||||||
|
B_infinity = GE()
|
||||||
|
B_infinity_str = "INFINITY"
|
||||||
|
assert dleq_generate_proof(a, B_infinity, auxrand, m=msg) is None
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), f"{a:064x}", B_infinity_str, auxrand.hex(), msg.hex(), "INVALID", f"Failure case (B is point at infinity)"))
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
|
||||||
|
def gen_all_verify_proof_vectors(f):
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(("index", "point_G", "point_A", "point_B", "point_C", "proof", "message", "result_success", "comment"))
|
||||||
|
|
||||||
|
# success cases (same as above)
|
||||||
|
idx = 0
|
||||||
|
for i in range(NUM_SUCCESS_TEST_VECTORS):
|
||||||
|
G, _, A, _, B, C, _, msg, proof = TEST_VECTOR_DATA[i]
|
||||||
|
assert dleq_verify_proof(A, B, C, proof, G=G, m=msg)
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(),
|
||||||
|
C.to_bytes_compressed().hex(), proof.hex(), msg.hex(), "TRUE", f"Success case {i+1}"))
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
# other permutations of A, B, C should always fail
|
||||||
|
for i, points in enumerate(([A, C, B], [B, A, C], [B, C, A], [C, A, B], [C, B, A])):
|
||||||
|
assert not dleq_verify_proof(points[0], points[1], points[2], proof, m=msg)
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), points[0].to_bytes_compressed().hex(), points[1].to_bytes_compressed().hex(),
|
||||||
|
points[2].to_bytes_compressed().hex(), proof.hex(), msg.hex(), "FALSE", f"Swapped points case {i+1}"))
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
# modifying proof should fail (flip one bit)
|
||||||
|
proof_damage_pos = random_scalar_int(idx, "damage_pos") % 256
|
||||||
|
proof_damaged = list(proof)
|
||||||
|
proof_damaged[proof_damage_pos // 8] ^= (1 << (proof_damage_pos % 8))
|
||||||
|
proof_damaged = bytes(proof_damaged)
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(),
|
||||||
|
C.to_bytes_compressed().hex(), proof_damaged.hex(), msg.hex(), "FALSE", f"Tampered proof (random bit-flip)"))
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
# modifying message should fail (flip one bit)
|
||||||
|
msg_damage_pos = random_scalar_int(idx, "damage_pos") % 256
|
||||||
|
msg_damaged = list(msg)
|
||||||
|
msg_damaged[proof_damage_pos // 8] ^= (1 << (msg_damage_pos % 8))
|
||||||
|
msg_damaged = bytes(msg_damaged)
|
||||||
|
writer.writerow((idx, G.to_bytes_compressed().hex(), A.to_bytes_compressed().hex(), B.to_bytes_compressed().hex(),
|
||||||
|
C.to_bytes_compressed().hex(), proof.hex(), msg_damaged.hex(), "FALSE", f"Tampered message (random bit-flip)"))
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(f"Generating {FILENAME_GENERATE_PROOF_TEST}...")
|
||||||
|
with open(FILENAME_GENERATE_PROOF_TEST, "w", encoding="utf-8") as fil_generate_proof:
|
||||||
|
gen_all_generate_proof_vectors(fil_generate_proof)
|
||||||
|
print(f"Generating {FILENAME_VERIFY_PROOF_TEST}...")
|
||||||
|
with open(FILENAME_VERIFY_PROOF_TEST, "w", encoding="utf-8") as fil_verify_proof:
|
||||||
|
gen_all_verify_proof_vectors(fil_verify_proof)
|
146
bip-0374/reference.py
Executable file
146
bip-0374/reference.py
Executable file
|
@ -0,0 +1,146 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Reference implementation of DLEQ BIP for secp256k1 with unit tests."""
|
||||||
|
|
||||||
|
from hashlib import sha256
|
||||||
|
import random
|
||||||
|
from secp256k1 import G, GE
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
DLEQ_TAG_AUX = "BIP0374/aux"
|
||||||
|
DLEQ_TAG_NONCE = "BIP0374/nonce"
|
||||||
|
DLEQ_TAG_CHALLENGE = "BIP0374/challenge"
|
||||||
|
|
||||||
|
|
||||||
|
def TaggedHash(tag: str, data: bytes) -> bytes:
|
||||||
|
ss = sha256(tag.encode()).digest()
|
||||||
|
ss += ss
|
||||||
|
ss += data
|
||||||
|
return sha256(ss).digest()
|
||||||
|
|
||||||
|
|
||||||
|
def xor_bytes(lhs: bytes, rhs: bytes) -> bytes:
|
||||||
|
assert len(lhs) == len(rhs)
|
||||||
|
return bytes([lhs[i] ^ rhs[i] for i in range(len(lhs))])
|
||||||
|
|
||||||
|
|
||||||
|
def dleq_challenge(
|
||||||
|
A: GE, B: GE, C: GE, R1: GE, R2: GE, m: bytes | None, G: GE,
|
||||||
|
) -> int:
|
||||||
|
if m is not None:
|
||||||
|
assert len(m) == 32
|
||||||
|
m = bytes([]) if m is None else m
|
||||||
|
return int.from_bytes(
|
||||||
|
TaggedHash(
|
||||||
|
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
|
||||||
|
A = a * G
|
||||||
|
C = a * B
|
||||||
|
t = xor_bytes(a.to_bytes(32, "big"), TaggedHash(DLEQ_TAG_AUX, r))
|
||||||
|
rand = TaggedHash(
|
||||||
|
DLEQ_TAG_NONCE, t + A.to_bytes_compressed() + C.to_bytes_compressed()
|
||||||
|
)
|
||||||
|
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
|
||||||
|
# TODO: implement subtraction operator (__sub__) for GE class to simplify these terms
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class DLEQTests(unittest.TestCase):
|
||||||
|
def test_dleq(self):
|
||||||
|
seed = random.randrange(sys.maxsize)
|
||||||
|
random.seed(seed)
|
||||||
|
print(f"PRNG seed is: {seed}")
|
||||||
|
for _ in range(10):
|
||||||
|
# generate random keypairs for both parties
|
||||||
|
a = random.randrange(1, GE.ORDER)
|
||||||
|
A = a * G
|
||||||
|
b = random.randrange(1, GE.ORDER)
|
||||||
|
B = b * G
|
||||||
|
|
||||||
|
# create shared secret
|
||||||
|
C = a * B
|
||||||
|
|
||||||
|
# create dleq proof
|
||||||
|
rand_aux = random.randbytes(32)
|
||||||
|
proof = dleq_generate_proof(a, B, rand_aux)
|
||||||
|
self.assertTrue(proof is not None)
|
||||||
|
# verify dleq proof
|
||||||
|
success = dleq_verify_proof(A, B, C, proof)
|
||||||
|
self.assertTrue(success)
|
||||||
|
|
||||||
|
# flip a random bit in the dleq proof and check that verification fails
|
||||||
|
for _ in range(5):
|
||||||
|
proof_damaged = list(proof)
|
||||||
|
proof_damaged[random.randrange(len(proof))] ^= 1 << (
|
||||||
|
random.randrange(8)
|
||||||
|
)
|
||||||
|
success = dleq_verify_proof(A, B, C, bytes(proof_damaged))
|
||||||
|
self.assertFalse(success)
|
||||||
|
|
||||||
|
# create the same dleq proof with a message
|
||||||
|
message = random.randbytes(32)
|
||||||
|
proof = dleq_generate_proof(a, B, rand_aux, m=message)
|
||||||
|
self.assertTrue(proof is not None)
|
||||||
|
# verify dleq proof with a message
|
||||||
|
success = dleq_verify_proof(A, B, C, proof, m=message)
|
||||||
|
self.assertTrue(success)
|
||||||
|
|
||||||
|
# flip a random bit in the dleq proof and check that verification fails
|
||||||
|
for _ in range(5):
|
||||||
|
proof_damaged = list(proof)
|
||||||
|
proof_damaged[random.randrange(len(proof))] ^= 1 << (
|
||||||
|
random.randrange(8)
|
||||||
|
)
|
||||||
|
success = dleq_verify_proof(A, B, C, bytes(proof_damaged))
|
||||||
|
self.assertFalse(success)
|
77
bip-0374/run_test_vectors.py
Executable file
77
bip-0374/run_test_vectors.py
Executable file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run the BIP-DLEQ test vectors."""
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from reference import (
|
||||||
|
dleq_generate_proof,
|
||||||
|
dleq_verify_proof,
|
||||||
|
)
|
||||||
|
from secp256k1 import GE
|
||||||
|
|
||||||
|
|
||||||
|
FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv')
|
||||||
|
FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv')
|
||||||
|
|
||||||
|
|
||||||
|
all_passed = True
|
||||||
|
print("-----------------------------------------")
|
||||||
|
print("----- Proof generation test vectors -----")
|
||||||
|
print("-----------------------------------------")
|
||||||
|
with open(FILENAME_GENERATE_PROOF_TEST, newline='') as csvfile:
|
||||||
|
reader = csv.reader(csvfile)
|
||||||
|
reader.__next__()
|
||||||
|
for row in reader:
|
||||||
|
(index, point_G_hex, seckey_a_hex, point_B_hex, aux_rand_hex, msg_hex, result_str, comment) = row
|
||||||
|
print(seckey_a_hex)
|
||||||
|
G = GE() if point_G_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_G_hex))
|
||||||
|
a = int.from_bytes(bytes.fromhex(seckey_a_hex), 'big')
|
||||||
|
B = GE() if point_B_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_B_hex))
|
||||||
|
aux_rand = bytes.fromhex(aux_rand_hex)
|
||||||
|
msg = bytes.fromhex(msg_hex)
|
||||||
|
print('Test vector', ('#' + index).rjust(3, ' ') + ':' + f' ({comment})')
|
||||||
|
expected_result = None if result_str == 'INVALID' else bytes.fromhex(result_str)
|
||||||
|
actual_result = dleq_generate_proof(a, B, aux_rand, G=G, m=msg)
|
||||||
|
if expected_result == actual_result:
|
||||||
|
print(' * Passed proof generation test.')
|
||||||
|
else:
|
||||||
|
print(' * Failed proof generation test.')
|
||||||
|
print(' Expected proof: ', expected_result.hex() if expected_result is not None else 'INVALID')
|
||||||
|
print(' Actual proof: ', actual_result.hex() if actual_result is not None else 'INVALID')
|
||||||
|
all_passed = False
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
print("-------------------------------------------")
|
||||||
|
print("----- Proof verification test vectors -----")
|
||||||
|
print("-------------------------------------------")
|
||||||
|
with open(FILENAME_VERIFY_PROOF_TEST, newline='') as csvfile:
|
||||||
|
reader = csv.reader(csvfile)
|
||||||
|
reader.__next__()
|
||||||
|
for row in reader:
|
||||||
|
(index, point_G_hex, point_A_hex, point_B_hex, point_C_hex, proof_hex, msg_hex, result_success, comment) = row
|
||||||
|
G = GE() if point_G_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_G_hex))
|
||||||
|
A = GE() if point_A_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_A_hex))
|
||||||
|
B = GE() if point_B_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_B_hex))
|
||||||
|
C = GE() if point_C_hex == 'INFINITY' else GE.from_bytes(bytes.fromhex(point_C_hex))
|
||||||
|
proof = bytes.fromhex(proof_hex)
|
||||||
|
msg = bytes.fromhex(msg_hex)
|
||||||
|
print('Test vector', ('#' + index).rjust(3, ' ') + ':' + f' ({comment})')
|
||||||
|
expected_result = result_success == 'TRUE'
|
||||||
|
actual_result = dleq_verify_proof(A, B, C, proof, G=G, m=msg)
|
||||||
|
if expected_result == actual_result:
|
||||||
|
print(' * Passed proof verification test.')
|
||||||
|
else:
|
||||||
|
print(' * Failed proof verification test.')
|
||||||
|
print(' Expected verification result: ', expected_result)
|
||||||
|
print(' Actual verification result: ', actual_result)
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
|
||||||
|
print()
|
||||||
|
if all_passed:
|
||||||
|
print('All test vectors passed.')
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print('Some test vectors failed.')
|
||||||
|
sys.exit(1)
|
356
bip-0374/secp256k1.py
Executable file
356
bip-0374/secp256k1.py
Executable file
|
@ -0,0 +1,356 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# 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
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from hashlib import sha256
|
||||||
|
|
||||||
|
class FE:
|
||||||
|
"""Objects of this class represent elements of the field GF(2**256 - 2**32 - 977).
|
||||||
|
|
||||||
|
They are represented internally in numerator / denominator form, in order to delay inversions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The size of the field (also its modulus and characteristic).
|
||||||
|
SIZE = 2**256 - 2**32 - 977
|
||||||
|
|
||||||
|
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, FE):
|
||||||
|
num = a._num
|
||||||
|
den = a._den
|
||||||
|
else:
|
||||||
|
num = a % FE.SIZE
|
||||||
|
den = 1
|
||||||
|
if isinstance(b, FE):
|
||||||
|
den = (den * b._num) % FE.SIZE
|
||||||
|
num = (num * b._den) % FE.SIZE
|
||||||
|
else:
|
||||||
|
den = (den * b) % FE.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, FE):
|
||||||
|
return FE(self._num * a._den + self._den * a._num, self._den * a._den)
|
||||||
|
return FE(self._num + self._den * a, self._den)
|
||||||
|
|
||||||
|
def __radd__(self, a):
|
||||||
|
"""Compute the sum of an integer and a field element."""
|
||||||
|
return FE(a) + self
|
||||||
|
|
||||||
|
def __sub__(self, a):
|
||||||
|
"""Compute the difference of two field elements (second may be int)."""
|
||||||
|
if isinstance(a, FE):
|
||||||
|
return FE(self._num * a._den - self._den * a._num, self._den * a._den)
|
||||||
|
return FE(self._num - self._den * a, self._den)
|
||||||
|
|
||||||
|
def __rsub__(self, a):
|
||||||
|
"""Compute the difference of an integer and a field element."""
|
||||||
|
return FE(a) - self
|
||||||
|
|
||||||
|
def __mul__(self, a):
|
||||||
|
"""Compute the product of two field elements (second may be int)."""
|
||||||
|
if isinstance(a, FE):
|
||||||
|
return FE(self._num * a._num, self._den * a._den)
|
||||||
|
return FE(self._num * a, self._den)
|
||||||
|
|
||||||
|
def __rmul__(self, a):
|
||||||
|
"""Compute the product of an integer with a field element."""
|
||||||
|
return FE(a) * self
|
||||||
|
|
||||||
|
def __truediv__(self, a):
|
||||||
|
"""Compute the ratio of two field elements (second may be int)."""
|
||||||
|
return FE(self, a)
|
||||||
|
|
||||||
|
def __pow__(self, a):
|
||||||
|
"""Raise a field element to an integer power."""
|
||||||
|
return FE(pow(self._num, a, FE.SIZE), pow(self._den, a, FE.SIZE))
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
"""Negate a field element."""
|
||||||
|
return FE(-self._num, self._den)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
"""Convert a field element to an integer in range 0..p-1. The result is cached."""
|
||||||
|
if self._den != 1:
|
||||||
|
self._num = (self._num * pow(self._den, -1, FE.SIZE)) % FE.SIZE
|
||||||
|
self._den = 1
|
||||||
|
return self._num
|
||||||
|
|
||||||
|
def sqrt(self):
|
||||||
|
"""Compute the square root of a field element if it exists (None otherwise).
|
||||||
|
|
||||||
|
Due to the fact that our modulus 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, (FE.SIZE + 1) // 4, FE.SIZE)
|
||||||
|
if s**2 % FE.SIZE == v:
|
||||||
|
return FE(s)
|
||||||
|
return None
|
||||||
|
|
||||||
|
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..p-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, FE):
|
||||||
|
return (self._num * a._den - self._den * a._num) % FE.SIZE == 0
|
||||||
|
return (self._num - self._den * a) % FE.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')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_bytes(b):
|
||||||
|
"""Convert a 32-byte array to a field element (BE byte order, no overflow allowed)."""
|
||||||
|
v = int.from_bytes(b, 'big')
|
||||||
|
if v >= FE.SIZE:
|
||||||
|
return None
|
||||||
|
return FE(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"FE(0x{int(self):x})"
|
||||||
|
|
||||||
|
|
||||||
|
class GE:
|
||||||
|
"""Objects of this class represent secp256k1 group elements (curve points or infinity)
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Order of the group (number of points on the curve, plus 1 for infinity)
|
||||||
|
ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||||
|
|
||||||
|
# Number of valid distinct x coordinates on the curve.
|
||||||
|
ORDER_HALF = ORDER // 2
|
||||||
|
|
||||||
|
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 mul(*aps):
|
||||||
|
"""Compute a (batch) scalar group element multiplication.
|
||||||
|
|
||||||
|
GE.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 = [(a % GE.ORDER, 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(a)
|
||||||
|
return GE.mul((a, self))
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
"""Compute the negation of a group element."""
|
||||||
|
if self.infinity:
|
||||||
|
return self
|
||||||
|
return GE(self.x, -self.y)
|
||||||
|
|
||||||
|
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_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:
|
||||||
|
return None
|
||||||
|
if not y.is_even():
|
||||||
|
y = -y
|
||||||
|
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:
|
||||||
|
if b[0] != 2 and b[0] != 3:
|
||||||
|
return None
|
||||||
|
x = FE.from_bytes(b[1:])
|
||||||
|
if x is None:
|
||||||
|
return None
|
||||||
|
r = GE.lift_x(x)
|
||||||
|
if r is None:
|
||||||
|
return None
|
||||||
|
if b[0] == 3:
|
||||||
|
r = -r
|
||||||
|
return r
|
||||||
|
else:
|
||||||
|
if b[0] != 4:
|
||||||
|
return None
|
||||||
|
x = FE.from_bytes(b[1:33])
|
||||||
|
y = FE.from_bytes(b[33:])
|
||||||
|
if y**2 != x**3 + 7:
|
||||||
|
return None
|
||||||
|
return GE(x, y)
|
||||||
|
|
||||||
|
@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(b)
|
||||||
|
if x is None:
|
||||||
|
return None
|
||||||
|
return GE.lift_x(x)
|
||||||
|
|
||||||
|
@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})"
|
||||||
|
|
||||||
|
# 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 = a % GE.ORDER
|
||||||
|
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)
|
||||||
|
|
||||||
|
class TestFrameworkSecp256k1(unittest.TestCase):
|
||||||
|
def test_H(self):
|
||||||
|
H = sha256(G.to_bytes_uncompressed()).digest()
|
||||||
|
assert GE.lift_x(FE.from_bytes(H)) is not None
|
||||||
|
self.assertEqual(H.hex(), "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")
|
9
bip-0374/test_vectors_generate_proof.csv
Normal file
9
bip-0374/test_vectors_generate_proof.csv
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
index,point_G,scalar_a,point_B,auxrand_r,message,result_proof,comment
|
||||||
|
0,02cef38f55e78b321a1f785cb1c6e33dfcef9784c18bdc4e279801c449ccdfb88e,07ff93d43f1012a5d4a44aba55240212ed39c87b3344e46757d99f24177fc576,02dad4b35c2379ba8334c9a5dda8f6e6d5cd575a7cc9d3ca4faaac51839daaa30f,cb979b0fc8ccc7f237751e719d992fcc324b6500af33999cd54a3e5c05fb1ea4,efb07d4b382d3da1079fbf24df623ba6c2e4c764993bbfa6dd7a4fe4aaf33859,51ce8becf2726c8fed85957500de5d58e0349b8ed0fe40aee2c122288ee21f8fe4d1ca31f5e3c4833fb83a654b044298aceef34881c950efefdc7b64cb5db93e,Success case 1
|
||||||
|
1,02464e351831efedb755223cabbf664f10564b4742c725c023034bc928ed339e0e,f4e9172285393c6ada994c811b3e50fc47e96421ea7e54f4a4e459528d4cf562,03fe589b0fa23f060f6d4d1e76b9b19d5bb3db0e56d39a4303913de0e706463008,75f12482b9209dae12230ea1f8bf69723a1b447d361db8f510dd9ab33556fd4c,76184ce9eea5b339ebf5304b57452c1ada1466610f0a58574d6c496798cee04b,be01fd4ec6ee4b08adb36ddb7b0290e09710f842f8623f1afc8c0ff00bdd6cee5256b10341c30d4f393d9b2c462b1534d06e2cff60920993c6dc240806576d80,Success case 2
|
||||||
|
2,0222db2054fef98344352a13bc0304a71da7b5e9a2f7fd1f3c9f3519a3d9377fb7,589476913e763b60d5c2a5bfb39230ec669caac1b44312e9bcd2d3f4473abfef,03bc7a19970c812118f74ba659b491e00dade6096ff62d1afe032a92b8671498ed,4da1c4c4b0f9db4eb6b2e5cb648d7e8a0aa35aa5c4ec4d07f096e0e03deca366,66503623468a78cfcef47888c85e0010ecd897f441d263448bfc7a89b882ab20,a9e3603f2cb11c74dba678448cc5bc6ae6de372502392d1914e976229cb06c401f12bd03dbab57c2cd1a209adb51c14387f82e938a8a9d363fc8dc1e76456dac,Success case 3
|
||||||
|
3,03dfa65bd3711eba75fa1996a0c1d95a4419bd835304152d9aa6efa590670f2af6,24d0ed3fc189eb1b64e5dc9dd4af0f3c8c143b0c79cb5fcca0dfa08a11cc60a1,03b51081323d38fb0b75f0c1ec6755fdb79c239c327ca11269fe68ba8a878b704e,31a68d6db27f6404bbceff646ff1b26a34704a0105a36c5a845d0257cea19c9b,f2996b3766d123a949e65541baf1d89d446360d05af51bd93f0445d8c472c952,0dbd32f1ecd950987bda4b163e5ea536e4e43e8e2f26bcf235ff799c12089f21d5a27f90f144aaaddf5a05390c44442aa13d9fbfec8cd53d3659942617ce5cb4,Success case 4
|
||||||
|
4,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,73fffa796edb72d111b5e0bbda1608f098ac98120796f971b438691e1bfb7b96,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,Success case 5
|
||||||
|
5,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,0000000000000000000000000000000000000000000000000000000000000000,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,INVALID,Failure case (a=0)
|
||||||
|
6,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,INVALID,Failure case (a=N [group order])
|
||||||
|
7,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,73fffa796edb72d111b5e0bbda1608f098ac98120796f971b438691e1bfb7b96,INFINITY,1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,INVALID,Failure case (B is point at infinity)
|
|
13
bip-0374/test_vectors_verify_proof.csv
Normal file
13
bip-0374/test_vectors_verify_proof.csv
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
index,point_G,point_A,point_B,point_C,proof,message,result_success,comment
|
||||||
|
0,02cef38f55e78b321a1f785cb1c6e33dfcef9784c18bdc4e279801c449ccdfb88e,02b540b22c2c5ef0dc886abdaad27498453d893265560bc08a187319af6f845f58,02dad4b35c2379ba8334c9a5dda8f6e6d5cd575a7cc9d3ca4faaac51839daaa30f,03fefe00951dcd0ef10b12523393c2b8113119de4fdeeab320694e96bdccd2775b,51ce8becf2726c8fed85957500de5d58e0349b8ed0fe40aee2c122288ee21f8fe4d1ca31f5e3c4833fb83a654b044298aceef34881c950efefdc7b64cb5db93e,efb07d4b382d3da1079fbf24df623ba6c2e4c764993bbfa6dd7a4fe4aaf33859,TRUE,Success case 1
|
||||||
|
1,02464e351831efedb755223cabbf664f10564b4742c725c023034bc928ed339e0e,032baaf1b10845a51b551196984a91efe2adf9d41b92bec3927218e6e4ca344002,03fe589b0fa23f060f6d4d1e76b9b19d5bb3db0e56d39a4303913de0e706463008,031f59aa1df22190e00380d8c5941adf899f596593765a1251005fd24f2bf7c884,be01fd4ec6ee4b08adb36ddb7b0290e09710f842f8623f1afc8c0ff00bdd6cee5256b10341c30d4f393d9b2c462b1534d06e2cff60920993c6dc240806576d80,76184ce9eea5b339ebf5304b57452c1ada1466610f0a58574d6c496798cee04b,TRUE,Success case 2
|
||||||
|
2,0222db2054fef98344352a13bc0304a71da7b5e9a2f7fd1f3c9f3519a3d9377fb7,026aa26fcd626f8f55295859e9f8dd1f103149dd64d77c2bbba1bcf33bb37ebaa2,03bc7a19970c812118f74ba659b491e00dade6096ff62d1afe032a92b8671498ed,035628d1a69910daef614c7cae68d71ece55c5908af2360629e25c1b7de21eeb4b,a9e3603f2cb11c74dba678448cc5bc6ae6de372502392d1914e976229cb06c401f12bd03dbab57c2cd1a209adb51c14387f82e938a8a9d363fc8dc1e76456dac,66503623468a78cfcef47888c85e0010ecd897f441d263448bfc7a89b882ab20,TRUE,Success case 3
|
||||||
|
3,03dfa65bd3711eba75fa1996a0c1d95a4419bd835304152d9aa6efa590670f2af6,031bf61ba89009ee1266c9003a72e8e07d77877678ccda7f15325aadcd64ed186b,03b51081323d38fb0b75f0c1ec6755fdb79c239c327ca11269fe68ba8a878b704e,02d1b1f37a80217ba73785babfa63251052775f9d3ca65060054033288b7a3f66b,0dbd32f1ecd950987bda4b163e5ea536e4e43e8e2f26bcf235ff799c12089f21d5a27f90f144aaaddf5a05390c44442aa13d9fbfec8cd53d3659942617ce5cb4,f2996b3766d123a949e65541baf1d89d446360d05af51bd93f0445d8c472c952,TRUE,Success case 4
|
||||||
|
4,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,TRUE,Success case 5
|
||||||
|
5,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,FALSE,Swapped points case 1
|
||||||
|
6,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,FALSE,Swapped points case 2
|
||||||
|
7,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,FALSE,Swapped points case 3
|
||||||
|
8,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,FALSE,Swapped points case 4
|
||||||
|
9,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,FALSE,Swapped points case 5
|
||||||
|
10,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,5bd36d18c6e75e50f1fbbaa7596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4,FALSE,Tampered proof (random bit-flip)
|
||||||
|
11,02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7,0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e,03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01,03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472,5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d,0ceb45f560f2cf6b76a139f7e2c47c5ca6d26d6a3a210e59f197413bbec040b4,FALSE,Tampered message (random bit-flip)
|
|
261
bip-0375.mediawiki
Normal file
261
bip-0375.mediawiki
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 375
|
||||||
|
Layer: Applications
|
||||||
|
Title: Sending Silent Payments with PSBTs
|
||||||
|
Author: Andrew Toth <andrewstoth@gmail.com>
|
||||||
|
Ava Chow <me@achow101.com>
|
||||||
|
josibake <josibake@protonmail.com>
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0375
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2025-01-08
|
||||||
|
License: BSD-2-Clause
|
||||||
|
Post-History: https://groups.google.com/g/bitcoindev/c/5G5wzqUXyk4
|
||||||
|
Requires: 352, 370, 374
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
==Introduction==
|
||||||
|
|
||||||
|
===Abstract===
|
||||||
|
|
||||||
|
This document proposes additional fields and updated role responsibilities for BIP370 PSBTv2
|
||||||
|
which adds support for sending to silent payments as described in BIP352.
|
||||||
|
|
||||||
|
===Copyright===
|
||||||
|
|
||||||
|
This BIP is licensed under the 2-clause BSD license.
|
||||||
|
|
||||||
|
===Motivation===
|
||||||
|
|
||||||
|
Partially Signed Bitcoin Transaction Version 2 as described in BIP370 is not compatible with sending to silent payments as described in BIP352.
|
||||||
|
In particular, the output script of a silent payment cannot be computed until after all transaction inputs have been added.
|
||||||
|
Additionally, the silent payment outputs computed by a signer must be verifiable by other entities, otherwise funds could be sent to an incorrect output script.
|
||||||
|
Therefore, new fields and role responsibilities must be added to carry, compute, and verify the silent payment data.
|
||||||
|
|
||||||
|
==Specification==
|
||||||
|
|
||||||
|
This document specifies new fields and new field inclusion/exclusion requirements.
|
||||||
|
|
||||||
|
The new global types are defined as follows:
|
||||||
|
|
||||||
|
{|
|
||||||
|
! Name
|
||||||
|
! <tt><keytype></tt>
|
||||||
|
! <tt><keydata></tt>
|
||||||
|
! <tt><keydata></tt> Description
|
||||||
|
! <tt><valuedata></tt>
|
||||||
|
! <tt><valuedata></tt> Description
|
||||||
|
! Versions Requiring Inclusion
|
||||||
|
! Versions Requiring Exclusion
|
||||||
|
! Versions Allowing Inclusion
|
||||||
|
|-
|
||||||
|
| Silent Payment Global ECDH Share
|
||||||
|
| <tt>PSBT_GLOBAL_SP_ECDH_SHARE = 0x07</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this ECDH share is for.
|
||||||
|
| <tt><33 byte share></tt>
|
||||||
|
| An ECDH share for a scan key. The ECDH shared is computed with ''a * B_scan'', where ''a'' is the sum of all private keys of all eligible inputs, and ''B_scan'' is the scan key of a recipient.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
|-
|
||||||
|
| Silent Payment Global DLEQ Proof
|
||||||
|
| <tt>PSBT_GLOBAL_SP_DLEQ = 0x08</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this proof covers.
|
||||||
|
| <tt><64-byte proof></tt>
|
||||||
|
| A BIP374 DLEQ proof computed for the matching ECDH share.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
|}
|
||||||
|
|
||||||
|
The new per-input types are defined as follows:
|
||||||
|
|
||||||
|
{|
|
||||||
|
! Name
|
||||||
|
! <tt><keytype></tt>
|
||||||
|
! <tt><keydata></tt>
|
||||||
|
! <tt><keydata></tt> Description
|
||||||
|
! <tt><valuedata></tt>
|
||||||
|
! <tt><valuedata></tt> Description
|
||||||
|
! Versions Requiring Inclusion
|
||||||
|
! Versions Requiring Exclusion
|
||||||
|
! Versions Allowing Inclusion
|
||||||
|
|-
|
||||||
|
| Silent Payment Input ECDH Share
|
||||||
|
| <tt>PSBT_IN_SP_ECDH_SHARE = 0x1d</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this ECDH share is for.
|
||||||
|
| <tt><33 byte share></tt>
|
||||||
|
| An ECDH share for a scan key. The ECDH shared is computed with ''a * B_scan'', where ''a'' is the private key of the corresponding prevout public key, and ''B_scan'' is the scan key of a recipient.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
|-
|
||||||
|
| Silent Payment Input DLEQ Proof
|
||||||
|
| <tt>PSBT_IN_SP_DLEQ = 0x1e</tt>
|
||||||
|
| <tt><33 byte scan key></tt>
|
||||||
|
| The scan key that this proof covers.
|
||||||
|
| <tt><64-byte proof></tt>
|
||||||
|
| A BIP374 DLEQ proof computed for the matching ECDH share.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
|}
|
||||||
|
|
||||||
|
The new per-output types are defined as follows:
|
||||||
|
|
||||||
|
{|
|
||||||
|
! Name
|
||||||
|
! <tt><keytype></tt>
|
||||||
|
! <tt><keydata></tt>
|
||||||
|
! <tt><keydata></tt> Description
|
||||||
|
! <tt><valuedata></tt>
|
||||||
|
! <tt><valuedata></tt> Description
|
||||||
|
! Versions Requiring Inclusion
|
||||||
|
! Versions Requiring Exclusion
|
||||||
|
! Versions Allowing Inclusion
|
||||||
|
|-
|
||||||
|
| Silent Payment Data
|
||||||
|
| <tt>PSBT_OUT_SP_V0_INFO = 0x09</tt>
|
||||||
|
| None
|
||||||
|
| No key data
|
||||||
|
| <tt><33 byte scan key> <33 byte spend key></tt>
|
||||||
|
| The scan and spend public keys from the silent payments address.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
|-
|
||||||
|
| Silent Payment Label
|
||||||
|
| <tt>PSBT_OUT_SP_V0_LABEL = 0x10</tt>
|
||||||
|
| None
|
||||||
|
| No key data
|
||||||
|
| <tt><32-bit little endian uint label></tt>
|
||||||
|
| The label to use to compute the spend key of the silent payments address to verify change.
|
||||||
|
|
|
||||||
|
| 0
|
||||||
|
| 2
|
||||||
|
|}
|
||||||
|
|
||||||
|
<tt>PSBT_OUT_SCRIPT</tt> is modified to be optional for outputs in silent payments capable PSBTs. If this field is not included in the output, then the field PSBT_OUT_SP_V0_INFO must be included.
|
||||||
|
If a PSBT_OUT_SCRIPT is not present for an output, then that output is being sent to a silent payment address represented by PSBT_OUT_SP_V0_INFO but the script has not yet been computed.
|
||||||
|
If both PSBT_OUT_SCRIPT and PSBT_OUT_SP_V0_INFO are present for an output, then the PSBT_OUT_SCRIPT is the computed output script corresponding to the silent payment address in PSBT_OUT_SP_V0_INFO.
|
||||||
|
If only PSBT_OUT_SCRIPT is present for an output, then the output is not being sent to a silent payment address.
|
||||||
|
|
||||||
|
===Unique Identification===
|
||||||
|
|
||||||
|
Silent payment capable PSBTs can be uniquely identified the same way as PSBTv2s, except when including silent payment outputs. If an output contains the the PSBT_OUT_SP_V0_INFO field, it must use that field instead of PSBT_OUT_SCRIPT as the output script when creating the unsigned transaction used for unique identification.<ref name="why_use_sp_info_field"> ''' Why use PSBT_OUT_SP_V0_INFO when serializing for a unique identifier?''' Since the same silent payment capable PSBT is valid whether or not a PSBT_OUT_SCRIPT is included in an output that has PSBT_OUT_SP_V0_INFO set, using the PSBT_OUT_SCRIPT if present for the unique identifier will cause malleability. The identifier will be different depending on whether PSBT_OUT_SCRIPT is present, so always using PSBT_OUT_SP_V0_INFO if it exists makes sure the PSBT is always identified uniquely.</ref>
|
||||||
|
The PSBT_OUT_SP_V0_INFO should be serialized as a zero byte for the version, followed by the 33 bytes of the scan key and then 33 bytes for the spend key.
|
||||||
|
|
||||||
|
|
||||||
|
==Roles==
|
||||||
|
|
||||||
|
This document modifies some existing roles.
|
||||||
|
|
||||||
|
===Constructor===
|
||||||
|
|
||||||
|
All rules must be followed from PSBTv2 for this role, with the following exception:
|
||||||
|
When an output is added, it must have either PSBT_OUT_SCRIPT or PSBT_OUT_SP_V0_INFO, or both, set.
|
||||||
|
|
||||||
|
Additionally to PSBTv2, the Constructor must also follow additional rules:
|
||||||
|
|
||||||
|
Inputs spending an output with script using Segwit version > 1 may only be added if there are no outputs with PSBT_OUT_SP_V0_INFO set.
|
||||||
|
Outputs with PSBT_OUT_SP_V0_INFO set may only be added if there are no inputs spending an output script using Segwit version > 1.
|
||||||
|
|
||||||
|
===Updater===
|
||||||
|
|
||||||
|
The updater should add a PSBT_IN_BIP32_DERIVATION for any p2wpkh, p2sh-p2wpkh, or p2pkh input so the public key is available for creating the ecdh_shared_secret when the private key is not known. If the updater does not want to reveal the fingerprint or derivation path, it can set the value of the field to zero.
|
||||||
|
|
||||||
|
====Change Detection====
|
||||||
|
|
||||||
|
Updaters may add two PSBT_OUT_BIP32_DERIVATION key-value-pairs with the corresponding derivation path of both the scan and spend keys. A label can be specified in PSBT_OUT_SP_V0_LABEL. The Signer can then use these fields to verify that the silent payment code is change.
|
||||||
|
|
||||||
|
===Signer===
|
||||||
|
|
||||||
|
All rules must be followed from PSBTv2 for this role. If there are any outputs with PSBT_OUT_SP_V0_INFO set, then the following additional rules must also be adhered to:
|
||||||
|
|
||||||
|
If any input is spending an output with script using Segwit version > 1, the Signer must fail.
|
||||||
|
|
||||||
|
For each output with PSBT_OUT_SP_V0_INFO set, the Signer should:
|
||||||
|
* Compute and set an ECDH share and DLEQ proof for each input it has the private key for, or set a global ECDH share and DLEQ proof if it has private keys for all eligible inputs.
|
||||||
|
* Verify the DLEQ proofs for all inputs it does not have the private keys for, or the global DLEQ proof if it is set.
|
||||||
|
* If all eligible inputs have an ECDH share or the global ECDH share is set, compute and set the PSBT_OUT_SCRIPT.
|
||||||
|
|
||||||
|
If the Signer sets any missing PSBT_OUT_SCRIPTs, it must set the Inputs Modifiable and Outputs Modifiable flags to False.
|
||||||
|
|
||||||
|
If any output does not have PSBT_OUT_SCRIPT set, the Signer must not yet add a signature.
|
||||||
|
|
||||||
|
The Signer should additionally compute the silent payment addresses, optionally showing this data to the user instead of the computed segwit v1 addresses.
|
||||||
|
|
||||||
|
If a sighash type is provided and there are silent payment outputs present, the signer must fail if the sighash type is not SIGHASH_ALL.
|
||||||
|
If a sighash type is not provided and there are silent payment outputs present, the signer must sign using SIGHASH_ALL.<ref name="why_use_sighash_all"> ''' Why use only SIGHASH_ALL?''' BIP352 allows signing with SIGHASH_NONE and SIGHASH_SINGLE. However, silent payment capable PSBTs compute the output scripts deterministically based on the number and position of silent payment codes with the same scan key. SIGHASH_NONE and SIGHASH_SINGLE allow changing the amount or position of silent payment codes with the same scan and spend keys, which would invalidate computed output scripts.</ref>
|
||||||
|
|
||||||
|
====Computing the ECDH Shares and DLEQ Proofs====
|
||||||
|
|
||||||
|
For each output with PSBT_OUT_SP_V0_INFO set, the Signer may generate a proof for other entities to generate the output scripts and verify that the output scripts were generated correctly.
|
||||||
|
|
||||||
|
If the Signer has the private keys for all eligible inputs, the Signer should generate a global ECDH share for each scan key ''B<sub>scan</sub>'' as follows:
|
||||||
|
|
||||||
|
Using the notation from [https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#specification BIP352]
|
||||||
|
|
||||||
|
* Let ''a<sub>n</sub>'' be the sum of the private keys ''a'' of all eligible inputs
|
||||||
|
* Let ''C = a<sub>n</sub>·B<sub>scan</sub>''
|
||||||
|
|
||||||
|
Set the key as ''B<sub>scan</sub>'' and the value as ''C'' for the PSBT_GLOBAL_SP_ECDH_SHARE field.
|
||||||
|
|
||||||
|
Compute the DLEQ proof for ''C'' using [https://github.com/bitcoin/bips/blob/master/bip-0374.mediawiki#user-content-DLEQ_Proof_Generation BIP374 GenerateProof] and passing ''a<sub>n</sub>'' as ''a'' and ''B<sub>scan</sub>'' as ''B''.
|
||||||
|
Set the key as ''B<sub>scan</sub>'' and the value as the proof for the PSBT_GLOBAL_SP_DLEQ field.
|
||||||
|
|
||||||
|
If the Signer has the private keys for some eligible inputs or does not want to create a global ECDH share, the Signer should generate a per-input ECDH share for each scan key ''B<sub>scan</sub>'' as follows:
|
||||||
|
|
||||||
|
Using the notation from [https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#specification BIP352], for each eligible input:
|
||||||
|
|
||||||
|
* Let ''a'' be the private key of the input
|
||||||
|
* Let ''C = a·B<sub>scan</sub>''
|
||||||
|
|
||||||
|
Set the key as ''B<sub>scan</sub>'' and the value as ''C'' for the PSBT_IN_SP_ECDH_SHARE field of the input.
|
||||||
|
|
||||||
|
Compute the DLEQ proof for ''C'' using [https://github.com/bitcoin/bips/blob/master/bip-0374.mediawiki#user-content-DLEQ_Proof_Generation BIP374 GenerateProof] and passing ''B<sub>scan</sub>'' as ''B''.
|
||||||
|
Set the key as ''B<sub>scan</sub>'' and the value as the proof for the PSBT_IN_SP_DLEQ field of the input.
|
||||||
|
|
||||||
|
====Verifying the DLEQ Proof====
|
||||||
|
|
||||||
|
For each output with PSBT_OUT_SP_V0_INFO set, the Signer should verify the ECDH shares for all eligible inputs it does not have the private key for using the proofs provided by other Signers.
|
||||||
|
|
||||||
|
If PSBT_GLOBAL_SP_ECDH_SHARE and PSBT_GLOBAL_SP_DLEQ are set, verify as follows:
|
||||||
|
|
||||||
|
* Let ''A<sub>n</sub>'' be the sum of the public keys ''A'' of all eligible inputs
|
||||||
|
|
||||||
|
Using [https://github.com/bitcoin/bips/blob/master/bip-0374.mediawiki#dleq-proof-verification BIP374 VerifyProof] and passing ''A'' as ''A<sub>n</sub>'', ''B'' as ''B<sub>scan</sub>'', ''C'' as the value of PSBT_GLOBAL_SP_ECDH_SHARE, and ''proof'' as the value of PSBT_GLOBAL_SP_DLEQ.
|
||||||
|
|
||||||
|
If PSBT_IN_SP_ECDH_SHARE and PSBT_IN_SP_DLEQ are set for a particular input, verify as follows:
|
||||||
|
|
||||||
|
Using [https://github.com/bitcoin/bips/blob/master/bip-0374.mediawiki#dleq-proof-verification BIP374 VerifyProof] and passing ''A'' as the public key of the input, ''B'' as ''B<sub>scan</sub>'', ''C'' as the value of PSBT_IN_SP_ECDH_SHARE, and ''proof'' as the value of PSBT_IN_SP_DLEQ.
|
||||||
|
|
||||||
|
|
||||||
|
====Computing the Output Scripts====
|
||||||
|
|
||||||
|
Compute the PSBT_OUT_SCRIPT using the procedure in [https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#user-content-Creating_outputs BIP352] but substituting ''a·B<sub>scan</sub>'' with the PSBT_GLOBAL_SP_ECDH_SHARE for that scan key if available, or the sum of all PSBT_IN_SP_ECDH_SHAREs for that scan key.
|
||||||
|
If there are multiple silent payment codes with the same scan key, sort the codes lexicographically in ascending order to determine the ordering of the ''k'' value.
|
||||||
|
If there are multiple silent payment codes with both the same scan and spend keys, sort the subgroup by output index in ascending order.
|
||||||
|
|
||||||
|
===Transaction Extractor===
|
||||||
|
|
||||||
|
For silent payment capable PSBTs, the transaction extractor should compute all output scripts for silent payment codes and verify they are correct using the ECDH shares and DLEQ proofs, otherwise fail.
|
||||||
|
|
||||||
|
==Backwards Compatibility==
|
||||||
|
|
||||||
|
Silent payment capable PSBTs are backwards compatible with PSBTv2 once all outputs have PSBT_OUT_SCRIPT set. Otherwise they are not backwards compatible.
|
||||||
|
|
||||||
|
==Test Vectors==
|
||||||
|
|
||||||
|
Todo
|
||||||
|
|
||||||
|
==Rationale==
|
||||||
|
|
||||||
|
<references/>
|
||||||
|
|
||||||
|
==Reference implementation==
|
||||||
|
|
||||||
|
Todo
|
423
bip-0379.md
Normal file
423
bip-0379.md
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 379
|
||||||
|
Layer: Applications
|
||||||
|
Title: Miniscript
|
||||||
|
Author: Pieter Wuille <pieter@wuille.net>
|
||||||
|
Andrew Poelstra <andrew.poelstra@gmail.com>
|
||||||
|
Sanket Kanjalkar <sanket1729@gmail.com>
|
||||||
|
Antoine Poinsot <darosior@protonmail.com>
|
||||||
|
Ava Chow <me@achow101.com>
|
||||||
|
Comments-Summary: No comments yet.
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0379
|
||||||
|
Status: Draft
|
||||||
|
Type: Informational
|
||||||
|
Created: 2023-10-10
|
||||||
|
License: CC0-1.0
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Abstract
|
||||||
|
|
||||||
|
This document specifies Miniscript, a language for writing (a subset of) Bitcoin Scripts in a
|
||||||
|
structured way, enabling analysis, composition, generic signing and more.
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
This document is licensed under the Creative Commons CC0 1.0 Universal license.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Bitcoin Script is an unusual stack-based language with many edge cases, designed for implementing
|
||||||
|
spending conditions consisting of various combinations of signatures, hash locks, and time locks.
|
||||||
|
Yet, despite being limited in functionality, it is still highly nontrivial to:
|
||||||
|
|
||||||
|
* Given a combination of spending conditions, finding the most economical script to implement it.
|
||||||
|
* Given two scripts, construct a script that implements a composition of their spending conditions (e.g. a multisig where one of the "keys" is another multisig).
|
||||||
|
* Given a script, find out what spending conditions it permits.
|
||||||
|
* Given a script and access to a sufficient set of private keys, construct a general satisfying witness for it.
|
||||||
|
* Given a script, be able to predict the cost of spending an output.
|
||||||
|
* Given a script, know whether particular resource limitations like the ops limit might be hit when spending.
|
||||||
|
|
||||||
|
Miniscript functions as a representation for scripts that makes this sort of operations possible.
|
||||||
|
It has a structure that allows composition. It is very easy to statically analyze for various
|
||||||
|
properties (spending conditions, correctness, security properties, malleability, ...). It can be
|
||||||
|
targeted by spending policy compilers. Finally, compatible scripts can easily be converted to
|
||||||
|
Miniscript form - avoiding the need for additional metadata for e.g. signing devices that support
|
||||||
|
it.
|
||||||
|
|
||||||
|
## Specification
|
||||||
|
|
||||||
|
These specifications apply to P2WSH ([BIP 141](bip-0141.mediawiki)) and Tapscript ([BIP 342](bip-0342.mediawiki)) scripts, with only minor
|
||||||
|
variations between the two. Differences are noted inline. Unless explicitly stated otherwise,
|
||||||
|
specifications apply to both. P2SH and bare scripts are excluded from this specification.
|
||||||
|
|
||||||
|
### Translation Table
|
||||||
|
|
||||||
|
Miniscript consists of a set of script *fragments* which are designed to be safely and correctly composable.
|
||||||
|
|
||||||
|
This table shows all Miniscript *fragments* and their associated semantics and Bitcoin Script.
|
||||||
|
Fragments that do not change the semantics of their subexpressions are called *wrappers*. Normal
|
||||||
|
fragments use a `fragment(arg1,arg2,...)` notation, while wrappers are written using
|
||||||
|
prefixes separated from other fragments by a colon. The colon is dropped between subsequent
|
||||||
|
wrappers; e.g. `dv:older(144)` is the `d:` wrapper applied to the
|
||||||
|
`v:` wrapper applied to the `older` fragment for 144 blocks.
|
||||||
|
|
||||||
|
The `pk`, `pkh`, and `and_n` fragments and `t:`,
|
||||||
|
`l:`, and `u:` wrappers are syntactic sugar for other Miniscripts, as listed
|
||||||
|
in the table below. Note that `<20>` are in hex representation in this document.
|
||||||
|
|
||||||
|
Miniscript fragments are expected to be used in [BIP 382](bip-0382.mediawiki) `wsh()` descriptors
|
||||||
|
and [BIP 386](bip-0386.mediawiki) `tr()` descriptors. Key expressions are specified in
|
||||||
|
[BIP 380](bip-0380.mediawiki#user-content-Key_Expressions). Additionally, BIPs 382 and 386 specify
|
||||||
|
restrictions on key expressions and what they resolve to - these apply to key expressions in
|
||||||
|
Miniscript. BIP 382's key expression restrictions apply to Miniscript in P2WSH contexts, and BIP
|
||||||
|
386's key expression restrictions apply to Miniscript in P2TR contexts. From a user's perspective,
|
||||||
|
Miniscript is not a separate language, but rather a significant expansion of the descriptor language.
|
||||||
|
|
||||||
|
| Semantics | Miniscript Fragment | Bitcoin Script
|
||||||
|
|----------------------------------------------------------|-------------------------------|---------------
|
||||||
|
| false | `0` | `0`
|
||||||
|
| true | `1` | `1`
|
||||||
|
| check(key) | `pk_k(key)` | `<key>`
|
||||||
|
| | `pk_h(key)` | `DUP HASH160 <HASH160(key)> EQUALVERIFY `
|
||||||
|
| | `pk(key)` = `c:pk_k(key)` | `<key> CHECKSIG`
|
||||||
|
| | `pkh(key)` = `c:pk_h(key)` | `DUP HASH160 <HASH160(key)> EQUALVERIFY CHECKSIG`
|
||||||
|
| nSequence ≥ n (and compatible) | `older(n)` | `<n> CHECKSEQUENCEVERIFY`
|
||||||
|
| nLockTime ≥ n (and compatible) | `after(n)` | `<n> CHECKLOCKTIMEVERIFY`
|
||||||
|
| len(x) = 32 and SHA256(x) = h | `sha256(h)` | `SIZE <20> EQUALVERIFY SHA256 <h> EQUAL`
|
||||||
|
| len(x) = 32 and HASH256(x) = h | `hash256(h)` | `SIZE <20> EQUALVERIFY HASH256 <h> EQUAL`
|
||||||
|
| len(x) = 32 and RIPEMD160(x) = h | `ripemd160(h)` | `SIZE <20> EQUALVERIFY RIPEMD160 <h> EQUAL`
|
||||||
|
| len(x) = 32 and HASH160(x) = h | `hash160(h)` | `SIZE <20> EQUALVERIFY HASH160 <h> EQUAL`
|
||||||
|
| (X and Y) or Z | `andor(X,Y,Z)` | `[X] NOTIF [Z] ELSE [Y] ENDIF`
|
||||||
|
| X and Y | `and_v(X,Y)` | `[X] [Y]`
|
||||||
|
| | `and_b(X,Y)` | `[X] [Y] BOOLAND`
|
||||||
|
| | `and_n(X,Y)` = `andor(X,Y,0)` | `[X] NOTIF 0 ELSE [Y] ENDIF`
|
||||||
|
| X or Z | `or_b(X,Z)` | `[X] [Z] BOOLOR`
|
||||||
|
| | `or_c(X,Z)` | `[X] NOTIF [Z] ENDIF`
|
||||||
|
| | `or_d(X,Z)` | `[X] IFDUP NOTIF [Z] ENDIF`
|
||||||
|
| | `or_i(X,Z)` | `IF [X] ELSE [Z] ENDIF`
|
||||||
|
| X_1 + ... + X_n = k | `thresh(k,X_1,...,X_n)` | `[X_1] [X_2] ADD ... [X_n] ADD ... <k> EQUAL`
|
||||||
|
| check(key_1) + ... + check(key_n) = k *(P2WSH only)* | `multi(k,key_1,...,key_n)` | `<k> <key_1> ... <key_n> <n> CHECKMULTISIG`
|
||||||
|
| check(key_1) + ... + check(key_n) = k *(Tapscript only)* | `multi_a(k,key_1,...,key_n)` | `<key_1> CHECKSIG <key_2> CHECKSIGADD ... <key_n> CHECKSIGADD <k> NUMEQUAL`
|
||||||
|
| X (identities) | `a:X` | `TOALTSTACK [X] FROMALTSTACK`
|
||||||
|
| | `s:X` | `SWAP [X]`
|
||||||
|
| | `c:X` | `[X] CHECKSIG`
|
||||||
|
| | `t:X` = `and_v(X,1)` | `[X] 1`
|
||||||
|
| | `d:X` | `DUP IF [X] ENDIF`
|
||||||
|
| | `v:X` | `[X] VERIFY (or VERIFY version of last opcode in [X])`
|
||||||
|
| | `j:X` | `SIZE 0NOTEQUAL IF [X] ENDIF`
|
||||||
|
| | `n:X` | `[X] 0NOTEQUAL`
|
||||||
|
| | `l:X` = `or_i(0,X)` | `IF 0 ELSE [X] ENDIF`
|
||||||
|
| | `u:X` = `or_i(X,0)` | `IF [X] ELSE 0 ENDIF`
|
||||||
|
|
||||||
|
### Type System
|
||||||
|
|
||||||
|
Not every Miniscript expression can be composed with every other. Some return their result by
|
||||||
|
putting true or false on the stack; others can only abort or continue. Some require subexpressions
|
||||||
|
that consume an exactly known number of arguments, while others need a subexpression that has a
|
||||||
|
nonzero top stack element to satisfy. To model all these properties, we define a correctness type
|
||||||
|
system for Miniscript.
|
||||||
|
|
||||||
|
#### Correctness
|
||||||
|
|
||||||
|
Every miniscript expression has one of four basic types: "**B**" (base), "**V**" (verify),
|
||||||
|
"**K**" (key) and "**W**" (wrapped). Then there are 5 type modifiers that guarantee additional
|
||||||
|
properties: "**z**" (zero-arg), "**o**" (one-arg), "**n**" (nonzero), "**d**"
|
||||||
|
(dissatisfiable), and "**u**" (unit).
|
||||||
|
|
||||||
|
The following table lists the correctness requirements for each of the Miniscript expressions, and
|
||||||
|
its type properties in function of those of their subexpressions.
|
||||||
|
|
||||||
|
| Miniscript | Requires | Type | Properties
|
||||||
|
|------------------------------|-------------------------------------------------------|-------------|-----------
|
||||||
|
| `0` | | B | z; u; d
|
||||||
|
| `1` | | B | z; u
|
||||||
|
| `pk_k(key)` | | K | o; n; d; u
|
||||||
|
| `pk_h(key)` | | K | n; d; u
|
||||||
|
| `older(n)`, `after(n)` | 1 ≤ n < 2<sup>31</sup> | B | z
|
||||||
|
| `sha256(h)` | | B | o; n; d; u
|
||||||
|
| `ripemd160(h)` | | B | o; n; d; u
|
||||||
|
| `hash256(h)` | | B | o; n; d; u
|
||||||
|
| `hash160(h)` | | B | o; n; d; u
|
||||||
|
| `andor(X,Y,Z)` | X is Bdu; Y and Z are both B, K, or V | same as Y/Z | z=z<sub>X</sub>z<sub>Y</sub>z<sub>Z</sub>; o=z<sub>X</sub>o<sub>Y</sub>o<sub>Z</sub> or o<sub>X</sub>z<sub>Y</sub>z<sub>Z</sub>; u=u<sub>Y</sub>u<sub>Z</sub>; d=d<sub>Z</sub>
|
||||||
|
| `and_v(X,Y)` | X is V; Y is B, K, or V | same as Y | z=z<sub>X</sub>z<sub>Y</sub>; o=z<sub>X</sub>o<sub>Y</sub> or z<sub>Y</sub>o<sub>X</sub>; n=n<sub>X</sub> or z<sub>X</sub>n<sub>Y</sub>; u=u<sub>Y</sub>
|
||||||
|
| `and_b(X,Y)` | X is B; Y is W | B | z=z<sub>X</sub>z<sub>Y</sub>; o=z<sub>X</sub>o<sub>Y</sub> or z<sub>Y</sub>o<sub>X</sub>; n=n<sub>X</sub> or z<sub>X</sub>n<sub>Y</sub>; d=d<sub>X</sub>d<sub>Y</sub>; u
|
||||||
|
| `or_b(X,Z)` | X is Bd; Z is Wd | B | z=z<sub>X</sub>z<sub>Z</sub>; o=z<sub>X</sub>o<sub>Z</sub> or z<sub>Z</sub>o<sub>X</sub>; d; u
|
||||||
|
| `or_c(X,Z)` | X is Bdu; Z is V | V | z=z<sub>X</sub>z<sub>Z</sub>; o=o<sub>X</sub>z<sub>Z</sub>
|
||||||
|
| `or_d(X,Z)` | X is Bdu; Z is B | B | z=z<sub>X</sub>z<sub>Z</sub>; o=o<sub>X</sub>z<sub>Z</sub>; d=d<sub>Z</sub>; u=u<sub>Z</sub>
|
||||||
|
| `or_i(X,Z)` | both are B, K, or V | same as X/Z | o=z<sub>X</sub>z<sub>Z</sub>; u=u<sub>X</sub>u<sub>Z</sub>; d=d<sub>X</sub> or d<sub>Z</sub>
|
||||||
|
| `thresh(k,X_1,...,X_n)` | 1 ≤ k ≤ n; X<sub>1</sub> is Bdu; others are Wdu | B | z=all are z; o=all are z except one is o; d; u
|
||||||
|
| `multi(k,key_1,...,key_n)` | 1 ≤ k ≤ n ≤ 20 | B | n; d; u
|
||||||
|
| `multi_a(k,key_1,...,key_n)` | 1 ≤ k ≤ n | B | d; u
|
||||||
|
| `a:X` | X is B | W | d=d<sub>X</sub>; u=u<sub>X</sub>
|
||||||
|
| `s:X` | X is Bo | W | d=d<sub>X</sub>; u=u<sub>X</sub>
|
||||||
|
| `c:X` | X is K | B | o=o<sub>X</sub>; n=n<sub>X</sub>; d=d<sub>X</sub>; u
|
||||||
|
| `d:X` | X is Vz | B | o; n; d; *(Tapscript only)* u
|
||||||
|
| `v:X` | X is B | V | z=z<sub>X</sub>; o=o<sub>X</sub>; n=n<sub>X</sub>
|
||||||
|
| `j:X` | X is Bn | B | o=o<sub>X</sub>; n; d; u=u<sub>X</sub>
|
||||||
|
| `n:X` | X is B | B | z=z<sub>X</sub>; o=o<sub>X</sub>; n=n<sub>X</sub>; d=d<sub>X</sub>; u
|
||||||
|
|
||||||
|
#### Timelock Type Mixing
|
||||||
|
|
||||||
|
There is one additional correctness property that Miniscript expressions must satisfy:
|
||||||
|
the four timelock types (absolute time based, absolute height based, relative time based, and
|
||||||
|
relative height based) must not be mixed in an incompatible way.
|
||||||
|
|
||||||
|
Within `and` combinators and the `thresh` combinator where k >= 2, it is illegal for both absolute
|
||||||
|
height based and time based timelocks to appear, or for both relative height based and time based
|
||||||
|
timelocks to appear.
|
||||||
|
|
||||||
|
For all other combinators, it is legal to mix timelock types. It is also always legal to
|
||||||
|
mix absolute and relative timelocks (even if one is height based and the other is time based).
|
||||||
|
|
||||||
|
#### Malleability
|
||||||
|
|
||||||
|
Malleability is the ability for a third party (someone who does *not* hold a participating private
|
||||||
|
key) to modify an existing satisfaction into another valid satisfaction. To analyze the
|
||||||
|
malleability guarantees of a script we define three additional type properties: "**s**" (signed),
|
||||||
|
"**f**" (forced) and "**e**" (expressive).
|
||||||
|
|
||||||
|
The following table lists the malleability properties and requirement of each fragment.
|
||||||
|
|
||||||
|
| Miniscript | Requires | Properties
|
||||||
|
|------------------------------|---------------------------------------------------------------------|-----------
|
||||||
|
| `0` | | s, e
|
||||||
|
| `1` | | f
|
||||||
|
| `pk_k(key)` | | s, e
|
||||||
|
| `pk_h(key)` | | s, e
|
||||||
|
| `older(n)` | | f
|
||||||
|
| `after(n)` | | f
|
||||||
|
| `sha256(h)` | |
|
||||||
|
| `ripemd160(h)` | |
|
||||||
|
| `hash256(h)` | |
|
||||||
|
| `hash160(h)` | |
|
||||||
|
| `andor(X,Y,Z)` | e<sub>X</sub> and (s<sub>X</sub> or s<sub>Y</sub> or s<sub>Z</sub>) | s=s<sub>Z</sub> and (s<sub>X</sub> or s<sub>Y</sub>); f=f<sub>Z</sub> and (s<sub>X</sub> or f<sub>Y</sub>); e=e<sub>Z</sub> and (s<sub>X</sub> or f<sub>Y</sub>)
|
||||||
|
| `and_v(X,Y)` | | s=s<sub>X</sub> or s<sub>Y</sub>; f=s<sub>X</sub> or f<sub>Y</sub>
|
||||||
|
| `and_b(X,Y)` | | s=s<sub>X </sub>or s<sub>Y;</sub> f=f<sub>Xf</sub><sub>Y</sub> or s<sub>X</sub>f<sub>X</sub> or s<sub>Y</sub>f<sub>Y</sub>; e=e<sub>X</sub>e<sub>Y</sub>s<sub>X</sub>s<sub>Y</sub>
|
||||||
|
| `or_b(X,Z)` | e<sub>Xe</sub><sub>Z </sub>and (s<sub>X</sub> or s<sub>Z</sub>) | s=s<sub>X</sub>s<sub>Z</sub>; e
|
||||||
|
| `or_c(X,Z)` | e<sub>X</sub> and (s<sub>X</sub> or s<sub>Z</sub>) | s=s<sub>X</sub>s<sub>Z</sub>; f
|
||||||
|
| `or_d(X,Z)` | e<sub>X</sub> and (s<sub>X</sub> or s<sub>Z</sub>) | s=s<sub>X</sub>s<sub>Z</sub>; f=f<sub>Z</sub>; e=e<sub>Z</sub>
|
||||||
|
| `or_i(X,Z)` | s<sub>X</sub> or s<sub>Z</sub> | s=s<sub>X</sub>s<sub>Z</sub>; f=f<sub>X</sub>f<sub>Z</sub>; e=e<sub>X</sub>f<sub>Z</sub> or e<sub>Z</sub>f<sub>X</sub>
|
||||||
|
| `thresh(k,X_1,...,X_n)` | all are e; at most k are non-s | s=at most k-1 are non-s; e=all are s
|
||||||
|
| `multi(k,key_1,...,key_n)` | | s; e
|
||||||
|
| `multi_a(k,key_1,...,key_n)` | | s; e
|
||||||
|
| `a:X` | | s=s<sub>X</sub>; f=f<sub>X</sub>; e=e<sub>X</sub>
|
||||||
|
| `s:X` | | s=s<sub>X</sub>; f=f<sub>X</sub>; e=e<sub>X</sub>
|
||||||
|
| `c:X` | | s; f=f<sub>X</sub>; e=e<sub>X</sub>
|
||||||
|
| `d:X` | | s=s<sub>X</sub>; e
|
||||||
|
| `v:X` | | s=s<sub>X</sub>; f
|
||||||
|
| `j:X` | | s=s<sub>X</sub>; e=f<sub>X
|
||||||
|
| `n:X` | | s=s<sub>X</sub>; f=f<sub>X</sub>; e=e<sub>X</sub>
|
||||||
|
|
||||||
|
### Satisfaction
|
||||||
|
|
||||||
|
The following table shows all valid satisfactions and dissatisfactions for every Miniscript, using
|
||||||
|
satisfactions and dissatisfactions of its subexpressions. Multiple possibilities are separated by
|
||||||
|
semicolons. Some options are inefficient and provably unnecessary to the satisfaction algorithm
|
||||||
|
described below, but are valid according to script rules and could be used by a malleator or other
|
||||||
|
non-standard actor. These are called *non-canonical* options, and are listed for completeness, but
|
||||||
|
~~[struckthrough]~~. The fragments where a satisfaction or dissatisfaction does not exist will
|
||||||
|
contain *(none)*. The fragments where the satisfaction or dissatisfaction is to provide no data
|
||||||
|
will contain *(empty)*.
|
||||||
|
|
||||||
|
| Miniscript | Dissatisfactions (dsat) | Satisfactions (sat)
|
||||||
|
|------------------------------|---------------------------------------------------------|--------------------
|
||||||
|
| `0` | *(empty)* | *(none)*
|
||||||
|
| `1` | *(none)* | *(empty)*
|
||||||
|
| `pk_k(key)` | 0 | sig
|
||||||
|
| `pk_h(key)` | 0 key | sig key
|
||||||
|
| `older(n)` | *(none)* | *(empty)*
|
||||||
|
| `after(n)` | *(none)* | *(empty)*
|
||||||
|
| `sha256(h)` | any 32-byte vector except the preimage | preimage
|
||||||
|
| `ripemd160(h)` | any 32-byte vector except the preimage | preimage
|
||||||
|
| `hash256(h)` | any 32-byte vector except the preimage | preimage
|
||||||
|
| `hash160(h)` | any 32-byte vector except the preimage | preimage
|
||||||
|
| `andor(X,Y,Z)` | dsat(Z) dsat(X); ~~[dsat(Y) sat(X)]~~ | sat(Y) sat(X); sat(Z) dsat(X)
|
||||||
|
| `and_v(X,Y)` | *(none)*; ~~[dsat(Y) sat(X)]~~ | sat(Y) sat(X)
|
||||||
|
| `and_b(X,Y)` | dsat(Y) dsat(X); ~~[sat(Y) dsat(X)]; [dsat(Y) sat(X)]~~ | sat(Y) sat(X)
|
||||||
|
| `or_b(X,Z)` | dsat(Z) dsat(X) | dsat(Z) sat(X); sat(Z) dsat(X); ~~[sat(Z) sat(X)]~~
|
||||||
|
| `or_c(X,Z)` | *(none)* | sat(X); sat(Z) dsat(X)
|
||||||
|
| `or_d(X,Z)` | dsat(Z) dsat(X) | sat(X); sat(Z) dsat(X)
|
||||||
|
| `or_i(X,Z)` | dsat(X) 1; dsat(Z) 0 | sat(X) 1; sat(Z) 0
|
||||||
|
| `thresh(k,X_1,...,X_n)` | All dsats; ~~[Sats/dsats with 1 ≤ #(sats) ≠ k]~~ | Sats/dsats with #(sats) = k
|
||||||
|
| `multi(k,key_1,...,key_n)` | 0 0 ... 0 (k+1 times) | 0 sig ... sig
|
||||||
|
| `multi_a(k,key_1,...,key_n)` | 0 ... 0 (n times); ~~[sig/0 with #(sig) ≠ k]~~ | sig/0 with #(sig) = k and #(sigs/0) = n
|
||||||
|
| `a:X` | dsat(X) | sat(X)
|
||||||
|
| `s:X` | dsat(X) | sat(X)
|
||||||
|
| `c:X` | dsat(X) | sat(X)
|
||||||
|
| `d:X` | 0 | sat(X) 1
|
||||||
|
| `v:X` | *(none)* | sat(X)
|
||||||
|
| `j:X` | 0; ~~[dsat(X) (if nonzero top stack)]~~ | sat(X)
|
||||||
|
| `n:X` | dsat(X) | sat(X)
|
||||||
|
|
||||||
|
#### Non-malleable Satisfaction Algorithm
|
||||||
|
|
||||||
|
In order to produce non-malleable satisfactions we make use of a function that returns the optimal
|
||||||
|
satisfaction and dissatisfaction for a given expression (if any exist), or a special DONTUSE ("don't use") value,
|
||||||
|
together with an optional HASSIG ("has signature") marker that tracks whether the solution contains at least one
|
||||||
|
signature. To implement the function:
|
||||||
|
* Invoke the function recursively for all subexpressions, obtaining all their satisfactions/dissatisfactions.
|
||||||
|
* Iterate over all the valid satisfactions/dissatisfactions in the table above (including the non-canonical ones), taking into account:
|
||||||
|
* The dissatisfactions for `sha256`, `ripemd160`, `hash256`, and `hash160` are always malleable, so instead use DONTUSE there.
|
||||||
|
* The non-canonical options for `and_b`, `or_b`, and `thresh` are always overcomplete, so instead use DONTUSE there as well (with HASSIG flag if the original non-canonical solution had one).
|
||||||
|
* The satisfactions for `pk_k`, `pk_h`, and `multi` can be marked HASSIG.
|
||||||
|
* When constructing solutions by combining results for subexpressions, the result is DONTUSE if any of the constituent results is DONTUSE. Furthermore, the result gets the HASSIG tag if any of the constituents does.
|
||||||
|
* If among all valid solutions (including DONTUSE ones) more than one does not have the HASSIG marker, return DONTUSE.
|
||||||
|
* If instead exactly one does not have the HASSIG marker, return that solution.
|
||||||
|
* If all valid solutions have the HASSIG marker, but all of them are DONTUSE, return DONTUSE-HASSIG. The HASSIG marker is important because while this represents a choice between multiple options that would cause malleability if used, they are not available to the attacker, and we may be able to avoid them entirely still.
|
||||||
|
* Otherwise, all not-DONTUSE options are valid, so return the smallest one (in terms of witness size).
|
||||||
|
|
||||||
|
To produce an overall satisfaction, invoke the function on the toplevel expression. If no valid
|
||||||
|
satisfaction is returned, or it is DONTUSE, fail. Otherwise, if any timelocking is used in the
|
||||||
|
script but the result does not have the HASSIG flag, also fail. If the satisfaction is both not
|
||||||
|
DONTUSE and HASSIG, return it.
|
||||||
|
|
||||||
|
|
||||||
|
## Discussion
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Miniscript primarily aims to provide guarantees on the correctness of a Bitcoin Script. That is, to
|
||||||
|
guarantee **consensus soundness** and **standardness completeness**. Consensus soundness means
|
||||||
|
it is not possible to construct a consensus-valid witness for a Bitcoin Script unless the Miniscript
|
||||||
|
spending conditions are met. Standardness completeness means a standardness-valid witness can be
|
||||||
|
created for all spending paths of a Miniscript, assuming the resource limits are respected and there
|
||||||
|
is no timelock mixing.
|
||||||
|
|
||||||
|
Additionally, Miniscript can guarantee the non-malleability and maximum size of a witness. These can
|
||||||
|
assist in assessing the soundness of protocols where transaction fees (and therefore transaction
|
||||||
|
size) are security-critical parameters.
|
||||||
|
|
||||||
|
Hash preimages are constrained to 32 bytes to disallow various forms of griefing, including making
|
||||||
|
non-standard (un-relayable) transactions, consensus-invalid swaps across blockchains, as well as
|
||||||
|
ensure that satisfaction cost can be accurately calculated.
|
||||||
|
|
||||||
|
In order for these properties to not just apply to script, but to an entire transaction, it's
|
||||||
|
important that the witness commits to all data relevant for verification. In practice this means
|
||||||
|
that scripts whose conditions can be met without any digital signature are insecure. Besides being
|
||||||
|
trivially insecure, note how a transaction lacking a signature check allows an attacker to change
|
||||||
|
its nLockTime and nSequence fields to meet additional timelock conditions.
|
||||||
|
|
||||||
|
### Type System
|
||||||
|
|
||||||
|
To statically verify the correctness and malleability guarantees discussed in the previous section,
|
||||||
|
we define a type system. See the specifications above for a reference of each fragment's
|
||||||
|
requirements and properties. Here we give more information about each type.
|
||||||
|
|
||||||
|
Every expression has one of four basic types:
|
||||||
|
* "**B**" Base expressions. These take their inputs from the top of the stack. When satisfied, they push a nonzero value of up to 4 bytes onto the stack. When dissatisfied, they push an exact 0 onto the stack (if dissatisfaction without aborting is possible at all). This type is used for most expressions, and required for the top level expression. An example is `older(n)` = `<n> CHECKSEQUENCEVERIFY`.
|
||||||
|
* "**V**" Verify expressions. Like "B", these take their inputs from the top of the stack. Upon satisfaction however, they continue without pushing anything. They cannot be dissatisfied (will abort instead). A "V" can be obtained using the `v:` wrapper on a "B" expression, or by combining other "V" expressions using `and_v`, `or_i`, `or_c`, or `andor`. An example is `v:pk(key)` = `<key> CHECKSIGVERIFY`.
|
||||||
|
* "**K**" Key expressions. They again take their inputs from the top of the stack, but instead of verifying a condition directly they always push a public key onto the stack, for which a signature is still required to satisfy the expression. A "K" can be converted into a "B" using the `c:` wrapper. An example is `pk_h(key)` = `DUP HASH160 <Hash160(key)> EQUALVERIFY`.
|
||||||
|
* "**W**" Wrapped expressions. They take their inputs from one below the top of the stack, and push a nonzero (in case of satisfaction) or zero (in case of dissatisfaction) either on top of the stack, or one below. So for example a 3-input "W" would take the stack "A B C D E F" and turn it into "A B F 0" or "A B 0 F" in case of dissatisfaction, and "A B F n" or "A B n F" in case of satisfaction (with n a nonzero value). Every "W" is either `s:B` (SWAP B) or `a:B` (TOALTSTACK B FROMALTSTACK). An example is `s:pk(key)` = `SWAP <key> CHECKSIG`.
|
||||||
|
|
||||||
|
Then there are 6 type modifiers, which guarantee additional properties:
|
||||||
|
* "**z**" Zero-arg: this expression always consumes exactly 0 stack elements.
|
||||||
|
* "**o**" One-arg: this expression always consumes exactly 1 stack element.
|
||||||
|
* "**n**" Nonzero: this expression always consumes at least 1 stack element, no satisfaction for this expression requires the top input stack element to be zero.
|
||||||
|
* "**d**" Dissatisfiable: a dissatisfaction for this expression can unconditionally be constructed. This implies the dissatisfaction cannot include any signature or hash preimage, and cannot rely on timelocks being satisfied.
|
||||||
|
* "**u**" Unit: when satisfied, this expression will put an exact 1 on the stack (as opposed to any nonzero value).
|
||||||
|
* "**k**" No timelock mixing. This expression does not contain a mix of heightlock and timelock of the same type. If the miniscript does not have the "k" property, the miniscript template will not match the user expectation of the corresponding spending policy.
|
||||||
|
|
||||||
|
Finally to analyze malleability guarantees we introduce 3 new type modifiers:
|
||||||
|
* "**s**" Signed: satisfying this expression always requires a signature (predicting whether all satisfactions will be HASSIG).
|
||||||
|
* "**f**" Forced: dissatisfying this expression always requires a signature (predicting whether all dissatisfactions will be HASSIG).
|
||||||
|
* "**e**" Expressive: this requires a unique unconditional dissatisfaction to exist, and forces all conditional dissatisfactions (if any) to require a signature.
|
||||||
|
|
||||||
|
|
||||||
|
### Malleability
|
||||||
|
|
||||||
|
Since Segwit, malleating a transaction no longer breaks the validity of unconfirmed descendant
|
||||||
|
transactions. However, unintentional malleability may still have a number of much weaker undesirable
|
||||||
|
effects. If a witness can be stuffed with additional data, the transaction's feerate will go down,
|
||||||
|
potentially to the point where its ability to propagate and get confirmed is impacted. Additionally,
|
||||||
|
malleability can be exploited to add roundtrips to BIP152 block propagation, by trying to get
|
||||||
|
different miners to mine different versions of the same transaction. Finally, malleability may
|
||||||
|
interfere with the usage of hash locks as a mechanism for publishing preimages.
|
||||||
|
|
||||||
|
Using the malleability type properties it is possible to determine statically whether a script can
|
||||||
|
be non-malleably satisfied under all circumstances. In many cases it is reasonable to only accept
|
||||||
|
such guaranteed-non-malleable scripts, as unexpected behavior can occur when using other scripts.
|
||||||
|
|
||||||
|
For example, when running the non-malleable satisfaction algorithm above, adding available
|
||||||
|
preimages, or increasing the nLockTime/nSequence values actually may make it fail where it succeeded
|
||||||
|
before. This is because a larger set of met conditions may mean an existing satisfaction goes from
|
||||||
|
non-malleable to malleable. Restricting things to scripts that are guaranteed to be satisfiable in a
|
||||||
|
non-malleable way avoids this problem.
|
||||||
|
|
||||||
|
When analysing Miniscripts for resource limits, restricting yourself to just non-malleable solutions
|
||||||
|
(or even non-malleable scripts) also leads to tighter bounds, as all non-canonical satisfactions and
|
||||||
|
dissatisfactions can be left out of consideration.
|
||||||
|
|
||||||
|
The malleability analysis makes the following assumptions:
|
||||||
|
* The attacker does not have access to any of the private keys of public keys that participate in the Script. Participants with private keys inherently have the ability to produce different satisfactions by creating multiple signatures. While it is also interesting to study the impact rogue participants can have, we treat it as a distinct problem.
|
||||||
|
* The attacker only has access to hash preimages that honest users have access to as well. This is a reasonable assumption because hash preimages are revealed once globally, and then available to everyone. On the other hand, making the assumption that attackers may have access to more preimages than honest users makes a large portion of scripts impossible to satisfy in a non-malleable way.
|
||||||
|
* The attacker gets to see exactly one satisfying witness of any transaction. If he sees multiple, it becomes possible for the attacker to mix and match different satisfactions. This is very hard to reason about.
|
||||||
|
* We restrict this analysis to scripts where no public key is repeated. If signatures constructed for one part of the script can be bound to other checks in the same script, a variant of the mixing from the previous point becomes available that is equally hard to reason about. Furthermore this situation can be avoided by using separate keys.
|
||||||
|
* The attacker is constrained by common standardness rules. A miner may be able to malleate a witness considered non-malleable by Miniscript.
|
||||||
|
|
||||||
|
#### Non-Malleable Satisfaction
|
||||||
|
|
||||||
|
Malleable satisfactions or dissatisfactions appear whenever options are available to attackers distinct from the one taken by honest users. This can happen for multiple reasons:
|
||||||
|
1. Two or more options for a satisfaction or dissatisfaction are listed in the table above which are both available to attackers directly. Regardless of which option is used in the honest solution, the attacker can change the solution to the other one.
|
||||||
|
2. Two or more options for a satisfaction or dissatisfaction are listed in the table above, only one of which is available to attackers, but the honest solution uses another one. In that case, the attacker can modify the solution to pick the one available to him.
|
||||||
|
3. The honest users pick a solution that contains a satisfaction which can be turned into a dissatisfaction without invalidating the overall witness. Those are called overcomplete solutions.
|
||||||
|
|
||||||
|
Because we assume attackers never have access to private keys, we can treat any solution that
|
||||||
|
includes a signature as one that is unavailable to attackers. For others, the worst case is that the
|
||||||
|
attacker has access to every solution the honest users have, but no others: for preimages this is an
|
||||||
|
explicit assumption, while timelock availability is determined by the nLockTime and nSequence fields
|
||||||
|
in the transaction. As long as the overall satisfaction includes at least one signature, those
|
||||||
|
values are fixed, and timelock availability is identical for attackers and honest users.
|
||||||
|
|
||||||
|
The description of the non-malleable satisfaction algorithm can be used to show that no
|
||||||
|
non-canonical solutions listed in the satisfaction table can occur inside non-malleable
|
||||||
|
satisfaction:
|
||||||
|
* Some of the non-canonical options (the `or_b`, `and_b`, and `thresh` ones) are overcomplete, and thus can clearly not appear in non-malleable satisfactions.
|
||||||
|
* The fact that non-"d" expressions cannot be dissatisfied in valid witnesses rules out the usage of the non-canonical `and_v` dissatisfaction.
|
||||||
|
* "d" expressions are defined to be unconditionally dissatisfiable, which implies that for those a non-HASSIG dissatisfaction must exist. Non-HASSIG solutions must be preferred over HASSIG ones (reason 2), and when multiple non-HASSIG ones exist, none can be used (reason 1). This lets us rule out the other non-canonical options in the table:
|
||||||
|
* `j:X` is always "d", its non-HASSIG dissatisfaction "0" always exists, and thus rules out any usage of "dsat(X)".
|
||||||
|
* If `andor(X,Y,Z)` is "d", a non-HASSIG dissatisfaction "dsat(Z) dsat(X)" must exist, and thus rules out any usage of "dsat(Y) sat(X)".
|
||||||
|
* If `and_b(X,Y)` is "d", a non-HASSIG dissatisfaction "dsat(Y) dsat(X)" must exist, and thus rules out any usage of "dsat(Y) sat(X)" and "sat(Y) dsat(X)". Those are also overcomplete.
|
||||||
|
* `thresh(k,...)` is always "d", a non-HASSIG dissatisfaction with just dissatisfactions must exist due to typing rules, and thus rules out usage of the other dissatisfactions. They are also overcomplete.
|
||||||
|
|
||||||
|
|
||||||
|
### Resource Limits
|
||||||
|
|
||||||
|
Various types of Bitcoin Scripts have different resource limitations, either through consensus or standardness. Some of them affect otherwise valid Miniscripts:
|
||||||
|
* In P2WSH, scripts larger than 3600 bytes are invalid by standardness. In Tapscript, scripts are implicitly bounded by the maximum size of a block (1 million virtual bytes).
|
||||||
|
* In P2WSH, script satisfactions where the total number of non-push opcodes plus the number of keys participating in all executed `CHECKMULTISIG` is above 201 are invalid by consensus.
|
||||||
|
* In both Tapscript and P2WSH, script satisfactions which make the stack exceed 1000 elements before or during execution are invalid.
|
||||||
|
* In P2WSH, satisfactions with a witness consisting of over 100 stack elements (excluding the script itself) are invalid by standardness.
|
||||||
|
|
||||||
|
A static analysis can be performed on a Miniscript to verify if none, all or any of the spending
|
||||||
|
paths hit any of the limits.
|
||||||
|
|
||||||
|
|
||||||
|
## Test Vectors
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## Backwards Compatibility
|
||||||
|
|
||||||
|
Miniscript's syntax is compatible with BIP 380 Output Script Descriptors, and should be considered
|
||||||
|
an extension to it that provides a new type of Script expression that is only valid in
|
||||||
|
`wsh()` and `tr()` contexts. As these are wholly new expressions, they are not
|
||||||
|
compatible with any existing implementation of descriptors. Additionally, the scripts produced are
|
||||||
|
unlikely to be standard scripts.
|
||||||
|
|
||||||
|
The `pk()`, `pkh()`, `multi()`, and `multi_a()`
|
||||||
|
fragments overlap with existing descriptors. These parse to the same semantic meanings as those
|
||||||
|
descriptors and produce the same scripts.
|
||||||
|
|
||||||
|
## Reference Implementation
|
||||||
|
|
||||||
|
A first reference implementation and documentation for Miniscript in P2WSH was originally published at
|
||||||
|
https://github.com/sipa/miniscript .
|
||||||
|
|
||||||
|
The reference implementation for Miniscript in P2WSH was introduced in Bitcoin Core through PRs
|
||||||
|
[24147](https://github.com/bitcoin/bitcoin/pull/24147), [24148](https://github.com/bitcoin/bitcoin/pull/24148), and
|
||||||
|
[24149](https://github.com/bitcoin/bitcoin/pull/24149). The last one to be merged was released in Bitcoin
|
||||||
|
Core version 25.0.
|
||||||
|
|
||||||
|
The reference implementation for Miniscript in Tapscript was introduced in Bitcoin Core in PR
|
||||||
|
[27255](https://github.com/bitcoin/bitcoin/pull/27255). This PR was merged and released in Bitcoin Core
|
||||||
|
version 26.0.
|
|
@ -6,7 +6,7 @@
|
||||||
Ava Chow <me@achow101.com>
|
Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0380
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0380
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2021-06-27
|
Created: 2021-06-27
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
@ -332,4 +332,7 @@ This Table lists all available Script expressions and the BIPs specifying them.
|
||||||
|-
|
|-
|
||||||
| <tt>tr(KEY)</tt>, <tt>tr(KEY, TREE)</tt>
|
| <tt>tr(KEY)</tt>, <tt>tr(KEY, TREE)</tt>
|
||||||
| [[bip-0386.mediawiki|386]]
|
| [[bip-0386.mediawiki|386]]
|
||||||
|
|-
|
||||||
|
| <tt>musig(KEY, KEY, ..., KEY)</tt>
|
||||||
|
| [[bip-0390.mediawiki|390]]
|
||||||
|}
|
|}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Ava Chow <me@achow101.com>
|
Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0381
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0381
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2021-06-27
|
Created: 2021-06-27
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
@ -111,7 +111,7 @@ Invalid descriptors
|
||||||
|
|
||||||
* <tt>pk()</tt> only accepts key expressions: <tt>pk(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
* <tt>pk()</tt> only accepts key expressions: <tt>pk(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
||||||
* <tt>pkh()</tt> only accepts key expressions: <tt>pkh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
* <tt>pkh()</tt> only accepts key expressions: <tt>pkh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
||||||
* <tt>sh()</tt> only acceps script expressions: <tt>sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)</tt>
|
* <tt>sh()</tt> only accepts script expressions: <tt>sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)</tt>
|
||||||
* <tt>sh()</tt> is top level only: <tt>sh(sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))</tt>
|
* <tt>sh()</tt> is top level only: <tt>sh(sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))</tt>
|
||||||
|
|
||||||
==Backwards Compatibility==
|
==Backwards Compatibility==
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Ava Chow <me@achow101.com>
|
Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0382
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0382
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2021-06-27
|
Created: 2021-06-27
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Ava Chow <me@achow101.com>
|
Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0383
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0383
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2021-06-27
|
Created: 2021-06-27
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Ava Chow <me@achow101.com>
|
Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0384
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0384
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2021-06-27
|
Created: 2021-06-27
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Ava Chow <me@achow101.com>
|
Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0385
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0385
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2021-06-27
|
Created: 2021-06-27
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Ava Chow <me@achow101.com>
|
Ava Chow <me@achow101.com>
|
||||||
Comments-Summary: No comments yet.
|
Comments-Summary: No comments yet.
|
||||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0386
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0386
|
||||||
Status: Draft
|
Status: Final
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Created: 2021-06-27
|
Created: 2021-06-27
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
@ -101,7 +101,7 @@ Valid descriptors followed by the scripts they produce. Descriptors involving de
|
||||||
|
|
||||||
Invalid Descriptors
|
Invalid Descriptors
|
||||||
|
|
||||||
* Uncompressed private key: <tt>tr(5kyzdueo39z3fprtux2qbbwgnnp5ztd7yyr2sc1j299sbcnwjss)</tt>
|
* Uncompressed private key: <tt>tr(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)</tt>
|
||||||
* Uncompressed public key: <tt>tr(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)</tt>
|
* Uncompressed public key: <tt>tr(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)</tt>
|
||||||
* <tt>tr()</tt> nested in <tt>wsh</tt>: <tt>wsh(tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
* <tt>tr()</tt> nested in <tt>wsh</tt>: <tt>wsh(tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
||||||
* <tt>tr()</tt> nested in <tt>sh</tt>: <tt>sh(tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
* <tt>tr()</tt> nested in <tt>sh</tt>: <tt>sh(tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
||||||
|
|
101
bip-0387.mediawiki
Normal file
101
bip-0387.mediawiki
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 387
|
||||||
|
Layer: Applications
|
||||||
|
Title: Tapscript Multisig Output Script Descriptors
|
||||||
|
Author: Pieter Wuille <pieter@wuille.net>
|
||||||
|
Ava Chow <me@achow101.com>
|
||||||
|
Comments-Summary: No comments yet.
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0387
|
||||||
|
Status: Final
|
||||||
|
Type: Informational
|
||||||
|
Created: 2024-04-17
|
||||||
|
License: BSD-2-Clause
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
==Abstract==
|
||||||
|
|
||||||
|
This document specifies <tt>multi_a()</tt> and <tt>sortedmulti_a()</tt> output script descriptors.
|
||||||
|
Like BIP 383's <tt>multi()</tt> and <tt>sortedmulti()</tt>, both functions take a threshold and one
|
||||||
|
or more public keys and produce a multisig script. The primary distinction is that <tt>multi_a()</tt>
|
||||||
|
and <tt>sortedmulti_a()</tt> only produce tapscripts and are only allowed in a tapscript context.
|
||||||
|
|
||||||
|
==Copyright==
|
||||||
|
|
||||||
|
This BIP is licensed under the BSD 2-clause license.
|
||||||
|
|
||||||
|
==Motivation==
|
||||||
|
|
||||||
|
The most common complex script used in Bitcoin is a threshold multisig.
|
||||||
|
These expressions allow specifying multisig scripts as a descriptor.
|
||||||
|
|
||||||
|
==Specification==
|
||||||
|
|
||||||
|
Two new script expressions are defined: <tt>multi_a()</tt> and <tt>sortedmulti_a()</tt>.
|
||||||
|
Both expressions produce the scripts of the same template and take the same arguments.
|
||||||
|
They are written as <tt>multi_a(k,KEY_1,KEY_2,...,KEY_n)</tt>.
|
||||||
|
<tt>k</tt> is the threshold - the number of keys that must sign the input for the script to be valid.
|
||||||
|
<tt>KEY_1,KEY_2,...,KEY_n</tt> are the key expressions for the multisig. <tt>k</tt> must be less than or equal to <tt>n</tt>.
|
||||||
|
|
||||||
|
<tt>multi_a()</tt> and <tt>sortedmulti_a()</tt> expressions can only be used inside of a <tt>tr()</tt> descriptor.
|
||||||
|
The maximum number of keys is 999.
|
||||||
|
|
||||||
|
The output script produced also depends on the value of <tt>k</tt>. If <tt>k</tt> is less than or equal to 16:
|
||||||
|
<pre>
|
||||||
|
KEY_1 OP_CHECKSIG KEY_2 OP_CHECKSIGADD ... KEY_n OP_CHECKSIGADD OP_k OP_NUMEQUAL
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
if <tt>k</tt> is greater than 16:
|
||||||
|
<pre>
|
||||||
|
KEY_1 OP_CHECKSIG KEY_2 OP_CHECKSIGADD ... KEY_n OP_CHECKSIGADD k OP_NUMEQUAL
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
===<tt>sortedmulti_a()</tt>===
|
||||||
|
|
||||||
|
The only change for <tt>sortedmulti_a()</tt> is that the x-only public keys are sorted lexicographically prior to the creation of the output script.
|
||||||
|
This sorting is on the keys that are to be put into the output script, i.e. after all extended keys are derived.
|
||||||
|
|
||||||
|
===Multiple Extended Keys</tt>===
|
||||||
|
|
||||||
|
When one or more of the key expressions in a <tt>multi_a()</tt> or <tt>sortedmulti_a()</tt> expression are extended keys, the derived keys use the same child index.
|
||||||
|
This changes the keys in lockstep and allows for output scripts to be indexed in the same way that the derived keys are indexed.
|
||||||
|
|
||||||
|
==Test Vectors==
|
||||||
|
|
||||||
|
Valid descriptors followed by the scripts they produce. Descriptors involving derived child keys will have the 0th, 1st, and 2nd scripts listed.
|
||||||
|
|
||||||
|
* <tt>tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,multi_a(1,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))</tt>
|
||||||
|
** <tt>5120eb5bd3894327d75093891cc3a62506df7d58ec137fcd104cdd285d67816074f3</tt>
|
||||||
|
* <tt>tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,multi_a(1,669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))</tt>
|
||||||
|
** <tt>5120eb5bd3894327d75093891cc3a62506df7d58ec137fcd104cdd285d67816074f3</tt>
|
||||||
|
* <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,multi_a(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))</tt>
|
||||||
|
** <tt>51202eea93581594a43c0c8423b70dc112e5651df63984d108d4fc8ccd3b63b4eafa</tt>
|
||||||
|
* <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,sortedmulti_a(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))</tt>
|
||||||
|
** <tt>512016fa6a6ba7e98c54b5bf43b3144912b78a61b60b02f6a74172b8dcb35b12bc30</tt>
|
||||||
|
* <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,sortedmulti_a(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*))</tt>
|
||||||
|
** <tt>5120abd47468515223f58a1a18edfde709a7a2aab2b696d59ecf8c34f0ba274ef772</tt>
|
||||||
|
** <tt>5120fe62e7ed20705bd1d3678e072bc999acb014f07795fa02cb8f25a7aa787e8cbd</tt>
|
||||||
|
** <tt>51201311093750f459039adaa2a5ed23b0f7a8ae2c2ffb07c5390ea37e2fb1050b41</tt>
|
||||||
|
* <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,multi_a(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))</tt>
|
||||||
|
** <tt>5120e4c8f2b0a7d3a688ac131cb03248c0d4b0a59bbd4f37211c848cfbd22a981192</tt>
|
||||||
|
** <tt>5120827faedaa21e52fca2ac83b53afd1ab7d4d1e6ce67ff42b19f2723d48b5a19ab</tt>
|
||||||
|
** <tt>5120647495ed09de61a3a324704f9203c130d655bf3141f9b748df8f7be7e9af55a4</tt>
|
||||||
|
|
||||||
|
Invalid descriptors
|
||||||
|
|
||||||
|
* Unsupported top level: <tt>multi_a(1,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0)</tt>
|
||||||
|
* Unsupported <tt>sh()</tt> context: <tt>sh(multi_a(1,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))</tt>
|
||||||
|
* Unsupported <tt>wsh()</tt> context: <tt>wsh(multi_a(1,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))</tt>
|
||||||
|
* Invalid threshold: <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,multi_a(a,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
||||||
|
* Threshold of 0: <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,multi_a(0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))</tt>
|
||||||
|
* Uncompressed pubkey: <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,multi_a(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))</tt>
|
||||||
|
* Threshold larger than keys: <tt>tr(50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,multi_a(3,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))</tt>
|
||||||
|
|
||||||
|
==Backwards Compatibility==
|
||||||
|
|
||||||
|
<tt>multi_a()</tt> and <tt>sortedmulti_a()</tt> descriptors use the format and general operation specified in [[bip-0380.mediawiki|380]].
|
||||||
|
As these are wholly new descriptors, they are not compatible with any implementation.
|
||||||
|
However, the scripts produced are standard scripts, so existing software are likely to be familiar with them.
|
||||||
|
|
||||||
|
==Reference Implementation==
|
||||||
|
|
||||||
|
<tt>multi_a()</tt> and <tt>sortedmulti_a()</tt> descriptors were implemented in Bitcoin Core in https://github.com/bitcoin/bitcoin/pull/24043 and have been available since version 24.0.
|
348
bip-0388.mediawiki
Normal file
348
bip-0388.mediawiki
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
<pre>
|
||||||
|
BIP: 388
|
||||||
|
Layer: Applications
|
||||||
|
Title: Wallet Policies for Descriptor Wallets
|
||||||
|
Author: Salvatore Ingala <salvatoshi@protonmail.com>
|
||||||
|
Comments-Summary: No comments yet.
|
||||||
|
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0388
|
||||||
|
Status: Proposed
|
||||||
|
Type: Standards Track
|
||||||
|
Created: 2022-11-16
|
||||||
|
License: BSD-2-Clause
|
||||||
|
Post-History: 2022-05-10: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-May/020423.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
== Abstract ==
|
||||||
|
|
||||||
|
Software wallets and hardware signing devices typically partition funds into separate "accounts". When signing or visualizing a transaction, aggregate flows of funds of all accounts affected by the transaction may (and should) be displayed to the user.
|
||||||
|
|
||||||
|
Wallet policies build on top of output script descriptors to represent such accounts in a compact, reviewable way.
|
||||||
|
An account encompasses a logical group of receive and change addresses, and each wallet policy represents all descriptors necessary to describe an account in its entirety.
|
||||||
|
|
||||||
|
We simplify the language to suit devices with limited memory, where even keeping the entire descriptor in memory could be a major hurdle, by reducing the generality of descriptors to just the essential features and by separating the extended pubkeys and other key information from the descriptor.
|
||||||
|
|
||||||
|
This results in a more compact representation and simplifies the inspection of the policy by the user.
|
||||||
|
|
||||||
|
The compilation of wallet policies to the corresponding descriptor is trivial, and the reverse process is easy for supported descriptors, because the language is kept similar to that of output script descriptors.
|
||||||
|
|
||||||
|
== Copyright ==
|
||||||
|
|
||||||
|
This BIP is licensed under the BSD 2-clause license.
|
||||||
|
|
||||||
|
== Motivation ==
|
||||||
|
|
||||||
|
''[[bip-0380.mediawiki|Output Script Descriptors]]'' were introduced in Bitcoin Core as a way to represent collections of output scripts. It is a general and flexible language, designed to catch all the possible use-cases of bitcoin wallets (that is, if you know the script and you have the necessary keys, it will be possible to sign transactions with any descriptor-based software wallet).
|
||||||
|
|
||||||
|
Unfortunately, descriptors are not a perfect match for the typical usage of hardware signing devices (often also called ''hardware wallets''). Most of them have some of the following limitations when compared to a general-purpose machine running Bitcoin Core:
|
||||||
|
|
||||||
|
* they are embedded devices with limited RAM, and computational power;
|
||||||
|
* they cannot import additional private keys (that is, they can only sign with keys derived from a single seed via [[bip-0032.mediawiki|BIP-32]]);
|
||||||
|
* they have limited storage, or they might not have persistent storage at all (''stateless design'').
|
||||||
|
|
||||||
|
Moreover, other limitations like the limited size of the screen might affect what design choices are available in practice. Therefore, minimizing the amount of information shown on-screen is important for a good user experience. The ability for the user to completely validate on-screen the kind of script used (and each of the involved keys) is crucial for secure usage, as the machine that is interacting with the hardware signer (and running the software wallet) is considered untrusted.
|
||||||
|
|
||||||
|
A more native, compact representation of the wallet receive and change addresses might also benefit the UX of software wallets when they use descriptors (possibly with miniscript) for representing complex locking conditions.
|
||||||
|
|
||||||
|
We remark that wallet policies are not related to the ''policy'' language, a higher level language that can be compiled to [[bip-0379.md|miniscript]].
|
||||||
|
|
||||||
|
=== Security, privacy and UX concerns for hardware signing devices ===
|
||||||
|
|
||||||
|
The usage of complex scripts presents challenges in terms of both security and user experience for a hardware signing device.
|
||||||
|
|
||||||
|
==== Security issues ====
|
||||||
|
|
||||||
|
Hardware signing devices strive to guarantee that no action can be performed without the user’s consent as long as the user correctly verifies the information that is shown on the device’s screen before approving.
|
||||||
|
|
||||||
|
This must hold even in scenarios where the attacker has full control of the machine that is connected to the signing device, and can execute arbitrary requests, or tamper with the legitimate user's requests.
|
||||||
|
|
||||||
|
Therefore, it is not at all trivial to allow complex scripts, especially if they contain keys that belong to third parties.
|
||||||
|
The hardware signing device must guarantee that the user knows precisely what "policy" is being used to spend the funds, and that any "unspent" funds (if any) that is sent to a change address will be protected by the same policy.
|
||||||
|
|
||||||
|
This makes it impossible for an attacker to surreptitiously modify the policy, therefore stealing or burning the user's funds.
|
||||||
|
|
||||||
|
==== Avoiding key reuse ====
|
||||||
|
|
||||||
|
Reusing public keys within a script is a source of malleability when using miniscript policies, which has potential security implications.
|
||||||
|
|
||||||
|
Reusing keys across different UTXOs harms user privacy by allowing external parties to link these UTXOs to the same entity once they are spent.
|
||||||
|
|
||||||
|
By constraining the derivation path patterns to have a uniform structure, wallet policies prevent key reuse among the same or different UTXOs of the same account.
|
||||||
|
|
||||||
|
It is strongly recommended to avoid key reuse across accounts. Distinct public keys per account can be guaranteed by using distinct hardened derivation paths. This specification does not mandate hardened derivation in order to maintain compatibility with existing deployments that do not adhere to this recommendation.
|
||||||
|
|
||||||
|
It is out of scope for this document to guarantee that users do not reuse extended public keys among different wallet accounts. This is still very important, but the responsibility is left to the users and their software wallet.
|
||||||
|
|
||||||
|
==== UX issues ====
|
||||||
|
|
||||||
|
Miniscript (and taproot trees) allow substantially more complex spending policies. It is a challenge to ensure that the user can practically verify such spending policies per the screen.
|
||||||
|
|
||||||
|
We set two fundamental design goals:
|
||||||
|
* Minimize the amount of information that is shown on screen - so that the user can actually validate it.
|
||||||
|
* Minimize the number of times the user has to validate such information.
|
||||||
|
|
||||||
|
Designing a secure protocol for the coordination of a descriptor wallet among distant parties is also a challenging problem that is out of the scope of this document. See [[bip-0129.mediawiki|BIP-129 (Bitcoin Secure Multisig Setup)]] for an approach designed for multisig wallets. Regardless of the approach, the ability for the user to carefully verify all the details of the spending policies using the hardware signer's screen is a prerequisite for security in adversarial environments.
|
||||||
|
|
||||||
|
=== Policy registration as a solution ===
|
||||||
|
|
||||||
|
A solution to address the security concerns, and part of the UX concerns, is to have a registration flow for the wallet policy in the hardware signing device. The ''wallet policy'' must contain enough information to generate all the relevant addresses/scripts, and for the hardware signing device to identify the keys that it controls and that are needed to spend the funds sent to those addresses.
|
||||||
|
|
||||||
|
Before a new policy is used for the first time, the user will register a wallet policy into the hardware device. While the details of the process are out of scope in this document, the flow should be something similar to the following:
|
||||||
|
|
||||||
|
# The software wallet initiates a ''wallet policy registration'' on the hardware signing device; the information should include the wallet policy, but also a unique ''name'' that identifies the policy.
|
||||||
|
# The device shows the wallet policy to the user using the secure screen.
|
||||||
|
# After inspecting the policy and comparing it with a trusted source (for example a printed backup), the user approves the policy.
|
||||||
|
# If stateful, the hardware signing device persists the policy in its permanent memory; if stateless, it returns a "proof of registration".
|
||||||
|
|
||||||
|
The '''proof of registration''' will allow the hardware signer to verify that a certain policy was indeed previously approved by the user, and is therefore safe to use without repeating the expensive user verification procedure. The details of how to create a proof of registration are out of scope for this document; using a Message Authentication Code on a hash committing to the wallet policy, its name and any additional metadata is an effective solution if correctly executed.
|
||||||
|
|
||||||
|
Once a policy is registered, the hardware signing device can perform the typical operations securely:
|
||||||
|
* generating receive and change addresses;
|
||||||
|
* showing addresses on the secure screen;
|
||||||
|
* sign transactions spending from a wallet, while correctly identifying change addresses and computing the transaction fees.
|
||||||
|
|
||||||
|
Before any of the actions mentioned above, the hardware signing device will retrieve the policy from its permanent storage if stateful; if stateless it will validate the proof of registration before using the wallet policy provided by the client.
|
||||||
|
|
||||||
|
Once the previously registered policy is correctly identified and approved by the user (for example by showing its name), and as long as the policy registration was executed securely, hardware signing devices can provide a user experience similar to the usual one for single-signature transactions.
|
||||||
|
|
||||||
|
=== Avoiding blowup in descriptor size ===
|
||||||
|
|
||||||
|
While reusing a pubkey in different branches of a miniscript is explicitly forbidden by miniscript (as it has certain negative security implications), it is still reasonable to reuse the same xpub in multiple places, albeit with different final steps of derivation (so that the actual pubkeys that are used in the script are indeed different).
|
||||||
|
|
||||||
|
In fact, there are many reasonable spending policies with a quadratic size in the number of participants. For example, using Taproot, a 3-of-5 threshold wallet could use:
|
||||||
|
* a key path with a 5-of-5 [[bip-0327.mediawiki|MuSig2]] aggregated key
|
||||||
|
* a script tree with 11 leaves:
|
||||||
|
** 10 different scripts using a 3-of-3 MuSig2 aggregated key, plus
|
||||||
|
** a final leaf with a fallback 3-of-5 multisig using <tt>multi_a</tt> (in case interactive signing is not available).
|
||||||
|
|
||||||
|
With each xpub being 118 bytes long, the repetition of xpubs makes the descriptor become extremely large.
|
||||||
|
|
||||||
|
Replacing the common part of the key with a short key placeholder and organizing all the key expressions in a separate list helps to keep the size of the wallet policy small, which is crucial to allow human inspection during the registration flow.
|
||||||
|
|
||||||
|
== Specification ==
|
||||||
|
|
||||||
|
This section formally defines wallet policies, and how they relate to output script descriptors.
|
||||||
|
|
||||||
|
=== Formal definition ===
|
||||||
|
|
||||||
|
A ''wallet policy'' is composed by a ''wallet descriptor template'', together with a vector of ''key information items''.
|
||||||
|
|
||||||
|
==== Wallet descriptor template ====
|
||||||
|
|
||||||
|
A ''wallet descriptor template'' is a <tt>SCRIPT</tt> expression.
|
||||||
|
|
||||||
|
<tt>SCRIPT</tt> expressions:
|
||||||
|
* <tt>sh(SCRIPT)</tt> (top level only): P2SH embed the argument.
|
||||||
|
* <tt>wsh(SCRIPT)</tt> (top level or inside <tt>sh</tt> only): P2WSH embed the argument.
|
||||||
|
* <tt>pkh(KEY)</tt> (not inside <tt>tr</tt>): P2PKH output for the given public key.
|
||||||
|
* <tt>wpkh(KEY)</tt> (top level or inside <tt>sh</tt> only): P2WPKH output for the given compressed pubkey.
|
||||||
|
* <tt>multi(k,KEY_1,KEY_2,...,KEY_n)</tt> (inside <tt>sh</tt> or <tt>wsh</tt> only): ''k''-of-''n'' multisig script.
|
||||||
|
* <tt>sortedmulti(k,KEY_1,KEY_2,...,KEY_n)</tt> (inside <tt>sh</tt> or <tt>wsh</tt> only): ''k''-of-''n'' multisig script with keys sorted lexicographically in the resulting script.
|
||||||
|
* <tt>tr(KEY)</tt> or <tt>tr(KEY,TREE)</tt> (top level only): P2TR output with the specified key as internal key, and optionally a tree of script paths.
|
||||||
|
* any valid miniscript template (inside <tt>wsh</tt> or <tt>tr</tt> only).
|
||||||
|
|
||||||
|
See [[bip-0379.md|BIP-379]] for a precise specification of all the valid miniscript <tt>SCRIPT</tt> expressions.
|
||||||
|
|
||||||
|
<tt>TREE</tt> expressions:
|
||||||
|
* any <tt>SCRIPT</tt> expression
|
||||||
|
* An open brace <tt>{</tt>, a <tt>TREE</tt> expression, a comma <tt>,</tt>, a <tt>TREE</tt> expression, and a closing brace <tt>}</tt>
|
||||||
|
|
||||||
|
|
||||||
|
<tt>KEY</tt> expressions consist of
|
||||||
|
* a <tt>KP</tt> expression
|
||||||
|
* ''always'' followed by either:
|
||||||
|
** the string <tt>/**</tt>, or
|
||||||
|
** a string of the form <tt>/<NUM;NUM>/*</tt>, for two distinct decimal numbers <tt>NUM</tt> representing unhardened derivations, or
|
||||||
|
** any of the additional, implementation-specific valid derivation path patterns (see [[#optional-derivation-paths|Optional derivation paths]] below).
|
||||||
|
|
||||||
|
<tt>KP</tt> expressions (key placeholders) consist of either:
|
||||||
|
* a <tt>KI</tt> (key index) expression, or
|
||||||
|
* (only inside <tt>tr</tt>): <tt>musig(KI_1,KI_2,...,KI_n)</tt>
|
||||||
|
|
||||||
|
A <tt>KI</tt> (key index) expression consists of:
|
||||||
|
* a single character <tt>@</tt>
|
||||||
|
* followed by a non-negative decimal number, with no leading zeros (except for <tt>@0</tt>)
|
||||||
|
|
||||||
|
The <tt>/**</tt> in the placeholder template represents commonly used paths for receive/change addresses, and is equivalent to <tt><0;1>/*</tt>.
|
||||||
|
|
||||||
|
Note that while [[bip-0389.mediawiki|BIP-389]] allows multipath <tt>/<NUM;NUM;...;NUM></tt> expressions with an arbitrary number of options, this specification restricts it to exactly 2 choices (with the typical meaning of receive/change addresses).
|
||||||
|
|
||||||
|
<tt>SCRIPT</tt>, <tt>TREE</tt> and <tt>KEY</tt> expressions map directly to the corresponding concepts defined in [[bip-0380.mediawiki|BIP-380]] for output script descriptors.
|
||||||
|
|
||||||
|
Each <tt>KEY</tt> expression always corresponds to a precise public key in the final bitcoin Script. Therefore, all the derivation steps in the BIP-32 hierarchy are included in a <tt>KEY</tt> expression.
|
||||||
|
|
||||||
|
Each <tt>KP</tt> (key placeholder) expression, on the other hand, maps to the root of all the corresponding public keys for all the possible UTXOs that belong to the account represented in the wallet policy. Therefore, no derivation steps are allowed in a <tt>KP</tt> expression.
|
||||||
|
|
||||||
|
A <tt>KI</tt> (key index) <tt>@i</tt> for some number ''i'' represents the ''i''-th key in the vector of key information items (which must be of size at least ''i + 1'', or the wallet policy is invalid).
|
||||||
|
|
||||||
|
Note: while descriptor templates for miniscript are not formally defined in this version of the document (pending standardization), it is straightforward to adapt this approach by adding additional <tt>SCRIPT</tt> expressions.
|
||||||
|
|
||||||
|
==== Key information vector ====
|
||||||
|
|
||||||
|
Each element of the key origin information vector is a <tt>KEY_INFO</tt> expression, containing an extended public key, and (optionally) its key origin.
|
||||||
|
|
||||||
|
* Optionally, key origin information, consisting of:
|
||||||
|
** An open bracket <tt>[</tt>
|
||||||
|
** Exactly 8 hex characters for the fingerprint of the master key from which this key is derived from (see [[bip-0032.mediawiki|BIP-32]] for details)
|
||||||
|
** Followed by zero or more <tt>/NUM'</tt> or <tt>/NUM</tt> path elements to indicate hardened or unhardened derivation steps between the fingerprint and the xpub that follows
|
||||||
|
** A closing bracket <tt>]</tt>
|
||||||
|
* Followed by the actual key, which is a serialized extended public key (as defined in [[bip-0032.mediawiki|BIP-32]]).
|
||||||
|
|
||||||
|
==== Additional rules ====
|
||||||
|
|
||||||
|
A wallet policy must have at least one key placeholder and the corresponding key.
|
||||||
|
|
||||||
|
The public keys obtained by deserializing elements of the key information vector must be pairwise distinct<ref>'''Why must public keys be distinct?''' Reusing pubkeys could be insecure in the context of wallet policies containing [https://bitcoin.sipa.be/miniscript/ miniscript]. Avoiding repeated public keys altogether avoids the problem at the source.</ref>.
|
||||||
|
|
||||||
|
If two <tt>KEY</tt> are <tt>KP/<M;N>/*</tt> and <tt>KP/<P;Q>/*</tt> for the same key placeholder <tt>KP</tt>, then the sets <tt>{M, N}</tt> and <tt>{P, Q}</tt> must be disjoint. Two <tt>musig</tt> key placeholders are the same if they have exactly the same set of key indexes (regardless of the order).
|
||||||
|
|
||||||
|
Repeated <tt>KI</tt> expressions are not allowed inside a <tt>musig</tt> placeholder.
|
||||||
|
|
||||||
|
The key information vector should be ordered so that placeholder <tt>@i</tt> never appears for the first time before an occurrence of <tt>@j</tt> for some <tt>j < i</tt>; for example, the first placeholder is always <tt>@0</tt>, the next one is <tt>@1</tt>, etc.
|
||||||
|
|
||||||
|
=== Descriptor derivation ===
|
||||||
|
|
||||||
|
From a wallet descriptor template (and the associated vector of key information items), one can therefore obtain the corresponding multipath descriptor by:
|
||||||
|
|
||||||
|
* replacing each key placeholder with the corresponding key origin information;
|
||||||
|
* replacing every <tt>/**</tt> with <tt>/<0;1>/*</tt>.
|
||||||
|
|
||||||
|
For example, the wallet descriptor <tt>pkh(@0/**)</tt> with key information
|
||||||
|
<tt>["[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"]</tt>
|
||||||
|
produces the following multipath descriptor:
|
||||||
|
|
||||||
|
<tt>pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/<0;1>/*)</tt>
|
||||||
|
|
||||||
|
=== Implementation guidelines ===
|
||||||
|
|
||||||
|
It is acceptable to implement only a subset of the possible wallet policies defined by this standard. It is recommended that any limitations are clearly documented.
|
||||||
|
|
||||||
|
Implementations can add additional metadata that is stored together with the wallet policy for the purpose of wallet policy registration and later usage. Metadata can be vendor-specific and is out of the scope of this document.
|
||||||
|
|
||||||
|
Any implementation in a software wallet that allows wallet policies not matching any of the specifications in [[bip-0044.mediawiki|BIP-44]], [[bip-0049.mediawiki|BIP-49]], [[bip-0084.mediawiki|BIP-84]], [[bip-0086.mediawiki|BIP-86]] (especially if involving external cosigners) should put great care into a process for backing up the wallet policy that represents the account. In fact, unlike standard single-signature scenarios, the seed alone is no longer enough to discover wallet policies with existing funds, and the loss of the backup is likely to lead to permanent loss of funds. Unlike the seed, leaking such backups only affects the privacy of the user, but it does not allow the attacker to steal funds.
|
||||||
|
|
||||||
|
=== Optional derivation paths ===
|
||||||
|
|
||||||
|
In order to allow supporting legacy derivation schemes (for example, using simply <tt>/*</tt> instead of the more common <tt>/<M;N>/*</tt> scheme most software wallets use today), or other schemes that are not covered in this document, implementations might choose to permit additional derivation patterns for the key placeholder (<tt>KP</tt>) expressions.
|
||||||
|
|
||||||
|
However, care needs to be taken in view of the following considerations:
|
||||||
|
|
||||||
|
* Allowing derivation schemes with a different length or cardinality in the same wallet policy would make it difficult to guarantee that there are no repeated pubkeys for every possible address generated by the policy. For example, <tt>@0/<0;1>/*</tt> and <tt>@1/*</tt> would generate the same pubkeys if the second public key in the key information vector is one of the first two unhardened children of the first public key. This could cause malleability with potential security implications (for example, in policies containing miniscript).
|
||||||
|
* Allowing naked pubkeys with no <tt>/*</tt> suffix (for example a descriptor template like <tt>wsh(multi(2,@0,@1/<0;1>/*))</tt>) would cause a pubkey to be repeated in every output generated from the policy, which would result in a total loss of privacy.
|
||||||
|
|
||||||
|
== Examples ==
|
||||||
|
|
||||||
|
In the examples in this section, the vector of key information items is omitted. See the test vectors below for complete examples.
|
||||||
|
|
||||||
|
Common single-signature account patterns:
|
||||||
|
* <tt>pkh(@0/**)</tt> (legacy).
|
||||||
|
* <tt>wpkh(@0/**)</tt> (native segwit).
|
||||||
|
* <tt>sh(wpkh(@0/**))</tt> (nested segwit).
|
||||||
|
* <tt>tr(@0/**)</tt> (taproot single-signature account).
|
||||||
|
|
||||||
|
Common multisig schemes:
|
||||||
|
* <tt>wsh(multi(2,@0/**,@1/**))</tt> - SegWit 2-of-2 multisig, keys in order.
|
||||||
|
* <tt>sh(sortedmulti(2,@0/**,@1/**,@2/**))</tt> - Legacy 2-of-3 multisig, sorted keys.
|
||||||
|
* <tt>tr(musig(@0/**,@1/**))</tt> - MuSig2 2-of-2 in the taproot keypath
|
||||||
|
|
||||||
|
Some miniscript policies in <tt>wsh</tt>:
|
||||||
|
* <tt>wsh(and_v(v:pk(@0/**),or_d(pk(@1/**),older(12960))))</tt> - Trust-minimized second factor, degrading to a single signature after about 90 days.
|
||||||
|
* <tt>wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@2/**),sln:older(12960)))</tt> - A 3-of-3 wallet that becomes a 2-of-3 if coins are not spent for about 90 days.
|
||||||
|
* <tt>wsh(or_d(pk(@0/**),and_v(v:multi(2,@1/**,@2/**,@3/**),older(65535))))</tt> - A singlesig wallet with automatic inheritance to a timelocked 2-of-3 multisig of family members.
|
||||||
|
|
||||||
|
== Test Vectors ==
|
||||||
|
|
||||||
|
=== Valid policies ===
|
||||||
|
|
||||||
|
[[bip-0044.mediawiki|BIP-44]], first account
|
||||||
|
Descriptor template: pkh(@0/**)
|
||||||
|
Keys info: ["[6738736c/44'/0'/0']xpub6Br37sWxruYfT8ASpCjVHKGwgdnYFEn98DwiN76i2oyY6fgH1LAPmmDcF46xjxJr22gw4jmVjTE2E3URMnRPEPYyo1zoPSUba563ESMXCeb"]
|
||||||
|
Descriptor: pkh([6738736c/44'/0'/0']xpub6Br37sWxruYfT8ASpCjVHKGwgdnYFEn98DwiN76i2oyY6fgH1LAPmmDcF46xjxJr22gw4jmVjTE2E3URMnRPEPYyo1zoPSUba563ESMXCeb/<0;1>/*)
|
||||||
|
<br>
|
||||||
|
[[bip-0049.mediawiki|BIP-49]], second account
|
||||||
|
Descriptor template: sh(wpkh(@0/**))
|
||||||
|
Keys info: ["[6738736c/49'/0'/1']xpub6Bex1CHWGXNNwGVKHLqNC7kcV348FxkCxpZXyCWp1k27kin8sRPayjZUKDjyQeZzGUdyeAj2emoW5zStFFUAHRgd5w8iVVbLgZ7PmjAKAm9"]
|
||||||
|
Descriptor: sh(wpkh([6738736c/49'/0'/1']xpub6Bex1CHWGXNNwGVKHLqNC7kcV348FxkCxpZXyCWp1k27kin8sRPayjZUKDjyQeZzGUdyeAj2emoW5zStFFUAHRgd5w8iVVbLgZ7PmjAKAm9/<0;1>/*))
|
||||||
|
<br>
|
||||||
|
[[bip-0084.mediawiki|BIP-84]], third account
|
||||||
|
Descriptor template: wpkh(@0/**)
|
||||||
|
Keys info: ["[6738736c/84'/0'/2']xpub6CRQzb8u9dmMcq5XAwwRn9gcoYCjndJkhKgD11WKzbVGd932UmrExWFxCAvRnDN3ez6ZujLmMvmLBaSWdfWVn75L83Qxu1qSX4fJNrJg2Gt"]
|
||||||
|
Descriptor: wpkh([6738736c/84'/0'/2']xpub6CRQzb8u9dmMcq5XAwwRn9gcoYCjndJkhKgD11WKzbVGd932UmrExWFxCAvRnDN3ez6ZujLmMvmLBaSWdfWVn75L83Qxu1qSX4fJNrJg2Gt/<0;1>/*)
|
||||||
|
<br>
|
||||||
|
[[bip-0086.mediawiki|BIP-86]], first account
|
||||||
|
Descriptor template: tr(@0/**)
|
||||||
|
Keys info: ["[6738736c/86'/0'/0']xpub6CryUDWPS28eR2cDyojB8G354izmx294BdjeSvH469Ty3o2E6Tq5VjBJCn8rWBgesvTJnyXNAJ3QpLFGuNwqFXNt3gn612raffLWfdHNkYL"]
|
||||||
|
Descriptor: tr([6738736c/86'/0'/0']xpub6CryUDWPS28eR2cDyojB8G354izmx294BdjeSvH469Ty3o2E6Tq5VjBJCn8rWBgesvTJnyXNAJ3QpLFGuNwqFXNt3gn612raffLWfdHNkYL/<0;1>/*)
|
||||||
|
<br>
|
||||||
|
[[bip-0048.mediawiki|BIP-48]] P2WSH multisig
|
||||||
|
Descriptor template: wsh(sortedmulti(2,@0/**,@1/**))
|
||||||
|
Keys info: ["[6738736c/48'/0'/0'/2']xpub6FC1fXFP1GXLX5TKtcjHGT4q89SDRehkQLtbKJ2PzWcvbBHtyDsJPLtpLtkGqYNYZdVVAjRQ5kug9CsapegmmeRutpP7PW4u4wVF9JfkDhw", "[b2b1f0cf/48'/0'/0'/2']xpub6EWhjpPa6FqrcaPBuGBZRJVjzGJ1ZsMygRF26RwN932Vfkn1gyCiTbECVitBjRCkexEvetLdiqzTcYimmzYxyR1BZ79KNevgt61PDcukmC7"]
|
||||||
|
Descriptor: wsh(sortedmulti(2,[6738736c/48'/0'/0'/2']xpub6FC1fXFP1GXLX5TKtcjHGT4q89SDRehkQLtbKJ2PzWcvbBHtyDsJPLtpLtkGqYNYZdVVAjRQ5kug9CsapegmmeRutpP7PW4u4wVF9JfkDhw/<0;1>/*,[b2b1f0cf/48'/0'/0'/2']xpub6EWhjpPa6FqrcaPBuGBZRJVjzGJ1ZsMygRF26RwN932Vfkn1gyCiTbECVitBjRCkexEvetLdiqzTcYimmzYxyR1BZ79KNevgt61PDcukmC7/<0;1>/*))
|
||||||
|
<br>
|
||||||
|
Miniscript: A 3-of-3 that becomes a 2-of-3 after 90 days
|
||||||
|
Descriptor template: wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@2/**),sln:older(12960)))
|
||||||
|
Keys info: ["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa", "[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js", "[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2"]
|
||||||
|
Descriptor: wsh(thresh(3,pk([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<0;1>/*),s:pk([b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js/<0;1>/*),s:pk([a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2/<0;1>/*),sln:older(12960)))
|
||||||
|
<br>
|
||||||
|
Miniscript: A singlesig wallet with automatic inheritance to a timelocked 2-of-3 multisig
|
||||||
|
Descriptor template: wsh(or_d(pk(@0/**),and_v(v:multi(2,@1/**,@2/**,@3/**),older(65535))))
|
||||||
|
Keys info: ["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa", "[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js", "[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2", "[bb641298/44'/0'/0'/100']xpub6Dz8PHFmXkYkykQ83ySkruky567XtJb9N69uXScJZqweYiQn6FyieajdiyjCvWzRZ2GoLHMRE1cwDfuJZ6461YvNRGVBJNnLA35cZrQKSRJ"]
|
||||||
|
Descriptor: wsh(or_d(pk([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<0;1>/*),and_v(v:multi(2,[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js/<0;1>/*,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2/<0;1>/*,[bb641298/44'/0'/0'/100']xpub6Dz8PHFmXkYkykQ83ySkruky567XtJb9N69uXScJZqweYiQn6FyieajdiyjCvWzRZ2GoLHMRE1cwDfuJZ6461YvNRGVBJNnLA35cZrQKSRJ/<0;1>/*),older(65535))))
|
||||||
|
<br>
|
||||||
|
Taproot wallet policy with sortedmulti_a and a miniscript leaf
|
||||||
|
Descriptor template: tr(@0/**,{sortedmulti_a(1,@0/<2;3>/*,@1/**),or_b(pk(@2/**),s:pk(@3/**))})
|
||||||
|
Keys info: ["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa", "xpub6Fc2TRaCWNgfT49nRGG2G78d1dPnjhW66gEXi7oYZML7qEFN8e21b2DLDipTZZnfV6V7ivrMkvh4VbnHY2ChHTS9qM3XVLJiAgcfagYQk6K", "xpub6GxHB9kRdFfTqYka8tgtX9Gh3Td3A9XS8uakUGVcJ9NGZ1uLrGZrRVr67DjpMNCHprZmVmceFTY4X4wWfksy8nVwPiNvzJ5pjLxzPtpnfEM", "xpub6GjFUVVYewLj5no5uoNKCWuyWhQ1rKGvV8DgXBG9Uc6DvAKxt2dhrj1EZFrTNB5qxAoBkVW3wF8uCS3q1ri9fueAa6y7heFTcf27Q4gyeh6"]
|
||||||
|
Descriptor: tr([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<0;1>/*,{sortedmulti_a(1,xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<2;3>/*,xpub6Fc2TRaCWNgfT49nRGG2G78d1dPnjhW66gEXi7oYZML7qEFN8e21b2DLDipTZZnfV6V7ivrMkvh4VbnHY2ChHTS9qM3XVLJiAgcfagYQk6K/<0;1>/*),or_b(pk(xpub6GxHB9kRdFfTqYka8tgtX9Gh3Td3A9XS8uakUGVcJ9NGZ1uLrGZrRVr67DjpMNCHprZmVmceFTY4X4wWfksy8nVwPiNvzJ5pjLxzPtpnfEM/<0;1>/*),s:pk(xpub6GjFUVVYewLj5no5uoNKCWuyWhQ1rKGvV8DgXBG9Uc6DvAKxt2dhrj1EZFrTNB5qxAoBkVW3wF8uCS3q1ri9fueAa6y7heFTcf27Q4gyeh6/<0;1>/*))})
|
||||||
|
<br>
|
||||||
|
Taproot MuSig2 3-of-3 in the key path, with three 2-of-2 MuSig2 recovery paths after 90 days in the script paths
|
||||||
|
Descriptor template: tr(musig(@0,@1,@2)/**,{and_v(v:pk(musig(@0,@1)/**),older(12960)),{and_v(v:pk(musig(@0,@2)/**),older(12960)),and_v(v:pk(musig(@1,@2)/**),older(12960))}})
|
||||||
|
Keys info: ["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa", "[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js", "[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2"]
|
||||||
|
Descriptor: tr(musig([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa,[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2)/<0;1>/*,{and_v(v:pk(musig([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa,[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js)/<0;1>/*),older(12960)),{and_v(v:pk(musig([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2)/<0;1>/*),older(12960)),and_v(v:pk(musig([b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2)/<0;1>/*),older(12960))}})
|
||||||
|
|
||||||
|
=== Invalid policies ===
|
||||||
|
|
||||||
|
The following descriptor templates are invalid:
|
||||||
|
|
||||||
|
* <tt>pkh(@0)</tt>: Key placeholder with no path following it
|
||||||
|
* <tt>pkh(@0/0/**)</tt>: Key placeholder with an explicit path present
|
||||||
|
* <tt>sh(multi(1,@1/**,@0/**))</tt>: Key placeholders out of order
|
||||||
|
* <tt>sh(multi(1,@0/**,@2/**))</tt>: Skipped key placeholder <tt>@1</tt>
|
||||||
|
* <tt>sh(multi(1,@0/**,@0/**))</tt>: Repeated keys with the same path expression
|
||||||
|
* <tt>sh(multi(1,@0/<0;1>/*,@0/<1;2>/*))</tt>: Non-disjoint multipath expressions (<tt>@0/1/*</tt> appears twice)
|
||||||
|
* <tt>sh(multi(1,@0/**,xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/<0;1>/*))</tt>: Expression with a non-KP key present
|
||||||
|
* <tt>pkh(@0/<0;1;2>/*)</tt>: Solved cardinality > 2
|
||||||
|
* <tt>tr(musig(@0/**,@1/**))</tt>: Derivation before aggregation is not allowed in wallet policies (despite being allowed in [[bip-0390.mediawiki|BIP-390]])
|
||||||
|
|
||||||
|
Remark: some of the examples of invalid descriptor templates may be valid via optional extensions.
|
||||||
|
|
||||||
|
== Backwards Compatibility ==
|
||||||
|
|
||||||
|
The <tt>@</tt> character used for key placeholders is not part of the syntax of output script descriptors, therefore any valid descriptor with at least one <tt>KEY</tt> expression is not a valid descriptor template. Vice versa, any descriptor template with at least one key placeholder is not a valid output script descriptor.
|
||||||
|
|
||||||
|
Adoption of wallet policies in software and hardware wallets is opt-in. Conversion from wallet policies to the corresponding descriptors is programmatically extremely easy, and conversion from descriptors to wallet policies (when respecting the required patterns) can be automated. See the reference implementation below for some examples of conversion.
|
||||||
|
|
||||||
|
Software wallets are recommended to allow exporting plain descriptors for the purposes of interoperability with software not using wallet policies.
|
||||||
|
|
||||||
|
== Reference Implementation ==
|
||||||
|
|
||||||
|
Wallet policies are implemented in
|
||||||
|
* the [https://github.com/LedgerHQ/app-bitcoin-new Ledger bitcoin application] since version 2.1.0;
|
||||||
|
* the [https://github.com/digitalbitbox/bitbox02-firmware BitBox02 firmware] since version v9.15.0;
|
||||||
|
* [https://github.com/Blockstream/Jade Blockstream Jade] since version v1.0.24, via [https://github.com/ElementsProject/libwally-core libwally-core] v1.0.0.
|
||||||
|
|
||||||
|
For development and testing purposes, we provide a [[bip-0388/wallet_policies.py|Python 3.7 reference implementation]] of simple classes to handle wallet policies, and the conversion to/from output script descriptors.
|
||||||
|
The reference implementation is for demonstration purposes only and not to be used in production environments.
|
||||||
|
|
||||||
|
== Change Log ==
|
||||||
|
|
||||||
|
* '''1.1.0''' (2024-11):
|
||||||
|
** Added support for <tt>musig</tt> key placeholders in descriptor templates.
|
||||||
|
* '''1.0.0''' (2024-05):
|
||||||
|
** Initial version.
|
||||||
|
|
||||||
|
== Footnotes ==
|
||||||
|
|
||||||
|
<references />
|
||||||
|
|
||||||
|
== Acknowledgments ==
|
||||||
|
|
||||||
|
The authors would like to thank the people who provided feedback in the bitcoin-dev list, and in person.
|
202
bip-0388/wallet_policies.py
Executable file
202
bip-0388/wallet_policies.py
Executable file
|
@ -0,0 +1,202 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from typing import Iterable, List, Mapping, Tuple, Generator
|
||||||
|
|
||||||
|
|
||||||
|
def find_all(text: str, pattern: str, start: int = 0) -> Generator[int, None, None]:
|
||||||
|
"""Generates all the positions of `pattern` as a substring of `text`, starting from index at least `start`."""
|
||||||
|
while True:
|
||||||
|
start = text.find(pattern, start)
|
||||||
|
if start == -1:
|
||||||
|
return
|
||||||
|
yield start
|
||||||
|
start += len(pattern)
|
||||||
|
|
||||||
|
|
||||||
|
def find_first(text: str, start_pos: int, patterns: Iterable[str]) -> int:
|
||||||
|
"""Returns the position of the first occurrence of any of the elements in `patterns` as a substring of `text`,
|
||||||
|
or -1 if none of the patterns is found."""
|
||||||
|
matches = (text.find(x, start_pos) for x in patterns)
|
||||||
|
return min((x for x in matches if x != -1), default=-1)
|
||||||
|
|
||||||
|
|
||||||
|
def find_key_end_position(desc: str, start_pos: int) -> int:
|
||||||
|
"""Assuming that `start_pos` is the beginning of a KEY expression (and not musig), finds the position of the end
|
||||||
|
of the key expression, excluding (if present) the final derivation steps after an xpub. This is the information
|
||||||
|
that goes into an entry of the vector of key information of the wallet policy."""
|
||||||
|
|
||||||
|
has_orig_info = True if desc[start_pos] == '[' else False
|
||||||
|
|
||||||
|
if has_orig_info:
|
||||||
|
closing_bracket_pos = desc.find("]", start_pos)
|
||||||
|
if closing_bracket_pos == -1:
|
||||||
|
raise Exception("Invalid descriptor: could not find closing ']'")
|
||||||
|
key_pos_start = closing_bracket_pos + 1
|
||||||
|
else:
|
||||||
|
key_pos_start = start_pos
|
||||||
|
|
||||||
|
# find the earliest occurrence of ",", a ")" or a "/" (it must find at least 1)
|
||||||
|
end_pos = find_first(desc, key_pos_start, [",", ")", "/"])
|
||||||
|
if end_pos == -1:
|
||||||
|
raise Exception(
|
||||||
|
"Invalid descriptor: cannot find the end of key expression")
|
||||||
|
|
||||||
|
return end_pos
|
||||||
|
|
||||||
|
|
||||||
|
class WalletPolicy(object):
|
||||||
|
"""Simple class to represent wallet policies. This is a toy implementation that does not parse the descriptor
|
||||||
|
template. A more robust implementation would build the abstract syntax tree of the template and of the descriptor,
|
||||||
|
allowing one to detect errors, and manipulate it semantically instead of relying on string manipulation."""
|
||||||
|
|
||||||
|
def __init__(self, descriptor_template: str, keys_info: List[str]):
|
||||||
|
self.descriptor_template = descriptor_template
|
||||||
|
self.keys_info = keys_info
|
||||||
|
|
||||||
|
def to_descriptor(self) -> str:
|
||||||
|
"""Converts a wallet policy into the descriptor (with the /<M,N> syntax, if present)."""
|
||||||
|
|
||||||
|
desc = self.descriptor_template
|
||||||
|
|
||||||
|
# replace each "/**" with "/<0;1>/*"
|
||||||
|
desc = desc.replace("/**", "/<0;1>/*")
|
||||||
|
|
||||||
|
# process all the @N expressions in decreasing order. This guarantees that string replacements
|
||||||
|
# works as expected (as any prefix expression is processed after).
|
||||||
|
for i in reversed(range(len(self.keys_info))):
|
||||||
|
desc = desc.replace(f"@{i}", self.keys_info[i])
|
||||||
|
|
||||||
|
# there should not be any remaining "@" expressions
|
||||||
|
if desc.find("@") != -1:
|
||||||
|
return Exception("Invalid descriptor template: contains invalid key index")
|
||||||
|
|
||||||
|
return desc
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_descriptor(cls, descriptor: str) -> 'WalletPolicy':
|
||||||
|
"""Converts a "reasonable" descriptor (with the /<M,N> syntax) into the corresponding wallet policy."""
|
||||||
|
|
||||||
|
# list of pairs of integers, where the tuple (m,n) with m < n means a key expression starts at
|
||||||
|
# m (inclusive) and at n (exclusive)
|
||||||
|
key_expressions: List[Tuple[int, int]] = []
|
||||||
|
|
||||||
|
key_with_orig_pos_start = None
|
||||||
|
|
||||||
|
def parse_key_expressions(only_first=False, handle_musig=False):
|
||||||
|
# Starting at the position in `key_with_orig_pos_start`, parses a number of key expressions, and updates
|
||||||
|
# the `key_expressions` array accordingly.
|
||||||
|
# If `only_first` is `True`, it stops after parsing a single key expression.
|
||||||
|
# If `handle_musig` is `True`, and a key expression is a `musig` operator, it recursively parses
|
||||||
|
# the keys in the musig expression. `musig` inside `musig` is not allowed.
|
||||||
|
|
||||||
|
nonlocal key_with_orig_pos_start
|
||||||
|
if key_with_orig_pos_start is None:
|
||||||
|
raise Exception("Unexpected error")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if handle_musig and descriptor[key_with_orig_pos_start:].startswith("musig"):
|
||||||
|
closing_parenthesis_pos = find_first(
|
||||||
|
descriptor, key_with_orig_pos_start, [")"])
|
||||||
|
if closing_parenthesis_pos == -1:
|
||||||
|
raise Exception(
|
||||||
|
"Invalid descriptor: musig without closing parenthesis")
|
||||||
|
key_with_orig_pos_start = key_with_orig_pos_start + \
|
||||||
|
len("musig(")
|
||||||
|
parse_key_expressions(
|
||||||
|
only_first=False, handle_musig=False)
|
||||||
|
|
||||||
|
key_pos_end = closing_parenthesis_pos + 1
|
||||||
|
else:
|
||||||
|
key_pos_end = find_key_end_position(
|
||||||
|
descriptor, key_with_orig_pos_start)
|
||||||
|
key_expressions.append(
|
||||||
|
(key_with_orig_pos_start, key_pos_end))
|
||||||
|
|
||||||
|
if descriptor[key_pos_end] == '/':
|
||||||
|
# find the actual end (comma or closing parenthesis)
|
||||||
|
key_pos_end = find_first(
|
||||||
|
descriptor, key_pos_end, [",", ")"])
|
||||||
|
if key_pos_end == -1:
|
||||||
|
raise Exception(
|
||||||
|
"Invalid descriptor: unterminated key expression")
|
||||||
|
|
||||||
|
if descriptor[key_pos_end] == ',':
|
||||||
|
# There is another key expression, repeat from after the comma
|
||||||
|
key_with_orig_pos_start = key_pos_end + 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if only_first:
|
||||||
|
break
|
||||||
|
|
||||||
|
# operators for which the KEY is the first argument
|
||||||
|
operators_key_first = ["pk", "pkh", "pk_h", "pk_k", "tr"]
|
||||||
|
# operators for which the KEY is everything except the first argument
|
||||||
|
operators_key_all_but_first = [
|
||||||
|
"multi", "sortedmulti", "multi_a", "sortedmulti_a"]
|
||||||
|
for op in operators_key_first + operators_key_all_but_first:
|
||||||
|
for op_pos_start in find_all(descriptor, op + "("):
|
||||||
|
|
||||||
|
# ignore if not a whole word (otherwise "sortedmulti" would be found inside "multi")
|
||||||
|
if op_pos_start > 0 and 'a' <= desc[op_pos_start - 1] <= 'z':
|
||||||
|
continue
|
||||||
|
|
||||||
|
if op in operators_key_all_but_first:
|
||||||
|
# skip the first argument (we know it's not a KEY expression, so it does not have a comma)
|
||||||
|
first_comma_pos = descriptor.find(",", op_pos_start)
|
||||||
|
if first_comma_pos == -1:
|
||||||
|
raise Exception(
|
||||||
|
"Invalid descriptor: multi, sortedmulti, multi_a and sortedmulti_a must have at least two arguments")
|
||||||
|
key_with_orig_pos_start = 1 + first_comma_pos
|
||||||
|
else:
|
||||||
|
# other operators, the first argument is already a KEY expression
|
||||||
|
key_with_orig_pos_start = op_pos_start + len(op) + 1
|
||||||
|
|
||||||
|
only_first = op in operators_key_first
|
||||||
|
parse_key_expressions(
|
||||||
|
only_first=only_first, handle_musig=True)
|
||||||
|
|
||||||
|
result: List[str] = []
|
||||||
|
keys: List[str] = []
|
||||||
|
keys_to_idx: Mapping[str, int] = {}
|
||||||
|
|
||||||
|
prev_end = 0
|
||||||
|
for start, end in sorted(key_expressions):
|
||||||
|
result.append(descriptor[prev_end:start])
|
||||||
|
|
||||||
|
key = descriptor[start:end]
|
||||||
|
if key not in keys_to_idx:
|
||||||
|
idx = len(keys)
|
||||||
|
keys.append(key)
|
||||||
|
keys_to_idx[key] = idx
|
||||||
|
else:
|
||||||
|
idx = keys_to_idx[key]
|
||||||
|
result.append(f"@{idx}")
|
||||||
|
|
||||||
|
prev_end = end
|
||||||
|
|
||||||
|
result.append(descriptor[prev_end:])
|
||||||
|
|
||||||
|
return cls("".join(result), keys)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
descriptors = [
|
||||||
|
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/**)",
|
||||||
|
"wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/**,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/**))",
|
||||||
|
"tr([12345678/44'/0'/0']xpub6BVZ6JrGsWsUbpP74S8rnz13hVFDtYtKyuTTEYPNSF6GFpDFpL1YXWg3BpwpUWAnsZZ7Qe3XKz7GL3BEx3RQVq61cxqSkjceq25S1xFKFVa,{pk(xpub6AGdromjXf5yf3m7ndaCoR9Ac3UjwTvQ7QQkZoyoh2vfGE9i1AwB2vCbvjTpBL1KRERUsGszg63SVNXsHZU3CiykQqtZPrdXKMdaG2vs6uu),pk(xpub6AnhdkteWC4kPQvkY3QQXGmDCMfmFoYzEQ7FwRFa4BQ1a22k4VL4BD3Jdcog2Sf2KzBscXXAdPRMgjCBDeq6bAryqnMaWX2FaVUGPxWMLDh)})",
|
||||||
|
"tr(xpub6AEWqA1MNRzBBXenkug4NtNguDKTNcXoKQj8fU9VQyid38yikruFRffjoDm9UEaHGEJ6jQxjYdWWZRxR7Xy5ePrQNjohXJuNzkRNSiiBUcE,sortedmulti_a(2,[11223344/44'/0'/0']xpub6AyJhEKxcPaPnYNuA7VBeUQ24v6mEzzPSX5AJm3TSyg1Zsti7rnGKy1Hg6JAdXKF4QUmFZbby9p97AjBNm2VFCEec2ip5C9JntyxosmCeMW,xpub6AQVHBgieCHpGo4GhpGAo4v9v7hfr2Kr4D8ZQJqJwbEyZwtW3pWYSLRQyrNYbTzpoq6XpFtaKZGnEGUMtiydCgqsJDAZNqs9L5QDNKqUBsV))",
|
||||||
|
"tr([11111111/44'/0'/0']xpub6CLZSUDtcUhJVDoPSY8pSRKi4W1RSSLBgwZ2AYmwTH9Yv5tPVFHZxJBUQ27QLLwHej6kfo9DQQbwaHmpXsQq59CjtsE2gNLHmojwgMrsQNe/**,{and_v(v:pk([22222222/44'/0'/0']xpub6CiztfGsUxmpwkWe6gvz8d5VHyFLDoiPpeUfWmQ2vWAhQL3Z1hhEc6PE4irFs4bzjS7dCB4yyinaubrCpFJq4bcKGCD4jjqTxaWiKAJ7mvJ/**),older(52596)),multi_a(2,[33333333/44'/0'/0']xpub6DTZd6od7is2wxXndmE7zaUifzFPwVKshVSGEZedfTJtUjfLyhy4hgCW15hvxRpGaDmtiFoJKaCEaSRfXrQBuYRx18zwquy46dwBsJnsrz2/**,[44444444/44'/0'/0']xpub6BnK4wFbPeLZM4VNjoUA4yLCru6kCT3bhDJNBhbzHLGp1fmgK6muz27h4drixJZeHG8vSS5U5EYyE3gE8ozG94iNg3NDYE8M5YafvhzhMR9/**)})",
|
||||||
|
"tr(musig([33333333/44'/0'/0']xpub6DTZd6od7is2wxXndmE7zaUifzFPwVKshVSGEZedfTJtUjfLyhy4hgCW15hvxRpGaDmtiFoJKaCEaSRfXrQBuYRx18zwquy46dwBsJnsrz2,[44444444/44'/0'/0']xpub6BnK4wFbPeLZM4VNjoUA4yLCru6kCT3bhDJNBhbzHLGp1fmgK6muz27h4drixJZeHG8vSS5U5EYyE3gE8ozG94iNg3NDYE8M5YafvhzhMR9)/**,{and_v(v:pk([22222222/44'/0'/0']xpub6CiztfGsUxmpwkWe6gvz8d5VHyFLDoiPpeUfWmQ2vWAhQL3Z1hhEc6PE4irFs4bzjS7dCB4yyinaubrCpFJq4bcKGCD4jjqTxaWiKAJ7mvJ/**),older(52596)),pk([11111111/44'/0'/0']xpub6CLZSUDtcUhJVDoPSY8pSRKi4W1RSSLBgwZ2AYmwTH9Yv5tPVFHZxJBUQ27QLLwHej6kfo9DQQbwaHmpXsQq59CjtsE2gNLHmojwgMrsQNe/**)})",
|
||||||
|
]
|
||||||
|
|
||||||
|
for desc in descriptors:
|
||||||
|
# Demoes the conversion from a "sane" descriptor to a wallet policy
|
||||||
|
print(f"Descriptor:\n{desc}")
|
||||||
|
wp = WalletPolicy.from_descriptor(desc)
|
||||||
|
print(f"Policy descriptor template:\n{wp.descriptor_template}")
|
||||||
|
print(f"Keys:\n{wp.keys_info}")
|
||||||
|
print("======================================================\n")
|
||||||
|
|
||||||
|
# Converting back to descriptors also works, as long as we take care of /**
|
||||||
|
assert wp.to_descriptor().replace("/<0;1>/*", "/**") == desc
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue