@exodus/bitcoinjs-lib
Advanced tools
Comparing version 6.0.2-beta.5 to 6.1.5-exodus.0
{ | ||
"name": "@exodus/bitcoinjs-lib", | ||
"version": "6.0.2-beta.5", | ||
"version": "6.1.5-exodus.0", | ||
"description": "Client-side Bitcoin JavaScript library", | ||
@@ -18,3 +18,3 @@ "main": "./src/index.js", | ||
"scripts": { | ||
"audit": "NPM_AUDIT_IGNORE_DEV=1 NPM_AUDIT_IGNORE_LEVEL=low npm-audit-whitelister .npm-audit-whitelister.json", | ||
"audit": "better-npm-audit audit -l high", | ||
"build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs", | ||
@@ -32,8 +32,8 @@ "build:tests": "npm run clean:jstests && tsc -p ./test/tsconfig.json", | ||
"integration": "npm run build && npm run nobuild:integration", | ||
"lint": "tslint -p tsconfig.json -c tslint.json", | ||
"lint:tests": "tslint -p test/tsconfig.json -c tslint.json", | ||
"lint": "eslint ts_src/** src/**/*.js", | ||
"lint:tests": "eslint test/**/*.spec.ts", | ||
"mocha:ts": "mocha --recursive --require test/ts-node-register", | ||
"nobuild:coverage-report": "nyc report --reporter=lcov", | ||
"nobuild:coverage-html": "nyc report --reporter=html", | ||
"nobuild:coverage": "npm run build:tests && nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha && npm run clean:jstests", | ||
"nobuild:coverage": "npm run build:tests && nyc --check-coverage --branches 85 --functions 90 --lines 90 mocha && npm run clean:jstests", | ||
"nobuild:integration": "npm run mocha:ts -- --timeout 50000 'test/integration/*.ts'", | ||
@@ -55,8 +55,8 @@ "nobuild:unit": "npm run mocha:ts -- 'test/*.ts'", | ||
"bech32": "^2.0.0", | ||
"bip174": "^2.0.1", | ||
"bip174": "^2.1.1", | ||
"bs58check": "^2.1.2", | ||
"create-hash": "^1.1.0", | ||
"ripemd160": "^2.0.2", | ||
"typeforce": "^1.11.3", | ||
"varuint-bitcoin": "^1.1.2", | ||
"wif": "^2.0.1" | ||
"varuint-bitcoin": "^1.1.2" | ||
}, | ||
@@ -71,5 +71,8 @@ "devDependencies": { | ||
"@types/randombytes": "^2.0.0", | ||
"@types/wif": "^2.0.2", | ||
"bip32": "^3.0.1", | ||
"bip39": "^3.0.2", | ||
"@types/ripemd160": "^2.0.0", | ||
"@typescript-eslint/eslint-plugin": "^5.45.0", | ||
"@typescript-eslint/parser": "^5.45.0", | ||
"better-npm-audit": "^3.7.3", | ||
"bip32": "^4.0.0", | ||
"bip39": "^3.1.0", | ||
"bip65": "^1.0.1", | ||
@@ -80,8 +83,10 @@ "bip68": "^1.0.3", | ||
"ecpair": "^2.0.1", | ||
"eslint": "^8.29.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"hoodwink": "^2.0.0", | ||
"minimaldata": "^1.0.2", | ||
"mocha": "^7.1.1", | ||
"npm-audit-whitelister": "^1.0.2", | ||
"mocha": "^10.0.0", | ||
"nyc": "^15.1.0", | ||
"prettier": "1.16.4", | ||
"prettier": "^2.8.0", | ||
"proxyquire": "^2.0.1", | ||
@@ -93,3 +98,2 @@ "randombytes": "^2.1.0", | ||
"ts-node": "^8.3.0", | ||
"tslint": "^6.1.3", | ||
"typescript": "^4.4.4" | ||
@@ -96,0 +100,0 @@ }, |
@@ -12,3 +12,2 @@ # BitcoinJS (bitcoinjs-lib) | ||
## Can I trust this code? | ||
@@ -27,3 +26,2 @@ > Don't trust. Verify. | ||
## Documentation | ||
@@ -34,2 +32,15 @@ Presently, we do not have any formal documentation other than our [examples](#examples), please [ask for help](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new) if our examples aren't enough to guide you. | ||
## How can I contact the developers outside of Github? | ||
**Most of the time, this is not appropriate. Creating issues and pull requests in the open will help others with similar issues, so please try to use public issues and pull requests for communication.** | ||
That said, sometimes developers might be open to taking things off the record (ie. You want to share code that you don't want public to get help with it). In that case, please negotiate on the public issues as to where you will contact. | ||
We have created public rooms on IRC (`#bitcoinjs` on `libera.chat`) and Matrix (`#bitcoinjs-dev:matrix.org`). These two channels have been joined together in a Matrix "Space" which has the Matrix room AND an IRC bridge room that can converse with the IRC room. The "Space" is `#bitcoinjs-space:matrix.org`. | ||
Matrix and IRC both have functions for direct messaging, but IRC is not end to end encrypted, so Matrix is recommended for most communication. The official Matrix client maintained by the Matrix core team is called "Element" and can be downloaded here: https://element.io/download (Account creation is free on the matrix.org server, which is the default setting for Element.) | ||
We used to have a Slack. It is dead. If you find it, no one will answer you most likely. | ||
No we will not make a Discord. | ||
## Installation | ||
@@ -71,5 +82,5 @@ ``` bash | ||
Finally, **adhere to best practice**. | ||
We are not an authorative source of best practice, but, at the very least: | ||
We are not an authoritative source of best practice, but, at the very least: | ||
* [Don't re-use addresses](https://en.bitcoin.it/wiki/Address_reuse). | ||
* [Don't reuse addresses](https://en.bitcoin.it/wiki/Address_reuse). | ||
* Don't share BIP32 extended public keys ('xpubs'). [They are a liability](https://bitcoin.stackexchange.com/questions/56916/derivation-of-parent-private-key-from-non-hardened-child), and it only takes 1 misplaced private key (or a buggy implementation!) and you are vulnerable to **catastrophic fund loss**. | ||
@@ -83,5 +94,17 @@ * [Don't use `Math.random`](https://security.stackexchange.com/questions/181580/why-is-math-random-not-designed-to-be-cryptographically-secure) - in any way - don't. | ||
### Browser | ||
The recommended method of using `bitcoinjs-lib` in your browser is through [Browserify](https://github.com/substack/node-browserify). | ||
If you're familiar with how to use browserify, ignore this and carry on, otherwise, it is recommended to read the tutorial at https://browserify.org/. | ||
The recommended method of using `bitcoinjs-lib` in your browser is through [browserify](http://browserify.org/). | ||
If you'd like to use a different (more modern) build tool than `browserify`, you can compile just this library and its dependencies into a single JavaScript file: | ||
```sh | ||
$ npm install bitcoinjs-lib browserify | ||
$ npx browserify --standalone bitcoin - -o bitcoinjs-lib.js <<<"module.exports = require('bitcoinjs-lib');" | ||
``` | ||
Which you can then import as an ESM module: | ||
```javascript | ||
<script type="module">import "/scripts/bitcoinjs-lib.js"</script> | ||
```` | ||
**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). | ||
@@ -99,4 +122,4 @@ | ||
- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.md) | ||
- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts) | ||
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) | ||
@@ -103,0 +126,0 @@ - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) |
/// <reference types="node" /> | ||
import { Network } from './networks'; | ||
import { TinySecp256k1Interface } from './types'; | ||
export interface Base58CheckResult { | ||
@@ -17,3 +16,3 @@ hash: Buffer; | ||
export declare function toBech32(data: Buffer, version: number, prefix: string): string; | ||
export declare function fromOutputScript(output: Buffer, network?: Network, eccLib?: TinySecp256k1Interface): string; | ||
export declare function toOutputScript(address: string, network?: Network, eccLib?: TinySecp256k1Interface): Buffer; | ||
export declare function fromOutputScript(output: Buffer, network?: Network): string; | ||
export declare function toOutputScript(address: string, network?: Network): Buffer; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.toOutputScript = exports.fromOutputScript = exports.toBech32 = exports.toBase58Check = exports.fromBech32 = exports.fromBase58Check = void 0; | ||
exports.toOutputScript = | ||
exports.fromOutputScript = | ||
exports.toBech32 = | ||
exports.toBase58Check = | ||
exports.fromBech32 = | ||
exports.fromBase58Check = | ||
void 0; | ||
const networks = require('./networks'); | ||
@@ -39,3 +45,3 @@ const payments = require('./payments'); | ||
function fromBase58Check(address) { | ||
const payload = bs58check.decode(address); | ||
const payload = Buffer.from(bs58check.decode(address)); | ||
// TODO: 4.0.0, move to "toOutputScript" | ||
@@ -90,3 +96,3 @@ if (payload.length < 21) throw new TypeError(address + ' is too short'); | ||
exports.toBech32 = toBech32; | ||
function fromOutputScript(output, network, eccLib) { | ||
function fromOutputScript(output, network) { | ||
// TODO: Network | ||
@@ -107,3 +113,3 @@ network = network || networks.bitcoin; | ||
try { | ||
if (eccLib) return payments.p2tr({ output, network }, { eccLib }).address; | ||
return payments.p2tr({ output, network }).address; | ||
} catch (e) {} | ||
@@ -116,3 +122,3 @@ try { | ||
exports.fromOutputScript = fromOutputScript; | ||
function toOutputScript(address, network, eccLib) { | ||
function toOutputScript(address, network) { | ||
network = network || networks.bitcoin; | ||
@@ -142,5 +148,4 @@ let decodeBase58; | ||
} else if (decodeBech32.version === 1) { | ||
if (decodeBech32.data.length === 32 && eccLib) | ||
return payments.p2tr({ pubkey: decodeBech32.data }, { eccLib }) | ||
.output; | ||
if (decodeBech32.data.length === 32) | ||
return payments.p2tr({ pubkey: decodeBech32.data }).output; | ||
} else if ( | ||
@@ -147,0 +152,0 @@ decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION && |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.BufferReader = exports.BufferWriter = exports.cloneBuffer = exports.reverseBuffer = exports.writeUInt64LE = exports.readUInt64LE = exports.varuint = void 0; | ||
exports.BufferReader = | ||
exports.BufferWriter = | ||
exports.cloneBuffer = | ||
exports.reverseBuffer = | ||
exports.writeUInt64LE = | ||
exports.readUInt64LE = | ||
exports.varuint = | ||
void 0; | ||
const types = require('./types'); | ||
@@ -56,2 +63,5 @@ const { typeforce } = types; | ||
class BufferWriter { | ||
static withCapacity(size) { | ||
return new BufferWriter(Buffer.alloc(size)); | ||
} | ||
constructor(buffer, offset = 0) { | ||
@@ -62,5 +72,2 @@ this.buffer = buffer; | ||
} | ||
static withCapacity(size) { | ||
return new BufferWriter(Buffer.alloc(size)); | ||
} | ||
writeUInt8(i) { | ||
@@ -67,0 +74,0 @@ this.offset = this.buffer.writeUInt8(i, this.offset); |
@@ -7,5 +7,10 @@ /// <reference types="node" /> | ||
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 const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"]; | ||
export type TaggedHashPrefix = typeof TAGS[number]; | ||
type TaggedHashPrefixes = { | ||
[key in TaggedHashPrefix]: Buffer; | ||
}; | ||
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ | ||
export declare const TAGGED_HASH_PREFIXES: TaggedHashPrefixes; | ||
export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer; | ||
export {}; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0; | ||
exports.taggedHash = | ||
exports.TAGGED_HASH_PREFIXES = | ||
exports.TAGS = | ||
exports.hash256 = | ||
exports.hash160 = | ||
exports.sha256 = | ||
exports.sha1 = | ||
exports.ripemd160 = | ||
void 0; | ||
const createHash = require('create-hash'); | ||
const RipeMd160 = require('ripemd160'); | ||
function ripemd160(buffer) { | ||
try { | ||
return createHash('rmd160') | ||
.update(buffer) | ||
.digest(); | ||
return createHash('rmd160').update(buffer).digest(); | ||
} catch (err) { | ||
return createHash('ripemd160') | ||
.update(buffer) | ||
.digest(); | ||
try { | ||
return createHash('ripemd160').update(buffer).digest(); | ||
} catch (err2) { | ||
return new RipeMd160().update(buffer).digest(); | ||
} | ||
} | ||
@@ -18,11 +27,7 @@ } | ||
function sha1(buffer) { | ||
return createHash('sha1') | ||
.update(buffer) | ||
.digest(); | ||
return createHash('sha1').update(buffer).digest(); | ||
} | ||
exports.sha1 = sha1; | ||
function sha256(buffer) { | ||
return createHash('sha256') | ||
.update(buffer) | ||
.digest(); | ||
return createHash('sha256').update(buffer).digest(); | ||
} | ||
@@ -38,3 +43,3 @@ exports.sha256 = sha256; | ||
exports.hash256 = hash256; | ||
const TAGS = [ | ||
exports.TAGS = [ | ||
'BIP0340/challenge', | ||
@@ -51,4 +56,4 @@ 'BIP0340/aux', | ||
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */ | ||
const TAGGED_HASH_PREFIXES = Object.fromEntries( | ||
TAGS.map(tag => { | ||
exports.TAGGED_HASH_PREFIXES = Object.fromEntries( | ||
exports.TAGS.map(tag => { | ||
const tagHash = sha256(Buffer.from(tag)); | ||
@@ -59,4 +64,4 @@ return [tag, Buffer.concat([tagHash, tagHash])]; | ||
function taggedHash(prefix, data) { | ||
return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data])); | ||
return sha256(Buffer.concat([exports.TAGGED_HASH_PREFIXES[prefix], data])); | ||
} | ||
exports.taggedHash = taggedHash; |
@@ -15,1 +15,2 @@ import * as address from './address'; | ||
export { Input as TxInput, Output as TxOutput } from './transaction'; | ||
export { initEccLib } from './ecc_lib'; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0; | ||
exports.initEccLib = | ||
exports.Transaction = | ||
exports.opcodes = | ||
exports.Psbt = | ||
exports.Block = | ||
exports.script = | ||
exports.payments = | ||
exports.networks = | ||
exports.crypto = | ||
exports.address = | ||
void 0; | ||
const address = require('./address'); | ||
@@ -17,3 +27,3 @@ exports.address = address; | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return block_1.Block; | ||
@@ -25,3 +35,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return psbt_1.Psbt; | ||
@@ -33,3 +43,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return ops_1.OPS; | ||
@@ -41,5 +51,12 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return transaction_1.Transaction; | ||
}, | ||
}); | ||
var ecc_lib_1 = require('./ecc_lib'); | ||
Object.defineProperty(exports, 'initEccLib', { | ||
enumerable: true, | ||
get: function () { | ||
return ecc_lib_1.initEccLib; | ||
}, | ||
}); |
@@ -120,2 +120,3 @@ 'use strict'; | ||
OP_NOP10: 185, | ||
OP_CHECKSIGADD: 186, | ||
OP_PUBKEYHASH: 253, | ||
@@ -122,0 +123,0 @@ OP_PUBKEY: 254, |
/// <reference types="node" /> | ||
import { Network } from '../networks'; | ||
import { TaprootLeaf, TinySecp256k1Interface } from '../types'; | ||
import { Taptree } from '../types'; | ||
import { p2data as embed } from './embed'; | ||
@@ -28,16 +28,15 @@ import { p2ms } from './p2ms'; | ||
redeem?: Payment; | ||
scriptsTree?: any; | ||
scriptLeaf?: TaprootLeaf; | ||
redeemVersion?: number; | ||
scriptTree?: Taptree; | ||
witness?: Buffer[]; | ||
} | ||
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; | ||
export declare type PaymentFunction = () => Payment; | ||
export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment; | ||
export type PaymentFunction = () => Payment; | ||
export interface PaymentOpts { | ||
validate?: boolean; | ||
allowIncomplete?: boolean; | ||
eccLib?: TinySecp256k1Interface; | ||
} | ||
export declare type StackElement = Buffer | number; | ||
export declare type Stack = StackElement[]; | ||
export declare type StackFunction = () => Stack; | ||
export type StackElement = Buffer | number; | ||
export type Stack = StackElement[]; | ||
export type StackFunction = () => Stack; | ||
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr }; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.p2tr = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; | ||
exports.p2tr = | ||
exports.p2wsh = | ||
exports.p2wpkh = | ||
exports.p2sh = | ||
exports.p2pkh = | ||
exports.p2pk = | ||
exports.p2ms = | ||
exports.embed = | ||
void 0; | ||
const embed_1 = require('./embed'); | ||
Object.defineProperty(exports, 'embed', { | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return embed_1.p2data; | ||
@@ -14,3 +22,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return p2ms_1.p2ms; | ||
@@ -22,3 +30,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return p2pk_1.p2pk; | ||
@@ -30,3 +38,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return p2pkh_1.p2pkh; | ||
@@ -38,3 +46,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return p2sh_1.p2sh; | ||
@@ -46,3 +54,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return p2wpkh_1.p2wpkh; | ||
@@ -54,3 +62,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return p2wsh_1.p2wsh; | ||
@@ -62,3 +70,3 @@ }, | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return p2tr_1.p2tr; | ||
@@ -65,0 +73,0 @@ }, |
@@ -30,3 +30,3 @@ 'use strict'; | ||
const _address = lazy.value(() => { | ||
const payload = bs58check.decode(a.address); | ||
const payload = Buffer.from(bs58check.decode(a.address)); | ||
const version = payload.readUInt8(0); | ||
@@ -33,0 +33,0 @@ const hash = payload.slice(1); |
@@ -51,3 +51,3 @@ 'use strict'; | ||
const _address = lazy.value(() => { | ||
const payload = bs58check.decode(a.address); | ||
const payload = Buffer.from(bs58check.decode(a.address)); | ||
const version = payload.readUInt8(0); | ||
@@ -62,5 +62,6 @@ const hash = payload.slice(1); | ||
const chunks = _chunks(); | ||
const lastChunk = chunks[chunks.length - 1]; | ||
return { | ||
network, | ||
output: chunks[chunks.length - 1], | ||
output: lastChunk === OPS.OP_FALSE ? Buffer.from([]) : lastChunk, | ||
input: bscript.compile(chunks.slice(0, -1)), | ||
@@ -142,2 +143,10 @@ witness: a.witness || [], | ||
throw new TypeError('Redeem.output too short'); | ||
if (redeem.output.byteLength > 520) | ||
throw new TypeError( | ||
'Redeem.output unspendable if larger than 520 bytes', | ||
); | ||
if (bscript.countNonPushOnlyOPs(decompile) > 201) | ||
throw new TypeError( | ||
'Redeem.output unspendable with more than 201 non-push ops', | ||
); | ||
// match hash against other sources | ||
@@ -144,0 +153,0 @@ const hash2 = bcrypto.hash160(redeem.output); |
@@ -8,8 +8,8 @@ 'use strict'; | ||
const types_1 = require('../types'); | ||
const taprootutils_1 = require('./taprootutils'); | ||
const ecc_lib_1 = require('../ecc_lib'); | ||
const bip341_1 = require('./bip341'); | ||
const lazy = require('./lazy'); | ||
const bech32_1 = require('bech32'); | ||
const testecc_1 = require('./testecc'); | ||
const OPS = bscript.OPS; | ||
const TAPROOT_VERSION = 0x01; | ||
const TAPROOT_WITNESS_VERSION = 0x01; | ||
const ANNEX_PREFIX = 0x50; | ||
@@ -26,7 +26,2 @@ function p2tr(a, opts) { | ||
opts = Object.assign({ validate: true }, opts || {}); | ||
const _ecc = lazy.value(() => { | ||
if (!opts.eccLib) throw new Error('ECC Library is missing for p2tr.'); | ||
(0, testecc_1.testEcc)(opts.eccLib); | ||
return opts.eccLib; | ||
}); | ||
(0, types_1.typeforce)( | ||
@@ -41,11 +36,20 @@ { | ||
pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), | ||
signature: types_1.typeforce.maybe(types_1.typeforce.BufferN(64)), | ||
signature: types_1.typeforce.maybe( | ||
types_1.typeforce.anyOf( | ||
types_1.typeforce.BufferN(64), | ||
types_1.typeforce.BufferN(65), | ||
), | ||
), | ||
witness: types_1.typeforce.maybe( | ||
types_1.typeforce.arrayOf(types_1.typeforce.Buffer), | ||
), | ||
// scriptsTree: typef.maybe(typef.TaprootNode), // use merkel.isMast ? | ||
scriptLeaf: types_1.typeforce.maybe({ | ||
version: types_1.typeforce.maybe(types_1.typeforce.Number), | ||
scriptTree: types_1.typeforce.maybe(types_1.isTaptree), | ||
redeem: types_1.typeforce.maybe({ | ||
output: types_1.typeforce.maybe(types_1.typeforce.Buffer), | ||
redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number), | ||
witness: types_1.typeforce.maybe( | ||
types_1.typeforce.arrayOf(types_1.typeforce.Buffer), | ||
), | ||
}), | ||
redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number), | ||
}, | ||
@@ -64,2 +68,3 @@ a, | ||
}); | ||
// remove annex if present, ignored by taproot | ||
const _witness = lazy.value(() => { | ||
@@ -71,3 +76,2 @@ if (!a.witness || !a.witness.length) return; | ||
) { | ||
// remove annex, ignored by taproot | ||
return a.witness.slice(0, -1); | ||
@@ -77,2 +81,7 @@ } | ||
}); | ||
const _hashTree = lazy.value(() => { | ||
if (a.scriptTree) return (0, bip341_1.toHashTree)(a.scriptTree); | ||
if (a.hash) return { hash: a.hash }; | ||
return; | ||
}); | ||
const network = a.network || networks_1.bitcoin; | ||
@@ -83,16 +92,18 @@ const o = { name: 'p2tr', network }; | ||
const words = bech32_1.bech32m.toWords(o.pubkey); | ||
words.unshift(TAPROOT_VERSION); | ||
words.unshift(TAPROOT_WITNESS_VERSION); | ||
return bech32_1.bech32m.encode(network.bech32, words); | ||
}); | ||
lazy.prop(o, 'hash', () => { | ||
if (a.hash) return a.hash; | ||
if (a.scriptsTree) | ||
return (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; | ||
const hashTree = _hashTree(); | ||
if (hashTree) return hashTree.hash; | ||
const w = _witness(); | ||
if (w && w.length > 1) { | ||
const controlBlock = w[w.length - 1]; | ||
const leafVersion = controlBlock[0] & 0b11111110; | ||
const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK; | ||
const script = w[w.length - 2]; | ||
const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); | ||
return (0, taprootutils_1.rootHashFromPath)(controlBlock, leafHash); | ||
const leafHash = (0, bip341_1.tapleafHash)({ | ||
output: script, | ||
version: leafVersion, | ||
}); | ||
return (0, bip341_1.rootHashFromPath)(controlBlock, leafHash); | ||
} | ||
@@ -105,5 +116,23 @@ return null; | ||
}); | ||
lazy.prop(o, 'scriptLeaf', () => { | ||
if (a.scriptLeaf) return a.scriptLeaf; | ||
lazy.prop(o, 'redeemVersion', () => { | ||
if (a.redeemVersion) return a.redeemVersion; | ||
if ( | ||
a.redeem && | ||
a.redeem.redeemVersion !== undefined && | ||
a.redeem.redeemVersion !== null | ||
) { | ||
return a.redeem.redeemVersion; | ||
} | ||
return bip341_1.LEAF_VERSION_TAPSCRIPT; | ||
}); | ||
lazy.prop(o, 'redeem', () => { | ||
const witness = _witness(); // witness without annex | ||
if (!witness || witness.length < 2) return; | ||
return { | ||
output: witness[witness.length - 2], | ||
witness: witness.slice(0, -2), | ||
redeemVersion: | ||
witness[witness.length - 1][0] & types_1.TAPLEAF_VERSION_MASK, | ||
}; | ||
}); | ||
lazy.prop(o, 'pubkey', () => { | ||
@@ -114,3 +143,3 @@ if (a.pubkey) return a.pubkey; | ||
if (o.internalPubkey) { | ||
const tweakedKey = tweakKey(o.internalPubkey, o.hash, _ecc()); | ||
const tweakedKey = (0, bip341_1.tweakKey)(o.internalPubkey, o.hash); | ||
if (tweakedKey) return tweakedKey.x; | ||
@@ -127,28 +156,25 @@ } | ||
if (a.signature) return a.signature; | ||
if (!a.witness || a.witness.length !== 1) return; | ||
return a.witness[0]; | ||
const witness = _witness(); // witness without annex | ||
if (!witness || witness.length !== 1) return; | ||
return witness[0]; | ||
}); | ||
lazy.prop(o, 'input', () => { | ||
// todo | ||
}); | ||
lazy.prop(o, 'witness', () => { | ||
if (a.witness) return a.witness; | ||
if (a.scriptsTree && a.scriptLeaf && a.internalPubkey) { | ||
// todo: optimize/cache | ||
const hashTree = (0, taprootutils_1.toHashTree)(a.scriptsTree); | ||
const leafHash = (0, taprootutils_1.tapLeafHash)( | ||
a.scriptLeaf.output, | ||
a.scriptLeaf.version, | ||
); | ||
const path = (0, taprootutils_1.findScriptPath)(hashTree, leafHash); | ||
const outputKey = tweakKey(a.internalPubkey, hashTree.hash, _ecc()); | ||
const hashTree = _hashTree(); | ||
if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) { | ||
const leafHash = (0, bip341_1.tapleafHash)({ | ||
output: a.redeem.output, | ||
version: o.redeemVersion, | ||
}); | ||
const path = (0, bip341_1.findScriptPath)(hashTree, leafHash); | ||
if (!path) return; | ||
const outputKey = (0, bip341_1.tweakKey)(a.internalPubkey, hashTree.hash); | ||
if (!outputKey) return; | ||
const version = a.scriptLeaf.version || 0xc0; | ||
const controlBock = buffer_1.Buffer.concat( | ||
[ | ||
buffer_1.Buffer.from([version | outputKey.parity]), | ||
buffer_1.Buffer.from([o.redeemVersion | outputKey.parity]), | ||
a.internalPubkey, | ||
].concat(path.reverse()), | ||
].concat(path), | ||
); | ||
return [a.scriptLeaf.output, controlBock]; | ||
return [a.redeem.output, controlBock]; | ||
} | ||
@@ -163,3 +189,3 @@ if (a.signature) return [a.signature]; | ||
throw new TypeError('Invalid prefix or Network mismatch'); | ||
if (_address().version !== TAPROOT_VERSION) | ||
if (_address().version !== TAPROOT_WITNESS_VERSION) | ||
throw new TypeError('Invalid address version'); | ||
@@ -187,3 +213,3 @@ if (_address().data.length !== 32) | ||
if (a.internalPubkey) { | ||
const tweakedKey = tweakKey(a.internalPubkey, o.hash, _ecc()); | ||
const tweakedKey = (0, bip341_1.tweakKey)(a.internalPubkey, o.hash); | ||
if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x)) | ||
@@ -194,10 +220,39 @@ throw new TypeError('Pubkey mismatch'); | ||
if (pubkey && pubkey.length) { | ||
if (!_ecc().isXOnlyPoint(pubkey)) | ||
if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(pubkey)) | ||
throw new TypeError('Invalid pubkey for p2tr'); | ||
} | ||
if (a.hash && a.scriptsTree) { | ||
const hash = (0, taprootutils_1.toHashTree)(a.scriptsTree).hash; | ||
if (!a.hash.equals(hash)) throw new TypeError('Hash mismatch'); | ||
const hashTree = _hashTree(); | ||
if (a.hash && hashTree) { | ||
if (!a.hash.equals(hashTree.hash)) throw new TypeError('Hash mismatch'); | ||
} | ||
if (a.redeem && a.redeem.output && hashTree) { | ||
const leafHash = (0, bip341_1.tapleafHash)({ | ||
output: a.redeem.output, | ||
version: o.redeemVersion, | ||
}); | ||
if (!(0, bip341_1.findScriptPath)(hashTree, leafHash)) | ||
throw new TypeError('Redeem script not in tree'); | ||
} | ||
const witness = _witness(); | ||
// compare the provided redeem data with the one computed from witness | ||
if (a.redeem && o.redeem) { | ||
if (a.redeem.redeemVersion) { | ||
if (a.redeem.redeemVersion !== o.redeem.redeemVersion) | ||
throw new TypeError('Redeem.redeemVersion and witness mismatch'); | ||
} | ||
if (a.redeem.output) { | ||
if (bscript.decompile(a.redeem.output).length === 0) | ||
throw new TypeError('Redeem.output is invalid'); | ||
// output redeem is constructed from the witness | ||
if (o.redeem.output && !a.redeem.output.equals(o.redeem.output)) | ||
throw new TypeError('Redeem.output and witness mismatch'); | ||
} | ||
if (a.redeem.witness) { | ||
if ( | ||
o.redeem.witness && | ||
!stacksEqual(a.redeem.witness, o.redeem.witness) | ||
) | ||
throw new TypeError('Redeem.witness and witness mismatch'); | ||
} | ||
} | ||
if (witness && witness.length) { | ||
@@ -213,5 +268,3 @@ if (witness.length === 1) { | ||
throw new TypeError( | ||
`The control-block length is too small. Got ${ | ||
controlBlock.length | ||
}, expected min 33.`, | ||
`The control-block length is too small. Got ${controlBlock.length}, expected min 33.`, | ||
); | ||
@@ -230,12 +283,12 @@ if ((controlBlock.length - 33) % 32 !== 0) | ||
throw new TypeError('Internal pubkey mismatch'); | ||
if (!_ecc().isXOnlyPoint(internalPubkey)) | ||
if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(internalPubkey)) | ||
throw new TypeError('Invalid internalPubkey for p2tr witness'); | ||
const leafVersion = controlBlock[0] & 0b11111110; | ||
const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK; | ||
const script = witness[witness.length - 2]; | ||
const leafHash = (0, taprootutils_1.tapLeafHash)(script, leafVersion); | ||
const hash = (0, taprootutils_1.rootHashFromPath)( | ||
controlBlock, | ||
leafHash, | ||
); | ||
const outputKey = tweakKey(internalPubkey, hash, _ecc()); | ||
const leafHash = (0, bip341_1.tapleafHash)({ | ||
output: script, | ||
version: leafVersion, | ||
}); | ||
const hash = (0, bip341_1.rootHashFromPath)(controlBlock, leafHash); | ||
const outputKey = (0, bip341_1.tweakKey)(internalPubkey, hash); | ||
if (!outputKey) | ||
@@ -254,13 +307,7 @@ // todo: needs test data | ||
exports.p2tr = p2tr; | ||
function tweakKey(pubKey, h, eccLib) { | ||
if (!buffer_1.Buffer.isBuffer(pubKey)) return null; | ||
if (pubKey.length !== 32) return null; | ||
if (h && h.length !== 32) return null; | ||
const tweakHash = (0, taprootutils_1.tapTweakHash)(pubKey, h); | ||
const res = eccLib.xOnlyPointAddTweak(pubKey, tweakHash); | ||
if (!res || res.xOnlyPubkey === null) return null; | ||
return { | ||
parity: res.parity, | ||
x: buffer_1.Buffer.from(res.xOnlyPubkey), | ||
}; | ||
function stacksEqual(a, b) { | ||
if (a.length !== b.length) return false; | ||
return a.every((x, i) => { | ||
return x.equals(b[i]); | ||
}); | ||
} |
@@ -169,6 +169,15 @@ 'use strict'; | ||
throw new TypeError('Ambiguous witness source'); | ||
// is the redeem output non-empty? | ||
// is the redeem output non-empty/valid? | ||
if (a.redeem.output) { | ||
if (bscript.decompile(a.redeem.output).length === 0) | ||
const decompile = bscript.decompile(a.redeem.output); | ||
if (!decompile || decompile.length < 1) | ||
throw new TypeError('Redeem.output is invalid'); | ||
if (a.redeem.output.byteLength > 3600) | ||
throw new TypeError( | ||
'Redeem.output unspendable if larger than 3600 bytes', | ||
); | ||
if (bscript.countNonPushOnlyOPs(decompile) > 201) | ||
throw new TypeError( | ||
'Redeem.output unspendable with more than 201 non-push ops', | ||
); | ||
// match hash against other sources | ||
@@ -175,0 +184,0 @@ const hash2 = bcrypto.sha256(a.redeem.output); |
@@ -6,3 +6,2 @@ /// <reference types="node" /> | ||
import { Transaction } from './transaction'; | ||
import { TinySecp256k1Interface } from './types'; | ||
export interface TransactionInput { | ||
@@ -23,3 +22,3 @@ hash: string | Buffer; | ||
} | ||
export declare type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean; | ||
export type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean; | ||
/** | ||
@@ -86,3 +85,6 @@ * Psbt class can parse and generate a PSBT binary based off of the BIP174. | ||
finalizeAllInputs(): this; | ||
finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this; | ||
finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc): this; | ||
finalizeTaprootInput(inputIndex: number, tapLeafHashToFinalize?: Buffer, finalScriptsFunc?: FinalTaprootScriptsFunc): this; | ||
private _finalizeInput; | ||
private _finalizeTaprootInput; | ||
getInputType(inputIndex: number): AllScriptType; | ||
@@ -95,2 +97,4 @@ inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean; | ||
validateSignaturesOfInput(inputIndex: number, validator: ValidateSigFunction, pubkey?: Buffer): boolean; | ||
private _validateSignaturesOfInput; | ||
private validateSignaturesOfTaprootInput; | ||
signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this; | ||
@@ -103,3 +107,10 @@ signAllInputsHDAsync(hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise<void>; | ||
signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this; | ||
signTaprootInput(inputIndex: number, keyPair: Signer, tapLeafHashToSign?: Buffer, sighashTypes?: number[]): this; | ||
private _signInput; | ||
private _signTaprootInput; | ||
signInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise<void>; | ||
signTaprootInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, tapLeafHash?: Buffer, sighashTypes?: number[]): Promise<void>; | ||
private _signInputAsync; | ||
private _signTaprootInputAsync; | ||
private checkTaprootHashesForSig; | ||
toBuffer(): Buffer; | ||
@@ -119,7 +130,6 @@ toHex(): string; | ||
maximumFeeRate?: number; | ||
eccLib?: TinySecp256k1Interface; | ||
} | ||
interface PsbtInputExtended extends PsbtInput, TransactionInput { | ||
} | ||
declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; | ||
type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; | ||
interface PsbtOutputExtendedAddress extends PsbtOutput { | ||
@@ -154,3 +164,2 @@ address: string; | ||
sign(hash: Buffer): Buffer; | ||
signSchnorr?(hash: Buffer): Buffer; | ||
} | ||
@@ -163,3 +172,2 @@ /** | ||
sign(hash: Buffer): Promise<Buffer>; | ||
signSchnorr?(hash: Buffer): Promise<Buffer>; | ||
} | ||
@@ -186,3 +194,3 @@ export interface Signer { | ||
*/ | ||
declare type FinalScriptsFunc = (inputIndex: number, // Which input is it? | ||
type FinalScriptsFunc = (inputIndex: number, // Which input is it? | ||
input: PsbtInput, // The PSBT input contents | ||
@@ -196,3 +204,8 @@ script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.) | ||
}; | ||
declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'taproot' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard'; | ||
type FinalTaprootScriptsFunc = (inputIndex: number, // Which input is it? | ||
input: PsbtInput, // The PSBT input contents | ||
tapLeafHashToFinalize?: Buffer) => { | ||
finalScriptWitness: Buffer | undefined; | ||
}; | ||
type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard'; | ||
export {}; |
765
src/psbt.js
@@ -9,7 +9,9 @@ 'use strict'; | ||
const bufferutils_1 = require('./bufferutils'); | ||
const crypto_1 = require('./crypto'); | ||
const networks_1 = require('./networks'); | ||
const payments = require('./payments'); | ||
const bip341_1 = require('./payments/bip341'); | ||
const bscript = require('./script'); | ||
const transaction_1 = require('./transaction'); | ||
const bip371_1 = require('./psbt/bip371'); | ||
const psbtutils_1 = require('./psbt/psbtutils'); | ||
/** | ||
@@ -64,2 +66,16 @@ * These are the default arguments for a Psbt instance. | ||
class Psbt { | ||
static fromBase64(data, opts = {}) { | ||
const buffer = Buffer.from(data, 'base64'); | ||
return this.fromBuffer(buffer, opts); | ||
} | ||
static fromHex(data, opts = {}) { | ||
const buffer = Buffer.from(data, 'hex'); | ||
return this.fromBuffer(buffer, opts); | ||
} | ||
static fromBuffer(buffer, opts = {}) { | ||
const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); | ||
const psbt = new Psbt(opts, psbtBase); | ||
checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); | ||
return psbt; | ||
} | ||
constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) { | ||
@@ -83,3 +99,2 @@ this.data = data; | ||
__UNSAFE_SIGN_NONSEGWIT: false, | ||
__EC_LIB: opts.eccLib, | ||
}; | ||
@@ -96,16 +111,2 @@ if (this.data.inputs.length === 0) this.setVersion(2); | ||
} | ||
static fromBase64(data, opts = {}) { | ||
const buffer = Buffer.from(data, 'base64'); | ||
return this.fromBuffer(buffer, opts); | ||
} | ||
static fromHex(data, opts = {}) { | ||
const buffer = Buffer.from(data, 'hex'); | ||
return this.fromBuffer(buffer, opts); | ||
} | ||
static fromBuffer(buffer, opts = {}) { | ||
const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); | ||
const psbt = new Psbt(opts, psbtBase); | ||
checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); | ||
return psbt; | ||
} | ||
get inputCount() { | ||
@@ -140,3 +141,2 @@ return this.data.inputs.length; | ||
this.opts.network, | ||
this.__CACHE.__EC_LIB, | ||
); | ||
@@ -208,2 +208,3 @@ } catch (_) {} | ||
} | ||
(0, bip371_1.checkTaprootInputFields)(inputData, inputData, 'addInput'); | ||
checkInputsForPartialSig(this.data.inputs, 'addInput'); | ||
@@ -245,9 +246,6 @@ if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); | ||
const { network } = this.opts; | ||
const script = (0, address_1.toOutputScript)( | ||
address, | ||
network, | ||
this.__CACHE.__EC_LIB, | ||
); | ||
const script = (0, address_1.toOutputScript)(address, network); | ||
outputData = Object.assign(outputData, { script }); | ||
} | ||
(0, bip371_1.checkTaprootOutputFields)(outputData, outputData, 'addOutput'); | ||
const c = this.__CACHE; | ||
@@ -289,2 +287,27 @@ this.data.addOutput(outputData); | ||
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); | ||
if ((0, bip371_1.isTaprootInput)(input)) | ||
return this._finalizeTaprootInput( | ||
inputIndex, | ||
input, | ||
undefined, | ||
finalScriptsFunc, | ||
); | ||
return this._finalizeInput(inputIndex, input, finalScriptsFunc); | ||
} | ||
finalizeTaprootInput( | ||
inputIndex, | ||
tapLeafHashToFinalize, | ||
finalScriptsFunc = bip371_1.tapScriptFinalizer, | ||
) { | ||
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); | ||
if ((0, bip371_1.isTaprootInput)(input)) | ||
return this._finalizeTaprootInput( | ||
inputIndex, | ||
input, | ||
tapLeafHashToFinalize, | ||
finalScriptsFunc, | ||
); | ||
throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`); | ||
} | ||
_finalizeInput(inputIndex, input, finalScriptsFunc = getFinalScripts) { | ||
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( | ||
@@ -297,4 +320,3 @@ inputIndex, | ||
checkPartialSigSighashes(input); | ||
const fn = finalScriptsFunc || getFinalScripts; | ||
const { finalScriptSig, finalScriptWitness } = fn( | ||
const { finalScriptSig, finalScriptWitness } = finalScriptsFunc( | ||
inputIndex, | ||
@@ -306,3 +328,2 @@ input, | ||
isP2WSH, | ||
this.__CACHE.__EC_LIB, | ||
); | ||
@@ -317,9 +338,36 @@ if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); | ||
} | ||
_finalizeTaprootInput( | ||
inputIndex, | ||
input, | ||
tapLeafHashToFinalize, | ||
finalScriptsFunc = bip371_1.tapScriptFinalizer, | ||
) { | ||
if (!input.witnessUtxo) | ||
throw new Error( | ||
`Cannot finalize input #${inputIndex}. Missing withness utxo.`, | ||
); | ||
// Check key spend first. Increased privacy and reduced block space. | ||
if (input.tapKeySig) { | ||
const payment = payments.p2tr({ | ||
output: input.witnessUtxo.script, | ||
signature: input.tapKeySig, | ||
}); | ||
const finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)( | ||
payment.witness, | ||
); | ||
this.data.updateInput(inputIndex, { finalScriptWitness }); | ||
} else { | ||
const { finalScriptWitness } = finalScriptsFunc( | ||
inputIndex, | ||
input, | ||
tapLeafHashToFinalize, | ||
); | ||
this.data.updateInput(inputIndex, { finalScriptWitness }); | ||
} | ||
this.data.clearFinalizedInput(inputIndex); | ||
return this; | ||
} | ||
getInputType(inputIndex) { | ||
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); | ||
const { script } = getScriptAndAmountFromUtxo( | ||
inputIndex, | ||
input, | ||
this.__CACHE, | ||
); | ||
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); | ||
const result = getMeaningfulScript( | ||
@@ -334,6 +382,3 @@ script, | ||
const type = result.type === 'raw' ? '' : result.type + '-'; | ||
const mainType = classifyScript( | ||
result.meaningfulScript, | ||
this.__CACHE.__EC_LIB, | ||
); | ||
const mainType = classifyScript(result.meaningfulScript); | ||
return type + mainType; | ||
@@ -372,2 +417,12 @@ } | ||
const input = this.data.inputs[inputIndex]; | ||
if ((0, bip371_1.isTaprootInput)(input)) | ||
return this.validateSignaturesOfTaprootInput( | ||
inputIndex, | ||
validator, | ||
pubkey, | ||
); | ||
return this._validateSignaturesOfInput(inputIndex, validator, pubkey); | ||
} | ||
_validateSignaturesOfInput(inputIndex, validator, pubkey) { | ||
const input = this.data.inputs[inputIndex]; | ||
const partialSig = (input || {}).partialSig; | ||
@@ -386,11 +441,4 @@ if (!input || !partialSig || partialSig.length < 1) | ||
let sighashCache; | ||
const scriptType = this.getInputType(inputIndex); | ||
for (const pSig of mySigs) { | ||
const sig = | ||
scriptType === 'taproot' | ||
? { | ||
signature: pSig.signature, | ||
hashType: transaction_1.Transaction.SIGHASH_DEFAULT, | ||
} | ||
: bscript.signature.decode(pSig.signature); | ||
const sig = bscript.signature.decode(pSig.signature); | ||
const { hash, script } = | ||
@@ -401,3 +449,2 @@ sighashCache !== sig.hashType | ||
Object.assign({}, input, { sighashType: sig.hashType }), | ||
this.data.inputs, | ||
this.__CACHE, | ||
@@ -415,2 +462,53 @@ true, | ||
} | ||
validateSignaturesOfTaprootInput(inputIndex, validator, pubkey) { | ||
const input = this.data.inputs[inputIndex]; | ||
const tapKeySig = (input || {}).tapKeySig; | ||
const tapScriptSig = (input || {}).tapScriptSig; | ||
if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length)) | ||
throw new Error('No signatures to validate'); | ||
if (typeof validator !== 'function') | ||
throw new Error('Need validator function to validate signatures'); | ||
pubkey = pubkey && (0, bip371_1.toXOnly)(pubkey); | ||
const allHashses = pubkey | ||
? getTaprootHashesForSig( | ||
inputIndex, | ||
input, | ||
this.data.inputs, | ||
pubkey, | ||
this.__CACHE, | ||
) | ||
: getAllTaprootHashesForSig( | ||
inputIndex, | ||
input, | ||
this.data.inputs, | ||
this.__CACHE, | ||
); | ||
if (!allHashses.length) throw new Error('No signatures for this pubkey'); | ||
const tapKeyHash = allHashses.find(h => !h.leafHash); | ||
let validationResultCount = 0; | ||
if (tapKeySig && tapKeyHash) { | ||
const isValidTapkeySig = validator( | ||
tapKeyHash.pubkey, | ||
tapKeyHash.hash, | ||
trimTaprootSig(tapKeySig), | ||
); | ||
if (!isValidTapkeySig) return false; | ||
validationResultCount++; | ||
} | ||
if (tapScriptSig) { | ||
for (const tapSig of tapScriptSig) { | ||
const tapSigHash = allHashses.find(h => tapSig.pubkey.equals(h.pubkey)); | ||
if (tapSigHash) { | ||
const isValidTapScriptSig = validator( | ||
tapSig.pubkey, | ||
tapSigHash.hash, | ||
trimTaprootSig(tapSig.signature), | ||
); | ||
if (!isValidTapScriptSig) return false; | ||
validationResultCount++; | ||
} | ||
} | ||
} | ||
return validationResultCount > 0; | ||
} | ||
signAllInputsHD( | ||
@@ -499,6 +597,3 @@ hdKeyPair, | ||
} | ||
signAllInputs( | ||
keyPair, | ||
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], | ||
) { | ||
signAllInputs(keyPair, sighashTypes) { | ||
if (!keyPair || !keyPair.publicKey) | ||
@@ -523,6 +618,3 @@ throw new Error('Need Signer to sign input'); | ||
} | ||
signAllInputsAsync( | ||
keyPair, | ||
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], | ||
) { | ||
signAllInputsAsync(keyPair, sighashTypes) { | ||
return new Promise((resolve, reject) => { | ||
@@ -556,3 +648,32 @@ if (!keyPair || !keyPair.publicKey) | ||
} | ||
signInput( | ||
signInput(inputIndex, keyPair, sighashTypes) { | ||
if (!keyPair || !keyPair.publicKey) | ||
throw new Error('Need Signer to sign input'); | ||
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); | ||
if ((0, bip371_1.isTaprootInput)(input)) { | ||
return this._signTaprootInput( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
undefined, | ||
sighashTypes, | ||
); | ||
} | ||
return this._signInput(inputIndex, keyPair, sighashTypes); | ||
} | ||
signTaprootInput(inputIndex, keyPair, tapLeafHashToSign, sighashTypes) { | ||
if (!keyPair || !keyPair.publicKey) | ||
throw new Error('Need Signer to sign input'); | ||
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); | ||
if ((0, bip371_1.isTaprootInput)(input)) | ||
return this._signTaprootInput( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
tapLeafHashToSign, | ||
sighashTypes, | ||
); | ||
throw new Error(`Input #${inputIndex} is not of type Taproot.`); | ||
} | ||
_signInput( | ||
inputIndex, | ||
@@ -562,4 +683,2 @@ keyPair, | ||
) { | ||
if (!keyPair || !keyPair.publicKey) | ||
throw new Error('Need Signer to sign input'); | ||
const { hash, sighashType } = getHashAndSighashType( | ||
@@ -572,72 +691,187 @@ this.data.inputs, | ||
); | ||
const scriptType = this.getInputType(inputIndex); | ||
if (scriptType === 'taproot') { | ||
if (!keyPair.signSchnorr) { | ||
throw new Error( | ||
`Need Schnorr Signer to sign taproot input #${inputIndex}.`, | ||
const partialSig = [ | ||
{ | ||
pubkey: keyPair.publicKey, | ||
signature: bscript.signature.encode(keyPair.sign(hash), sighashType), | ||
}, | ||
]; | ||
this.data.updateInput(inputIndex, { partialSig }); | ||
return this; | ||
} | ||
_signTaprootInput( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
tapLeafHashToSign, | ||
allowedSighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT], | ||
) { | ||
const hashesForSig = this.checkTaprootHashesForSig( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
tapLeafHashToSign, | ||
allowedSighashTypes, | ||
); | ||
const tapKeySig = hashesForSig | ||
.filter(h => !h.leafHash) | ||
.map(h => | ||
(0, bip371_1.serializeTaprootSignature)( | ||
keyPair.signSchnorr(h.hash), | ||
input.sighashType, | ||
), | ||
)[0]; | ||
const tapScriptSig = hashesForSig | ||
.filter(h => !!h.leafHash) | ||
.map(h => ({ | ||
pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey), | ||
signature: (0, bip371_1.serializeTaprootSignature)( | ||
keyPair.signSchnorr(h.hash), | ||
input.sighashType, | ||
), | ||
leafHash: h.leafHash, | ||
})); | ||
if (tapKeySig) { | ||
this.data.updateInput(inputIndex, { tapKeySig }); | ||
} | ||
if (tapScriptSig.length) { | ||
this.data.updateInput(inputIndex, { tapScriptSig }); | ||
} | ||
return this; | ||
} | ||
signInputAsync(inputIndex, keyPair, sighashTypes) { | ||
return Promise.resolve().then(() => { | ||
if (!keyPair || !keyPair.publicKey) | ||
throw new Error('Need Signer to sign input'); | ||
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); | ||
if ((0, bip371_1.isTaprootInput)(input)) | ||
return this._signTaprootInputAsync( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
undefined, | ||
sighashTypes, | ||
); | ||
} | ||
return this._signInputAsync(inputIndex, keyPair, sighashTypes); | ||
}); | ||
} | ||
signTaprootInputAsync(inputIndex, keyPair, tapLeafHash, sighashTypes) { | ||
return Promise.resolve().then(() => { | ||
if (!keyPair || !keyPair.publicKey) | ||
throw new Error('Need Signer to sign input'); | ||
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); | ||
if ((0, bip371_1.isTaprootInput)(input)) | ||
return this._signTaprootInputAsync( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
tapLeafHash, | ||
sighashTypes, | ||
); | ||
throw new Error(`Input #${inputIndex} is not of type Taproot.`); | ||
}); | ||
} | ||
_signInputAsync( | ||
inputIndex, | ||
keyPair, | ||
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], | ||
) { | ||
const { hash, sighashType } = getHashAndSighashType( | ||
this.data.inputs, | ||
inputIndex, | ||
keyPair.publicKey, | ||
this.__CACHE, | ||
sighashTypes, | ||
); | ||
return Promise.resolve(keyPair.sign(hash)).then(signature => { | ||
const partialSig = [ | ||
{ | ||
pubkey: keyPair.publicKey, | ||
signature: keyPair.signSchnorr(hash), | ||
signature: bscript.signature.encode(signature, sighashType), | ||
}, | ||
]; | ||
// must be changed to use the `updateInput()` public API | ||
this.data.inputs[inputIndex].partialSig = partialSig; | ||
} else { | ||
const partialSig = [ | ||
{ | ||
pubkey: keyPair.publicKey, | ||
signature: bscript.signature.encode(keyPair.sign(hash), sighashType), | ||
}, | ||
]; | ||
this.data.updateInput(inputIndex, { partialSig }); | ||
} | ||
return this; | ||
}); | ||
} | ||
signInputAsync( | ||
async _signTaprootInputAsync( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], | ||
tapLeafHash, | ||
sighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT], | ||
) { | ||
return Promise.resolve().then(() => { | ||
if (!keyPair || !keyPair.publicKey) | ||
throw new Error('Need Signer to sign input'); | ||
const { hash, sighashType } = getHashAndSighashType( | ||
this.data.inputs, | ||
inputIndex, | ||
keyPair.publicKey, | ||
this.__CACHE, | ||
sighashTypes, | ||
); | ||
const scriptType = this.getInputType(inputIndex); | ||
if (scriptType === 'taproot') { | ||
if (!keyPair.signSchnorr) { | ||
throw new Error( | ||
`Need Schnorr Signer to sign taproot input #${inputIndex}.`, | ||
); | ||
} | ||
return Promise.resolve(keyPair.signSchnorr(hash)).then(signature => { | ||
const partialSig = [ | ||
{ | ||
pubkey: keyPair.publicKey, | ||
signature, | ||
}, | ||
]; | ||
// must be changed to use the `updateInput()` public API | ||
this.data.inputs[inputIndex].partialSig = partialSig; | ||
}); | ||
} | ||
return Promise.resolve(keyPair.sign(hash)).then(signature => { | ||
const partialSig = [ | ||
{ | ||
pubkey: keyPair.publicKey, | ||
signature: bscript.signature.encode(signature, sighashType), | ||
const hashesForSig = this.checkTaprootHashesForSig( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
tapLeafHash, | ||
sighashTypes, | ||
); | ||
const signaturePromises = []; | ||
const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0]; | ||
if (tapKeyHash) { | ||
const tapKeySigPromise = Promise.resolve( | ||
keyPair.signSchnorr(tapKeyHash.hash), | ||
).then(sig => { | ||
return { | ||
tapKeySig: (0, bip371_1.serializeTaprootSignature)( | ||
sig, | ||
input.sighashType, | ||
), | ||
}; | ||
}); | ||
signaturePromises.push(tapKeySigPromise); | ||
} | ||
const tapScriptHashes = hashesForSig.filter(h => !!h.leafHash); | ||
if (tapScriptHashes.length) { | ||
const tapScriptSigPromises = tapScriptHashes.map(tsh => { | ||
return Promise.resolve(keyPair.signSchnorr(tsh.hash)).then( | ||
signature => { | ||
const tapScriptSig = [ | ||
{ | ||
pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey), | ||
signature: (0, bip371_1.serializeTaprootSignature)( | ||
signature, | ||
input.sighashType, | ||
), | ||
leafHash: tsh.leafHash, | ||
}, | ||
]; | ||
return { tapScriptSig }; | ||
}, | ||
]; | ||
this.data.updateInput(inputIndex, { partialSig }); | ||
); | ||
}); | ||
signaturePromises.push(...tapScriptSigPromises); | ||
} | ||
return Promise.all(signaturePromises).then(results => { | ||
results.forEach(v => this.data.updateInput(inputIndex, v)); | ||
}); | ||
} | ||
checkTaprootHashesForSig( | ||
inputIndex, | ||
input, | ||
keyPair, | ||
tapLeafHashToSign, | ||
allowedSighashTypes, | ||
) { | ||
if (typeof keyPair.signSchnorr !== 'function') | ||
throw new Error( | ||
`Need Schnorr Signer to sign taproot input #${inputIndex}.`, | ||
); | ||
const hashesForSig = getTaprootHashesForSig( | ||
inputIndex, | ||
input, | ||
this.data.inputs, | ||
keyPair.publicKey, | ||
this.__CACHE, | ||
tapLeafHashToSign, | ||
allowedSighashTypes, | ||
); | ||
if (!hashesForSig || !hashesForSig.length) | ||
throw new Error( | ||
`Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString( | ||
'hex', | ||
)}`, | ||
); | ||
return hashesForSig; | ||
} | ||
toBuffer() { | ||
@@ -661,2 +895,7 @@ checkCache(this.__CACHE); | ||
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); | ||
(0, bip371_1.checkTaprootInputFields)( | ||
this.data.inputs[inputIndex], | ||
updateData, | ||
'updateInput', | ||
); | ||
this.data.updateInput(inputIndex, updateData); | ||
@@ -673,2 +912,8 @@ if (updateData.nonWitnessUtxo) { | ||
updateOutput(outputIndex, updateData) { | ||
const outputData = this.data.outputs[outputIndex]; | ||
(0, bip371_1.checkTaprootOutputFields)( | ||
outputData, | ||
updateData, | ||
'updateOutput', | ||
); | ||
this.data.updateOutput(outputIndex, updateData); | ||
@@ -755,3 +1000,2 @@ return this; | ||
case 'witnesspubkeyhash': | ||
case 'taproot': | ||
return hasSigs(1, input.partialSig); | ||
@@ -789,19 +1033,2 @@ case 'multisig': | ||
} | ||
function isPaymentFactory(payment) { | ||
return (script, eccLib) => { | ||
try { | ||
payment({ output: script }, { eccLib }); | ||
return true; | ||
} catch (err) { | ||
return false; | ||
} | ||
}; | ||
} | ||
const isP2MS = isPaymentFactory(payments.p2ms); | ||
const isP2PK = isPaymentFactory(payments.p2pk); | ||
const isP2PKH = isPaymentFactory(payments.p2pkh); | ||
const isP2WPKH = isPaymentFactory(payments.p2wpkh); | ||
const isP2WSHScript = isPaymentFactory(payments.p2wsh); | ||
const isP2SHScript = isPaymentFactory(payments.p2sh); | ||
const isP2TR = isPaymentFactory(payments.p2tr); | ||
function bip32DerivationIsMine(root) { | ||
@@ -840,33 +1067,7 @@ return d => { | ||
inputs.forEach(input => { | ||
let throws = false; | ||
let pSigs = []; | ||
if ((input.partialSig || []).length === 0) { | ||
if (!input.finalScriptSig && !input.finalScriptWitness) return; | ||
pSigs = getPsigsFromInputFinalScripts(input); | ||
} else { | ||
pSigs = input.partialSig; | ||
} | ||
pSigs.forEach(pSig => { | ||
const { hashType } = bscript.signature.decode(pSig.signature); | ||
const whitelist = []; | ||
const isAnyoneCanPay = | ||
hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; | ||
if (isAnyoneCanPay) whitelist.push('addInput'); | ||
const hashMod = hashType & 0x1f; | ||
switch (hashMod) { | ||
case transaction_1.Transaction.SIGHASH_ALL: | ||
break; | ||
case transaction_1.Transaction.SIGHASH_SINGLE: | ||
case transaction_1.Transaction.SIGHASH_NONE: | ||
whitelist.push('addOutput'); | ||
whitelist.push('setInputSequence'); | ||
break; | ||
} | ||
if (whitelist.indexOf(action) === -1) { | ||
throws = true; | ||
} | ||
}); | ||
if (throws) { | ||
const throws = (0, bip371_1.isTaprootInput)(input) | ||
? (0, bip371_1.checkTaprootInputForSigs)(input, action) | ||
: (0, psbtutils_1.checkInputForSig)(input, action); | ||
if (throws) | ||
throw new Error('Can not modify transaction, signatures exist.'); | ||
} | ||
}); | ||
@@ -885,3 +1086,3 @@ } | ||
function checkScriptForPubkey(pubkey, script, action) { | ||
if (!pubkeyInScript(pubkey, script)) { | ||
if (!(0, psbtutils_1.pubkeyInScript)(pubkey, script)) { | ||
throw new Error( | ||
@@ -951,12 +1152,4 @@ `Can not ${action} for this input with the key ${pubkey.toString('hex')}`, | ||
} | ||
function getFinalScripts( | ||
inputIndex, | ||
input, | ||
script, | ||
isSegwit, | ||
isP2SH, | ||
isP2WSH, | ||
eccLib, | ||
) { | ||
const scriptType = classifyScript(script, eccLib); | ||
function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) { | ||
const scriptType = classifyScript(script); | ||
if (!canFinalize(input, script, scriptType)) | ||
@@ -989,5 +1182,9 @@ throw new Error(`Can not finalize input #${inputIndex}`); | ||
if (p2wsh) { | ||
finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness); | ||
finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)( | ||
p2wsh.witness, | ||
); | ||
} else { | ||
finalScriptWitness = witnessStackToScriptWitness(payment.witness); | ||
finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)( | ||
payment.witness, | ||
); | ||
} | ||
@@ -1020,3 +1217,2 @@ if (p2sh) { | ||
input, | ||
inputs, | ||
cache, | ||
@@ -1032,20 +1228,7 @@ false, | ||
} | ||
function getHashForSig( | ||
inputIndex, | ||
input, | ||
inputs, | ||
cache, | ||
forValidate, | ||
sighashTypes, | ||
) { | ||
function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { | ||
const unsignedTx = cache.__TX; | ||
const sighashType = | ||
input.sighashType || transaction_1.Transaction.SIGHASH_ALL; | ||
if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { | ||
const str = sighashTypeToString(sighashType); | ||
throw new Error( | ||
`Sighash type is not allowed. Retry the sign method passing the ` + | ||
`sighashTypes array of whitelisted types. Sighash type: ${str}`, | ||
); | ||
} | ||
checkSighashTypeAllowed(sighashType, sighashTypes); | ||
let hash; | ||
@@ -1088,6 +1271,7 @@ let prevout; | ||
); | ||
} else if (isP2WPKH(meaningfulScript)) { | ||
} else if ((0, psbtutils_1.isP2WPKH)(meaningfulScript)) { | ||
// P2WPKH uses the P2PKH template for prevoutScript when signing | ||
const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) }) | ||
.output; | ||
const signingScript = payments.p2pkh({ | ||
hash: meaningfulScript.slice(2), | ||
}).output; | ||
hash = unsignedTx.hashForWitnessV0( | ||
@@ -1099,14 +1283,2 @@ inputIndex, | ||
); | ||
} else if (isP2TR(meaningfulScript, cache.__EC_LIB)) { | ||
const prevOuts = inputs.map((i, index) => | ||
getScriptAndAmountFromUtxo(index, i, cache), | ||
); | ||
const signingScripts = prevOuts.map(o => o.script); | ||
const values = prevOuts.map(o => o.value); | ||
hash = unsignedTx.hashForWitnessV1( | ||
inputIndex, | ||
signingScripts, | ||
values, | ||
transaction_1.Transaction.SIGHASH_DEFAULT, | ||
); | ||
} else { | ||
@@ -1144,2 +1316,95 @@ // non-segwit | ||
} | ||
function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) { | ||
const allPublicKeys = []; | ||
if (input.tapInternalKey) { | ||
const key = getPrevoutTaprootKey(inputIndex, input, cache); | ||
if (key) { | ||
allPublicKeys.push(key); | ||
} | ||
} | ||
if (input.tapScriptSig) { | ||
const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey); | ||
allPublicKeys.push(...tapScriptPubkeys); | ||
} | ||
const allHashes = allPublicKeys.map(pubicKey => | ||
getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache), | ||
); | ||
return allHashes.flat(); | ||
} | ||
function getPrevoutTaprootKey(inputIndex, input, cache) { | ||
const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); | ||
return (0, psbtutils_1.isP2TR)(script) ? script.subarray(2, 34) : null; | ||
} | ||
function trimTaprootSig(signature) { | ||
return signature.length === 64 ? signature : signature.subarray(0, 64); | ||
} | ||
function getTaprootHashesForSig( | ||
inputIndex, | ||
input, | ||
inputs, | ||
pubkey, | ||
cache, | ||
tapLeafHashToSign, | ||
allowedSighashTypes, | ||
) { | ||
const unsignedTx = cache.__TX; | ||
const sighashType = | ||
input.sighashType || transaction_1.Transaction.SIGHASH_DEFAULT; | ||
checkSighashTypeAllowed(sighashType, allowedSighashTypes); | ||
const prevOuts = inputs.map((i, index) => | ||
getScriptAndAmountFromUtxo(index, i, cache), | ||
); | ||
const signingScripts = prevOuts.map(o => o.script); | ||
const values = prevOuts.map(o => o.value); | ||
const hashes = []; | ||
if (input.tapInternalKey && !tapLeafHashToSign) { | ||
const outputKey = | ||
getPrevoutTaprootKey(inputIndex, input, cache) || Buffer.from([]); | ||
if ((0, bip371_1.toXOnly)(pubkey).equals(outputKey)) { | ||
const tapKeyHash = unsignedTx.hashForWitnessV1( | ||
inputIndex, | ||
signingScripts, | ||
values, | ||
sighashType, | ||
); | ||
hashes.push({ pubkey, hash: tapKeyHash }); | ||
} | ||
} | ||
const tapLeafHashes = (input.tapLeafScript || []) | ||
.filter(tapLeaf => (0, psbtutils_1.pubkeyInScript)(pubkey, tapLeaf.script)) | ||
.map(tapLeaf => { | ||
const hash = (0, bip341_1.tapleafHash)({ | ||
output: tapLeaf.script, | ||
version: tapLeaf.leafVersion, | ||
}); | ||
return Object.assign({ hash }, tapLeaf); | ||
}) | ||
.filter( | ||
tapLeaf => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash), | ||
) | ||
.map(tapLeaf => { | ||
const tapScriptHash = unsignedTx.hashForWitnessV1( | ||
inputIndex, | ||
signingScripts, | ||
values, | ||
transaction_1.Transaction.SIGHASH_DEFAULT, | ||
tapLeaf.hash, | ||
); | ||
return { | ||
pubkey, | ||
hash: tapScriptHash, | ||
leafHash: tapLeaf.hash, | ||
}; | ||
}); | ||
return hashes.concat(tapLeafHashes); | ||
} | ||
function checkSighashTypeAllowed(sighashType, sighashTypes) { | ||
if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { | ||
const str = sighashTypeToString(sighashType); | ||
throw new Error( | ||
`Sighash type is not allowed. Retry the sign method passing the ` + | ||
`sighashTypes array of whitelisted types. Sighash type: ${str}`, | ||
); | ||
} | ||
} | ||
function getPayment(script, scriptType, partialSig) { | ||
@@ -1175,28 +1440,5 @@ let payment; | ||
break; | ||
case 'taproot': | ||
payment = payments.p2tr( | ||
{ | ||
output: script, | ||
signature: partialSig[0].signature, | ||
}, | ||
{ validate: false }, | ||
); | ||
break; | ||
} | ||
return payment; | ||
} | ||
function getPsigsFromInputFinalScripts(input) { | ||
const scriptItems = !input.finalScriptSig | ||
? [] | ||
: bscript.decompile(input.finalScriptSig) || []; | ||
const witnessItems = !input.finalScriptWitness | ||
? [] | ||
: bscript.decompile(input.finalScriptWitness) || []; | ||
return scriptItems | ||
.concat(witnessItems) | ||
.filter(item => { | ||
return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); | ||
}) | ||
.map(sig => ({ signature: sig })); | ||
} | ||
function getScriptFromInput(inputIndex, input, cache) { | ||
@@ -1229,7 +1471,3 @@ const unsignedTx = cache.__TX; | ||
} | ||
if ( | ||
input.witnessScript || | ||
isP2WPKH(res.script) || | ||
isP2TR(res.script, cache.__EC_LIB) | ||
) { | ||
if (input.witnessScript || (0, psbtutils_1.isP2WPKH)(res.script)) { | ||
res.isSegwit = true; | ||
@@ -1324,24 +1562,2 @@ } | ||
} | ||
function witnessStackToScriptWitness(witness) { | ||
let buffer = Buffer.allocUnsafe(0); | ||
function writeSlice(slice) { | ||
buffer = Buffer.concat([buffer, Buffer.from(slice)]); | ||
} | ||
function writeVarInt(i) { | ||
const currentLen = buffer.length; | ||
const varintLen = varuint.encodingLength(i); | ||
buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); | ||
varuint.encode(i, buffer, currentLen); | ||
} | ||
function writeVarSlice(slice) { | ||
writeVarInt(slice.length); | ||
writeSlice(slice); | ||
} | ||
function writeVector(vector) { | ||
writeVarInt(vector.length); | ||
vector.forEach(writeVarSlice); | ||
} | ||
writeVector(witness); | ||
return buffer; | ||
} | ||
function addNonWitnessTxCache(cache, input, inputIndex) { | ||
@@ -1408,2 +1624,6 @@ cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; | ||
} | ||
function getScriptFromUtxo(inputIndex, input, cache) { | ||
const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); | ||
return script; | ||
} | ||
function getScriptAndAmountFromUtxo(inputIndex, input, cache) { | ||
@@ -1428,3 +1648,3 @@ if (input.witnessUtxo !== undefined) { | ||
function pubkeyInInput(pubkey, input, inputIndex, cache) { | ||
const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache); | ||
const script = getScriptFromUtxo(inputIndex, input, cache); | ||
const { meaningfulScript } = getMeaningfulScript( | ||
@@ -1437,3 +1657,3 @@ script, | ||
); | ||
return pubkeyInScript(pubkey, meaningfulScript); | ||
return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript); | ||
} | ||
@@ -1449,3 +1669,3 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { | ||
); | ||
return pubkeyInScript(pubkey, meaningfulScript); | ||
return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript); | ||
} | ||
@@ -1498,5 +1718,6 @@ function redeemFromFinalScriptSig(finalScript) { | ||
) { | ||
const isP2SH = isP2SHScript(script); | ||
const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); | ||
const isP2WSH = isP2WSHScript(script); | ||
const isP2SH = (0, psbtutils_1.isP2SHScript)(script); | ||
const isP2SHP2WSH = | ||
isP2SH && redeemScript && (0, psbtutils_1.isP2WSHScript)(redeemScript); | ||
const isP2WSH = (0, psbtutils_1.isP2WSHScript)(script); | ||
if (isP2SH && redeemScript === undefined) | ||
@@ -1536,26 +1757,14 @@ throw new Error('scriptPubkey is P2SH but redeemScript missing'); | ||
function checkInvalidP2WSH(script) { | ||
if (isP2WPKH(script) || isP2SHScript(script)) { | ||
if ( | ||
(0, psbtutils_1.isP2WPKH)(script) || | ||
(0, psbtutils_1.isP2SHScript)(script) | ||
) { | ||
throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); | ||
} | ||
} | ||
function pubkeyInScript(pubkey, script) { | ||
const pubkeyHash = (0, crypto_1.hash160)(pubkey); | ||
const pubkeyXOnly = pubkey.slice(1, 33); | ||
const decompiled = bscript.decompile(script); | ||
if (decompiled === null) throw new Error('Unknown script error'); | ||
return decompiled.some(element => { | ||
if (typeof element === 'number') return false; | ||
return ( | ||
element.equals(pubkey) || | ||
element.equals(pubkeyHash) || | ||
element.equals(pubkeyXOnly) | ||
); | ||
}); | ||
} | ||
function classifyScript(script, eccLib) { | ||
if (isP2WPKH(script)) return 'witnesspubkeyhash'; | ||
if (isP2PKH(script)) return 'pubkeyhash'; | ||
if (isP2MS(script)) return 'multisig'; | ||
if (isP2PK(script)) return 'pubkey'; | ||
if (isP2TR(script, eccLib)) return 'taproot'; | ||
function classifyScript(script) { | ||
if ((0, psbtutils_1.isP2WPKH)(script)) return 'witnesspubkeyhash'; | ||
if ((0, psbtutils_1.isP2PKH)(script)) return 'pubkeyhash'; | ||
if ((0, psbtutils_1.isP2MS)(script)) return 'multisig'; | ||
if ((0, psbtutils_1.isP2PK)(script)) return 'pubkey'; | ||
return 'nonstandard'; | ||
@@ -1562,0 +1771,0 @@ } |
@@ -8,2 +8,3 @@ /// <reference types="node" /> | ||
export declare function isPushOnly(value: Stack): boolean; | ||
export declare function countNonPushOnlyOPs(value: Stack): number; | ||
export declare function compile(chunks: Buffer | Stack): Buffer; | ||
@@ -10,0 +11,0 @@ export declare function decompile(buffer: Buffer | Array<number | Buffer>): Array<number | Buffer> | null; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.signature = exports.number = exports.isCanonicalScriptSignature = exports.isDefinedHashType = exports.isCanonicalPubKey = exports.toStack = exports.fromASM = exports.toASM = exports.decompile = exports.compile = exports.isPushOnly = exports.OPS = void 0; | ||
exports.signature = | ||
exports.number = | ||
exports.isCanonicalScriptSignature = | ||
exports.isDefinedHashType = | ||
exports.isCanonicalPubKey = | ||
exports.toStack = | ||
exports.fromASM = | ||
exports.toASM = | ||
exports.decompile = | ||
exports.compile = | ||
exports.countNonPushOnlyOPs = | ||
exports.isPushOnly = | ||
exports.OPS = | ||
void 0; | ||
const bip66 = require('./bip66'); | ||
@@ -8,3 +21,3 @@ const ops_1 = require('./ops'); | ||
enumerable: true, | ||
get: function() { | ||
get: function () { | ||
return ops_1.OPS; | ||
@@ -34,2 +47,6 @@ }, | ||
exports.isPushOnly = isPushOnly; | ||
function countNonPushOnlyOPs(value) { | ||
return value.length - value.filter(isPushOnlyChunk).length; | ||
} | ||
exports.countNonPushOnlyOPs = countNonPushOnlyOPs; | ||
function asMinimalOP(buffer) { | ||
@@ -182,4 +199,3 @@ if (buffer.length === 0) return ops_1.OPS.OP_0; | ||
exports.isCanonicalScriptSignature = isCanonicalScriptSignature; | ||
// tslint:disable-next-line variable-name | ||
exports.number = scriptNumber; | ||
exports.signature = scriptSignature; |
@@ -392,3 +392,3 @@ 'use strict'; | ||
'TapSighash', | ||
Buffer.concat([Buffer.alloc(1), sigMsgWriter.end()]), | ||
Buffer.concat([Buffer.from([0x00]), sigMsgWriter.end()]), | ||
); | ||
@@ -395,0 +395,0 @@ } |
@@ -17,11 +17,18 @@ /// <reference types="node" /> | ||
} | ||
export interface TaprootLeaf { | ||
export interface Tapleaf { | ||
output: Buffer; | ||
version?: number; | ||
} | ||
export declare const TAPLEAF_VERSION_MASK = 254; | ||
export declare function isTapleaf(o: any): o is Tapleaf; | ||
/** | ||
* Binary tree repsenting script path spends for a Taproot input. | ||
* Each node is either a single Tapleaf, or a pair of Tapleaf | Taptree. | ||
* The tree has no balancing requirements. | ||
*/ | ||
export type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf; | ||
export declare function isTaptree(scriptTree: any): scriptTree is Taptree; | ||
export interface TinySecp256k1Interface { | ||
isXOnlyPoint(p: Uint8Array): boolean; | ||
xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; | ||
privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null; | ||
privateNegate(d: Uint8Array): Uint8Array; | ||
} | ||
@@ -28,0 +35,0 @@ export declare const Buffer256bit: any; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0; | ||
exports.oneOf = | ||
exports.Null = | ||
exports.BufferN = | ||
exports.Function = | ||
exports.UInt32 = | ||
exports.UInt8 = | ||
exports.tuple = | ||
exports.maybe = | ||
exports.Hex = | ||
exports.Buffer = | ||
exports.String = | ||
exports.Boolean = | ||
exports.Array = | ||
exports.Number = | ||
exports.Hash256bit = | ||
exports.Hash160bit = | ||
exports.Buffer256bit = | ||
exports.isTaptree = | ||
exports.isTapleaf = | ||
exports.TAPLEAF_VERSION_MASK = | ||
exports.Network = | ||
exports.ECPoint = | ||
exports.Satoshi = | ||
exports.Signer = | ||
exports.BIP32Path = | ||
exports.UInt31 = | ||
exports.isPoint = | ||
exports.typeforce = | ||
void 0; | ||
const buffer_1 = require('buffer'); | ||
@@ -71,9 +99,24 @@ exports.typeforce = require('typeforce'); | ||
}); | ||
exports.TAPLEAF_VERSION_MASK = 0xfe; | ||
function isTapleaf(o) { | ||
if (!o || !('output' in o)) return false; | ||
if (!buffer_1.Buffer.isBuffer(o.output)) return false; | ||
if (o.version !== undefined) | ||
return (o.version & exports.TAPLEAF_VERSION_MASK) === o.version; | ||
return true; | ||
} | ||
exports.isTapleaf = isTapleaf; | ||
function isTaptree(scriptTree) { | ||
if (!(0, exports.Array)(scriptTree)) return isTapleaf(scriptTree); | ||
if (scriptTree.length !== 2) return false; | ||
return scriptTree.every(t => isTaptree(t)); | ||
} | ||
exports.isTaptree = isTaptree; | ||
exports.Buffer256bit = exports.typeforce.BufferN(32); | ||
exports.Hash160bit = exports.typeforce.BufferN(20); | ||
exports.Hash256bit = exports.typeforce.BufferN(32); | ||
exports.Number = exports.typeforce.Number; // tslint:disable-line variable-name | ||
exports.Number = exports.typeforce.Number; | ||
exports.Array = exports.typeforce.Array; | ||
exports.Boolean = exports.typeforce.Boolean; // tslint:disable-line variable-name | ||
exports.String = exports.typeforce.String; // tslint:disable-line variable-name | ||
exports.Boolean = exports.typeforce.Boolean; | ||
exports.String = exports.typeforce.String; | ||
exports.Buffer = exports.typeforce.Buffer; | ||
@@ -80,0 +123,0 @@ exports.Hex = exports.typeforce.Hex; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
236489
63
6447
192
33
+ Addedripemd160@^2.0.2
- Removedwif@^2.0.1
- Removedwif@2.0.6(transitive)
Updatedbip174@^2.1.1