Skip to content

Commit 45187a3

Browse files
junderwreardencodeOttoAllmendingerTyler Levinesangaman
committed
Add taggedHash, sigHash v1
Co-authored-by: Brandon Black <[email protected]> Co-authored-by: Otto Allmendinger <[email protected]> Co-authored-by: Tyler Levine <[email protected]> Co-authored-by: Daniel McNally <[email protected]>
1 parent f484edd commit 45187a3

18 files changed

+559
-49
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@types/bs58check": "^2.1.0",
6363
"@types/create-hash": "^1.2.2",
6464
"@types/mocha": "^5.2.7",
65-
"@types/node": "^16.11.1",
65+
"@types/node": "^16.11.7",
6666
"@types/proxyquire": "^1.3.28",
6767
"@types/randombytes": "^2.0.0",
6868
"@types/wif": "^2.0.2",

src/bufferutils.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export declare function cloneBuffer(buffer: Buffer): Buffer;
1111
export declare class BufferWriter {
1212
buffer: Buffer;
1313
offset: number;
14+
static withCapacity(size: number): BufferWriter;
1415
constructor(buffer: Buffer, offset?: number);
1516
writeUInt8(i: number): void;
1617
writeInt32(i: number): void;
@@ -20,6 +21,7 @@ export declare class BufferWriter {
2021
writeSlice(slice: Buffer): void;
2122
writeVarSlice(slice: Buffer): void;
2223
writeVector(vector: Buffer[]): void;
24+
end(): Buffer;
2325
}
2426
/**
2527
* Helper class for reading of bitcoin data types from a buffer.

src/bufferutils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ class BufferWriter {
5858
this.offset = offset;
5959
typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]);
6060
}
61+
static withCapacity(size) {
62+
return new BufferWriter(Buffer.alloc(size));
63+
}
6164
writeUInt8(i) {
6265
this.offset = this.buffer.writeUInt8(i, this.offset);
6366
}
@@ -88,6 +91,12 @@ class BufferWriter {
8891
this.writeVarInt(vector.length);
8992
vector.forEach(buf => this.writeVarSlice(buf));
9093
}
94+
end() {
95+
if (this.buffer.length === this.offset) {
96+
return this.buffer;
97+
}
98+
throw new Error(`buffer size ${this.buffer.length}, offset ${this.offset}`);
99+
}
91100
}
92101
exports.BufferWriter = BufferWriter;
93102
/**

src/crypto.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ export declare function sha1(buffer: Buffer): Buffer;
44
export declare function sha256(buffer: Buffer): Buffer;
55
export declare function hash160(buffer: Buffer): Buffer;
66
export declare function hash256(buffer: Buffer): Buffer;
7+
declare const TAGS: readonly ["BIP0340/challenge", "BIP0340/aux", "BIP0340/nonce", "TapLeaf", "TapBranch", "TapSighash", "TapTweak", "KeyAgg list", "KeyAgg coefficient"];
8+
export declare type TaggedHashPrefix = typeof TAGS[number];
9+
export declare function taggedHash(prefix: TaggedHashPrefix, data: Buffer): Buffer;
10+
export {};

src/crypto.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
3-
exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0;
3+
exports.taggedHash = exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0;
44
const createHash = require('create-hash');
55
function ripemd160(buffer) {
66
try {
@@ -34,3 +34,25 @@ function hash256(buffer) {
3434
return sha256(sha256(buffer));
3535
}
3636
exports.hash256 = hash256;
37+
const TAGS = [
38+
'BIP0340/challenge',
39+
'BIP0340/aux',
40+
'BIP0340/nonce',
41+
'TapLeaf',
42+
'TapBranch',
43+
'TapSighash',
44+
'TapTweak',
45+
'KeyAgg list',
46+
'KeyAgg coefficient',
47+
];
48+
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
49+
const TAGGED_HASH_PREFIXES = Object.fromEntries(
50+
TAGS.map(tag => {
51+
const tagHash = sha256(Buffer.from(tag));
52+
return [tag, Buffer.concat([tagHash, tagHash])];
53+
}),
54+
);
55+
function taggedHash(prefix, data) {
56+
return sha256(Buffer.concat([TAGGED_HASH_PREFIXES[prefix], data]));
57+
}
58+
exports.taggedHash = taggedHash;

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as payments from './payments';
55
import * as script from './script';
66
export { address, crypto, networks, payments, script };
77
export { Block } from './block';
8+
export { TaggedHashPrefix } from './crypto';
89
export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt';
910
export { OPS as opcodes } from './ops';
1011
export { Transaction } from './transaction';

src/transaction.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ export interface Input {
1212
}
1313
export declare class Transaction {
1414
static readonly DEFAULT_SEQUENCE = 4294967295;
15+
static readonly SIGHASH_DEFAULT = 0;
1516
static readonly SIGHASH_ALL = 1;
1617
static readonly SIGHASH_NONE = 2;
1718
static readonly SIGHASH_SINGLE = 3;
1819
static readonly SIGHASH_ANYONECANPAY = 128;
20+
static readonly SIGHASH_OUTPUT_MASK = 3;
21+
static readonly SIGHASH_INPUT_MASK = 128;
1922
static readonly ADVANCED_TRANSACTION_MARKER = 0;
2023
static readonly ADVANCED_TRANSACTION_FLAG = 1;
2124
static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction;
@@ -42,6 +45,7 @@ export declare class Transaction {
4245
* This hash can then be used to sign the provided transaction input.
4346
*/
4447
hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer;
48+
hashForWitnessV1(inIndex: number, prevOutScripts: Buffer[], values: number[], hashType: number, leafHash?: Buffer, annex?: Buffer): Buffer;
4549
hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer;
4650
getHash(forWitness?: boolean): Buffer;
4751
getId(): string;

src/transaction.js

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function vectorSize(someVector) {
2020
}, 0)
2121
);
2222
}
23-
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
23+
const EMPTY_BUFFER = Buffer.allocUnsafe(0);
2424
const EMPTY_WITNESS = [];
2525
const ZERO = Buffer.from(
2626
'0000000000000000000000000000000000000000000000000000000000000000',
@@ -32,7 +32,7 @@ const ONE = Buffer.from(
3232
);
3333
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
3434
const BLANK_OUTPUT = {
35-
script: EMPTY_SCRIPT,
35+
script: EMPTY_BUFFER,
3636
valueBuffer: VALUE_UINT64_MAX,
3737
};
3838
function isOutput(out) {
@@ -124,7 +124,7 @@ class Transaction {
124124
this.ins.push({
125125
hash,
126126
index,
127-
script: scriptSig || EMPTY_SCRIPT,
127+
script: scriptSig || EMPTY_BUFFER,
128128
sequence: sequence,
129129
witness: EMPTY_WITNESS,
130130
}) - 1
@@ -247,7 +247,7 @@ class Transaction {
247247
} else {
248248
// "blank" others input scripts
249249
txTmp.ins.forEach(input => {
250-
input.script = EMPTY_SCRIPT;
250+
input.script = EMPTY_BUFFER;
251251
});
252252
txTmp.ins[inIndex].script = ourScript;
253253
}
@@ -257,6 +257,141 @@ class Transaction {
257257
txTmp.__toBuffer(buffer, 0, false);
258258
return bcrypto.hash256(buffer);
259259
}
260+
hashForWitnessV1(inIndex, prevOutScripts, values, hashType, leafHash, annex) {
261+
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message
262+
typeforce(
263+
types.tuple(
264+
types.UInt32,
265+
typeforce.arrayOf(types.Buffer),
266+
typeforce.arrayOf(types.Satoshi),
267+
types.UInt32,
268+
),
269+
arguments,
270+
);
271+
if (
272+
values.length !== this.ins.length ||
273+
prevOutScripts.length !== this.ins.length
274+
) {
275+
throw new Error('Must supply prevout script and value for all inputs');
276+
}
277+
const outputType =
278+
hashType === Transaction.SIGHASH_DEFAULT
279+
? Transaction.SIGHASH_ALL
280+
: hashType & Transaction.SIGHASH_OUTPUT_MASK;
281+
const inputType = hashType & Transaction.SIGHASH_INPUT_MASK;
282+
const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY;
283+
const isNone = outputType === Transaction.SIGHASH_NONE;
284+
const isSingle = outputType === Transaction.SIGHASH_SINGLE;
285+
let hashPrevouts = EMPTY_BUFFER;
286+
let hashAmounts = EMPTY_BUFFER;
287+
let hashScriptPubKeys = EMPTY_BUFFER;
288+
let hashSequences = EMPTY_BUFFER;
289+
let hashOutputs = EMPTY_BUFFER;
290+
if (!isAnyoneCanPay) {
291+
let bufferWriter = bufferutils_1.BufferWriter.withCapacity(
292+
36 * this.ins.length,
293+
);
294+
this.ins.forEach(txIn => {
295+
bufferWriter.writeSlice(txIn.hash);
296+
bufferWriter.writeUInt32(txIn.index);
297+
});
298+
hashPrevouts = bcrypto.sha256(bufferWriter.end());
299+
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
300+
8 * this.ins.length,
301+
);
302+
values.forEach(value => bufferWriter.writeUInt64(value));
303+
hashAmounts = bcrypto.sha256(bufferWriter.end());
304+
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
305+
prevOutScripts.map(varSliceSize).reduce((a, b) => a + b),
306+
);
307+
prevOutScripts.forEach(prevOutScript =>
308+
bufferWriter.writeVarSlice(prevOutScript),
309+
);
310+
hashScriptPubKeys = bcrypto.sha256(bufferWriter.end());
311+
bufferWriter = bufferutils_1.BufferWriter.withCapacity(
312+
4 * this.ins.length,
313+
);
314+
this.ins.forEach(txIn => bufferWriter.writeUInt32(txIn.sequence));
315+
hashSequences = bcrypto.sha256(bufferWriter.end());
316+
}
317+
if (!(isNone || isSingle)) {
318+
const txOutsSize = this.outs
319+
.map(output => 8 + varSliceSize(output.script))
320+
.reduce((a, b) => a + b);
321+
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(txOutsSize);
322+
this.outs.forEach(out => {
323+
bufferWriter.writeUInt64(out.value);
324+
bufferWriter.writeVarSlice(out.script);
325+
});
326+
hashOutputs = bcrypto.sha256(bufferWriter.end());
327+
} else if (isSingle && inIndex < this.outs.length) {
328+
const output = this.outs[inIndex];
329+
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
330+
8 + varSliceSize(output.script),
331+
);
332+
bufferWriter.writeUInt64(output.value);
333+
bufferWriter.writeVarSlice(output.script);
334+
hashOutputs = bcrypto.sha256(bufferWriter.end());
335+
}
336+
const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0);
337+
// Length calculation from:
338+
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-14
339+
// With extension from:
340+
// https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#signature-validation
341+
const sigMsgSize =
342+
174 -
343+
(isAnyoneCanPay ? 49 : 0) -
344+
(isNone ? 32 : 0) +
345+
(annex ? 32 : 0) +
346+
(leafHash ? 37 : 0);
347+
const sigMsgWriter = bufferutils_1.BufferWriter.withCapacity(sigMsgSize);
348+
sigMsgWriter.writeUInt8(hashType);
349+
// Transaction
350+
sigMsgWriter.writeInt32(this.version);
351+
sigMsgWriter.writeUInt32(this.locktime);
352+
sigMsgWriter.writeSlice(hashPrevouts);
353+
sigMsgWriter.writeSlice(hashAmounts);
354+
sigMsgWriter.writeSlice(hashScriptPubKeys);
355+
sigMsgWriter.writeSlice(hashSequences);
356+
if (!(isNone || isSingle)) {
357+
sigMsgWriter.writeSlice(hashOutputs);
358+
}
359+
// Input
360+
sigMsgWriter.writeUInt8(spendType);
361+
if (isAnyoneCanPay) {
362+
const input = this.ins[inIndex];
363+
sigMsgWriter.writeSlice(input.hash);
364+
sigMsgWriter.writeUInt32(input.index);
365+
sigMsgWriter.writeUInt64(values[inIndex]);
366+
sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]);
367+
sigMsgWriter.writeUInt32(input.sequence);
368+
} else {
369+
sigMsgWriter.writeUInt32(inIndex);
370+
}
371+
if (annex) {
372+
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
373+
varSliceSize(annex),
374+
);
375+
bufferWriter.writeVarSlice(annex);
376+
sigMsgWriter.writeSlice(bcrypto.sha256(bufferWriter.end()));
377+
}
378+
// Output
379+
if (isSingle) {
380+
sigMsgWriter.writeSlice(hashOutputs);
381+
}
382+
// BIP342 extension
383+
if (leafHash) {
384+
sigMsgWriter.writeSlice(leafHash);
385+
sigMsgWriter.writeUInt8(0);
386+
sigMsgWriter.writeUInt32(0xffffffff);
387+
}
388+
// Extra zero byte because:
389+
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19
390+
return bcrypto.taggedHash(
391+
'TapSighash',
392+
Buffer.concat([Buffer.of(0x00), sigMsgWriter.end()]),
393+
);
394+
}
260395
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
261396
typeforce(
262397
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
@@ -396,9 +531,12 @@ class Transaction {
396531
}
397532
exports.Transaction = Transaction;
398533
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
534+
Transaction.SIGHASH_DEFAULT = 0x00;
399535
Transaction.SIGHASH_ALL = 0x01;
400536
Transaction.SIGHASH_NONE = 0x02;
401537
Transaction.SIGHASH_SINGLE = 0x03;
402538
Transaction.SIGHASH_ANYONECANPAY = 0x80;
539+
Transaction.SIGHASH_OUTPUT_MASK = 0x03;
540+
Transaction.SIGHASH_INPUT_MASK = 0x80;
403541
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
404542
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;

test/bufferutils.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ describe('bufferutils', () => {
6666
);
6767
}
6868

69+
it('withCapacity', () => {
70+
const expectedBuffer = Buffer.from('04030201', 'hex');
71+
const withCapacity = BufferWriter.withCapacity(4);
72+
withCapacity.writeInt32(0x01020304);
73+
testBuffer(withCapacity, expectedBuffer);
74+
});
75+
6976
it('writeUint8', () => {
7077
const values = [0, 1, 254, 255];
7178
const expectedBuffer = Buffer.from([0, 1, 0xfe, 0xff]);
@@ -277,6 +284,16 @@ describe('bufferutils', () => {
277284
});
278285
testBuffer(bufferWriter, expectedBuffer);
279286
});
287+
288+
it('end', () => {
289+
const expected = Buffer.from('0403020108070605', 'hex');
290+
const bufferWriter = BufferWriter.withCapacity(8);
291+
bufferWriter.writeUInt32(0x01020304);
292+
bufferWriter.writeUInt32(0x05060708);
293+
const result = bufferWriter.end();
294+
testBuffer(bufferWriter, result);
295+
testBuffer(bufferWriter, expected);
296+
});
280297
});
281298

282299
describe('BufferReader', () => {

0 commit comments

Comments
 (0)