Skip to content

Commit 95acf9a

Browse files
authored
Merge pull request #38 from BitGo/BG-41154-sync-bitcoinjs
chore: abstract away number to TNumber
2 parents 2fb61c8 + 07dad16 commit 95acf9a

17 files changed

+1233
-132
lines changed

src/block.d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
/// <reference types="node" />
22
import { Transaction } from './transaction';
3-
export declare class Block {
4-
static fromBuffer(buffer: Buffer): Block;
5-
static fromHex(hex: string): Block;
3+
export declare class Block<TNumber extends number | bigint = number> {
4+
static fromBuffer<TNumber extends number | bigint = number>(buffer: Buffer, amountType?: 'number' | 'bigint'): Block<TNumber>;
5+
static fromHex<TNumber extends number | bigint = number>(hex: string, amountType?: 'number' | 'bigint'): Block<TNumber>;
66
static calculateTarget(bits: number): Buffer;
7-
static calculateMerkleRoot(transactions: Transaction[], forWitness?: boolean): Buffer;
7+
static calculateMerkleRoot<TNumber extends number | bigint = number>(transactions: Array<Transaction<TNumber>>, forWitness?: boolean): Buffer;
88
version: number;
99
prevHash?: Buffer;
1010
merkleRoot?: Buffer;
1111
timestamp: number;
1212
witnessCommit?: Buffer;
1313
bits: number;
1414
nonce: number;
15-
transactions?: Transaction[];
15+
transactions?: Array<Transaction<TNumber>>;
1616
getWitnessCommit(): Buffer | null;
1717
hasWitnessCommit(): boolean;
1818
hasWitness(): boolean;

src/block.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Block {
2424
this.nonce = 0;
2525
this.transactions = undefined;
2626
}
27-
static fromBuffer(buffer) {
27+
static fromBuffer(buffer, amountType = 'number') {
2828
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)');
2929
const bufferReader = new bufferutils_1.BufferReader(buffer);
3030
const block = new Block();
@@ -39,6 +39,7 @@ class Block {
3939
const tx = transaction_1.Transaction.fromBuffer(
4040
bufferReader.buffer.slice(bufferReader.offset),
4141
true,
42+
amountType,
4243
);
4344
bufferReader.offset += tx.byteLength();
4445
return tx;
@@ -54,8 +55,8 @@ class Block {
5455
if (witnessCommit) block.witnessCommit = witnessCommit;
5556
return block;
5657
}
57-
static fromHex(hex) {
58-
return Block.fromBuffer(Buffer.from(hex, 'hex'));
58+
static fromHex(hex, amountType = 'number') {
59+
return Block.fromBuffer(Buffer.from(hex, 'hex'), amountType);
5960
}
6061
static calculateTarget(bits) {
6162
const exponent = ((bits & 0xff000000) >> 24) - 3;

src/bufferutils.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/// <reference types="node" />
22
import * as varuint from 'varuint-bitcoin';
33
export { varuint };
4+
export declare function readUInt64BigIntLE(buffer: Buffer, offset: number): bigint;
45
export declare function readUInt64LE(buffer: Buffer, offset: number): number;
5-
export declare function writeUInt64LE(buffer: Buffer, value: number, offset: number): number;
6+
export declare function writeUInt64LE(buffer: Buffer, value: number | bigint, offset: number): number;
67
export declare function reverseBuffer(buffer: Buffer): Buffer;
78
export declare function cloneBuffer(buffer: Buffer): Buffer;
89
/**
@@ -16,7 +17,7 @@ export declare class BufferWriter {
1617
writeUInt8(i: number): void;
1718
writeInt32(i: number): void;
1819
writeUInt32(i: number): void;
19-
writeUInt64(i: number): void;
20+
writeUInt64(i: number | bigint): void;
2021
writeVarInt(i: number): void;
2122
writeSlice(slice: Buffer): void;
2223
writeVarSlice(slice: Buffer): void;
@@ -34,6 +35,7 @@ export declare class BufferReader {
3435
readInt32(): number;
3536
readUInt32(): number;
3637
readUInt64(): number;
38+
readUInt64BigInt(): bigint;
3739
readVarInt(): number;
3840
readSlice(n: number): Buffer;
3941
readVarSlice(): Buffer;

src/bufferutils.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
3-
exports.BufferReader = exports.BufferWriter = exports.cloneBuffer = exports.reverseBuffer = exports.writeUInt64LE = exports.readUInt64LE = exports.varuint = void 0;
3+
exports.BufferReader = exports.BufferWriter = exports.cloneBuffer = exports.reverseBuffer = exports.writeUInt64LE = exports.readUInt64LE = exports.readUInt64BigIntLE = exports.varuint = void 0;
44
const types = require('./types');
55
const { typeforce } = types;
66
const varuint = require('varuint-bitcoin');
77
exports.varuint = varuint;
88
// https://github.com/feross/buffer/blob/master/index.js#L1127
99
function verifuint(value, max) {
10-
if (typeof value !== 'number')
11-
throw new Error('cannot write a non-number as a number');
10+
if (typeof value !== 'number' && typeof value !== 'bigint')
11+
throw new Error('cannot write a non-number as a number or bigint');
1212
if (value < 0)
1313
throw new Error('specified a negative value for writing an unsigned value');
1414
if (value > max) throw new Error('RangeError: value out of range');
15-
if (Math.floor(value) !== value)
15+
if (typeof value === 'number' && Math.floor(value) !== value)
16+
// bigint is enforced int
1617
throw new Error('value has a fractional component');
1718
}
19+
function readUInt64BigIntLE(buffer, offset) {
20+
return buffer.readBigUInt64LE(offset);
21+
}
22+
exports.readUInt64BigIntLE = readUInt64BigIntLE;
1823
function readUInt64LE(buffer, offset) {
19-
const a = buffer.readUInt32LE(offset);
20-
let b = buffer.readUInt32LE(offset + 4);
21-
b *= 0x100000000;
22-
verifuint(b + a, 0x001fffffffffffff);
23-
return b + a;
24+
const result = readUInt64BigIntLE(buffer, offset);
25+
verifuint(result, 0x001fffffffffffff);
26+
return Number(result);
2427
}
2528
exports.readUInt64LE = readUInt64LE;
2629
function writeUInt64LE(buffer, value, offset) {
27-
verifuint(value, 0x001fffffffffffff);
28-
buffer.writeInt32LE(value & -1, offset);
29-
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
30+
if (typeof value === 'number') {
31+
verifuint(value, 0x001fffffffffffff);
32+
} else if (typeof value === 'bigint') {
33+
verifuint(value, BigInt('0xffffffffffffffff'));
34+
} else {
35+
throw new Error('value must be a number or bigint');
36+
}
37+
buffer.writeBigUInt64LE(BigInt(value), offset);
3038
return offset + 8;
3139
}
3240
exports.writeUInt64LE = writeUInt64LE;
@@ -128,6 +136,11 @@ class BufferReader {
128136
this.offset += 8;
129137
return result;
130138
}
139+
readUInt64BigInt() {
140+
const result = readUInt64BigIntLE(this.buffer, this.offset);
141+
this.offset += 8;
142+
return result;
143+
}
131144
readVarInt() {
132145
const vi = varuint.decode(this.buffer, this.offset);
133146
this.offset += varuint.decode.bytes;

src/transaction.d.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="node" />
2-
export interface Output {
2+
export interface Output<TNumber extends number | bigint = number> {
33
script: Buffer;
4-
value: number;
4+
value: TNumber;
55
}
66
export interface Input {
77
hash: Buffer;
@@ -10,7 +10,7 @@ export interface Input {
1010
sequence: number;
1111
witness: Buffer[];
1212
}
13-
export declare class Transaction {
13+
export declare class Transaction<TNumber extends number | bigint = number> {
1414
static readonly DEFAULT_SEQUENCE = 4294967295;
1515
static readonly SIGHASH_DEFAULT = 0;
1616
static readonly SIGHASH_ALL = 1;
@@ -21,21 +21,21 @@ export declare class Transaction {
2121
static readonly SIGHASH_INPUT_MASK = 128;
2222
static readonly ADVANCED_TRANSACTION_MARKER = 0;
2323
static readonly ADVANCED_TRANSACTION_FLAG = 1;
24-
static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction;
25-
static fromHex(hex: string): Transaction;
24+
static fromBuffer<TNumber extends number | bigint = number>(buffer: Buffer, _NO_STRICT?: boolean, amountType?: 'number' | 'bigint'): Transaction<TNumber>;
25+
static fromHex<TNumber extends number | bigint = number>(hex: string, amountType?: 'number' | 'bigint'): Transaction<TNumber>;
2626
static isCoinbaseHash(buffer: Buffer): boolean;
2727
version: number;
2828
locktime: number;
2929
ins: Input[];
30-
outs: Output[];
30+
outs: Array<Output<TNumber>>;
3131
isCoinbase(): boolean;
3232
addInput(hash: Buffer, index: number, sequence?: number, scriptSig?: Buffer): number;
33-
addOutput(scriptPubKey: Buffer, value: number): number;
33+
addOutput(scriptPubKey: Buffer, value: TNumber): number;
3434
hasWitnesses(): boolean;
3535
weight(): number;
3636
virtualSize(): number;
3737
byteLength(_ALLOW_WITNESS?: boolean): number;
38-
clone(): Transaction;
38+
clone(): Transaction<TNumber>;
3939
/**
4040
* Hash transaction for signing a specific input.
4141
*
@@ -45,8 +45,8 @@ export declare class Transaction {
4545
* This hash can then be used to sign the provided transaction input.
4646
*/
4747
hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer;
48-
hashForWitnessV1(inIndex: number, prevOutScripts: Buffer[], values: number[], hashType: number, leafHash?: Buffer, annex?: Buffer): Buffer;
49-
hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer;
48+
hashForWitnessV1(inIndex: number, prevOutScripts: Buffer[], values: TNumber[], hashType: number, leafHash?: Buffer, annex?: Buffer): Buffer;
49+
hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: TNumber, hashType: number): Buffer;
5050
getHash(forWitness?: boolean): Buffer;
5151
getId(): string;
5252
toBuffer(buffer?: Buffer, initialOffset?: number): Buffer;

src/transaction.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Transaction {
4545
this.ins = [];
4646
this.outs = [];
4747
}
48-
static fromBuffer(buffer, _NO_STRICT) {
48+
static fromBuffer(buffer, _NO_STRICT, amountType = 'number') {
4949
const bufferReader = new bufferutils_1.BufferReader(buffer);
5050
const tx = new Transaction();
5151
tx.version = bufferReader.readInt32();
@@ -73,7 +73,10 @@ class Transaction {
7373
const voutLen = bufferReader.readVarInt();
7474
for (let i = 0; i < voutLen; ++i) {
7575
tx.outs.push({
76-
value: bufferReader.readUInt64(),
76+
value:
77+
amountType === 'number'
78+
? bufferReader.readUInt64()
79+
: bufferReader.readUInt64BigInt(),
7780
script: bufferReader.readVarSlice(),
7881
});
7982
}
@@ -91,8 +94,8 @@ class Transaction {
9194
throw new Error('Transaction has unexpected data');
9295
return tx;
9396
}
94-
static fromHex(hex) {
95-
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false);
97+
static fromHex(hex, amountType = 'number') {
98+
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false, amountType);
9699
}
97100
static isCoinbaseHash(buffer) {
98101
typeforce(types.Hash256bit, buffer);

src/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export declare namespace BIP32Path {
77
var toJSON: () => string;
88
}
99
export declare function Signer(obj: any): boolean;
10-
export declare function Satoshi(value: number): boolean;
10+
export declare function Satoshi(value: number | bigint): boolean;
1111
export declare const ECPoint: any;
1212
export declare const Network: any;
1313
export interface XOnlyPointAddTweakResult {

src/types.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,12 @@ function Signer(obj) {
4848
}
4949
exports.Signer = Signer;
5050
const SATOSHI_MAX = 21 * 1e14;
51+
const BIG_SATOSHI_MAX = BigInt('1000000000000000000');
5152
function Satoshi(value) {
52-
return exports.typeforce.UInt53(value) && value <= SATOSHI_MAX;
53+
return (
54+
(exports.typeforce.UInt53(value) && value <= SATOSHI_MAX) ||
55+
(typeof value === 'bigint' && value >= 0 && value <= BIG_SATOSHI_MAX)
56+
);
5357
}
5458
exports.Satoshi = Satoshi;
5559
// external dependent types

test/bufferutils.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ describe('bufferutils', () => {
3232
});
3333
});
3434

35+
describe('readUInt64BigIntLE', () => {
36+
const checkValid = (f: { dec: string | number; hex: string }): void => {
37+
it('decodes ' + f.hex, () => {
38+
const buffer = Buffer.from(f.hex, 'hex');
39+
const num = bufferutils.readUInt64BigIntLE(buffer, 0);
40+
41+
assert.strictEqual(num, BigInt(f.dec));
42+
});
43+
};
44+
fixtures.valid.forEach(checkValid);
45+
fixtures.validBigInt.forEach(checkValid);
46+
});
47+
3548
describe('writeUInt64LE', () => {
3649
fixtures.valid.forEach(f => {
3750
it('encodes ' + f.dec, () => {
@@ -53,6 +66,29 @@ describe('bufferutils', () => {
5366
});
5467
});
5568

69+
describe('writeUInt64BigIntLE', () => {
70+
const checkValid = (f: { dec: string | number; hex: string }): void => {
71+
it('encodes ' + f.dec, () => {
72+
const buffer = Buffer.alloc(8, 0);
73+
74+
bufferutils.writeUInt64LE(buffer, BigInt(f.dec), 0);
75+
assert.strictEqual(buffer.toString('hex'), f.hex);
76+
});
77+
};
78+
fixtures.valid.forEach(checkValid);
79+
fixtures.validBigInt.forEach(checkValid);
80+
81+
fixtures.invalid.writeUInt64BigIntLE.forEach(f => {
82+
it('throws on ' + f.description, () => {
83+
const buffer = Buffer.alloc(8, 0);
84+
85+
assert.throws(() => {
86+
bufferutils.writeUInt64LE(buffer, BigInt(f.dec), 0);
87+
}, new RegExp(f.exception));
88+
});
89+
});
90+
});
91+
5692
describe('BufferWriter', () => {
5793
function testBuffer(
5894
bufferWriter: BufferWriter,

test/fixtures/bufferutils.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@
5757
"hex": "ffffffffffff1f00"
5858
}
5959
],
60+
"validBigInt": [
61+
{
62+
"dec": "9223372036854775808",
63+
"hex": "0000000000000080"
64+
},
65+
{
66+
"dec": "1000000000000000000",
67+
"hex": "000064a7b3b6e00d"
68+
},
69+
{
70+
"dec": "18446744073709551615",
71+
"hex": "ffffffffffffffff"
72+
}
73+
],
6074
"invalid": {
6175
"readUInt64LE": [
6276
{
@@ -97,6 +111,26 @@
97111
"hex": "",
98112
"dec": 0.1
99113
}
114+
],
115+
"writeUInt64BigIntLE": [
116+
{
117+
"description": "n === 2^64",
118+
"exception": "RangeError: value out of range",
119+
"hex": "000000000000000001",
120+
"dec": "18446744073709551616"
121+
},
122+
{
123+
"description": "n > 2^64",
124+
"exception": "RangeError: value out of range",
125+
"hex": "ff0000000000000001",
126+
"dec": "18446744073709551871"
127+
},
128+
{
129+
"description": "n < 0",
130+
"exception": "specified a negative value for writing an unsigned value",
131+
"hex": "",
132+
"dec": "-1"
133+
}
100134
]
101135
}
102136
}

0 commit comments

Comments
 (0)