diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index 7bc62cb..a9fdc58 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest services: regtest: - image: junderw/bitcoinjs-regtest-server@sha256:5b69cf95d9edf6d5b3a00504665d6b3c382a6aa3728fe8ce897974c519061463 + image: junderw/bitcoinjs-regtest-server@sha256:a46ec1a651ca5b1a5408f2b2526ea5f435421dd2bc2f28fae3bc33e1fd614552 ports: - 8080:8080 steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d8f91..a4e1eb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,3 @@ -# 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) diff --git a/README.md b/README.md index 9389a73..d974624 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # BitcoinJS (bitcoinjs-lib) -[](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml) [](https://www.npmjs.org/package/bitcoinjs-lib) [](https://github.com/prettier/prettier) +[](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) +[](https://www.npmjs.org/package/bitcoinjs-lib) + +[](https://github.com/prettier/prettier) A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify. @@ -91,8 +94,6 @@ 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) diff --git a/package-lock.json b/package-lock.json index 3e5bd51..4d00add 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.1", + "version": "5.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -416,9 +416,9 @@ "dev": true }, "@types/node": { - "version": "16.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", - "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==", + "version": "16.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", + "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", "dev": true }, "@types/proxyquire": { @@ -541,21 +541,31 @@ "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": "3.0.1", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.0.1.tgz", - "integrity": "sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", + "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", "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" }, @@ -631,6 +641,12 @@ "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", @@ -916,14 +932,44 @@ "dev": true }, "ecpair": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.0.1.tgz", - "integrity": "sha512-iT3wztQMeE/nDTlfnAg8dAFUfBS7Tq2BXzq3ae6L+pWgFU0fQ3l0woTzdTBrJV3OxBjxbzjq8EQhAbEmJNWFSw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-1.0.0.tgz", + "integrity": "sha512-1L+P/ivLC3eKHgqcX1M9tFYQWXDoqwJ3zQnN7zDaTtLpiCQKpFTaAZvnsPC5PkWB4q3EPFAHffCLvjfCqRjuwQ==", "dev": true, "requires": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" + "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 + } } }, "emoji-regex": { @@ -980,6 +1026,12 @@ "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", @@ -1152,6 +1204,16 @@ "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", @@ -1168,6 +1230,17 @@ "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", @@ -1553,6 +1626,18 @@ "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", @@ -1621,6 +1706,12 @@ "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", @@ -2374,12 +2465,16 @@ } }, "tiny-secp256k1": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.2.tgz", - "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", "dev": true, "requires": { - "uint8array-tools": "0.0.6" + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" } }, "to-fast-properties": { @@ -2488,12 +2583,6 @@ "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", diff --git a/package.json b/package.json index c5553d5..9d15766 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "6.0.1", + "version": "5.2.0", "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.7", + "@types/node": "^16.11.1", "@types/proxyquire": "^1.3.28", "@types/randombytes": "^2.0.0", "@types/wif": "^2.0.2", - "bip32": "^3.0.1", + "bip32": "^2.0.6", "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": "^2.0.1", + "ecpair": "^1.0.0", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", "mocha": "^7.1.1", @@ -84,7 +84,6 @@ "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" diff --git a/src/address.js b/src/address.js index 164bf7e..12938fc 100644 --- a/src/address.js +++ b/src/address.js @@ -13,11 +13,6 @@ 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 ( @@ -33,7 +28,6 @@ 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) { @@ -134,13 +128,11 @@ 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'); diff --git a/src/bufferutils.d.ts b/src/bufferutils.d.ts index b1d8966..40f89b3 100644 --- a/src/bufferutils.d.ts +++ b/src/bufferutils.d.ts @@ -11,7 +11,6 @@ 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; @@ -21,7 +20,6 @@ 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. diff --git a/src/bufferutils.js b/src/bufferutils.js index 83a013b..fcab0b7 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -58,9 +58,6 @@ 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); } @@ -91,12 +88,6 @@ 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; /** diff --git a/src/crypto.d.ts b/src/crypto.d.ts index ec088f3..1743681 100644 --- a/src/crypto.d.ts +++ b/src/crypto.d.ts @@ -4,7 +4,3 @@ 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 {}; diff --git a/src/crypto.js b/src/crypto.js index 3c308da..f53b041 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,6 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0; +exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0; const createHash = require('create-hash'); function ripemd160(buffer) { try { @@ -34,25 +34,3 @@ 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; diff --git a/src/index.d.ts b/src/index.d.ts index b93c2aa..5b339ca 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -5,7 +5,6 @@ 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'; diff --git a/src/transaction.d.ts b/src/transaction.d.ts index 613706b..c4de954 100644 --- a/src/transaction.d.ts +++ b/src/transaction.d.ts @@ -12,13 +12,10 @@ 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; @@ -45,7 +42,6 @@ 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; diff --git a/src/transaction.js b/src/transaction.js index 6f1382c..5a29569 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -20,7 +20,7 @@ function vectorSize(someVector) { }, 0) ); } -const EMPTY_BUFFER = Buffer.allocUnsafe(0); +const EMPTY_SCRIPT = 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_BUFFER, + script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX, }; function isOutput(out) { @@ -124,7 +124,7 @@ class Transaction { this.ins.push({ hash, index, - script: scriptSig || EMPTY_BUFFER, + script: scriptSig || EMPTY_SCRIPT, sequence: sequence, witness: EMPTY_WITNESS, }) - 1 @@ -247,7 +247,7 @@ class Transaction { } else { // "blank" others input scripts txTmp.ins.forEach(input => { - input.script = EMPTY_BUFFER; + input.script = EMPTY_SCRIPT; }); txTmp.ins[inIndex].script = ourScript; } @@ -257,141 +257,6 @@ 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), @@ -531,12 +396,9 @@ 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; diff --git a/test/bufferutils.spec.ts b/test/bufferutils.spec.ts index 0f1f1a9..213b156 100644 --- a/test/bufferutils.spec.ts +++ b/test/bufferutils.spec.ts @@ -66,13 +66,6 @@ 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]); @@ -284,16 +277,6 @@ 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', () => { diff --git a/test/crypto.spec.ts b/test/crypto.spec.ts index 0482ec9..89ffabb 100644 --- a/test/crypto.spec.ts +++ b/test/crypto.spec.ts @@ -1,12 +1,12 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; -import { crypto as bcrypto, TaggedHashPrefix } from '..'; +import { crypto as bcrypto } from '..'; import * as fixtures from './fixtures/crypto.json'; describe('crypto', () => { ['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => { describe(algorithm, () => { - fixtures.hashes.forEach(f => { + fixtures.forEach(f => { const fn = (bcrypto as any)[algorithm]; const expected = (f as any)[algorithm]; @@ -19,15 +19,4 @@ 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')); - }); - }); - }); }); diff --git a/test/fixtures/crypto.json b/test/fixtures/crypto.json index 1d1976b..102b50a 100644 --- a/test/fixtures/crypto.json +++ b/test/fixtures/crypto.json @@ -1,43 +1,34 @@ -{ - "hashes": [ - { - "hex": "0000000000000001", - "hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8", - "hash256": "3ae5c198d17634e79059c2cd735491553d22c4e09d1d9fea3ecf214565df2284", - "ripemd160": "8d1a05d1bc08870968eb8a81ad4393fd3aac6633", - "sha1": "cb473678976f425d6ec1339838f11011007ad27d", - "sha256": "cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50" - }, - { - "hex": "0101010101010101", - "hash160": "abaf1119f83e384210fe8e222eac76e2f0da39dc", - "hash256": "728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23", - "ripemd160": "5825701b4b9767fd35063b286dca3582853e0630", - "sha1": "c0357a32ed1f6a03be92dd094476f7f1a2e214ec", - "sha256": "04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061" - }, - { - "hex": "ffffffffffffffff", - "hash160": "f86221f5a1fca059a865c0b7d374dfa9d5f3aeb4", - "hash256": "752adad0a7b9ceca853768aebb6965eca126a62965f698a0c1bc43d83db632ad", - "ripemd160": "cb760221600ed34337ca3ab70016b5f58c838120", - "sha1": "be673e8a56eaa9d8c1d35064866701c11ef8e089", - "sha256": "12a3ae445661ce5dee78d0650d33362dec29c4f82af05e7e57fb595bbbacf0ca" - }, - { - "hex": "4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e65632061742066617563696275732073617069656e2c2076656c20666163696c6973697320617263752e20536564207574206d61737361206e6962682e205574206d6f6c6c69732070756c76696e6172206d617373612e20557420756c6c616d636f7270657220646f6c6f7220656e696d2c20696e206d6f6c657374696520656e696d20636f6e64696d656e74756d2061632e20416c697175616d206572617420766f6c75747061742e204e756c6c6120736f64616c657320617420647569206e656320", - "hash160": "9763e6b367c363bd6b88a7b361c98e6beee243a5", - "hash256": "033588797115feb3545052670cac2a46584ab3cb460de63756ee0275e66b5799", - "ripemd160": "cad8593dcdef12ee334c97bab9787f07b3f3a1a5", - "sha1": "10d96fb43aca84e342206887bbeed3065d4e4344", - "sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da" - } - ], - "taggedHash": [ - { - "tag": "TapTweak", - "hex": "0101010101010101", - "result": "71ae15bad52efcecf4c9f672bfbded68a4adb8258f1b95f0d06aefdb5ebd14e9" - } - ] -} \ No newline at end of file +[ + { + "hex": "0000000000000001", + "hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8", + "hash256": "3ae5c198d17634e79059c2cd735491553d22c4e09d1d9fea3ecf214565df2284", + "ripemd160": "8d1a05d1bc08870968eb8a81ad4393fd3aac6633", + "sha1": "cb473678976f425d6ec1339838f11011007ad27d", + "sha256": "cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50" + }, + { + "hex": "0101010101010101", + "hash160": "abaf1119f83e384210fe8e222eac76e2f0da39dc", + "hash256": "728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23", + "ripemd160": "5825701b4b9767fd35063b286dca3582853e0630", + "sha1": "c0357a32ed1f6a03be92dd094476f7f1a2e214ec", + "sha256": "04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061" + }, + { + "hex": "ffffffffffffffff", + "hash160": "f86221f5a1fca059a865c0b7d374dfa9d5f3aeb4", + "hash256": "752adad0a7b9ceca853768aebb6965eca126a62965f698a0c1bc43d83db632ad", + "ripemd160": "cb760221600ed34337ca3ab70016b5f58c838120", + "sha1": "be673e8a56eaa9d8c1d35064866701c11ef8e089", + "sha256": "12a3ae445661ce5dee78d0650d33362dec29c4f82af05e7e57fb595bbbacf0ca" + }, + { + "hex": "4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e65632061742066617563696275732073617069656e2c2076656c20666163696c6973697320617263752e20536564207574206d61737361206e6962682e205574206d6f6c6c69732070756c76696e6172206d617373612e20557420756c6c616d636f7270657220646f6c6f7220656e696d2c20696e206d6f6c657374696520656e696d20636f6e64696d656e74756d2061632e20416c697175616d206572617420766f6c75747061742e204e756c6c6120736f64616c657320617420647569206e656320", + "hash160": "9763e6b367c363bd6b88a7b361c98e6beee243a5", + "hash256": "033588797115feb3545052670cac2a46584ab3cb460de63756ee0275e66b5799", + "ripemd160": "cad8593dcdef12ee334c97bab9787f07b3f3a1a5", + "sha1": "10d96fb43aca84e342206887bbeed3065d4e4344", + "sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da" + } +] diff --git a/test/fixtures/transaction.json b/test/fixtures/transaction.json index 979549a..6bf6090 100644 --- a/test/fixtures/transaction.json +++ b/test/fixtures/transaction.json @@ -808,87 +808,6 @@ "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": [ { diff --git a/test/integration/addresses.spec.ts b/test/integration/addresses.spec.ts index d6e758b..2b24ef5 100644 --- a/test/integration/addresses.spec.ts +++ b/test/integration/addresses.spec.ts @@ -1,11 +1,8 @@ import * as assert from 'assert'; -import ECPairFactory from 'ecpair'; -import * as ecc from 'tiny-secp256k1'; +import { ECPair } from 'ecpair'; 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; diff --git a/test/integration/bip32.spec.ts b/test/integration/bip32.spec.ts index 938c281..7cd9e2f 100644 --- a/test/integration/bip32.spec.ts +++ b/test/integration/bip32.spec.ts @@ -1,12 +1,9 @@ import * as assert from 'assert'; -import BIP32Factory from 'bip32'; -import * as ecc from 'tiny-secp256k1'; +import * as bip32 from 'bip32'; 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!; } diff --git a/test/integration/cltv.spec.ts b/test/integration/cltv.spec.ts index 04944eb..c1a52de 100644 --- a/test/integration/cltv.spec.ts +++ b/test/integration/cltv.spec.ts @@ -1,11 +1,8 @@ import * as assert from 'assert'; -import ECPairFactory from 'ecpair'; -import * as ecc from 'tiny-secp256k1'; +import { ECPair } from 'ecpair'; 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'); diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 742d68f..9993d5c 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -1,12 +1,9 @@ import * as assert from 'assert'; import { PsbtInput } from 'bip174/src/lib/interfaces'; -import ECPairFactory from 'ecpair'; -import * as ecc from 'tiny-secp256k1'; +import { ECPair } from 'ecpair'; 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'); diff --git a/test/integration/payments.spec.ts b/test/integration/payments.spec.ts index d9d7fde..ea7294e 100644 --- a/test/integration/payments.spec.ts +++ b/test/integration/payments.spec.ts @@ -1,10 +1,7 @@ -import ECPairFactory from 'ecpair'; -import * as ecc from 'tiny-secp256k1'; +import { ECPair } from 'ecpair'; 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 }), diff --git a/test/integration/taproot.md b/test/integration/taproot.md deleted file mode 100644 index 4010340..0000000 --- a/test/integration/taproot.md +++ /dev/null @@ -1,156 +0,0 @@ -# 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); -}); -``` \ No newline at end of file diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts deleted file mode 100644 index f7b3733..0000000 --- a/test/integration/taproot.spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -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; -} diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index ba7f2fe..d4788dc 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -1,15 +1,11 @@ import * as assert from 'assert'; -import BIP32Factory from 'bip32'; -import * as ecc from 'tiny-secp256k1'; -import ECPairFactory from 'ecpair'; +import * as bip32 from 'bip32'; +import { ECPair } 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, diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index f583e80..05d4468 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -1,13 +1,9 @@ import * as assert from 'assert'; -import BIP32Factory from 'bip32'; -import * as ecc from 'tiny-secp256k1'; +import * as bip32 from 'bip32'; import * as crypto from 'crypto'; -import ECPairFactory from 'ecpair'; +import { ECPair } 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'; diff --git a/test/transaction.spec.ts b/test/transaction.spec.ts index 13d64d1..6744545 100644 --- a/test/transaction.spec.ts +++ b/test/transaction.spec.ts @@ -328,27 +328,6 @@ 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(() => { diff --git a/ts_src/address.ts b/ts_src/address.ts index 753589d..d8111a7 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -23,11 +23,6 @@ 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); @@ -49,8 +44,6 @@ 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); } @@ -170,14 +163,11 @@ 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, ]); - } } } diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts index 901d72a..43171c4 100644 --- a/ts_src/bufferutils.ts +++ b/ts_src/bufferutils.ts @@ -58,10 +58,6 @@ 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]); } @@ -103,13 +99,6 @@ 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}`); - } } /** diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index b7c355a..7f69c40 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -31,27 +31,3 @@ 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])); -} diff --git a/ts_src/index.ts b/ts_src/index.ts index d8b8619..4cc82bf 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -7,7 +7,6 @@ import * as script from './script'; export { address, crypto, networks, payments, script }; export { Block } from './block'; -export { TaggedHashPrefix } from './crypto'; export { Psbt, PsbtTxInput, diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 416f20e..c5dde9a 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -27,7 +27,7 @@ function vectorSize(someVector: Buffer[]): number { ); } -const EMPTY_BUFFER: Buffer = Buffer.allocUnsafe(0); +const EMPTY_SCRIPT: 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_BUFFER, + script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX, }; @@ -62,13 +62,10 @@ 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; @@ -177,7 +174,7 @@ export class Transaction { this.ins.push({ hash, index, - script: scriptSig || EMPTY_BUFFER, + script: scriptSig || EMPTY_SCRIPT, sequence: sequence as number, witness: EMPTY_WITNESS, }) - 1 @@ -329,7 +326,7 @@ export class Transaction { } else { // "blank" others input scripts txTmp.ins.forEach(input => { - input.script = EMPTY_BUFFER; + input.script = EMPTY_SCRIPT; }); txTmp.ins[inIndex].script = ourScript; } @@ -342,158 +339,6 @@ 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,