Compare commits

...

18 commits

Author SHA1 Message Date
Jonathan Underwood
ca9f9a5f46
Merge pull request #1760 from bitcoincoretech/motorina0/upgrade-ecpair-lib
upgrade ecpair lib to version 2.0.1
2022-01-06 16:34:04 -07:00
Vlad Stan
2edfb992fa test: upgrade ecpair lib to version 2.0.1 2021-12-17 13:27:35 +02:00
Jonathan Underwood
4e02ba70b1
Merge pull request #1754 from bitcoinjs/fix/addwarning
Add warning to future segwit version address generation/parsing
2021-11-27 08:43:54 +09:00
junderw
11202eb74c
6.0.1 2021-11-27 08:42:12 +09:00
junderw
93af5afe67
Add warning to future segwit version address generation/parsing 2021-11-27 08:35:19 +09:00
Jonathan Underwood
ac411e241b
Merge pull request #1747 from bitcoinjs/tests/taproot
Add taproot test with new CJS compatible tiny-secp256k1
2021-11-17 18:51:56 +09:00
junderw
4674433bb9
Update container 2021-11-17 16:06:45 +09:00
junderw
191b9e8573
Add taproot test with new CJS compatible tiny-secp256k1 2021-11-17 16:01:08 +09:00
junderw
424abf2376
Fix taproot example to follow the suggestion in BIP341 2021-11-15 08:25:22 +09:00
Jonathan Underwood
1f44f722d3
Merge pull request #1745 from brandonblack/feature/taproot
Taproot
2021-11-12 12:42:06 +09:00
junderw
31e512e63f
6.0.0 2021-11-12 12:40:09 +09:00
junderw
24e5cc0616
Add Taproot example 2021-11-12 12:39:56 +09:00
junderw
45187a32d0
Add taggedHash, sigHash v1
Co-authored-by: Brandon Black <brandonblack@bitgo.com>
Co-authored-by: Otto Allmendinger <otto@bitgo.com>
Co-authored-by: Tyler Levine <tyler@bitgo.com>
Co-authored-by: Daniel McNally <danielmcnally@bitgo.com>
2021-11-12 08:33:18 +09:00
Jonathan Underwood
f484edde01
Merge pull request #1743 from bitcoinjs/fix/errorForNewRegtest
Fix error message for Bitcoin Core v22.0
2021-11-11 21:21:18 +09:00
junderw
e0f1620ee3
Fix error message for Bitcoin Core v22.0 2021-11-11 21:18:41 +09:00
Jonathan Underwood
ee9c76376f
Merge pull request #1740 from finch185277/fix-readme-typo
fix typo in README
2021-11-03 20:04:25 +09:00
YU-AN, CHEN
8f31e88824
fix typo in README 2021-11-03 18:25:59 +08:00
Jonathan Underwood
5137976e7b
Merge pull request #1734 from bitcoinjs/feat/modular
Refactor: Remove all require statements, remove ECPair, remove tiny-secp256k1 dep
2021-10-21 14:42:23 +09:00
32 changed files with 933 additions and 180 deletions

View file

@ -41,7 +41,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
regtest: regtest:
image: junderw/bitcoinjs-regtest-server@sha256:a46ec1a651ca5b1a5408f2b2526ea5f435421dd2bc2f28fae3bc33e1fd614552 image: junderw/bitcoinjs-regtest-server@sha256:5b69cf95d9edf6d5b3a00504665d6b3c382a6aa3728fe8ce897974c519061463
ports: ports:
- 8080:8080 - 8080:8080
steps: steps:

View file

@ -1,3 +1,16 @@
# 6.0.0
__removed__
- bip32: Removed the re-export. Please add as dependency to your app instead.
- ECPair: Please use bip32 moving forward. ecpair package was created for those who need it.
- TransactionBuilder: Any internal files used only in TB (classify, templates, etc.) were also removed.
__added__
- taproot segwit v1 address support (bech32m) via address module (#1676)
- hashForWitnessV1 method on Transaction class (#1745)
__fixed__
- Transaction version read/write differed. (#1717)
# 5.2.0 # 5.2.0
__changed__ __changed__
- Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563) - Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563)

View file

@ -1,8 +1,5 @@
# BitcoinJS (bitcoinjs-lib) # BitcoinJS (bitcoinjs-lib)
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) [![Github CI](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml/badge.svg)](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml) [![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
[![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify. A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify.
@ -84,7 +81,7 @@ If you're familiar with how to use browserify, ignore this and carry on, otherwi
**NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](https://babeljs.io/docs/plugins/preset-es2015/) preset). **NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](https://babeljs.io/docs/plugins/preset-es2015/) preset).
**WARNING**: iOS devices have [problems](https://github.com/feross/buffer/issues/136), use atleast [buffer@5.0.5](https://github.com/feross/buffer/pull/155) or greater, and enforce the test suites (for `Buffer`, and any other dependency) pass before use. **WARNING**: iOS devices have [problems](https://github.com/feross/buffer/issues/136), use at least [buffer@5.0.5](https://github.com/feross/buffer/pull/155) or greater, and enforce the test suites (for `Buffer`, and any other dependency) pass before use.
### Typescript or VSCode users ### Typescript or VSCode users
Type declarations for Typescript are included in this library. Normal installation should include all the needed type information. Type declarations for Typescript are included in this library. Normal installation should include all the needed type information.
@ -94,6 +91,8 @@ The below examples are implemented as integration tests, they should be very eas
Otherwise, pull requests are appreciated. Otherwise, pull requests are appreciated.
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).
- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.md)
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)

135
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "bitcoinjs-lib", "name": "bitcoinjs-lib",
"version": "5.2.0", "version": "6.0.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -416,9 +416,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "16.11.1", "version": "16.11.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==",
"dev": true "dev": true
}, },
"@types/proxyquire": { "@types/proxyquire": {
@ -541,31 +541,21 @@
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
"dev": true "dev": true
}, },
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bip174": { "bip174": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz", "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz",
"integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ==" "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
}, },
"bip32": { "bip32": {
"version": "2.0.6", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz",
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "10.12.18", "@types/node": "10.12.18",
"bs58check": "^2.1.1", "bs58check": "^2.1.1",
"create-hash": "^1.2.0", "create-hash": "^1.2.0",
"create-hmac": "^1.1.7", "create-hmac": "^1.1.7",
"tiny-secp256k1": "^1.1.3",
"typeforce": "^1.11.5", "typeforce": "^1.11.5",
"wif": "^2.0.6" "wif": "^2.0.6"
}, },
@ -641,12 +631,6 @@
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
} }
}, },
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"browser-stdout": { "browser-stdout": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
@ -932,44 +916,14 @@
"dev": true "dev": true
}, },
"ecpair": { "ecpair": {
"version": "1.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/ecpair/-/ecpair-1.0.0.tgz", "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz",
"integrity": "sha512-1L+P/ivLC3eKHgqcX1M9tFYQWXDoqwJ3zQnN7zDaTtLpiCQKpFTaAZvnsPC5PkWB4q3EPFAHffCLvjfCqRjuwQ==", "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==",
"dev": true, "dev": true,
"requires": { "requires": {
"randombytes": "^2.0.1", "randombytes": "^2.1.0",
"tiny-secp256k1": "^1.1.6", "typeforce": "^1.18.0",
"typeforce": "^1.11.3", "wif": "^2.0.6"
"wif": "^2.0.1"
}
},
"elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dev": true,
"requires": {
"bn.js": "^4.11.9",
"brorand": "^1.1.0",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.1",
"inherits": "^2.0.4",
"minimalistic-assert": "^1.0.1",
"minimalistic-crypto-utils": "^1.0.1"
},
"dependencies": {
"bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
}
} }
}, },
"emoji-regex": { "emoji-regex": {
@ -1026,12 +980,6 @@
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true "dev": true
}, },
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true
},
"fill-keys": { "fill-keys": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz",
@ -1204,16 +1152,6 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"hasha": { "hasha": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
@ -1230,17 +1168,6 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true "dev": true
}, },
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"hoodwink": { "hoodwink": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz",
@ -1626,18 +1553,6 @@
"pushdata-bitcoin": "^1.0.1" "pushdata-bitcoin": "^1.0.1"
} }
}, },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"dev": true
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
"dev": true
},
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -1706,12 +1621,6 @@
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true "dev": true
}, },
"nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"dev": true
},
"node-environment-flags": { "node-environment-flags": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
@ -2465,16 +2374,12 @@
} }
}, },
"tiny-secp256k1": { "tiny-secp256k1": {
"version": "1.1.6", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.2.tgz",
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==",
"dev": true, "dev": true,
"requires": { "requires": {
"bindings": "^1.3.0", "uint8array-tools": "0.0.6"
"bn.js": "^4.11.8",
"create-hmac": "^1.1.7",
"elliptic": "^6.4.0",
"nan": "^2.13.2"
} }
}, },
"to-fast-properties": { "to-fast-properties": {
@ -2583,6 +2488,12 @@
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"dev": true "dev": true
}, },
"uint8array-tools": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.6.tgz",
"integrity": "sha512-aIvEHNRX1AlOYAr6qSUjQBn4mCduxx6MtC967aRDTb2UUBPj0Ix2ZFQDcmXUUO/UxRPHcw1f5a5nVbCSKDSOqA==",
"dev": true
},
"uuid": { "uuid": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "bitcoinjs-lib", "name": "bitcoinjs-lib",
"version": "5.2.0", "version": "6.0.1",
"description": "Client-side Bitcoin JavaScript library", "description": "Client-side Bitcoin JavaScript library",
"main": "./src/index.js", "main": "./src/index.js",
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
@ -62,18 +62,18 @@
"@types/bs58check": "^2.1.0", "@types/bs58check": "^2.1.0",
"@types/create-hash": "^1.2.2", "@types/create-hash": "^1.2.2",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@types/node": "^16.11.1", "@types/node": "^16.11.7",
"@types/proxyquire": "^1.3.28", "@types/proxyquire": "^1.3.28",
"@types/randombytes": "^2.0.0", "@types/randombytes": "^2.0.0",
"@types/wif": "^2.0.2", "@types/wif": "^2.0.2",
"bip32": "^2.0.6", "bip32": "^3.0.1",
"bip39": "^3.0.2", "bip39": "^3.0.2",
"bip65": "^1.0.1", "bip65": "^1.0.1",
"bip68": "^1.0.3", "bip68": "^1.0.3",
"bn.js": "^4.11.8", "bn.js": "^4.11.8",
"bs58": "^4.0.0", "bs58": "^4.0.0",
"dhttp": "^3.0.0", "dhttp": "^3.0.0",
"ecpair": "^1.0.0", "ecpair": "^2.0.1",
"hoodwink": "^2.0.0", "hoodwink": "^2.0.0",
"minimaldata": "^1.0.2", "minimaldata": "^1.0.2",
"mocha": "^7.1.1", "mocha": "^7.1.1",
@ -84,6 +84,7 @@
"randombytes": "^2.1.0", "randombytes": "^2.1.0",
"regtest-client": "0.2.0", "regtest-client": "0.2.0",
"rimraf": "^2.6.3", "rimraf": "^2.6.3",
"tiny-secp256k1": "^2.1.2",
"ts-node": "^8.3.0", "ts-node": "^8.3.0",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"typescript": "^4.4.4" "typescript": "^4.4.4"

View file

@ -13,6 +13,11 @@ const FUTURE_SEGWIT_MIN_SIZE = 2;
const FUTURE_SEGWIT_MAX_VERSION = 16; const FUTURE_SEGWIT_MAX_VERSION = 16;
const FUTURE_SEGWIT_MIN_VERSION = 1; const FUTURE_SEGWIT_MIN_VERSION = 1;
const FUTURE_SEGWIT_VERSION_DIFF = 0x50; const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
const FUTURE_SEGWIT_VERSION_WARNING =
'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' +
'with caution. Wallets should verify the segwit version from the output of fromBech32, ' +
'then decide when it is safe to use which version of segwit.';
function _toFutureSegwitAddress(output, network) { function _toFutureSegwitAddress(output, network) {
const data = output.slice(2); const data = output.slice(2);
if ( if (
@ -28,6 +33,7 @@ function _toFutureSegwitAddress(output, network) {
throw new TypeError('Invalid version for segwit address'); throw new TypeError('Invalid version for segwit address');
if (output[1] !== data.length) if (output[1] !== data.length)
throw new TypeError('Invalid script for segwit address'); throw new TypeError('Invalid script for segwit address');
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
return toBech32(data, version, network.bech32); return toBech32(data, version, network.bech32);
} }
function fromBase58Check(address) { function fromBase58Check(address) {
@ -128,13 +134,15 @@ function toOutputScript(address, network) {
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE && decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
) ) {
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
return bscript.compile([ return bscript.compile([
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF, decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
decodeBech32.data, decodeBech32.data,
]); ]);
} }
} }
}
throw new Error(address + ' has no matching Script'); throw new Error(address + ' has no matching Script');
} }
exports.toOutputScript = toOutputScript; exports.toOutputScript = toOutputScript;

View file

@ -11,6 +11,7 @@ export declare function cloneBuffer(buffer: Buffer): Buffer;
export declare class BufferWriter { export declare class BufferWriter {
buffer: Buffer; buffer: Buffer;
offset: number; offset: number;
static withCapacity(size: number): BufferWriter;
constructor(buffer: Buffer, offset?: number); constructor(buffer: Buffer, offset?: number);
writeUInt8(i: number): void; writeUInt8(i: number): void;
writeInt32(i: number): void; writeInt32(i: number): void;
@ -20,6 +21,7 @@ export declare class BufferWriter {
writeSlice(slice: Buffer): void; writeSlice(slice: Buffer): void;
writeVarSlice(slice: Buffer): void; writeVarSlice(slice: Buffer): void;
writeVector(vector: Buffer[]): void; writeVector(vector: Buffer[]): void;
end(): Buffer;
} }
/** /**
* Helper class for reading of bitcoin data types from a buffer. * Helper class for reading of bitcoin data types from a buffer.

View file

@ -58,6 +58,9 @@ class BufferWriter {
this.offset = offset; this.offset = offset;
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
} }
static withCapacity(size) {
return new BufferWriter(Buffer.alloc(size));
}
writeUInt8(i) { writeUInt8(i) {
this.offset = this.buffer.writeUInt8(i, this.offset); this.offset = this.buffer.writeUInt8(i, this.offset);
} }
@ -88,6 +91,12 @@ class BufferWriter {
this.writeVarInt(vector.length); this.writeVarInt(vector.length);
vector.forEach(buf => this.writeVarSlice(buf)); vector.forEach(buf => this.writeVarSlice(buf));
} }
end() {
if (this.buffer.length === this.offset) {
return this.buffer;
}
throw new Error(`buffer size ${this.buffer.length}, offset ${this.offset}`);
}
} }
exports.BufferWriter = BufferWriter; exports.BufferWriter = BufferWriter;
/** /**

4
src/crypto.d.ts vendored
View file

@ -4,3 +4,7 @@ export declare function sha1(buffer: Buffer): Buffer;
export declare function sha256(buffer: Buffer): Buffer; export declare function sha256(buffer: Buffer): Buffer;
export declare function hash160(buffer: Buffer): Buffer; export declare function hash160(buffer: Buffer): Buffer;
export declare function hash256(buffer: Buffer): Buffer; export declare function hash256(buffer: Buffer): Buffer;
declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"];
export declare type TaggedHashPrefix = typeof TAGS[number];
export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer;
export {};

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
Object.defineProperty(exports, '__esModule', { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0; exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0;
const createHash = require('create-hash'); const createHash = require('create-hash');
function ripemd160(buffer) { function ripemd160(buffer) {
try { try {
@ -34,3 +34,25 @@ function hash256(buffer) {
return sha256(sha256(buffer)); return sha256(sha256(buffer));
} }
exports.hash256 = hash256; exports.hash256 = hash256;
const TAGS = [
'BIP0340/challenge',
'BIP0340/aux',
'BIP0340/nonce',
'TapLeaf',
'TapBranch',
'TapSighash',
'TapTweak',
'KeyAgg list',
'KeyAgg coefficient',
];
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES = Object.fromEntries(
TAGS.map(tag => {
const tagHash = sha256(Buffer.from(tag));
return [tag, Buffer.concat([tagHash, tagHash])];
}),
);
function taggedHash(prefix, data) {
return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data]));
}
exports.taggedHash = taggedHash;

1
src/index.d.ts vendored
View file

@ -5,6 +5,7 @@ import * as payments from './payments';
import * as script from './script'; import * as script from './script';
export { address, crypto, networks, payments, script }; export { address, crypto, networks, payments, script };
export { Block } from './block'; export { Block } from './block';
export { TaggedHashPrefix } from './crypto';
export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt'; export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt';
export { OPS as opcodes } from './ops'; export { OPS as opcodes } from './ops';
export { Transaction } from './transaction'; export { Transaction } from './transaction';

View file

@ -12,10 +12,13 @@ export interface Input {
} }
export declare class Transaction { export declare class Transaction {
static readonly DEFAULT_SEQUENCE = 4294967295; static readonly DEFAULT_SEQUENCE = 4294967295;
static readonly SIGHASH_DEFAULT = 0;
static readonly SIGHASH_ALL = 1; static readonly SIGHASH_ALL = 1;
static readonly SIGHASH_NONE = 2; static readonly SIGHASH_NONE = 2;
static readonly SIGHASH_SINGLE = 3; static readonly SIGHASH_SINGLE = 3;
static readonly SIGHASH_ANYONECANPAY = 128; static readonly SIGHASH_ANYONECANPAY = 128;
static readonly SIGHASH_OUTPUT_MASK = 3;
static readonly SIGHASH_INPUT_MASK = 128;
static readonly ADVANCED_TRANSACTION_MARKER = 0; static readonly ADVANCED_TRANSACTION_MARKER = 0;
static readonly ADVANCED_TRANSACTION_FLAG = 1; static readonly ADVANCED_TRANSACTION_FLAG = 1;
static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction; static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction;
@ -42,6 +45,7 @@ export declare class Transaction {
* This hash can then be used to sign the provided transaction input. * This hash can then be used to sign the provided transaction input.
*/ */
hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer; hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer;
hashForWitnessV1(inIndex: number, prevOutScripts: Buffer[], values: number[], hashType: number, leafHash?: Buffer, annex?: Buffer): Buffer;
hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer; hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer;
getHash(forWitness?: boolean): Buffer; getHash(forWitness?: boolean): Buffer;
getId(): string; getId(): string;

View file

@ -20,7 +20,7 @@ function vectorSize(someVector) {
}, 0) }, 0)
); );
} }
const EMPTY_SCRIPT = Buffer.allocUnsafe(0); const EMPTY_BUFFER = Buffer.allocUnsafe(0);
const EMPTY_WITNESS = []; const EMPTY_WITNESS = [];
const ZERO = Buffer.from( const ZERO = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000', '0000000000000000000000000000000000000000000000000000000000000000',
@ -32,7 +32,7 @@ const ONE = Buffer.from(
); );
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
const BLANK_OUTPUT = { const BLANK_OUTPUT = {
script: EMPTY_SCRIPT, script: EMPTY_BUFFER,
valueBuffer: VALUE_UINT64_MAX, valueBuffer: VALUE_UINT64_MAX,
}; };
function isOutput(out) { function isOutput(out) {
@ -124,7 +124,7 @@ class Transaction {
this.ins.push({ this.ins.push({
hash, hash,
index, index,
script: scriptSig || EMPTY_SCRIPT, script: scriptSig || EMPTY_BUFFER,
sequence: sequence, sequence: sequence,
witness: EMPTY_WITNESS, witness: EMPTY_WITNESS,
}) - 1 }) - 1
@ -247,7 +247,7 @@ class Transaction {
} else { } else {
// "blank" others input scripts // "blank" others input scripts
txTmp.ins.forEach(input => { txTmp.ins.forEach(input => {
input.script = EMPTY_SCRIPT; input.script = EMPTY_BUFFER;
}); });
txTmp.ins[inIndex].script = ourScript; txTmp.ins[inIndex].script = ourScript;
} }
@ -257,6 +257,141 @@ class Transaction {
txTmp.__toBuffer(buffer, 0, false); txTmp.__toBuffer(buffer, 0, false);
return bcrypto.hash256(buffer); return bcrypto.hash256(buffer);
} }
hashForWitnessV1(inIndex, prevOutScripts, values, hashType, leafHash, annex) {
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message
typeforce(
types.tuple(
types.UInt32,
typeforce.arrayOf(types.Buffer),
typeforce.arrayOf(types.Satoshi),
types.UInt32,
),
arguments,
);
if (
values.length !== this.ins.length ||
prevOutScripts.length !== this.ins.length
) {
throw new Error('Must supply prevout script and value for all inputs');
}
const outputType =
hashType === Transaction.SIGHASH_DEFAULT
? Transaction.SIGHASH_ALL
: hashType & Transaction.SIGHASH_OUTPUT_MASK;
const inputType = hashType & Transaction.SIGHASH_INPUT_MASK;
const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY;
const isNone = outputType === Transaction.SIGHASH_NONE;
const isSingle = outputType === Transaction.SIGHASH_SINGLE;
let hashPrevouts = EMPTY_BUFFER;
let hashAmounts = EMPTY_BUFFER;
let hashScriptPubKeys = EMPTY_BUFFER;
let hashSequences = EMPTY_BUFFER;
let hashOutputs = EMPTY_BUFFER;
if (!isAnyoneCanPay) {
let bufferWriter = bufferutils_1.BufferWriter.withCapacity(
36 * this.ins.length,
);
this.ins.forEach(txIn => {
bufferWriter.writeSlice(txIn.hash);
bufferWriter.writeUInt32(txIn.index);
});
hashPrevouts = bcrypto.sha256(bufferWriter.end());
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
8 * this.ins.length,
);
values.forEach(value => bufferWriter.writeUInt64(value));
hashAmounts = bcrypto.sha256(bufferWriter.end());
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
prevOutScripts.map(varSliceSize).reduce((a, b) => a + b),
);
prevOutScripts.forEach(prevOutScript =>
bufferWriter.writeVarSlice(prevOutScript),
);
hashScriptPubKeys = bcrypto.sha256(bufferWriter.end());
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
4 * this.ins.length,
);
this.ins.forEach(txIn => bufferWriter.writeUInt32(txIn.sequence));
hashSequences = bcrypto.sha256(bufferWriter.end());
}
if (!(isNone || isSingle)) {
const txOutsSize = this.outs
.map(output => 8 + varSliceSize(output.script))
.reduce((a, b) => a + b);
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(txOutsSize);
this.outs.forEach(out => {
bufferWriter.writeUInt64(out.value);
bufferWriter.writeVarSlice(out.script);
});
hashOutputs = bcrypto.sha256(bufferWriter.end());
} else if (isSingle && inIndex < this.outs.length) {
const output = this.outs[inIndex];
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
8 + varSliceSize(output.script),
);
bufferWriter.writeUInt64(output.value);
bufferWriter.writeVarSlice(output.script);
hashOutputs = bcrypto.sha256(bufferWriter.end());
}
const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0);
// Length calculation from:
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-14
// With extension from:
// https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#signature-validation
const sigMsgSize =
174 -
(isAnyoneCanPay ? 49 : 0) -
(isNone ? 32 : 0) +
(annex ? 32 : 0) +
(leafHash ? 37 : 0);
const sigMsgWriter = bufferutils_1.BufferWriter.withCapacity(sigMsgSize);
sigMsgWriter.writeUInt8(hashType);
// Transaction
sigMsgWriter.writeInt32(this.version);
sigMsgWriter.writeUInt32(this.locktime);
sigMsgWriter.writeSlice(hashPrevouts);
sigMsgWriter.writeSlice(hashAmounts);
sigMsgWriter.writeSlice(hashScriptPubKeys);
sigMsgWriter.writeSlice(hashSequences);
if (!(isNone || isSingle)) {
sigMsgWriter.writeSlice(hashOutputs);
}
// Input
sigMsgWriter.writeUInt8(spendType);
if (isAnyoneCanPay) {
const input = this.ins[inIndex];
sigMsgWriter.writeSlice(input.hash);
sigMsgWriter.writeUInt32(input.index);
sigMsgWriter.writeUInt64(values[inIndex]);
sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]);
sigMsgWriter.writeUInt32(input.sequence);
} else {
sigMsgWriter.writeUInt32(inIndex);
}
if (annex) {
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
varSliceSize(annex),
);
bufferWriter.writeVarSlice(annex);
sigMsgWriter.writeSlice(bcrypto.sha256(bufferWriter.end()));
}
// Output
if (isSingle) {
sigMsgWriter.writeSlice(hashOutputs);
}
// BIP342 extension
if (leafHash) {
sigMsgWriter.writeSlice(leafHash);
sigMsgWriter.writeUInt8(0);
sigMsgWriter.writeUInt32(0xffffffff);
}
// Extra zero byte because:
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19
return bcrypto.taggedHash(
'TapSighash',
Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]),
);
}
hashForWitnessV0(inIndex, prevOutScript, value, hashType) { hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
typeforce( typeforce(
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
@ -396,9 +531,12 @@ class Transaction {
} }
exports.Transaction = Transaction; exports.Transaction = Transaction;
Transaction.DEFAULT_SEQUENCE = 0xffffffff; Transaction.DEFAULT_SEQUENCE = 0xffffffff;
Transaction.SIGHASH_DEFAULT = 0x00;
Transaction.SIGHASH_ALL = 0x01; Transaction.SIGHASH_ALL = 0x01;
Transaction.SIGHASH_NONE = 0x02; Transaction.SIGHASH_NONE = 0x02;
Transaction.SIGHASH_SINGLE = 0x03; Transaction.SIGHASH_SINGLE = 0x03;
Transaction.SIGHASH_ANYONECANPAY = 0x80; Transaction.SIGHASH_ANYONECANPAY = 0x80;
Transaction.SIGHASH_OUTPUT_MASK = 0x03;
Transaction.SIGHASH_INPUT_MASK = 0x80;
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00; Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01; Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;

View file

@ -66,6 +66,13 @@ describe('bufferutils', () => {
); );
} }
it('withCapacity', () => {
const expectedBuffer = Buffer.from('04030201', 'hex');
const withCapacity = BufferWriter.withCapacity(4);
withCapacity.writeInt32(0x01020304);
testBuffer(withCapacity, expectedBuffer);
});
it('writeUint8', () => { it('writeUint8', () => {
const values = [0, 1, 254, 255]; const values = [0, 1, 254, 255];
const expectedBuffer = Buffer.from([0, 1, 0xfe, 0xff]); const expectedBuffer = Buffer.from([0, 1, 0xfe, 0xff]);
@ -277,6 +284,16 @@ describe('bufferutils', () => {
}); });
testBuffer(bufferWriter, expectedBuffer); testBuffer(bufferWriter, expectedBuffer);
}); });
it('end', () => {
const expected = Buffer.from('0403020108070605', 'hex');
const bufferWriter = BufferWriter.withCapacity(8);
bufferWriter.writeUInt32(0x01020304);
bufferWriter.writeUInt32(0x05060708);
const result = bufferWriter.end();
testBuffer(bufferWriter, result);
testBuffer(bufferWriter, expected);
});
}); });
describe('BufferReader', () => { describe('BufferReader', () => {

View file

@ -1,12 +1,12 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { describe, it } from 'mocha'; import { describe, it } from 'mocha';
import { crypto as bcrypto } from '..'; import { crypto as bcrypto, TaggedHashPrefix } from '..';
import * as fixtures from './fixtures/crypto.json'; import * as fixtures from './fixtures/crypto.json';
describe('crypto', () => { describe('crypto', () => {
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => { ['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => {
describe(algorithm, () => { describe(algorithm, () => {
fixtures.forEach(f => { fixtures.hashes.forEach(f => {
const fn = (bcrypto as any)[algorithm]; const fn = (bcrypto as any)[algorithm];
const expected = (f as any)[algorithm]; const expected = (f as any)[algorithm];
@ -19,4 +19,15 @@ describe('crypto', () => {
}); });
}); });
}); });
describe('taggedHash', () => {
fixtures.taggedHash.forEach(f => {
const bytes = Buffer.from(f.hex, 'hex');
const expected = Buffer.from(f.result, 'hex');
it(`returns ${f.result} for taggedHash "${f.tag}" of ${f.hex}`, () => {
const actual = bcrypto.taggedHash(f.tag as TaggedHashPrefix, bytes);
assert.strictEqual(actual.toString('hex'), expected.toString('hex'));
});
});
});
}); });

View file

@ -1,4 +1,5 @@
[ {
"hashes": [
{ {
"hex": "0000000000000001", "hex": "0000000000000001",
"hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8", "hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8",
@ -31,4 +32,12 @@
"sha1": "10d96fb43aca84e342206887bbeed3065d4e4344", "sha1": "10d96fb43aca84e342206887bbeed3065d4e4344",
"sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da" "sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da"
} }
] ],
"taggedHash": [
{
"tag": "TapTweak",
"hex": "0101010101010101",
"result": "71ae15bad52efcecf4c9f672bfbded68a4adb8258f1b95f0d06aefdb5ebd14e9"
}
]
}

View file

@ -808,6 +808,87 @@
"value": 987654321 "value": 987654321
} }
], ],
"taprootSigning": [
{
"description": "P2TR key path",
"utxos": [
{
"scriptHex": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
"value": 420000000
},
{
"scriptHex": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
"value": 462000000
},
{
"scriptHex": "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac",
"value": 294000000
},
{
"scriptHex": "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
"value": 504000000
},
{
"scriptHex": "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
"value": 630000000
},
{
"scriptHex": "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc",
"value": 378000000
},
{
"scriptHex": "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
"value": 672000000
},
{
"scriptHex": "51200f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561",
"value": 546000000
},
{
"scriptHex": "5120053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587",
"value": 588000000
}
],
"txHex": "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffff0c638ca38362001f5e128a01ae2b379288eb22cfaf903652b2ec1c88588f487a0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd05000000000000000000081efa267f1f0e46e054ecec01773de7c844721e010c2db5d5864a6a6b53e013a010000000000000000a690669c3c4a62507d93609810c6de3f99d1a6e311fe39dd23683d695c07bdee0000000000ffffffff727ab5f877438496f8613ca84002ff38e8292f7bd11f0a9b9b83ebd16779669e0100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d",
"cases": [
{
"vin": 0,
"typeHex": "03",
"hash": "7e584883b084ace0469c6962a9a7d2a9060e1f3c218ab40d32c77651482122bc"
},
{
"vin": 1,
"typeHex": "83",
"hash": "325a644af47e8a5a2591cda0ab0723978537318f10e6a63d4eed783b96a71a4d"
},
{
"vin": 3,
"typeHex": "01",
"hash": "6ffd256e108685b41831385f57eebf2fca041bc6b5e607ea11b3e03d4cf9d9ba"
},
{
"vin": 4,
"typeHex": "00",
"hash": "9f90136737540ccc18707e1fd398ad222a1a7e4dd65cbfd22dbe4660191efa58"
},
{
"vin": 6,
"typeHex": "02",
"hash": "835c9ab6084ed9a8ae9b7cda21e0aa797aca3b76a54bd1e3c7db093f6c57e23f"
},
{
"vin": 7,
"typeHex": "82",
"hash": "df1cca638283c667084b8ffe6bf6e116cc5a53cf7ae1202c5fee45a9085f1ba5"
},
{
"vin": 8,
"typeHex": "81",
"hash": "30319859ca79ea1b7a9782e9daebc46e4ca4ca2bc04c9c53b2ec87fa83a526bd"
}
]
}
],
"invalid": { "invalid": {
"addInput": [ "addInput": [
{ {

View file

@ -1,8 +1,11 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { ECPair } from 'ecpair'; import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { describe, it } from 'mocha'; import { describe, it } from 'mocha';
import * as bitcoin from '../..'; import * as bitcoin from '../..';
import { regtestUtils } from './_regtest'; import { regtestUtils } from './_regtest';
const ECPair = ECPairFactory(ecc);
const dhttp = regtestUtils.dhttp; const dhttp = regtestUtils.dhttp;
const TESTNET = bitcoin.networks.testnet; const TESTNET = bitcoin.networks.testnet;

View file

@ -1,9 +1,12 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as bip32 from 'bip32'; import BIP32Factory from 'bip32';
import * as ecc from 'tiny-secp256k1';
import * as bip39 from 'bip39'; import * as bip39 from 'bip39';
import { describe, it } from 'mocha'; import { describe, it } from 'mocha';
import * as bitcoin from '../..'; import * as bitcoin from '../..';
const bip32 = BIP32Factory(ecc);
function getAddress(node: any, network?: any): string { function getAddress(node: any, network?: any): string {
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!; return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!;
} }

View file

@ -1,8 +1,11 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { ECPair } from 'ecpair'; import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { before, describe, it } from 'mocha'; import { before, describe, it } from 'mocha';
import * as bitcoin from '../..'; import * as bitcoin from '../..';
import { regtestUtils } from './_regtest'; import { regtestUtils } from './_regtest';
const ECPair = ECPairFactory(ecc);
const regtest = regtestUtils.network; const regtest = regtestUtils.network;
const bip65 = require('bip65'); const bip65 = require('bip65');
@ -250,7 +253,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
await regtestUtils.broadcast(tx.toHex()).catch(err => { await regtestUtils.broadcast(tx.toHex()).catch(err => {
assert.throws(() => { assert.throws(() => {
if (err) throw err; if (err) throw err;
}, /Error: non-final \(code 64\)/); }, /Error: non-final/);
}); });
}, },
); );

View file

@ -1,9 +1,12 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { PsbtInput } from 'bip174/src/lib/interfaces'; import { PsbtInput } from 'bip174/src/lib/interfaces';
import { ECPair } from 'ecpair'; import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { before, describe, it } from 'mocha'; import { before, describe, it } from 'mocha';
import * as bitcoin from '../..'; import * as bitcoin from '../..';
import { regtestUtils } from './_regtest'; import { regtestUtils } from './_regtest';
const ECPair = ECPairFactory(ecc);
const regtest = regtestUtils.network; const regtest = regtestUtils.network;
const bip68 = require('bip68'); const bip68 = require('bip68');
const varuint = require('varuint-bitcoin'); const varuint = require('varuint-bitcoin');
@ -219,7 +222,7 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
await regtestUtils.broadcast(tx.toHex()).catch(err => { await regtestUtils.broadcast(tx.toHex()).catch(err => {
assert.throws(() => { assert.throws(() => {
if (err) throw err; if (err) throw err;
}, /Error: non-BIP68-final \(code 64\)/); }, /Error: non-BIP68-final/);
}); });
}, },
); );

View file

@ -1,7 +1,10 @@
import { ECPair } from 'ecpair'; import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { describe, it } from 'mocha'; import { describe, it } from 'mocha';
import * as bitcoin from '../..'; import * as bitcoin from '../..';
import { regtestUtils } from './_regtest'; import { regtestUtils } from './_regtest';
const ECPair = ECPairFactory(ecc);
const NETWORK = regtestUtils.network; const NETWORK = regtestUtils.network;
const keyPairs = [ const keyPairs = [
ECPair.makeRandom({ network: NETWORK }), ECPair.makeRandom({ network: NETWORK }),

156
test/integration/taproot.md Normal file
View file

@ -0,0 +1,156 @@
# Taproot
A simple keyspend example that is possible with the current API is below.
## Current state of taproot support
- [x] segwit v1 address support via bech32m
- [x] segwit v1 sighash calculation on Transaction class
## TODO
- [ ] p2tr payment API to make script spends easier
- [ ] Support within the Psbt class
## Example
### Requirements
- npm dependencies
- bitcoinjs-lib v6.x.x
- bip32 v3.x.x
- tiny-secp256k1 v2.x.x
- regtest-client vx.x.x
- local regtest-server docker container running
- `docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server`
- node >= v14
```js
// Run this whole file as async
// Catch any errors at the bottom of the file
// and exit the process with 1 error code
(async () => {
// Order of the curve (N) - 1
const N_LESS_1 = Buffer.from(
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
'hex'
);
// 1 represented as 32 bytes BE
const ONE = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex'
);
const crypto = require('crypto');
// bitcoinjs-lib v6
const bitcoin = require('bitcoinjs-lib');
// bip32 v3 wraps tiny-secp256k1
const BIP32Wrapper = require('bip32').default;
const RegtestUtils = require('regtest-client').RegtestUtils;
// tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async
const ecc = await import('tiny-secp256k1');
// wrap the bip32 library
const bip32 = BIP32Wrapper(ecc);
// set up dependencies
const APIPASS = process.env.APIPASS || 'satoshi';
// docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server
const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1';
const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
// End imports
const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network);
const output = createKeySpendOutput(myKey.publicKey);
const address = bitcoin.address.fromOutputScript(
output,
regtestUtils.network
);
// amount from faucet
const amount = 42e4;
// amount to send
const sendAmount = amount - 1e4;
// get faucet
const unspent = await regtestUtils.faucetComplex(output, amount);
const tx = createSigned(
myKey,
unspent.txId,
unspent.vout,
sendAmount,
[output],
[amount]
);
const hex = tx.toHex();
console.log('Valid tx sent from:');
console.log(address);
console.log('tx hex:');
console.log(hex);
await regtestUtils.broadcast(hex);
await regtestUtils.verify({
txId: tx.getId(),
address,
vout: 0,
value: sendAmount,
});
// Function for creating a tweaked p2tr key-spend only address
// (This is recommended by BIP341)
function createKeySpendOutput(publicKey) {
// x-only pubkey (remove 1 byte y parity)
const myXOnlyPubkey = publicKey.slice(1, 33);
const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey);
const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash);
if (tweakResult === null) throw new Error('Invalid Tweak');
const { xOnlyPubkey: tweaked } = tweakResult;
// scriptPubkey
return Buffer.concat([
// witness v1, PUSH_DATA 32 bytes
Buffer.from([0x51, 0x20]),
// x-only tweaked pubkey
tweaked,
]);
}
// Function for signing for a tweaked p2tr key-spend only address
// (Required for the above address)
function signTweaked(messageHash, key) {
const privateKey =
key.publicKey[0] === 2
? key.privateKey
: ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey), ONE);
const tweakHash = bitcoin.crypto.taggedHash(
'TapTweak',
key.publicKey.slice(1, 33)
);
const newPrivateKey = ecc.privateAdd(privateKey, tweakHash);
if (newPrivateKey === null) throw new Error('Invalid Tweak');
return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32));
}
// Function for creating signed tx
function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
const tx = new bitcoin.Transaction();
tx.version = 2;
// Add input
tx.addInput(Buffer.from(txid, 'hex').reverse(), vout);
// Add output
tx.addOutput(scriptPubkeys[0], amountToSend);
const sighash = tx.hashForWitnessV1(
0, // which input
scriptPubkeys, // All previous outputs of all inputs
values, // All previous values of all inputs
bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
);
const signature = Buffer.from(signTweaked(sighash, key));
// witness stack for keypath spend is just the signature.
// If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value
tx.ins[0].witness = [signature];
return tx;
}
})().catch((err) => {
console.error(err);
process.exit(1);
});
```

View file

@ -0,0 +1,122 @@
import BIP32Factory from 'bip32';
import * as ecc from 'tiny-secp256k1';
import { describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
const rng = require('randombytes');
const regtest = regtestUtils.network;
const bip32 = BIP32Factory(ecc);
describe('bitcoinjs-lib (transaction with taproot)', () => {
it('can create (and broadcast via 3PBP) a taproot keyspend Transaction', async () => {
const myKey = bip32.fromSeed(rng(64), regtest);
const output = createKeySpendOutput(myKey.publicKey);
const address = bitcoin.address.fromOutputScript(output, regtest);
// amount from faucet
const amount = 42e4;
// amount to send
const sendAmount = amount - 1e4;
// get faucet
const unspent = await regtestUtils.faucetComplex(output, amount);
const tx = createSigned(
myKey,
unspent.txId,
unspent.vout,
sendAmount,
[output],
[amount],
);
const hex = tx.toHex();
// console.log('Valid tx sent from:');
// console.log(address);
// console.log('tx hex:');
// console.log(hex);
await regtestUtils.broadcast(hex);
await regtestUtils.verify({
txId: tx.getId(),
address,
vout: 0,
value: sendAmount,
});
});
});
// Order of the curve (N) - 1
const N_LESS_1 = Buffer.from(
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
'hex',
);
// 1 represented as 32 bytes BE
const ONE = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex',
);
// Function for creating a tweaked p2tr key-spend only address
// (This is recommended by BIP341)
function createKeySpendOutput(publicKey: Buffer): Buffer {
// x-only pubkey (remove 1 byte y parity)
const myXOnlyPubkey = publicKey.slice(1, 33);
const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey);
const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash);
if (tweakResult === null) throw new Error('Invalid Tweak');
const { xOnlyPubkey: tweaked } = tweakResult;
// scriptPubkey
return Buffer.concat([
// witness v1, PUSH_DATA 32 bytes
Buffer.from([0x51, 0x20]),
// x-only tweaked pubkey
tweaked,
]);
}
// Function for signing for a tweaked p2tr key-spend only address
// (Required for the above address)
interface KeyPair {
publicKey: Buffer;
privateKey?: Buffer;
}
function signTweaked(messageHash: Buffer, key: KeyPair): Uint8Array {
const privateKey =
key.publicKey[0] === 2
? key.privateKey
: ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey!)!, ONE)!;
const tweakHash = bitcoin.crypto.taggedHash(
'TapTweak',
key.publicKey.slice(1, 33),
);
const newPrivateKey = ecc.privateAdd(privateKey!, tweakHash);
if (newPrivateKey === null) throw new Error('Invalid Tweak');
return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32));
}
// Function for creating signed tx
function createSigned(
key: KeyPair,
txid: string,
vout: number,
amountToSend: number,
scriptPubkeys: Buffer[],
values: number[],
): bitcoin.Transaction {
const tx = new bitcoin.Transaction();
tx.version = 2;
// Add input
tx.addInput(Buffer.from(txid, 'hex').reverse(), vout);
// Add output
tx.addOutput(scriptPubkeys[0], amountToSend);
const sighash = tx.hashForWitnessV1(
0, // which input
scriptPubkeys, // All previous outputs of all inputs
values, // All previous values of all inputs
bitcoin.Transaction.SIGHASH_DEFAULT, // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
);
const signature = Buffer.from(signTweaked(sighash, key));
// witness stack for keypath spend is just the signature.
// If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value
tx.ins[0].witness = [signature];
return tx;
}

View file

@ -1,11 +1,15 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as bip32 from 'bip32'; import BIP32Factory from 'bip32';
import { ECPair } from 'ecpair'; import * as ecc from 'tiny-secp256k1';
import ECPairFactory from 'ecpair';
import { describe, it } from 'mocha'; import { describe, it } from 'mocha';
import * as bitcoin from '../..'; import * as bitcoin from '../..';
import { regtestUtils } from './_regtest'; import { regtestUtils } from './_regtest';
const ECPair = ECPairFactory(ecc);
const rng = require('randombytes'); const rng = require('randombytes');
const regtest = regtestUtils.network; const regtest = regtestUtils.network;
const bip32 = BIP32Factory(ecc);
const validator = ( const validator = (
pubkey: Buffer, pubkey: Buffer,

View file

@ -1,9 +1,13 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as bip32 from 'bip32'; import BIP32Factory from 'bip32';
import * as ecc from 'tiny-secp256k1';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { ECPair } from 'ecpair'; import ECPairFactory from 'ecpair';
import { describe, it } from 'mocha'; import { describe, it } from 'mocha';
const bip32 = BIP32Factory(ecc);
const ECPair = ECPairFactory(ecc);
import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..'; import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..';
import * as preFixtures from './fixtures/psbt.json'; import * as preFixtures from './fixtures/psbt.json';

View file

@ -328,6 +328,27 @@ describe('Transaction', () => {
}); });
}); });
describe('taprootSigning', () => {
fixtures.taprootSigning.forEach(f => {
const tx = Transaction.fromHex(f.txHex);
const prevOutScripts = f.utxos.map(({ scriptHex }) =>
Buffer.from(scriptHex, 'hex'),
);
const values = f.utxos.map(({ value }) => value);
f.cases.forEach(c => {
let hash: Buffer;
it(`should hash to ${c.hash} for ${f.description}:${c.vin}`, () => {
const hashType = Buffer.from(c.typeHex, 'hex').readUInt8(0);
hash = tx.hashForWitnessV1(c.vin, prevOutScripts, values, hashType);
assert.strictEqual(hash.toString('hex'), c.hash);
});
});
});
});
describe('setWitness', () => { describe('setWitness', () => {
it('only accepts a a witness stack (Array of Buffers)', () => { it('only accepts a a witness stack (Array of Buffers)', () => {
assert.throws(() => { assert.throws(() => {

View file

@ -23,6 +23,11 @@ const FUTURE_SEGWIT_MIN_SIZE: number = 2;
const FUTURE_SEGWIT_MAX_VERSION: number = 16; const FUTURE_SEGWIT_MAX_VERSION: number = 16;
const FUTURE_SEGWIT_MIN_VERSION: number = 1; const FUTURE_SEGWIT_MIN_VERSION: number = 1;
const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50; const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50;
const FUTURE_SEGWIT_VERSION_WARNING: string =
'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
'End users MUST be warned carefully in the GUI and asked if they wish to proceed ' +
'with caution. Wallets should verify the segwit version from the output of fromBech32, ' +
'then decide when it is safe to use which version of segwit.';
function _toFutureSegwitAddress(output: Buffer, network: Network): string { function _toFutureSegwitAddress(output: Buffer, network: Network): string {
const data = output.slice(2); const data = output.slice(2);
@ -44,6 +49,8 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string {
if (output[1] !== data.length) if (output[1] !== data.length)
throw new TypeError('Invalid script for segwit address'); throw new TypeError('Invalid script for segwit address');
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
return toBech32(data, version, network.bech32); return toBech32(data, version, network.bech32);
} }
@ -163,13 +170,16 @@ export function toOutputScript(address: string, network?: Network): Buffer {
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION && decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE && decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
) ) {
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
return bscript.compile([ return bscript.compile([
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF, decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
decodeBech32.data, decodeBech32.data,
]); ]);
} }
} }
}
throw new Error(address + ' has no matching Script'); throw new Error(address + ' has no matching Script');
} }

View file

@ -58,6 +58,10 @@ export function cloneBuffer(buffer: Buffer): Buffer {
* Helper class for serialization of bitcoin data types into a pre-allocated buffer. * Helper class for serialization of bitcoin data types into a pre-allocated buffer.
*/ */
export class BufferWriter { export class BufferWriter {
static withCapacity(size: number): BufferWriter {
return new BufferWriter(Buffer.alloc(size));
}
constructor(public buffer: Buffer, public offset: number = 0) { constructor(public buffer: Buffer, public offset: number = 0) {
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
} }
@ -99,6 +103,13 @@ export class BufferWriter {
this.writeVarInt(vector.length); this.writeVarInt(vector.length);
vector.forEach((buf: Buffer) => this.writeVarSlice(buf)); vector.forEach((buf: Buffer) => this.writeVarSlice(buf));
} }
end(): Buffer {
if (this.buffer.length === this.offset) {
return this.buffer;
}
throw new Error(`buffer size ${this.buffer.length}, offset ${this.offset}`);
}
} }
/** /**

View file

@ -31,3 +31,27 @@ export function hash160(buffer: Buffer): Buffer {
export function hash256(buffer: Buffer): Buffer { export function hash256(buffer: Buffer): Buffer {
return sha256(sha256(buffer)); return sha256(sha256(buffer));
} }
const TAGS = [
'BIP0340/challenge',
'BIP0340/aux',
'BIP0340/nonce',
'TapLeaf',
'TapBranch',
'TapSighash',
'TapTweak',
'KeyAgg list',
'KeyAgg coefficient',
] as const;
export type TaggedHashPrefix = typeof TAGS[number];
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES = Object.fromEntries(
TAGS.map(tag => {
const tagHash = sha256(Buffer.from(tag));
return [tag, Buffer.concat([tagHash, tagHash])];
}),
) as { [k in TaggedHashPrefix]: Buffer };
export function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer {
return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data]));
}

View file

@ -7,6 +7,7 @@ import * as script from './script';
export { address, crypto, networks, payments, script }; export { address, crypto, networks, payments, script };
export { Block } from './block'; export { Block } from './block';
export { TaggedHashPrefix } from './crypto';
export { export {
Psbt, Psbt,
PsbtTxInput, PsbtTxInput,

View file

@ -27,7 +27,7 @@ function vectorSize(someVector: Buffer[]): number {
); );
} }
const EMPTY_SCRIPT: Buffer = Buffer.allocUnsafe(0); const EMPTY_BUFFER: Buffer = Buffer.allocUnsafe(0);
const EMPTY_WITNESS: Buffer[] = []; const EMPTY_WITNESS: Buffer[] = [];
const ZERO: Buffer = Buffer.from( const ZERO: Buffer = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000', '0000000000000000000000000000000000000000000000000000000000000000',
@ -39,7 +39,7 @@ const ONE: Buffer = Buffer.from(
); );
const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex'); const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex');
const BLANK_OUTPUT = { const BLANK_OUTPUT = {
script: EMPTY_SCRIPT, script: EMPTY_BUFFER,
valueBuffer: VALUE_UINT64_MAX, valueBuffer: VALUE_UINT64_MAX,
}; };
@ -62,10 +62,13 @@ export interface Input {
export class Transaction { export class Transaction {
static readonly DEFAULT_SEQUENCE = 0xffffffff; static readonly DEFAULT_SEQUENCE = 0xffffffff;
static readonly SIGHASH_DEFAULT = 0x00;
static readonly SIGHASH_ALL = 0x01; static readonly SIGHASH_ALL = 0x01;
static readonly SIGHASH_NONE = 0x02; static readonly SIGHASH_NONE = 0x02;
static readonly SIGHASH_SINGLE = 0x03; static readonly SIGHASH_SINGLE = 0x03;
static readonly SIGHASH_ANYONECANPAY = 0x80; static readonly SIGHASH_ANYONECANPAY = 0x80;
static readonly SIGHASH_OUTPUT_MASK = 0x03;
static readonly SIGHASH_INPUT_MASK = 0x80;
static readonly ADVANCED_TRANSACTION_MARKER = 0x00; static readonly ADVANCED_TRANSACTION_MARKER = 0x00;
static readonly ADVANCED_TRANSACTION_FLAG = 0x01; static readonly ADVANCED_TRANSACTION_FLAG = 0x01;
@ -174,7 +177,7 @@ export class Transaction {
this.ins.push({ this.ins.push({
hash, hash,
index, index,
script: scriptSig || EMPTY_SCRIPT, script: scriptSig || EMPTY_BUFFER,
sequence: sequence as number, sequence: sequence as number,
witness: EMPTY_WITNESS, witness: EMPTY_WITNESS,
}) - 1 }) - 1
@ -326,7 +329,7 @@ export class Transaction {
} else { } else {
// "blank" others input scripts // "blank" others input scripts
txTmp.ins.forEach(input => { txTmp.ins.forEach(input => {
input.script = EMPTY_SCRIPT; input.script = EMPTY_BUFFER;
}); });
txTmp.ins[inIndex].script = ourScript; txTmp.ins[inIndex].script = ourScript;
} }
@ -339,6 +342,158 @@ export class Transaction {
return bcrypto.hash256(buffer); return bcrypto.hash256(buffer);
} }
hashForWitnessV1(
inIndex: number,
prevOutScripts: Buffer[],
values: number[],
hashType: number,
leafHash?: Buffer,
annex?: Buffer,
): Buffer {
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message
typeforce(
types.tuple(
types.UInt32,
typeforce.arrayOf(types.Buffer),
typeforce.arrayOf(types.Satoshi),
types.UInt32,
),
arguments,
);
if (
values.length !== this.ins.length ||
prevOutScripts.length !== this.ins.length
) {
throw new Error('Must supply prevout script and value for all inputs');
}
const outputType =
hashType === Transaction.SIGHASH_DEFAULT
? Transaction.SIGHASH_ALL
: hashType & Transaction.SIGHASH_OUTPUT_MASK;
const inputType = hashType & Transaction.SIGHASH_INPUT_MASK;
const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY;
const isNone = outputType === Transaction.SIGHASH_NONE;
const isSingle = outputType === Transaction.SIGHASH_SINGLE;
let hashPrevouts = EMPTY_BUFFER;
let hashAmounts = EMPTY_BUFFER;
let hashScriptPubKeys = EMPTY_BUFFER;
let hashSequences = EMPTY_BUFFER;
let hashOutputs = EMPTY_BUFFER;
if (!isAnyoneCanPay) {
let bufferWriter = BufferWriter.withCapacity(36 * this.ins.length);
this.ins.forEach(txIn => {
bufferWriter.writeSlice(txIn.hash);
bufferWriter.writeUInt32(txIn.index);
});
hashPrevouts = bcrypto.sha256(bufferWriter.end());
bufferWriter = BufferWriter.withCapacity(8 * this.ins.length);
values.forEach(value => bufferWriter.writeUInt64(value));
hashAmounts = bcrypto.sha256(bufferWriter.end());
bufferWriter = BufferWriter.withCapacity(
prevOutScripts.map(varSliceSize).reduce((a, b) => a + b),
);
prevOutScripts.forEach(prevOutScript =>
bufferWriter.writeVarSlice(prevOutScript),
);
hashScriptPubKeys = bcrypto.sha256(bufferWriter.end());
bufferWriter = BufferWriter.withCapacity(4 * this.ins.length);
this.ins.forEach(txIn => bufferWriter.writeUInt32(txIn.sequence));
hashSequences = bcrypto.sha256(bufferWriter.end());
}
if (!(isNone || isSingle)) {
const txOutsSize = this.outs
.map(output => 8 + varSliceSize(output.script))
.reduce((a, b) => a + b);
const bufferWriter = BufferWriter.withCapacity(txOutsSize);
this.outs.forEach(out => {
bufferWriter.writeUInt64(out.value);
bufferWriter.writeVarSlice(out.script);
});
hashOutputs = bcrypto.sha256(bufferWriter.end());
} else if (isSingle && inIndex < this.outs.length) {
const output = this.outs[inIndex];
const bufferWriter = BufferWriter.withCapacity(
8 + varSliceSize(output.script),
);
bufferWriter.writeUInt64(output.value);
bufferWriter.writeVarSlice(output.script);
hashOutputs = bcrypto.sha256(bufferWriter.end());
}
const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0);
// Length calculation from:
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-14
// With extension from:
// https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#signature-validation
const sigMsgSize =
174 -
(isAnyoneCanPay ? 49 : 0) -
(isNone ? 32 : 0) +
(annex ? 32 : 0) +
(leafHash ? 37 : 0);
const sigMsgWriter = BufferWriter.withCapacity(sigMsgSize);
sigMsgWriter.writeUInt8(hashType);
// Transaction
sigMsgWriter.writeInt32(this.version);
sigMsgWriter.writeUInt32(this.locktime);
sigMsgWriter.writeSlice(hashPrevouts);
sigMsgWriter.writeSlice(hashAmounts);
sigMsgWriter.writeSlice(hashScriptPubKeys);
sigMsgWriter.writeSlice(hashSequences);
if (!(isNone || isSingle)) {
sigMsgWriter.writeSlice(hashOutputs);
}
// Input
sigMsgWriter.writeUInt8(spendType);
if (isAnyoneCanPay) {
const input = this.ins[inIndex];
sigMsgWriter.writeSlice(input.hash);
sigMsgWriter.writeUInt32(input.index);
sigMsgWriter.writeUInt64(values[inIndex]);
sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]);
sigMsgWriter.writeUInt32(input.sequence);
} else {
sigMsgWriter.writeUInt32(inIndex);
}
if (annex) {
const bufferWriter = BufferWriter.withCapacity(varSliceSize(annex));
bufferWriter.writeVarSlice(annex);
sigMsgWriter.writeSlice(bcrypto.sha256(bufferWriter.end()));
}
// Output
if (isSingle) {
sigMsgWriter.writeSlice(hashOutputs);
}
// BIP342 extension
if (leafHash) {
sigMsgWriter.writeSlice(leafHash);
sigMsgWriter.writeUInt8(0);
sigMsgWriter.writeUInt32(0xffffffff);
}
// Extra zero byte because:
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19
return bcrypto.taggedHash(
'TapSighash',
Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]),
);
}
hashForWitnessV0( hashForWitnessV0(
inIndex: number, inIndex: number,
prevOutScript: Buffer, prevOutScript: Buffer,