Compare commits

...

14 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
32 changed files with 930 additions and 177 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.
@ -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');

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');

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,