Skip to content
Merged

Taproot #1745

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# 6.0.0
__removed__
- bip32: Removed the re-export. Please add as dependency to your app instead.
- ECPair: Please use bip32 moving forward. ecpair package was created for those who need it.
- TransactionBuilder: Any internal files used only in TB (classify, templates, etc.) were also removed.

__added__
- taproot segwit v1 address support (bech32m) via address module (#1676)
- hashForWitnessV1 method on Transaction class (#1745)

__fixed__
- Transaction version read/write differed. (#1717)

# 5.2.0
__changed__
- Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563)
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# BitcoinJS (bitcoinjs-lib)
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib)
[![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib)

[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
[![Github CI](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml/badge.svg)](https://github.com/bitcoinjs/bitcoinjs-lib/actions/workflows/main_ci.yml) [![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)

A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify.

Expand Down Expand Up @@ -94,6 +91,8 @@ The below examples are implemented as integration tests, they should be very eas
Otherwise, pull requests are appreciated.
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).

- [Taproot Key Spend](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.md)

- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bitcoinjs-lib",
"version": "5.2.0",
"version": "6.0.0",
"description": "Client-side Bitcoin JavaScript library",
"main": "./src/index.js",
"types": "./src/index.d.ts",
Expand Down Expand Up @@ -62,7 +62,7 @@
"@types/bs58check": "^2.1.0",
"@types/create-hash": "^1.2.2",
"@types/mocha": "^5.2.7",
"@types/node": "^16.11.1",
"@types/node": "^16.11.7",
"@types/proxyquire": "^1.3.28",
"@types/randombytes": "^2.0.0",
"@types/wif": "^2.0.2",
Expand Down
2 changes: 2 additions & 0 deletions src/bufferutils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export declare function cloneBuffer(buffer: Buffer): Buffer;
export declare class BufferWriter {
buffer: Buffer;
offset: number;
static withCapacity(size: number): BufferWriter;
constructor(buffer: Buffer, offset?: number);
writeUInt8(i: number): void;
writeInt32(i: number): void;
Expand All @@ -20,6 +21,7 @@ export declare class BufferWriter {
writeSlice(slice: Buffer): void;
writeVarSlice(slice: Buffer): void;
writeVector(vector: Buffer[]): void;
end(): Buffer;
}
/**
* Helper class for reading of bitcoin data types from a buffer.
Expand Down
9 changes: 9 additions & 0 deletions src/bufferutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class BufferWriter {
this.offset = offset;
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
}
static withCapacity(size) {
return new BufferWriter(Buffer.alloc(size));
}
writeUInt8(i) {
this.offset = this.buffer.writeUInt8(i, this.offset);
}
Expand Down Expand Up @@ -88,6 +91,12 @@ class BufferWriter {
this.writeVarInt(vector.length);
vector.forEach(buf => this.writeVarSlice(buf));
}
end() {
if (this.buffer.length === this.offset) {
return this.buffer;
}
throw new Error(`buffer size ${this.buffer.length}, offset ${this.offset}`);
}
}
exports.BufferWriter = BufferWriter;
/**
Expand Down
4 changes: 4 additions & 0 deletions src/crypto.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ export declare function sha1(buffer: Buffer): Buffer;
export declare function sha256(buffer: Buffer): Buffer;
export declare function hash160(buffer: Buffer): Buffer;
export declare function hash256(buffer: Buffer): Buffer;
declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"];
export declare type TaggedHashPrefix = typeof TAGS[number];
export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer;
export {};
24 changes: 23 additions & 1 deletion src/crypto.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0;
exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0;
const createHash = require('create-hash');
function ripemd160(buffer) {
try {
Expand Down Expand Up @@ -34,3 +34,25 @@ function hash256(buffer) {
return sha256(sha256(buffer));
}
exports.hash256 = hash256;
const TAGS = [
'BIP0340/challenge',
'BIP0340/aux',
'BIP0340/nonce',
'TapLeaf',
'TapBranch',
'TapSighash',
'TapTweak',
'KeyAgg list',
'KeyAgg coefficient',
];
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
const TAGGED_HASH_PREFIXES = Object.fromEntries(
TAGS.map(tag => {
const tagHash = sha256(Buffer.from(tag));
return [tag, Buffer.concat([tagHash, tagHash])];
}),
);
function taggedHash(prefix, data) {
return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data]));
}
exports.taggedHash = taggedHash;
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as payments from './payments';
import * as script from './script';
export { address, crypto, networks, payments, script };
export { Block } from './block';
export { TaggedHashPrefix } from './crypto';
export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt';
export { OPS as opcodes } from './ops';
export { Transaction } from './transaction';
Expand Down
4 changes: 4 additions & 0 deletions src/transaction.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ export interface Input {
}
export declare class Transaction {
static readonly DEFAULT_SEQUENCE = 4294967295;
static readonly SIGHASH_DEFAULT = 0;
static readonly SIGHASH_ALL = 1;
static readonly SIGHASH_NONE = 2;
static readonly SIGHASH_SINGLE = 3;
static readonly SIGHASH_ANYONECANPAY = 128;
static readonly SIGHASH_OUTPUT_MASK = 3;
static readonly SIGHASH_INPUT_MASK = 128;
static readonly ADVANCED_TRANSACTION_MARKER = 0;
static readonly ADVANCED_TRANSACTION_FLAG = 1;
static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction;
Expand All @@ -42,6 +45,7 @@ export declare class Transaction {
* This hash can then be used to sign the provided transaction input.
*/
hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer;
hashForWitnessV1(inIndex: number, prevOutScripts: Buffer[], values: number[], hashType: number, leafHash?: Buffer, annex?: Buffer): Buffer;
hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer;
getHash(forWitness?: boolean): Buffer;
getId(): string;
Expand Down
146 changes: 142 additions & 4 deletions src/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function vectorSize(someVector) {
}, 0)
);
}
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
const EMPTY_BUFFER = Buffer.allocUnsafe(0);
const EMPTY_WITNESS = [];
const ZERO = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
Expand All @@ -32,7 +32,7 @@ const ONE = Buffer.from(
);
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
const BLANK_OUTPUT = {
script: EMPTY_SCRIPT,
script: EMPTY_BUFFER,
valueBuffer: VALUE_UINT64_MAX,
};
function isOutput(out) {
Expand Down Expand Up @@ -124,7 +124,7 @@ class Transaction {
this.ins.push({
hash,
index,
script: scriptSig || EMPTY_SCRIPT,
script: scriptSig || EMPTY_BUFFER,
sequence: sequence,
witness: EMPTY_WITNESS,
}) - 1
Expand Down Expand Up @@ -247,7 +247,7 @@ class Transaction {
} else {
// "blank" others input scripts
txTmp.ins.forEach(input => {
input.script = EMPTY_SCRIPT;
input.script = EMPTY_BUFFER;
});
txTmp.ins[inIndex].script = ourScript;
}
Expand All @@ -257,6 +257,141 @@ class Transaction {
txTmp.__toBuffer(buffer, 0, false);
return bcrypto.hash256(buffer);
}
hashForWitnessV1(inIndex, prevOutScripts, values, hashType, leafHash, annex) {
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message
typeforce(
types.tuple(
types.UInt32,
typeforce.arrayOf(types.Buffer),
typeforce.arrayOf(types.Satoshi),
types.UInt32,
),
arguments,
);
if (
values.length !== this.ins.length ||
prevOutScripts.length !== this.ins.length
) {
throw new Error('Must supply prevout script and value for all inputs');
}
const outputType =
hashType === Transaction.SIGHASH_DEFAULT
? Transaction.SIGHASH_ALL
: hashType & Transaction.SIGHASH_OUTPUT_MASK;
const inputType = hashType & Transaction.SIGHASH_INPUT_MASK;
const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY;
const isNone = outputType === Transaction.SIGHASH_NONE;
const isSingle = outputType === Transaction.SIGHASH_SINGLE;
let hashPrevouts = EMPTY_BUFFER;
let hashAmounts = EMPTY_BUFFER;
let hashScriptPubKeys = EMPTY_BUFFER;
let hashSequences = EMPTY_BUFFER;
let hashOutputs = EMPTY_BUFFER;
if (!isAnyoneCanPay) {
let bufferWriter = bufferutils_1.BufferWriter.withCapacity(
36 * this.ins.length,
);
this.ins.forEach(txIn => {
bufferWriter.writeSlice(txIn.hash);
bufferWriter.writeUInt32(txIn.index);
});
hashPrevouts = bcrypto.sha256(bufferWriter.end());
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
8 * this.ins.length,
);
values.forEach(value => bufferWriter.writeUInt64(value));
hashAmounts = bcrypto.sha256(bufferWriter.end());
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
prevOutScripts.map(varSliceSize).reduce((a, b) => a + b),
);
prevOutScripts.forEach(prevOutScript =>
bufferWriter.writeVarSlice(prevOutScript),
);
hashScriptPubKeys = bcrypto.sha256(bufferWriter.end());
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
4 * this.ins.length,
);
this.ins.forEach(txIn => bufferWriter.writeUInt32(txIn.sequence));
hashSequences = bcrypto.sha256(bufferWriter.end());
}
if (!(isNone || isSingle)) {
const txOutsSize = this.outs
.map(output => 8 + varSliceSize(output.script))
.reduce((a, b) => a + b);
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(txOutsSize);
this.outs.forEach(out => {
bufferWriter.writeUInt64(out.value);
bufferWriter.writeVarSlice(out.script);
});
hashOutputs = bcrypto.sha256(bufferWriter.end());
} else if (isSingle && inIndex < this.outs.length) {
const output = this.outs[inIndex];
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
8 + varSliceSize(output.script),
);
bufferWriter.writeUInt64(output.value);
bufferWriter.writeVarSlice(output.script);
hashOutputs = bcrypto.sha256(bufferWriter.end());
}
const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0);
// Length calculation from:
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-14
// With extension from:
// https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#signature-validation
const sigMsgSize =
174 -
(isAnyoneCanPay ? 49 : 0) -
(isNone ? 32 : 0) +
(annex ? 32 : 0) +
(leafHash ? 37 : 0);
const sigMsgWriter = bufferutils_1.BufferWriter.withCapacity(sigMsgSize);
sigMsgWriter.writeUInt8(hashType);
// Transaction
sigMsgWriter.writeInt32(this.version);
sigMsgWriter.writeUInt32(this.locktime);
sigMsgWriter.writeSlice(hashPrevouts);
sigMsgWriter.writeSlice(hashAmounts);
sigMsgWriter.writeSlice(hashScriptPubKeys);
sigMsgWriter.writeSlice(hashSequences);
if (!(isNone || isSingle)) {
sigMsgWriter.writeSlice(hashOutputs);
}
// Input
sigMsgWriter.writeUInt8(spendType);
if (isAnyoneCanPay) {
const input = this.ins[inIndex];
sigMsgWriter.writeSlice(input.hash);
sigMsgWriter.writeUInt32(input.index);
sigMsgWriter.writeUInt64(values[inIndex]);
sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]);
sigMsgWriter.writeUInt32(input.sequence);
} else {
sigMsgWriter.writeUInt32(inIndex);
}
if (annex) {
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
varSliceSize(annex),
);
bufferWriter.writeVarSlice(annex);
sigMsgWriter.writeSlice(bcrypto.sha256(bufferWriter.end()));
}
// Output
if (isSingle) {
sigMsgWriter.writeSlice(hashOutputs);
}
// BIP342 extension
if (leafHash) {
sigMsgWriter.writeSlice(leafHash);
sigMsgWriter.writeUInt8(0);
sigMsgWriter.writeUInt32(0xffffffff);
}
// Extra zero byte because:
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19
return bcrypto.taggedHash(
'TapSighash',
Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]),
);
}
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
typeforce(
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
Expand Down Expand Up @@ -396,9 +531,12 @@ class Transaction {
}
exports.Transaction = Transaction;
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
Transaction.SIGHASH_DEFAULT = 0x00;
Transaction.SIGHASH_ALL = 0x01;
Transaction.SIGHASH_NONE = 0x02;
Transaction.SIGHASH_SINGLE = 0x03;
Transaction.SIGHASH_ANYONECANPAY = 0x80;
Transaction.SIGHASH_OUTPUT_MASK = 0x03;
Transaction.SIGHASH_INPUT_MASK = 0x80;
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
Loading