Compare commits
18 commits
feat/modul
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
ca9f9a5f46 | ||
|
2edfb992fa | ||
|
4e02ba70b1 | ||
|
11202eb74c | ||
|
93af5afe67 | ||
|
ac411e241b | ||
|
4674433bb9 | ||
|
191b9e8573 | ||
|
424abf2376 | ||
|
1f44f722d3 | ||
|
31e512e63f | ||
|
24e5cc0616 | ||
|
45187a32d0 | ||
|
f484edde01 | ||
|
e0f1620ee3 | ||
|
ee9c76376f | ||
|
8f31e88824 | ||
|
5137976e7b |
32 changed files with 933 additions and 180 deletions
2
.github/workflows/main_ci.yml
vendored
2
.github/workflows/main_ci.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
services:
|
||||
regtest:
|
||||
image: junderw/bitcoinjs-regtest-server@sha256:a46ec1a651ca5b1a5408f2b2526ea5f435421dd2bc2f28fae3bc33e1fd614552
|
||||
image: junderw/bitcoinjs-regtest-server@sha256:5b69cf95d9edf6d5b3a00504665d6b3c382a6aa3728fe8ce897974c519061463
|
||||
ports:
|
||||
- 8080:8080
|
||||
steps:
|
||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -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
|
||||
__changed__
|
||||
- Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563)
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
# BitcoinJS (bitcoinjs-lib)
|
||||
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib)
|
||||
[![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)
|
||||
[![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)
|
||||
|
||||
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).
|
||||
|
||||
**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
|
||||
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.
|
||||
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)
|
||||
- [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)
|
||||
|
|
135
package-lock.json
generated
135
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "bitcoinjs-lib",
|
||||
"version": "5.2.0",
|
||||
"version": "6.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -416,9 +416,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz",
|
||||
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==",
|
||||
"version": "16.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
|
||||
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/proxyquire": {
|
||||
|
@ -541,31 +541,21 @@
|
|||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz",
|
||||
"integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
|
||||
},
|
||||
"bip32": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
|
||||
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz",
|
||||
"integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "10.12.18",
|
||||
"bs58check": "^2.1.1",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"tiny-secp256k1": "^1.1.3",
|
||||
"typeforce": "^1.11.5",
|
||||
"wif": "^2.0.6"
|
||||
},
|
||||
|
@ -641,12 +631,6 @@
|
|||
"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": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
|
||||
|
@ -932,44 +916,14 @@
|
|||
"dev": true
|
||||
},
|
||||
"ecpair": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ecpair/-/ecpair-1.0.0.tgz",
|
||||
"integrity": "sha512-1L+P/ivLC3eKHgqcX1M9tFYQWXDoqwJ3zQnN7zDaTtLpiCQKpFTaAZvnsPC5PkWB4q3EPFAHffCLvjfCqRjuwQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz",
|
||||
"integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"randombytes": "^2.0.1",
|
||||
"tiny-secp256k1": "^1.1.6",
|
||||
"typeforce": "^1.11.3",
|
||||
"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
|
||||
}
|
||||
"randombytes": "^2.1.0",
|
||||
"typeforce": "^1.18.0",
|
||||
"wif": "^2.0.6"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
|
@ -1026,12 +980,6 @@
|
|||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz",
|
||||
|
@ -1204,16 +1152,6 @@
|
|||
"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": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
|
||||
|
@ -1230,17 +1168,6 @@
|
|||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz",
|
||||
|
@ -1626,18 +1553,6 @@
|
|||
"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": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
|
@ -1706,12 +1621,6 @@
|
|||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"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": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
|
||||
|
@ -2465,16 +2374,12 @@
|
|||
}
|
||||
},
|
||||
"tiny-secp256k1": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
|
||||
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.2.tgz",
|
||||
"integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bindings": "^1.3.0",
|
||||
"bn.js": "^4.11.8",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.4.0",
|
||||
"nan": "^2.13.2"
|
||||
"uint8array-tools": "0.0.6"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
|
@ -2583,6 +2488,12 @@
|
|||
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
|
||||
"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": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "bitcoinjs-lib",
|
||||
"version": "5.2.0",
|
||||
"version": "6.0.1",
|
||||
"description": "Client-side Bitcoin JavaScript library",
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.d.ts",
|
||||
|
@ -62,18 +62,18 @@
|
|||
"@types/bs58check": "^2.1.0",
|
||||
"@types/create-hash": "^1.2.2",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/node": "^16.11.7",
|
||||
"@types/proxyquire": "^1.3.28",
|
||||
"@types/randombytes": "^2.0.0",
|
||||
"@types/wif": "^2.0.2",
|
||||
"bip32": "^2.0.6",
|
||||
"bip32": "^3.0.1",
|
||||
"bip39": "^3.0.2",
|
||||
"bip65": "^1.0.1",
|
||||
"bip68": "^1.0.3",
|
||||
"bn.js": "^4.11.8",
|
||||
"bs58": "^4.0.0",
|
||||
"dhttp": "^3.0.0",
|
||||
"ecpair": "^1.0.0",
|
||||
"ecpair": "^2.0.1",
|
||||
"hoodwink": "^2.0.0",
|
||||
"minimaldata": "^1.0.2",
|
||||
"mocha": "^7.1.1",
|
||||
|
@ -84,6 +84,7 @@
|
|||
"randombytes": "^2.1.0",
|
||||
"regtest-client": "0.2.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"tiny-secp256k1": "^2.1.2",
|
||||
"ts-node": "^8.3.0",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "^4.4.4"
|
||||
|
|
|
@ -13,6 +13,11 @@ const FUTURE_SEGWIT_MIN_SIZE = 2;
|
|||
const FUTURE_SEGWIT_MAX_VERSION = 16;
|
||||
const FUTURE_SEGWIT_MIN_VERSION = 1;
|
||||
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) {
|
||||
const data = output.slice(2);
|
||||
if (
|
||||
|
@ -28,6 +33,7 @@ function _toFutureSegwitAddress(output, network) {
|
|||
throw new TypeError('Invalid version for segwit address');
|
||||
if (output[1] !== data.length)
|
||||
throw new TypeError('Invalid script for segwit address');
|
||||
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
|
||||
return toBech32(data, version, network.bech32);
|
||||
}
|
||||
function fromBase58Check(address) {
|
||||
|
@ -128,13 +134,15 @@ function toOutputScript(address, network) {
|
|||
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
|
||||
decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
|
||||
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
|
||||
)
|
||||
) {
|
||||
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
|
||||
return bscript.compile([
|
||||
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
|
||||
decodeBech32.data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(address + ' has no matching Script');
|
||||
}
|
||||
exports.toOutputScript = toOutputScript;
|
||||
|
|
2
src/bufferutils.d.ts
vendored
2
src/bufferutils.d.ts
vendored
|
@ -11,6 +11,7 @@ export declare function cloneBuffer(buffer: Buffer): Buffer;
|
|||
export declare class BufferWriter {
|
||||
buffer: Buffer;
|
||||
offset: number;
|
||||
static withCapacity(size: number): BufferWriter;
|
||||
constructor(buffer: Buffer, offset?: number);
|
||||
writeUInt8(i: number): void;
|
||||
writeInt32(i: number): void;
|
||||
|
@ -20,6 +21,7 @@ export declare class BufferWriter {
|
|||
writeSlice(slice: Buffer): void;
|
||||
writeVarSlice(slice: Buffer): void;
|
||||
writeVector(vector: Buffer[]): void;
|
||||
end(): Buffer;
|
||||
}
|
||||
/**
|
||||
* Helper class for reading of bitcoin data types from a buffer.
|
||||
|
|
|
@ -58,6 +58,9 @@ class BufferWriter {
|
|||
this.offset = offset;
|
||||
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
|
||||
}
|
||||
static withCapacity(size) {
|
||||
return new BufferWriter(Buffer.alloc(size));
|
||||
}
|
||||
writeUInt8(i) {
|
||||
this.offset = this.buffer.writeUInt8(i, this.offset);
|
||||
}
|
||||
|
@ -88,6 +91,12 @@ class BufferWriter {
|
|||
this.writeVarInt(vector.length);
|
||||
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;
|
||||
/**
|
||||
|
|
4
src/crypto.d.ts
vendored
4
src/crypto.d.ts
vendored
|
@ -4,3 +4,7 @@ export declare function sha1(buffer: Buffer): Buffer;
|
|||
export declare function sha256(buffer: Buffer): Buffer;
|
||||
export declare function hash160(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 {};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
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');
|
||||
function ripemd160(buffer) {
|
||||
try {
|
||||
|
@ -34,3 +34,25 @@ function hash256(buffer) {
|
|||
return sha256(sha256(buffer));
|
||||
}
|
||||
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
1
src/index.d.ts
vendored
|
@ -5,6 +5,7 @@ import * as payments from './payments';
|
|||
import * as script from './script';
|
||||
export { address, crypto, networks, payments, script };
|
||||
export { Block } from './block';
|
||||
export { TaggedHashPrefix } from './crypto';
|
||||
export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt';
|
||||
export { OPS as opcodes } from './ops';
|
||||
export { Transaction } from './transaction';
|
||||
|
|
4
src/transaction.d.ts
vendored
4
src/transaction.d.ts
vendored
|
@ -12,10 +12,13 @@ export interface Input {
|
|||
}
|
||||
export declare class Transaction {
|
||||
static readonly DEFAULT_SEQUENCE = 4294967295;
|
||||
static readonly SIGHASH_DEFAULT = 0;
|
||||
static readonly SIGHASH_ALL = 1;
|
||||
static readonly SIGHASH_NONE = 2;
|
||||
static readonly SIGHASH_SINGLE = 3;
|
||||
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_FLAG = 1;
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
getHash(forWitness?: boolean): Buffer;
|
||||
getId(): string;
|
||||
|
|
|
@ -20,7 +20,7 @@ function vectorSize(someVector) {
|
|||
}, 0)
|
||||
);
|
||||
}
|
||||
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
|
||||
const EMPTY_BUFFER = Buffer.allocUnsafe(0);
|
||||
const EMPTY_WITNESS = [];
|
||||
const ZERO = Buffer.from(
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
|
@ -32,7 +32,7 @@ const ONE = Buffer.from(
|
|||
);
|
||||
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
|
||||
const BLANK_OUTPUT = {
|
||||
script: EMPTY_SCRIPT,
|
||||
script: EMPTY_BUFFER,
|
||||
valueBuffer: VALUE_UINT64_MAX,
|
||||
};
|
||||
function isOutput(out) {
|
||||
|
@ -124,7 +124,7 @@ class Transaction {
|
|||
this.ins.push({
|
||||
hash,
|
||||
index,
|
||||
script: scriptSig || EMPTY_SCRIPT,
|
||||
script: scriptSig || EMPTY_BUFFER,
|
||||
sequence: sequence,
|
||||
witness: EMPTY_WITNESS,
|
||||
}) - 1
|
||||
|
@ -247,7 +247,7 @@ class Transaction {
|
|||
} else {
|
||||
// "blank" others input scripts
|
||||
txTmp.ins.forEach(input => {
|
||||
input.script = EMPTY_SCRIPT;
|
||||
input.script = EMPTY_BUFFER;
|
||||
});
|
||||
txTmp.ins[inIndex].script = ourScript;
|
||||
}
|
||||
|
@ -257,6 +257,141 @@ class Transaction {
|
|||
txTmp.__toBuffer(buffer, 0, false);
|
||||
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) {
|
||||
typeforce(
|
||||
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
|
||||
|
@ -396,9 +531,12 @@ class Transaction {
|
|||
}
|
||||
exports.Transaction = Transaction;
|
||||
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
|
||||
Transaction.SIGHASH_DEFAULT = 0x00;
|
||||
Transaction.SIGHASH_ALL = 0x01;
|
||||
Transaction.SIGHASH_NONE = 0x02;
|
||||
Transaction.SIGHASH_SINGLE = 0x03;
|
||||
Transaction.SIGHASH_ANYONECANPAY = 0x80;
|
||||
Transaction.SIGHASH_OUTPUT_MASK = 0x03;
|
||||
Transaction.SIGHASH_INPUT_MASK = 0x80;
|
||||
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
|
||||
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
|
||||
|
|
|
@ -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', () => {
|
||||
const values = [0, 1, 254, 255];
|
||||
const expectedBuffer = Buffer.from([0, 1, 0xfe, 0xff]);
|
||||
|
@ -277,6 +284,16 @@ describe('bufferutils', () => {
|
|||
});
|
||||
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', () => {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as assert from 'assert';
|
||||
import { describe, it } from 'mocha';
|
||||
import { crypto as bcrypto } from '..';
|
||||
import { crypto as bcrypto, TaggedHashPrefix } from '..';
|
||||
import * as fixtures from './fixtures/crypto.json';
|
||||
|
||||
describe('crypto', () => {
|
||||
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => {
|
||||
describe(algorithm, () => {
|
||||
fixtures.forEach(f => {
|
||||
fixtures.hashes.forEach(f => {
|
||||
const fn = (bcrypto 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'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
13
test/fixtures/crypto.json
vendored
13
test/fixtures/crypto.json
vendored
|
@ -1,4 +1,5 @@
|
|||
[
|
||||
{
|
||||
"hashes": [
|
||||
{
|
||||
"hex": "0000000000000001",
|
||||
"hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8",
|
||||
|
@ -31,4 +32,12 @@
|
|||
"sha1": "10d96fb43aca84e342206887bbeed3065d4e4344",
|
||||
"sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da"
|
||||
}
|
||||
]
|
||||
],
|
||||
"taggedHash": [
|
||||
{
|
||||
"tag": "TapTweak",
|
||||
"hex": "0101010101010101",
|
||||
"result": "71ae15bad52efcecf4c9f672bfbded68a4adb8258f1b95f0d06aefdb5ebd14e9"
|
||||
}
|
||||
]
|
||||
}
|
81
test/fixtures/transaction.json
vendored
81
test/fixtures/transaction.json
vendored
|
@ -808,6 +808,87 @@
|
|||
"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": {
|
||||
"addInput": [
|
||||
{
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
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 * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
|
||||
const ECPair = ECPairFactory(ecc);
|
||||
const dhttp = regtestUtils.dhttp;
|
||||
const TESTNET = bitcoin.networks.testnet;
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
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 { describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
|
||||
const bip32 = BIP32Factory(ecc);
|
||||
|
||||
function getAddress(node: any, network?: any): string {
|
||||
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
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 * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
|
||||
const ECPair = ECPairFactory(ecc);
|
||||
const regtest = regtestUtils.network;
|
||||
const bip65 = require('bip65');
|
||||
|
||||
|
@ -250,7 +253,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
|||
await regtestUtils.broadcast(tx.toHex()).catch(err => {
|
||||
assert.throws(() => {
|
||||
if (err) throw err;
|
||||
}, /Error: non-final \(code 64\)/);
|
||||
}, /Error: non-final/);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import * as assert from 'assert';
|
||||
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 * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
|
||||
const ECPair = ECPairFactory(ecc);
|
||||
const regtest = regtestUtils.network;
|
||||
const bip68 = require('bip68');
|
||||
const varuint = require('varuint-bitcoin');
|
||||
|
@ -219,7 +222,7 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
|||
await regtestUtils.broadcast(tx.toHex()).catch(err => {
|
||||
assert.throws(() => {
|
||||
if (err) throw err;
|
||||
}, /Error: non-BIP68-final \(code 64\)/);
|
||||
}, /Error: non-BIP68-final/);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { ECPair } from 'ecpair';
|
||||
import ECPairFactory from 'ecpair';
|
||||
import * as ecc from 'tiny-secp256k1';
|
||||
import { describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
|
||||
const ECPair = ECPairFactory(ecc);
|
||||
const NETWORK = regtestUtils.network;
|
||||
const keyPairs = [
|
||||
ECPair.makeRandom({ network: NETWORK }),
|
||||
|
|
156
test/integration/taproot.md
Normal file
156
test/integration/taproot.md
Normal 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);
|
||||
});
|
||||
```
|
122
test/integration/taproot.spec.ts
Normal file
122
test/integration/taproot.spec.ts
Normal 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;
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
import * as assert from 'assert';
|
||||
import * as bip32 from 'bip32';
|
||||
import { ECPair } from 'ecpair';
|
||||
import BIP32Factory from 'bip32';
|
||||
import * as ecc from 'tiny-secp256k1';
|
||||
import ECPairFactory from 'ecpair';
|
||||
import { describe, it } from 'mocha';
|
||||
import * as bitcoin from '../..';
|
||||
import { regtestUtils } from './_regtest';
|
||||
|
||||
const ECPair = ECPairFactory(ecc);
|
||||
const rng = require('randombytes');
|
||||
const regtest = regtestUtils.network;
|
||||
const bip32 = BIP32Factory(ecc);
|
||||
|
||||
const validator = (
|
||||
pubkey: Buffer,
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
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 { ECPair } from 'ecpair';
|
||||
import ECPairFactory from 'ecpair';
|
||||
import { describe, it } from 'mocha';
|
||||
|
||||
const bip32 = BIP32Factory(ecc);
|
||||
const ECPair = ECPairFactory(ecc);
|
||||
|
||||
import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..';
|
||||
|
||||
import * as preFixtures from './fixtures/psbt.json';
|
||||
|
|
|
@ -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', () => {
|
||||
it('only accepts a a witness stack (Array of Buffers)', () => {
|
||||
assert.throws(() => {
|
||||
|
|
|
@ -23,6 +23,11 @@ const FUTURE_SEGWIT_MIN_SIZE: number = 2;
|
|||
const FUTURE_SEGWIT_MAX_VERSION: number = 16;
|
||||
const FUTURE_SEGWIT_MIN_VERSION: number = 1;
|
||||
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 {
|
||||
const data = output.slice(2);
|
||||
|
@ -44,6 +49,8 @@ function _toFutureSegwitAddress(output: Buffer, network: Network): string {
|
|||
if (output[1] !== data.length)
|
||||
throw new TypeError('Invalid script for segwit address');
|
||||
|
||||
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
|
||||
|
||||
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.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
|
||||
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
|
||||
)
|
||||
) {
|
||||
console.warn(FUTURE_SEGWIT_VERSION_WARNING);
|
||||
|
||||
return bscript.compile([
|
||||
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
|
||||
decodeBech32.data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(address + ' has no matching Script');
|
||||
}
|
||||
|
|
|
@ -58,6 +58,10 @@ export function cloneBuffer(buffer: Buffer): Buffer {
|
|||
* Helper class for serialization of bitcoin data types into a pre-allocated buffer.
|
||||
*/
|
||||
export class BufferWriter {
|
||||
static withCapacity(size: number): BufferWriter {
|
||||
return new BufferWriter(Buffer.alloc(size));
|
||||
}
|
||||
|
||||
constructor(public buffer: Buffer, public offset: number = 0) {
|
||||
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
|
||||
}
|
||||
|
@ -99,6 +103,13 @@ export class BufferWriter {
|
|||
this.writeVarInt(vector.length);
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,3 +31,27 @@ export function hash160(buffer: Buffer): Buffer {
|
|||
export function hash256(buffer: Buffer): 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]));
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import * as script from './script';
|
|||
export { address, crypto, networks, payments, script };
|
||||
|
||||
export { Block } from './block';
|
||||
export { TaggedHashPrefix } from './crypto';
|
||||
export {
|
||||
Psbt,
|
||||
PsbtTxInput,
|
||||
|
|
|
@ -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 ZERO: Buffer = Buffer.from(
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
|
@ -39,7 +39,7 @@ const ONE: Buffer = Buffer.from(
|
|||
);
|
||||
const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex');
|
||||
const BLANK_OUTPUT = {
|
||||
script: EMPTY_SCRIPT,
|
||||
script: EMPTY_BUFFER,
|
||||
valueBuffer: VALUE_UINT64_MAX,
|
||||
};
|
||||
|
||||
|
@ -62,10 +62,13 @@ export interface Input {
|
|||
|
||||
export class Transaction {
|
||||
static readonly DEFAULT_SEQUENCE = 0xffffffff;
|
||||
static readonly SIGHASH_DEFAULT = 0x00;
|
||||
static readonly SIGHASH_ALL = 0x01;
|
||||
static readonly SIGHASH_NONE = 0x02;
|
||||
static readonly SIGHASH_SINGLE = 0x03;
|
||||
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_FLAG = 0x01;
|
||||
|
||||
|
@ -174,7 +177,7 @@ export class Transaction {
|
|||
this.ins.push({
|
||||
hash,
|
||||
index,
|
||||
script: scriptSig || EMPTY_SCRIPT,
|
||||
script: scriptSig || EMPTY_BUFFER,
|
||||
sequence: sequence as number,
|
||||
witness: EMPTY_WITNESS,
|
||||
}) - 1
|
||||
|
@ -326,7 +329,7 @@ export class Transaction {
|
|||
} else {
|
||||
// "blank" others input scripts
|
||||
txTmp.ins.forEach(input => {
|
||||
input.script = EMPTY_SCRIPT;
|
||||
input.script = EMPTY_BUFFER;
|
||||
});
|
||||
txTmp.ins[inIndex].script = ourScript;
|
||||
}
|
||||
|
@ -339,6 +342,158 @@ export class Transaction {
|
|||
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(
|
||||
inIndex: number,
|
||||
prevOutScript: Buffer,
|
||||
|
|
Loading…
Reference in a new issue