commit
1f44f722d3
21 changed files with 688 additions and 55 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,3 +1,16 @@
|
||||||
|
# 6.0.0
|
||||||
|
__removed__
|
||||||
|
- bip32: Removed the re-export. Please add as dependency to your app instead.
|
||||||
|
- ECPair: Please use bip32 moving forward. ecpair package was created for those who need it.
|
||||||
|
- TransactionBuilder: Any internal files used only in TB (classify, templates, etc.) were also removed.
|
||||||
|
|
||||||
|
__added__
|
||||||
|
- taproot segwit v1 address support (bech32m) via address module (#1676)
|
||||||
|
- hashForWitnessV1 method on Transaction class (#1745)
|
||||||
|
|
||||||
|
__fixed__
|
||||||
|
- Transaction version read/write differed. (#1717)
|
||||||
|
|
||||||
# 5.2.0
|
# 5.2.0
|
||||||
__changed__
|
__changed__
|
||||||
- Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563)
|
- Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563)
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
# BitcoinJS (bitcoinjs-lib)
|
# BitcoinJS (bitcoinjs-lib)
|
||||||
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib)
|
[![Github CI](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml/badge.svg)](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml) [![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
|
||||||
[![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib)
|
|
||||||
|
|
||||||
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
|
|
||||||
|
|
||||||
A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify.
|
A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify.
|
||||||
|
|
||||||
|
@ -94,6 +91,8 @@ The below examples are implemented as integration tests, they should be very eas
|
||||||
Otherwise, pull requests are appreciated.
|
Otherwise, pull requests are appreciated.
|
||||||
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).
|
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).
|
||||||
|
|
||||||
|
- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.md)
|
||||||
|
|
||||||
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
|
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
|
||||||
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
|
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
|
||||||
- [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
|
- [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
|
||||||
|
|
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "bitcoinjs-lib",
|
"name": "bitcoinjs-lib",
|
||||||
"version": "5.2.0",
|
"version": "6.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -416,9 +416,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "16.11.1",
|
"version": "16.11.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
|
||||||
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==",
|
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/proxyquire": {
|
"@types/proxyquire": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "bitcoinjs-lib",
|
"name": "bitcoinjs-lib",
|
||||||
"version": "5.2.0",
|
"version": "6.0.0",
|
||||||
"description": "Client-side Bitcoin JavaScript library",
|
"description": "Client-side Bitcoin JavaScript library",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
"types": "./src/index.d.ts",
|
"types": "./src/index.d.ts",
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
"@types/bs58check": "^2.1.0",
|
"@types/bs58check": "^2.1.0",
|
||||||
"@types/create-hash": "^1.2.2",
|
"@types/create-hash": "^1.2.2",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "^16.11.1",
|
"@types/node": "^16.11.7",
|
||||||
"@types/proxyquire": "^1.3.28",
|
"@types/proxyquire": "^1.3.28",
|
||||||
"@types/randombytes": "^2.0.0",
|
"@types/randombytes": "^2.0.0",
|
||||||
"@types/wif": "^2.0.2",
|
"@types/wif": "^2.0.2",
|
||||||
|
|
2
src/bufferutils.d.ts
vendored
2
src/bufferutils.d.ts
vendored
|
@ -11,6 +11,7 @@ export declare function cloneBuffer(buffer: Buffer): Buffer;
|
||||||
export declare class BufferWriter {
|
export declare class BufferWriter {
|
||||||
buffer: Buffer;
|
buffer: Buffer;
|
||||||
offset: number;
|
offset: number;
|
||||||
|
static withCapacity(size: number): BufferWriter;
|
||||||
constructor(buffer: Buffer, offset?: number);
|
constructor(buffer: Buffer, offset?: number);
|
||||||
writeUInt8(i: number): void;
|
writeUInt8(i: number): void;
|
||||||
writeInt32(i: number): void;
|
writeInt32(i: number): void;
|
||||||
|
@ -20,6 +21,7 @@ export declare class BufferWriter {
|
||||||
writeSlice(slice: Buffer): void;
|
writeSlice(slice: Buffer): void;
|
||||||
writeVarSlice(slice: Buffer): void;
|
writeVarSlice(slice: Buffer): void;
|
||||||
writeVector(vector: Buffer[]): void;
|
writeVector(vector: Buffer[]): void;
|
||||||
|
end(): Buffer;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Helper class for reading of bitcoin data types from a buffer.
|
* Helper class for reading of bitcoin data types from a buffer.
|
||||||
|
|
|
@ -58,6 +58,9 @@ class BufferWriter {
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
|
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
|
||||||
}
|
}
|
||||||
|
static withCapacity(size) {
|
||||||
|
return new BufferWriter(Buffer.alloc(size));
|
||||||
|
}
|
||||||
writeUInt8(i) {
|
writeUInt8(i) {
|
||||||
this.offset = this.buffer.writeUInt8(i, this.offset);
|
this.offset = this.buffer.writeUInt8(i, this.offset);
|
||||||
}
|
}
|
||||||
|
@ -88,6 +91,12 @@ class BufferWriter {
|
||||||
this.writeVarInt(vector.length);
|
this.writeVarInt(vector.length);
|
||||||
vector.forEach(buf => this.writeVarSlice(buf));
|
vector.forEach(buf => this.writeVarSlice(buf));
|
||||||
}
|
}
|
||||||
|
end() {
|
||||||
|
if (this.buffer.length === this.offset) {
|
||||||
|
return this.buffer;
|
||||||
|
}
|
||||||
|
throw new Error(`buffer size ${this.buffer.length}, offset ${this.offset}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.BufferWriter = BufferWriter;
|
exports.BufferWriter = BufferWriter;
|
||||||
/**
|
/**
|
||||||
|
|
4
src/crypto.d.ts
vendored
4
src/crypto.d.ts
vendored
|
@ -4,3 +4,7 @@ export declare function sha1(buffer: Buffer): Buffer;
|
||||||
export declare function sha256(buffer: Buffer): Buffer;
|
export declare function sha256(buffer: Buffer): Buffer;
|
||||||
export declare function hash160(buffer: Buffer): Buffer;
|
export declare function hash160(buffer: Buffer): Buffer;
|
||||||
export declare function hash256(buffer: Buffer): Buffer;
|
export declare function hash256(buffer: Buffer): Buffer;
|
||||||
|
declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"];
|
||||||
|
export declare type TaggedHashPrefix = typeof TAGS[number];
|
||||||
|
export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer;
|
||||||
|
export {};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0;
|
exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0;
|
||||||
const createHash = require('create-hash');
|
const createHash = require('create-hash');
|
||||||
function ripemd160(buffer) {
|
function ripemd160(buffer) {
|
||||||
try {
|
try {
|
||||||
|
@ -34,3 +34,25 @@ function hash256(buffer) {
|
||||||
return sha256(sha256(buffer));
|
return sha256(sha256(buffer));
|
||||||
}
|
}
|
||||||
exports.hash256 = hash256;
|
exports.hash256 = hash256;
|
||||||
|
const TAGS = [
|
||||||
|
'BIP0340/challenge',
|
||||||
|
'BIP0340/aux',
|
||||||
|
'BIP0340/nonce',
|
||||||
|
'TapLeaf',
|
||||||
|
'TapBranch',
|
||||||
|
'TapSighash',
|
||||||
|
'TapTweak',
|
||||||
|
'KeyAgg list',
|
||||||
|
'KeyAgg coefficient',
|
||||||
|
];
|
||||||
|
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
||||||
|
const TAGGED_HASH_PREFIXES = Object.fromEntries(
|
||||||
|
TAGS.map(tag => {
|
||||||
|
const tagHash = sha256(Buffer.from(tag));
|
||||||
|
return [tag, Buffer.concat([tagHash, tagHash])];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
function taggedHash(prefix, data) {
|
||||||
|
return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data]));
|
||||||
|
}
|
||||||
|
exports.taggedHash = taggedHash;
|
||||||
|
|
1
src/index.d.ts
vendored
1
src/index.d.ts
vendored
|
@ -5,6 +5,7 @@ import * as payments from './payments';
|
||||||
import * as script from './script';
|
import * as script from './script';
|
||||||
export { address, crypto, networks, payments, script };
|
export { address, crypto, networks, payments, script };
|
||||||
export { Block } from './block';
|
export { Block } from './block';
|
||||||
|
export { TaggedHashPrefix } from './crypto';
|
||||||
export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt';
|
export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt';
|
||||||
export { OPS as opcodes } from './ops';
|
export { OPS as opcodes } from './ops';
|
||||||
export { Transaction } from './transaction';
|
export { Transaction } from './transaction';
|
||||||
|
|
4
src/transaction.d.ts
vendored
4
src/transaction.d.ts
vendored
|
@ -12,10 +12,13 @@ export interface Input {
|
||||||
}
|
}
|
||||||
export declare class Transaction {
|
export declare class Transaction {
|
||||||
static readonly DEFAULT_SEQUENCE = 4294967295;
|
static readonly DEFAULT_SEQUENCE = 4294967295;
|
||||||
|
static readonly SIGHASH_DEFAULT = 0;
|
||||||
static readonly SIGHASH_ALL = 1;
|
static readonly SIGHASH_ALL = 1;
|
||||||
static readonly SIGHASH_NONE = 2;
|
static readonly SIGHASH_NONE = 2;
|
||||||
static readonly SIGHASH_SINGLE = 3;
|
static readonly SIGHASH_SINGLE = 3;
|
||||||
static readonly SIGHASH_ANYONECANPAY = 128;
|
static readonly SIGHASH_ANYONECANPAY = 128;
|
||||||
|
static readonly SIGHASH_OUTPUT_MASK = 3;
|
||||||
|
static readonly SIGHASH_INPUT_MASK = 128;
|
||||||
static readonly ADVANCED_TRANSACTION_MARKER = 0;
|
static readonly ADVANCED_TRANSACTION_MARKER = 0;
|
||||||
static readonly ADVANCED_TRANSACTION_FLAG = 1;
|
static readonly ADVANCED_TRANSACTION_FLAG = 1;
|
||||||
static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction;
|
static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction;
|
||||||
|
@ -42,6 +45,7 @@ export declare class Transaction {
|
||||||
* This hash can then be used to sign the provided transaction input.
|
* This hash can then be used to sign the provided transaction input.
|
||||||
*/
|
*/
|
||||||
hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer;
|
hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer;
|
||||||
|
hashForWitnessV1(inIndex: number, prevOutScripts: Buffer[], values: number[], hashType: number, leafHash?: Buffer, annex?: Buffer): Buffer;
|
||||||
hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer;
|
hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer;
|
||||||
getHash(forWitness?: boolean): Buffer;
|
getHash(forWitness?: boolean): Buffer;
|
||||||
getId(): string;
|
getId(): string;
|
||||||
|
|
|
@ -20,7 +20,7 @@ function vectorSize(someVector) {
|
||||||
}, 0)
|
}, 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
|
const EMPTY_BUFFER = Buffer.allocUnsafe(0);
|
||||||
const EMPTY_WITNESS = [];
|
const EMPTY_WITNESS = [];
|
||||||
const ZERO = Buffer.from(
|
const ZERO = Buffer.from(
|
||||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
@ -32,7 +32,7 @@ const ONE = Buffer.from(
|
||||||
);
|
);
|
||||||
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
|
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
|
||||||
const BLANK_OUTPUT = {
|
const BLANK_OUTPUT = {
|
||||||
script: EMPTY_SCRIPT,
|
script: EMPTY_BUFFER,
|
||||||
valueBuffer: VALUE_UINT64_MAX,
|
valueBuffer: VALUE_UINT64_MAX,
|
||||||
};
|
};
|
||||||
function isOutput(out) {
|
function isOutput(out) {
|
||||||
|
@ -124,7 +124,7 @@ class Transaction {
|
||||||
this.ins.push({
|
this.ins.push({
|
||||||
hash,
|
hash,
|
||||||
index,
|
index,
|
||||||
script: scriptSig || EMPTY_SCRIPT,
|
script: scriptSig || EMPTY_BUFFER,
|
||||||
sequence: sequence,
|
sequence: sequence,
|
||||||
witness: EMPTY_WITNESS,
|
witness: EMPTY_WITNESS,
|
||||||
}) - 1
|
}) - 1
|
||||||
|
@ -247,7 +247,7 @@ class Transaction {
|
||||||
} else {
|
} else {
|
||||||
// "blank" others input scripts
|
// "blank" others input scripts
|
||||||
txTmp.ins.forEach(input => {
|
txTmp.ins.forEach(input => {
|
||||||
input.script = EMPTY_SCRIPT;
|
input.script = EMPTY_BUFFER;
|
||||||
});
|
});
|
||||||
txTmp.ins[inIndex].script = ourScript;
|
txTmp.ins[inIndex].script = ourScript;
|
||||||
}
|
}
|
||||||
|
@ -257,6 +257,141 @@ class Transaction {
|
||||||
txTmp.__toBuffer(buffer, 0, false);
|
txTmp.__toBuffer(buffer, 0, false);
|
||||||
return bcrypto.hash256(buffer);
|
return bcrypto.hash256(buffer);
|
||||||
}
|
}
|
||||||
|
hashForWitnessV1(inIndex, prevOutScripts, values, hashType, leafHash, annex) {
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message
|
||||||
|
typeforce(
|
||||||
|
types.tuple(
|
||||||
|
types.UInt32,
|
||||||
|
typeforce.arrayOf(types.Buffer),
|
||||||
|
typeforce.arrayOf(types.Satoshi),
|
||||||
|
types.UInt32,
|
||||||
|
),
|
||||||
|
arguments,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
values.length !== this.ins.length ||
|
||||||
|
prevOutScripts.length !== this.ins.length
|
||||||
|
) {
|
||||||
|
throw new Error('Must supply prevout script and value for all inputs');
|
||||||
|
}
|
||||||
|
const outputType =
|
||||||
|
hashType === Transaction.SIGHASH_DEFAULT
|
||||||
|
? Transaction.SIGHASH_ALL
|
||||||
|
: hashType & Transaction.SIGHASH_OUTPUT_MASK;
|
||||||
|
const inputType = hashType & Transaction.SIGHASH_INPUT_MASK;
|
||||||
|
const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY;
|
||||||
|
const isNone = outputType === Transaction.SIGHASH_NONE;
|
||||||
|
const isSingle = outputType === Transaction.SIGHASH_SINGLE;
|
||||||
|
let hashPrevouts = EMPTY_BUFFER;
|
||||||
|
let hashAmounts = EMPTY_BUFFER;
|
||||||
|
let hashScriptPubKeys = EMPTY_BUFFER;
|
||||||
|
let hashSequences = EMPTY_BUFFER;
|
||||||
|
let hashOutputs = EMPTY_BUFFER;
|
||||||
|
if (!isAnyoneCanPay) {
|
||||||
|
let bufferWriter = bufferutils_1.BufferWriter.withCapacity(
|
||||||
|
36 * this.ins.length,
|
||||||
|
);
|
||||||
|
this.ins.forEach(txIn => {
|
||||||
|
bufferWriter.writeSlice(txIn.hash);
|
||||||
|
bufferWriter.writeUInt32(txIn.index);
|
||||||
|
});
|
||||||
|
hashPrevouts = bcrypto.sha256(bufferWriter.end());
|
||||||
|
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
|
||||||
|
8 * this.ins.length,
|
||||||
|
);
|
||||||
|
values.forEach(value => bufferWriter.writeUInt64(value));
|
||||||
|
hashAmounts = bcrypto.sha256(bufferWriter.end());
|
||||||
|
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
|
||||||
|
prevOutScripts.map(varSliceSize).reduce((a, b) => a + b),
|
||||||
|
);
|
||||||
|
prevOutScripts.forEach(prevOutScript =>
|
||||||
|
bufferWriter.writeVarSlice(prevOutScript),
|
||||||
|
);
|
||||||
|
hashScriptPubKeys = bcrypto.sha256(bufferWriter.end());
|
||||||
|
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
|
||||||
|
4 * this.ins.length,
|
||||||
|
);
|
||||||
|
this.ins.forEach(txIn => bufferWriter.writeUInt32(txIn.sequence));
|
||||||
|
hashSequences = bcrypto.sha256(bufferWriter.end());
|
||||||
|
}
|
||||||
|
if (!(isNone || isSingle)) {
|
||||||
|
const txOutsSize = this.outs
|
||||||
|
.map(output => 8 + varSliceSize(output.script))
|
||||||
|
.reduce((a, b) => a + b);
|
||||||
|
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(txOutsSize);
|
||||||
|
this.outs.forEach(out => {
|
||||||
|
bufferWriter.writeUInt64(out.value);
|
||||||
|
bufferWriter.writeVarSlice(out.script);
|
||||||
|
});
|
||||||
|
hashOutputs = bcrypto.sha256(bufferWriter.end());
|
||||||
|
} else if (isSingle && inIndex < this.outs.length) {
|
||||||
|
const output = this.outs[inIndex];
|
||||||
|
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
|
||||||
|
8 + varSliceSize(output.script),
|
||||||
|
);
|
||||||
|
bufferWriter.writeUInt64(output.value);
|
||||||
|
bufferWriter.writeVarSlice(output.script);
|
||||||
|
hashOutputs = bcrypto.sha256(bufferWriter.end());
|
||||||
|
}
|
||||||
|
const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0);
|
||||||
|
// Length calculation from:
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-14
|
||||||
|
// With extension from:
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#signature-validation
|
||||||
|
const sigMsgSize =
|
||||||
|
174 -
|
||||||
|
(isAnyoneCanPay ? 49 : 0) -
|
||||||
|
(isNone ? 32 : 0) +
|
||||||
|
(annex ? 32 : 0) +
|
||||||
|
(leafHash ? 37 : 0);
|
||||||
|
const sigMsgWriter = bufferutils_1.BufferWriter.withCapacity(sigMsgSize);
|
||||||
|
sigMsgWriter.writeUInt8(hashType);
|
||||||
|
// Transaction
|
||||||
|
sigMsgWriter.writeInt32(this.version);
|
||||||
|
sigMsgWriter.writeUInt32(this.locktime);
|
||||||
|
sigMsgWriter.writeSlice(hashPrevouts);
|
||||||
|
sigMsgWriter.writeSlice(hashAmounts);
|
||||||
|
sigMsgWriter.writeSlice(hashScriptPubKeys);
|
||||||
|
sigMsgWriter.writeSlice(hashSequences);
|
||||||
|
if (!(isNone || isSingle)) {
|
||||||
|
sigMsgWriter.writeSlice(hashOutputs);
|
||||||
|
}
|
||||||
|
// Input
|
||||||
|
sigMsgWriter.writeUInt8(spendType);
|
||||||
|
if (isAnyoneCanPay) {
|
||||||
|
const input = this.ins[inIndex];
|
||||||
|
sigMsgWriter.writeSlice(input.hash);
|
||||||
|
sigMsgWriter.writeUInt32(input.index);
|
||||||
|
sigMsgWriter.writeUInt64(values[inIndex]);
|
||||||
|
sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]);
|
||||||
|
sigMsgWriter.writeUInt32(input.sequence);
|
||||||
|
} else {
|
||||||
|
sigMsgWriter.writeUInt32(inIndex);
|
||||||
|
}
|
||||||
|
if (annex) {
|
||||||
|
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
|
||||||
|
varSliceSize(annex),
|
||||||
|
);
|
||||||
|
bufferWriter.writeVarSlice(annex);
|
||||||
|
sigMsgWriter.writeSlice(bcrypto.sha256(bufferWriter.end()));
|
||||||
|
}
|
||||||
|
// Output
|
||||||
|
if (isSingle) {
|
||||||
|
sigMsgWriter.writeSlice(hashOutputs);
|
||||||
|
}
|
||||||
|
// BIP342 extension
|
||||||
|
if (leafHash) {
|
||||||
|
sigMsgWriter.writeSlice(leafHash);
|
||||||
|
sigMsgWriter.writeUInt8(0);
|
||||||
|
sigMsgWriter.writeUInt32(0xffffffff);
|
||||||
|
}
|
||||||
|
// Extra zero byte because:
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19
|
||||||
|
return bcrypto.taggedHash(
|
||||||
|
'TapSighash',
|
||||||
|
Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]),
|
||||||
|
);
|
||||||
|
}
|
||||||
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
|
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
|
||||||
typeforce(
|
typeforce(
|
||||||
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
|
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
|
||||||
|
@ -396,9 +531,12 @@ class Transaction {
|
||||||
}
|
}
|
||||||
exports.Transaction = Transaction;
|
exports.Transaction = Transaction;
|
||||||
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
|
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
|
||||||
|
Transaction.SIGHASH_DEFAULT = 0x00;
|
||||||
Transaction.SIGHASH_ALL = 0x01;
|
Transaction.SIGHASH_ALL = 0x01;
|
||||||
Transaction.SIGHASH_NONE = 0x02;
|
Transaction.SIGHASH_NONE = 0x02;
|
||||||
Transaction.SIGHASH_SINGLE = 0x03;
|
Transaction.SIGHASH_SINGLE = 0x03;
|
||||||
Transaction.SIGHASH_ANYONECANPAY = 0x80;
|
Transaction.SIGHASH_ANYONECANPAY = 0x80;
|
||||||
|
Transaction.SIGHASH_OUTPUT_MASK = 0x03;
|
||||||
|
Transaction.SIGHASH_INPUT_MASK = 0x80;
|
||||||
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
|
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
|
||||||
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
|
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
|
||||||
|
|
|
@ -66,6 +66,13 @@ describe('bufferutils', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('withCapacity', () => {
|
||||||
|
const expectedBuffer = Buffer.from('04030201', 'hex');
|
||||||
|
const withCapacity = BufferWriter.withCapacity(4);
|
||||||
|
withCapacity.writeInt32(0x01020304);
|
||||||
|
testBuffer(withCapacity, expectedBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
it('writeUint8', () => {
|
it('writeUint8', () => {
|
||||||
const values = [0, 1, 254, 255];
|
const values = [0, 1, 254, 255];
|
||||||
const expectedBuffer = Buffer.from([0, 1, 0xfe, 0xff]);
|
const expectedBuffer = Buffer.from([0, 1, 0xfe, 0xff]);
|
||||||
|
@ -277,6 +284,16 @@ describe('bufferutils', () => {
|
||||||
});
|
});
|
||||||
testBuffer(bufferWriter, expectedBuffer);
|
testBuffer(bufferWriter, expectedBuffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('end', () => {
|
||||||
|
const expected = Buffer.from('0403020108070605', 'hex');
|
||||||
|
const bufferWriter = BufferWriter.withCapacity(8);
|
||||||
|
bufferWriter.writeUInt32(0x01020304);
|
||||||
|
bufferWriter.writeUInt32(0x05060708);
|
||||||
|
const result = bufferWriter.end();
|
||||||
|
testBuffer(bufferWriter, result);
|
||||||
|
testBuffer(bufferWriter, expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('BufferReader', () => {
|
describe('BufferReader', () => {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { describe, it } from 'mocha';
|
import { describe, it } from 'mocha';
|
||||||
import { crypto as bcrypto } from '..';
|
import { crypto as bcrypto, TaggedHashPrefix } from '..';
|
||||||
import * as fixtures from './fixtures/crypto.json';
|
import * as fixtures from './fixtures/crypto.json';
|
||||||
|
|
||||||
describe('crypto', () => {
|
describe('crypto', () => {
|
||||||
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => {
|
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => {
|
||||||
describe(algorithm, () => {
|
describe(algorithm, () => {
|
||||||
fixtures.forEach(f => {
|
fixtures.hashes.forEach(f => {
|
||||||
const fn = (bcrypto as any)[algorithm];
|
const fn = (bcrypto as any)[algorithm];
|
||||||
const expected = (f as any)[algorithm];
|
const expected = (f as any)[algorithm];
|
||||||
|
|
||||||
|
@ -19,4 +19,15 @@ describe('crypto', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('taggedHash', () => {
|
||||||
|
fixtures.taggedHash.forEach(f => {
|
||||||
|
const bytes = Buffer.from(f.hex, 'hex');
|
||||||
|
const expected = Buffer.from(f.result, 'hex');
|
||||||
|
it(`returns ${f.result} for taggedHash "${f.tag}" of ${f.hex}`, () => {
|
||||||
|
const actual = bcrypto.taggedHash(f.tag as TaggedHashPrefix, bytes);
|
||||||
|
assert.strictEqual(actual.toString('hex'), expected.toString('hex'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
11
test/fixtures/crypto.json
vendored
11
test/fixtures/crypto.json
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"hashes": [
|
||||||
{
|
{
|
||||||
"hex": "0000000000000001",
|
"hex": "0000000000000001",
|
||||||
"hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8",
|
"hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8",
|
||||||
|
@ -31,4 +32,12 @@
|
||||||
"sha1": "10d96fb43aca84e342206887bbeed3065d4e4344",
|
"sha1": "10d96fb43aca84e342206887bbeed3065d4e4344",
|
||||||
"sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da"
|
"sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"taggedHash": [
|
||||||
|
{
|
||||||
|
"tag": "TapTweak",
|
||||||
|
"hex": "0101010101010101",
|
||||||
|
"result": "71ae15bad52efcecf4c9f672bfbded68a4adb8258f1b95f0d06aefdb5ebd14e9"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
81
test/fixtures/transaction.json
vendored
81
test/fixtures/transaction.json
vendored
|
@ -808,6 +808,87 @@
|
||||||
"value": 987654321
|
"value": 987654321
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"taprootSigning": [
|
||||||
|
{
|
||||||
|
"description": "P2TR key path",
|
||||||
|
"utxos": [
|
||||||
|
{
|
||||||
|
"scriptHex": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
|
||||||
|
"value": 420000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
|
||||||
|
"value": 462000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac",
|
||||||
|
"value": 294000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
|
||||||
|
"value": 504000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
|
||||||
|
"value": 630000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc",
|
||||||
|
"value": 378000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
|
||||||
|
"value": 672000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "51200f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561",
|
||||||
|
"value": 546000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptHex": "5120053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587",
|
||||||
|
"value": 588000000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"txHex": "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffff0c638ca38362001f5e128a01ae2b379288eb22cfaf903652b2ec1c88588f487a0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd05000000000000000000081efa267f1f0e46e054ecec01773de7c844721e010c2db5d5864a6a6b53e013a010000000000000000a690669c3c4a62507d93609810c6de3f99d1a6e311fe39dd23683d695c07bdee0000000000ffffffff727ab5f877438496f8613ca84002ff38e8292f7bd11f0a9b9b83ebd16779669e0100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"vin": 0,
|
||||||
|
"typeHex": "03",
|
||||||
|
"hash": "7e584883b084ace0469c6962a9a7d2a9060e1f3c218ab40d32c77651482122bc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vin": 1,
|
||||||
|
"typeHex": "83",
|
||||||
|
"hash": "325a644af47e8a5a2591cda0ab0723978537318f10e6a63d4eed783b96a71a4d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vin": 3,
|
||||||
|
"typeHex": "01",
|
||||||
|
"hash": "6ffd256e108685b41831385f57eebf2fca041bc6b5e607ea11b3e03d4cf9d9ba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vin": 4,
|
||||||
|
"typeHex": "00",
|
||||||
|
"hash": "9f90136737540ccc18707e1fd398ad222a1a7e4dd65cbfd22dbe4660191efa58"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vin": 6,
|
||||||
|
"typeHex": "02",
|
||||||
|
"hash": "835c9ab6084ed9a8ae9b7cda21e0aa797aca3b76a54bd1e3c7db093f6c57e23f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vin": 7,
|
||||||
|
"typeHex": "82",
|
||||||
|
"hash": "df1cca638283c667084b8ffe6bf6e116cc5a53cf7ae1202c5fee45a9085f1ba5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vin": 8,
|
||||||
|
"typeHex": "81",
|
||||||
|
"hash": "30319859ca79ea1b7a9782e9daebc46e4ca4ca2bc04c9c53b2ec87fa83a526bd"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"invalid": {
|
"invalid": {
|
||||||
"addInput": [
|
"addInput": [
|
||||||
{
|
{
|
||||||
|
|
111
test/integration/taproot.md
Normal file
111
test/integration/taproot.md
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
# 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
|
||||||
|
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
|
||||||
|
import('tiny-secp256k1')
|
||||||
|
.then(async (ecc) => {
|
||||||
|
// End imports
|
||||||
|
|
||||||
|
// 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 });
|
||||||
|
|
||||||
|
const bip32 = BIP32Wrapper(ecc);
|
||||||
|
|
||||||
|
const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network);
|
||||||
|
// scriptPubkey
|
||||||
|
const output = Buffer.concat([
|
||||||
|
// witness v1, PUSH_DATA 32 bytes
|
||||||
|
Buffer.from([0x51, 0x20]),
|
||||||
|
// x-only pubkey (remove 1 byte y parity)
|
||||||
|
myKey.publicKey.slice(1, 33),
|
||||||
|
]);
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
// 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(key.signSchnorr(sighash));
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
```
|
|
@ -328,6 +328,27 @@ describe('Transaction', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('taprootSigning', () => {
|
||||||
|
fixtures.taprootSigning.forEach(f => {
|
||||||
|
const tx = Transaction.fromHex(f.txHex);
|
||||||
|
const prevOutScripts = f.utxos.map(({ scriptHex }) =>
|
||||||
|
Buffer.from(scriptHex, 'hex'),
|
||||||
|
);
|
||||||
|
const values = f.utxos.map(({ value }) => value);
|
||||||
|
|
||||||
|
f.cases.forEach(c => {
|
||||||
|
let hash: Buffer;
|
||||||
|
|
||||||
|
it(`should hash to ${c.hash} for ${f.description}:${c.vin}`, () => {
|
||||||
|
const hashType = Buffer.from(c.typeHex, 'hex').readUInt8(0);
|
||||||
|
|
||||||
|
hash = tx.hashForWitnessV1(c.vin, prevOutScripts, values, hashType);
|
||||||
|
assert.strictEqual(hash.toString('hex'), c.hash);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setWitness', () => {
|
describe('setWitness', () => {
|
||||||
it('only accepts a a witness stack (Array of Buffers)', () => {
|
it('only accepts a a witness stack (Array of Buffers)', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
|
|
|
@ -58,6 +58,10 @@ export function cloneBuffer(buffer: Buffer): Buffer {
|
||||||
* Helper class for serialization of bitcoin data types into a pre-allocated buffer.
|
* Helper class for serialization of bitcoin data types into a pre-allocated buffer.
|
||||||
*/
|
*/
|
||||||
export class BufferWriter {
|
export class BufferWriter {
|
||||||
|
static withCapacity(size: number): BufferWriter {
|
||||||
|
return new BufferWriter(Buffer.alloc(size));
|
||||||
|
}
|
||||||
|
|
||||||
constructor(public buffer: Buffer, public offset: number = 0) {
|
constructor(public buffer: Buffer, public offset: number = 0) {
|
||||||
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
|
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
|
||||||
}
|
}
|
||||||
|
@ -99,6 +103,13 @@ export class BufferWriter {
|
||||||
this.writeVarInt(vector.length);
|
this.writeVarInt(vector.length);
|
||||||
vector.forEach((buf: Buffer) => this.writeVarSlice(buf));
|
vector.forEach((buf: Buffer) => this.writeVarSlice(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
end(): Buffer {
|
||||||
|
if (this.buffer.length === this.offset) {
|
||||||
|
return this.buffer;
|
||||||
|
}
|
||||||
|
throw new Error(`buffer size ${this.buffer.length}, offset ${this.offset}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,3 +31,27 @@ export function hash160(buffer: Buffer): Buffer {
|
||||||
export function hash256(buffer: Buffer): Buffer {
|
export function hash256(buffer: Buffer): Buffer {
|
||||||
return sha256(sha256(buffer));
|
return sha256(sha256(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TAGS = [
|
||||||
|
'BIP0340/challenge',
|
||||||
|
'BIP0340/aux',
|
||||||
|
'BIP0340/nonce',
|
||||||
|
'TapLeaf',
|
||||||
|
'TapBranch',
|
||||||
|
'TapSighash',
|
||||||
|
'TapTweak',
|
||||||
|
'KeyAgg list',
|
||||||
|
'KeyAgg coefficient',
|
||||||
|
] as const;
|
||||||
|
export type TaggedHashPrefix = typeof TAGS[number];
|
||||||
|
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
||||||
|
const TAGGED_HASH_PREFIXES = Object.fromEntries(
|
||||||
|
TAGS.map(tag => {
|
||||||
|
const tagHash = sha256(Buffer.from(tag));
|
||||||
|
return [tag, Buffer.concat([tagHash, tagHash])];
|
||||||
|
}),
|
||||||
|
) as { [k in TaggedHashPrefix]: Buffer };
|
||||||
|
|
||||||
|
export function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer {
|
||||||
|
return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data]));
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import * as script from './script';
|
||||||
export { address, crypto, networks, payments, script };
|
export { address, crypto, networks, payments, script };
|
||||||
|
|
||||||
export { Block } from './block';
|
export { Block } from './block';
|
||||||
|
export { TaggedHashPrefix } from './crypto';
|
||||||
export {
|
export {
|
||||||
Psbt,
|
Psbt,
|
||||||
PsbtTxInput,
|
PsbtTxInput,
|
||||||
|
|
|
@ -27,7 +27,7 @@ function vectorSize(someVector: Buffer[]): number {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMPTY_SCRIPT: Buffer = Buffer.allocUnsafe(0);
|
const EMPTY_BUFFER: Buffer = Buffer.allocUnsafe(0);
|
||||||
const EMPTY_WITNESS: Buffer[] = [];
|
const EMPTY_WITNESS: Buffer[] = [];
|
||||||
const ZERO: Buffer = Buffer.from(
|
const ZERO: Buffer = Buffer.from(
|
||||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
@ -39,7 +39,7 @@ const ONE: Buffer = Buffer.from(
|
||||||
);
|
);
|
||||||
const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex');
|
const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex');
|
||||||
const BLANK_OUTPUT = {
|
const BLANK_OUTPUT = {
|
||||||
script: EMPTY_SCRIPT,
|
script: EMPTY_BUFFER,
|
||||||
valueBuffer: VALUE_UINT64_MAX,
|
valueBuffer: VALUE_UINT64_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,10 +62,13 @@ export interface Input {
|
||||||
|
|
||||||
export class Transaction {
|
export class Transaction {
|
||||||
static readonly DEFAULT_SEQUENCE = 0xffffffff;
|
static readonly DEFAULT_SEQUENCE = 0xffffffff;
|
||||||
|
static readonly SIGHASH_DEFAULT = 0x00;
|
||||||
static readonly SIGHASH_ALL = 0x01;
|
static readonly SIGHASH_ALL = 0x01;
|
||||||
static readonly SIGHASH_NONE = 0x02;
|
static readonly SIGHASH_NONE = 0x02;
|
||||||
static readonly SIGHASH_SINGLE = 0x03;
|
static readonly SIGHASH_SINGLE = 0x03;
|
||||||
static readonly SIGHASH_ANYONECANPAY = 0x80;
|
static readonly SIGHASH_ANYONECANPAY = 0x80;
|
||||||
|
static readonly SIGHASH_OUTPUT_MASK = 0x03;
|
||||||
|
static readonly SIGHASH_INPUT_MASK = 0x80;
|
||||||
static readonly ADVANCED_TRANSACTION_MARKER = 0x00;
|
static readonly ADVANCED_TRANSACTION_MARKER = 0x00;
|
||||||
static readonly ADVANCED_TRANSACTION_FLAG = 0x01;
|
static readonly ADVANCED_TRANSACTION_FLAG = 0x01;
|
||||||
|
|
||||||
|
@ -174,7 +177,7 @@ export class Transaction {
|
||||||
this.ins.push({
|
this.ins.push({
|
||||||
hash,
|
hash,
|
||||||
index,
|
index,
|
||||||
script: scriptSig || EMPTY_SCRIPT,
|
script: scriptSig || EMPTY_BUFFER,
|
||||||
sequence: sequence as number,
|
sequence: sequence as number,
|
||||||
witness: EMPTY_WITNESS,
|
witness: EMPTY_WITNESS,
|
||||||
}) - 1
|
}) - 1
|
||||||
|
@ -326,7 +329,7 @@ export class Transaction {
|
||||||
} else {
|
} else {
|
||||||
// "blank" others input scripts
|
// "blank" others input scripts
|
||||||
txTmp.ins.forEach(input => {
|
txTmp.ins.forEach(input => {
|
||||||
input.script = EMPTY_SCRIPT;
|
input.script = EMPTY_BUFFER;
|
||||||
});
|
});
|
||||||
txTmp.ins[inIndex].script = ourScript;
|
txTmp.ins[inIndex].script = ourScript;
|
||||||
}
|
}
|
||||||
|
@ -339,6 +342,158 @@ export class Transaction {
|
||||||
return bcrypto.hash256(buffer);
|
return bcrypto.hash256(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hashForWitnessV1(
|
||||||
|
inIndex: number,
|
||||||
|
prevOutScripts: Buffer[],
|
||||||
|
values: number[],
|
||||||
|
hashType: number,
|
||||||
|
leafHash?: Buffer,
|
||||||
|
annex?: Buffer,
|
||||||
|
): Buffer {
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message
|
||||||
|
typeforce(
|
||||||
|
types.tuple(
|
||||||
|
types.UInt32,
|
||||||
|
typeforce.arrayOf(types.Buffer),
|
||||||
|
typeforce.arrayOf(types.Satoshi),
|
||||||
|
types.UInt32,
|
||||||
|
),
|
||||||
|
arguments,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
values.length !== this.ins.length ||
|
||||||
|
prevOutScripts.length !== this.ins.length
|
||||||
|
) {
|
||||||
|
throw new Error('Must supply prevout script and value for all inputs');
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputType =
|
||||||
|
hashType === Transaction.SIGHASH_DEFAULT
|
||||||
|
? Transaction.SIGHASH_ALL
|
||||||
|
: hashType & Transaction.SIGHASH_OUTPUT_MASK;
|
||||||
|
|
||||||
|
const inputType = hashType & Transaction.SIGHASH_INPUT_MASK;
|
||||||
|
|
||||||
|
const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY;
|
||||||
|
const isNone = outputType === Transaction.SIGHASH_NONE;
|
||||||
|
const isSingle = outputType === Transaction.SIGHASH_SINGLE;
|
||||||
|
|
||||||
|
let hashPrevouts = EMPTY_BUFFER;
|
||||||
|
let hashAmounts = EMPTY_BUFFER;
|
||||||
|
let hashScriptPubKeys = EMPTY_BUFFER;
|
||||||
|
let hashSequences = EMPTY_BUFFER;
|
||||||
|
let hashOutputs = EMPTY_BUFFER;
|
||||||
|
|
||||||
|
if (!isAnyoneCanPay) {
|
||||||
|
let bufferWriter = BufferWriter.withCapacity(36 * this.ins.length);
|
||||||
|
this.ins.forEach(txIn => {
|
||||||
|
bufferWriter.writeSlice(txIn.hash);
|
||||||
|
bufferWriter.writeUInt32(txIn.index);
|
||||||
|
});
|
||||||
|
hashPrevouts = bcrypto.sha256(bufferWriter.end());
|
||||||
|
|
||||||
|
bufferWriter = BufferWriter.withCapacity(8 * this.ins.length);
|
||||||
|
values.forEach(value => bufferWriter.writeUInt64(value));
|
||||||
|
hashAmounts = bcrypto.sha256(bufferWriter.end());
|
||||||
|
|
||||||
|
bufferWriter = BufferWriter.withCapacity(
|
||||||
|
prevOutScripts.map(varSliceSize).reduce((a, b) => a + b),
|
||||||
|
);
|
||||||
|
prevOutScripts.forEach(prevOutScript =>
|
||||||
|
bufferWriter.writeVarSlice(prevOutScript),
|
||||||
|
);
|
||||||
|
hashScriptPubKeys = bcrypto.sha256(bufferWriter.end());
|
||||||
|
|
||||||
|
bufferWriter = BufferWriter.withCapacity(4 * this.ins.length);
|
||||||
|
this.ins.forEach(txIn => bufferWriter.writeUInt32(txIn.sequence));
|
||||||
|
hashSequences = bcrypto.sha256(bufferWriter.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(isNone || isSingle)) {
|
||||||
|
const txOutsSize = this.outs
|
||||||
|
.map(output => 8 + varSliceSize(output.script))
|
||||||
|
.reduce((a, b) => a + b);
|
||||||
|
const bufferWriter = BufferWriter.withCapacity(txOutsSize);
|
||||||
|
|
||||||
|
this.outs.forEach(out => {
|
||||||
|
bufferWriter.writeUInt64(out.value);
|
||||||
|
bufferWriter.writeVarSlice(out.script);
|
||||||
|
});
|
||||||
|
|
||||||
|
hashOutputs = bcrypto.sha256(bufferWriter.end());
|
||||||
|
} else if (isSingle && inIndex < this.outs.length) {
|
||||||
|
const output = this.outs[inIndex];
|
||||||
|
|
||||||
|
const bufferWriter = BufferWriter.withCapacity(
|
||||||
|
8 + varSliceSize(output.script),
|
||||||
|
);
|
||||||
|
bufferWriter.writeUInt64(output.value);
|
||||||
|
bufferWriter.writeVarSlice(output.script);
|
||||||
|
hashOutputs = bcrypto.sha256(bufferWriter.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0);
|
||||||
|
|
||||||
|
// Length calculation from:
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-14
|
||||||
|
// With extension from:
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#signature-validation
|
||||||
|
const sigMsgSize =
|
||||||
|
174 -
|
||||||
|
(isAnyoneCanPay ? 49 : 0) -
|
||||||
|
(isNone ? 32 : 0) +
|
||||||
|
(annex ? 32 : 0) +
|
||||||
|
(leafHash ? 37 : 0);
|
||||||
|
const sigMsgWriter = BufferWriter.withCapacity(sigMsgSize);
|
||||||
|
|
||||||
|
sigMsgWriter.writeUInt8(hashType);
|
||||||
|
// Transaction
|
||||||
|
sigMsgWriter.writeInt32(this.version);
|
||||||
|
sigMsgWriter.writeUInt32(this.locktime);
|
||||||
|
sigMsgWriter.writeSlice(hashPrevouts);
|
||||||
|
sigMsgWriter.writeSlice(hashAmounts);
|
||||||
|
sigMsgWriter.writeSlice(hashScriptPubKeys);
|
||||||
|
sigMsgWriter.writeSlice(hashSequences);
|
||||||
|
if (!(isNone || isSingle)) {
|
||||||
|
sigMsgWriter.writeSlice(hashOutputs);
|
||||||
|
}
|
||||||
|
// Input
|
||||||
|
sigMsgWriter.writeUInt8(spendType);
|
||||||
|
if (isAnyoneCanPay) {
|
||||||
|
const input = this.ins[inIndex];
|
||||||
|
sigMsgWriter.writeSlice(input.hash);
|
||||||
|
sigMsgWriter.writeUInt32(input.index);
|
||||||
|
sigMsgWriter.writeUInt64(values[inIndex]);
|
||||||
|
sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]);
|
||||||
|
sigMsgWriter.writeUInt32(input.sequence);
|
||||||
|
} else {
|
||||||
|
sigMsgWriter.writeUInt32(inIndex);
|
||||||
|
}
|
||||||
|
if (annex) {
|
||||||
|
const bufferWriter = BufferWriter.withCapacity(varSliceSize(annex));
|
||||||
|
bufferWriter.writeVarSlice(annex);
|
||||||
|
sigMsgWriter.writeSlice(bcrypto.sha256(bufferWriter.end()));
|
||||||
|
}
|
||||||
|
// Output
|
||||||
|
if (isSingle) {
|
||||||
|
sigMsgWriter.writeSlice(hashOutputs);
|
||||||
|
}
|
||||||
|
// BIP342 extension
|
||||||
|
if (leafHash) {
|
||||||
|
sigMsgWriter.writeSlice(leafHash);
|
||||||
|
sigMsgWriter.writeUInt8(0);
|
||||||
|
sigMsgWriter.writeUInt32(0xffffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra zero byte because:
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19
|
||||||
|
return bcrypto.taggedHash(
|
||||||
|
'TapSighash',
|
||||||
|
Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
hashForWitnessV0(
|
hashForWitnessV0(
|
||||||
inIndex: number,
|
inIndex: number,
|
||||||
prevOutScript: Buffer,
|
prevOutScript: Buffer,
|
||||||
|
|
Loading…
Reference in a new issue