Add taggedHash, sigHash v1
Co-authored-by: Brandon Black <brandonblack@bitgo.com> Co-authored-by: Otto Allmendinger <otto@bitgo.com> Co-authored-by: Tyler Levine <tyler@bitgo.com> Co-authored-by: Daniel McNally <danielmcnally@bitgo.com>
This commit is contained in:
parent
f484edde01
commit
45187a32d0
18 changed files with 559 additions and 49 deletions
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 {
|
||||
buffer: Buffer;
|
||||
offset: number;
|
||||
static withCapacity(size: number): BufferWriter;
|
||||
constructor(buffer: Buffer, offset?: number);
|
||||
writeUInt8(i: number): void;
|
||||
writeInt32(i: number): void;
|
||||
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
/**
|
||||
|
|
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 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 {};
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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
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';
|
||||
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';
|
||||
|
|
4
src/transaction.d.ts
vendored
4
src/transaction.d.ts
vendored
|
@ -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;
|
||||
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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…
Add table
Add a link
Reference in a new issue