Move tests to TypeScript (coverage is still JS based)

This commit is contained in:
junderw 2019-09-07 13:42:03 +09:00
parent 11e4a12caf
commit 6c08a0be40
No known key found for this signature in database
GPG key ID: B256185D3A971908
41 changed files with 3920 additions and 2712 deletions

3
.gitignore vendored
View file

@ -2,3 +2,6 @@ coverage
node_modules
.nyc_output
npm-debug.log
test/*.js
test/integration/*.js
!test/ts-node-register.js

712
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,9 @@
],
"scripts": {
"build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs",
"build:tests": "npm run clean:jstests && tsc -p ./test/tsconfig.json",
"clean": "rimraf src types",
"clean:jstests": "rimraf 'test/**/!(ts-node-register)*.js'",
"coverage-report": "npm run build && npm run nobuild:coverage-report",
"coverage-html": "npm run build && npm run nobuild:coverage-html",
"coverage": "npm run build && npm run nobuild:coverage",
@ -26,12 +28,13 @@
"gitdiff:ci": "npm run build && git diff --exit-code",
"integration": "npm run build && npm run nobuild:integration",
"lint": "tslint -p tsconfig.json -c tslint.json",
"mocha:ts": "mocha --recursive --require test/ts-node-register",
"nobuild:coverage-report": "nyc report --reporter=lcov",
"nobuild:coverage-html": "nyc report --reporter=html",
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha",
"nobuild:integration": "mocha --timeout 50000 test/integration/",
"nobuild:unit": "mocha",
"prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore",
"nobuild:coverage": "npm run build:tests && nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha && npm run clean:jstests",
"nobuild:integration": "npm run mocha:ts -- --timeout 50000 'test/integration/*.ts'",
"nobuild:unit": "npm run mocha:ts -- 'test/*.ts'",
"prettier": "prettier 'ts_src/**/*.ts' 'test/**/*.ts' --ignore-path ./.prettierignore",
"prettierjs": "prettier 'src/**/*.js' --ignore-path ./.prettierignore",
"test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage",
"unit": "npm run build && npm run nobuild:unit"
@ -63,7 +66,10 @@
"wif": "^2.0.1"
},
"devDependencies": {
"bip39": "^2.3.0",
"@types/bs58": "^4.0.0",
"@types/mocha": "^5.2.7",
"@types/proxyquire": "^1.3.28",
"bip39": "^3.0.2",
"bip65": "^1.0.1",
"bip68": "^1.0.3",
"bn.js": "^4.11.8",
@ -71,12 +77,13 @@
"dhttp": "^3.0.0",
"hoodwink": "^2.0.0",
"minimaldata": "^1.0.2",
"mocha": "^5.2.0",
"mocha": "^6.2.0",
"nyc": "^14.1.1",
"prettier": "1.16.4",
"proxyquire": "^2.0.1",
"regtest-client": "0.2.0",
"rimraf": "^2.6.3",
"ts-node": "^8.3.0",
"tslint": "^5.16.0",
"typescript": "3.2.2"
},

View file

@ -1,134 +1,148 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const baddress = require('../src/address')
const bscript = require('../src/script')
const fixtures = require('./fixtures/address.json')
const NETWORKS = Object.assign({
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as baddress from '../src/address';
import * as bscript from '../src/script';
import * as fixtures from './fixtures/address.json';
const NETWORKS = Object.assign(
{
litecoin: {
messagePrefix: '\x19Litecoin Signed Message:\n',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
private: 0x019d9cfe,
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0
}
}, require('../src/networks'))
wif: 0xb0,
},
},
require('../src/networks'),
);
describe('address', () => {
describe('fromBase58Check', () => {
fixtures.standard.forEach(f => {
if (!f.base58check) return
if (!f.base58check) return;
it('decodes ' + f.base58check, () => {
const decode = baddress.fromBase58Check(f.base58check)
const decode = baddress.fromBase58Check(f.base58check);
assert.strictEqual(decode.version, f.version)
assert.strictEqual(decode.hash.toString('hex'), f.hash)
})
})
assert.strictEqual(decode.version, f.version);
assert.strictEqual(decode.hash.toString('hex'), f.hash);
});
});
fixtures.invalid.fromBase58Check.forEach(f => {
it('throws on ' + f.exception, () => {
assert.throws(() => {
baddress.fromBase58Check(f.address)
}, new RegExp(f.address + ' ' + f.exception))
})
})
})
baddress.fromBase58Check(f.address);
}, new RegExp(f.address + ' ' + f.exception));
});
});
});
describe('fromBech32', () => {
fixtures.standard.forEach(f => {
if (!f.bech32) return
if (!f.bech32) return;
it('decodes ' + f.bech32, () => {
const actual = baddress.fromBech32(f.bech32)
const actual = baddress.fromBech32(f.bech32);
assert.strictEqual(actual.version, f.version)
assert.strictEqual(actual.prefix, NETWORKS[f.network].bech32)
assert.strictEqual(actual.data.toString('hex'), f.data)
})
})
assert.strictEqual(actual.version, f.version);
assert.strictEqual(actual.prefix, NETWORKS[f.network].bech32);
assert.strictEqual(actual.data.toString('hex'), f.data);
});
});
fixtures.invalid.bech32.forEach((f, i) => {
it('decode fails for ' + f.bech32 + '(' + f.exception + ')', () => {
fixtures.invalid.bech32.forEach(f => {
it('decode fails for ' + f.address + '(' + f.exception + ')', () => {
assert.throws(() => {
baddress.fromBech32(f.address)
}, new RegExp(f.exception))
})
})
})
baddress.fromBech32(f.address);
}, new RegExp(f.exception));
});
});
});
describe('fromOutputScript', () => {
fixtures.standard.forEach(f => {
it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => {
const script = bscript.fromASM(f.script)
const address = baddress.fromOutputScript(script, NETWORKS[f.network])
const script = bscript.fromASM(f.script);
const address = baddress.fromOutputScript(script, NETWORKS[f.network]);
assert.strictEqual(address, f.base58check || f.bech32.toLowerCase())
})
})
assert.strictEqual(address, f.base58check || f.bech32!.toLowerCase());
});
});
fixtures.invalid.fromOutputScript.forEach(f => {
it('throws when ' + f.script.slice(0, 30) + '... ' + f.exception, () => {
const script = bscript.fromASM(f.script)
const script = bscript.fromASM(f.script);
assert.throws(() => {
baddress.fromOutputScript(script)
}, new RegExp(f.exception))
})
})
})
baddress.fromOutputScript(script);
}, new RegExp(f.exception));
});
});
});
describe('toBase58Check', () => {
fixtures.standard.forEach(f => {
if (!f.base58check) return
if (!f.base58check) return;
it('encodes ' + f.hash + ' (' + f.network + ')', () => {
const address = baddress.toBase58Check(Buffer.from(f.hash, 'hex'), f.version)
const address = baddress.toBase58Check(
Buffer.from(f.hash, 'hex'),
f.version,
);
assert.strictEqual(address, f.base58check)
})
})
})
assert.strictEqual(address, f.base58check);
});
});
});
describe('toBech32', () => {
fixtures.bech32.forEach((f, i) => {
if (!f.bech32) return
const data = Buffer.from(f.data, 'hex')
fixtures.bech32.forEach(f => {
if (!f.address) return;
const data = Buffer.from(f.data, 'hex');
it('encode ' + f.address, () => {
assert.deepStrictEqual(baddress.toBech32(data, f.version, f.prefix), f.address)
})
})
assert.deepStrictEqual(
baddress.toBech32(data, f.version, f.prefix),
f.address.toLowerCase(),
);
});
});
fixtures.invalid.bech32.forEach((f, i) => {
if (!f.prefix || f.version === undefined || f.data === undefined) return
// TODO: These fixtures (according to TypeScript) have none of the data used below
fixtures.invalid.bech32.forEach((f: any) => {
if (!f.prefix || f.version === undefined || f.data === undefined) return;
it('encode fails (' + f.exception, () => {
assert.throws(() => {
baddress.toBech32(Buffer.from(f.data, 'hex'), f.version, f.prefix)
}, new RegExp(f.exception))
})
})
})
baddress.toBech32(Buffer.from(f.data, 'hex'), f.version, f.prefix);
}, new RegExp(f.exception));
});
});
});
describe('toOutputScript', () => {
fixtures.standard.forEach(f => {
it('decodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => {
const script = baddress.toOutputScript(f.base58check || f.bech32, NETWORKS[f.network])
const script = baddress.toOutputScript(
(f.base58check || f.bech32)!,
NETWORKS[f.network],
);
assert.strictEqual(bscript.toASM(script), f.script)
})
})
assert.strictEqual(bscript.toASM(script), f.script);
});
});
fixtures.invalid.toOutputScript.forEach(f => {
it('throws when ' + f.exception, () => {
assert.throws(() => {
baddress.toOutputScript(f.address, f.network)
}, new RegExp(f.address + ' ' + f.exception))
})
})
})
})
baddress.toOutputScript(f.address, f.network as any);
}, new RegExp(f.address + ' ' + f.exception));
});
});
});
});

View file

@ -1,226 +1,251 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const base58 = require('bs58')
const bitcoin = require('../')
const base58EncodeDecode = require('./fixtures/core/base58_encode_decode.json')
const base58KeysInvalid = require('./fixtures/core/base58_keys_invalid.json')
const base58KeysValid = require('./fixtures/core/base58_keys_valid.json')
const blocksValid = require('./fixtures/core/blocks.json')
const sigCanonical = require('./fixtures/core/sig_canonical.json')
const sigHash = require('./fixtures/core/sighash.json')
const sigNoncanonical = require('./fixtures/core/sig_noncanonical.json')
const txValid = require('./fixtures/core/tx_valid.json')
import * as assert from 'assert';
import * as base58 from 'bs58';
import { describe, it } from 'mocha';
import * as bitcoin from '..';
import * as base58EncodeDecode from './fixtures/core/base58_encode_decode.json';
import * as base58KeysInvalid from './fixtures/core/base58_keys_invalid.json';
import * as base58KeysValid from './fixtures/core/base58_keys_valid.json';
import * as blocksValid from './fixtures/core/blocks.json';
import * as sigCanonical from './fixtures/core/sig_canonical.json';
import * as sigHash from './fixtures/core/sighash.json';
import * as sigNoncanonical from './fixtures/core/sig_noncanonical.json';
import * as txValid from './fixtures/core/tx_valid.json';
describe('Bitcoin-core', () => {
// base58EncodeDecode
describe('base58', () => {
base58EncodeDecode.forEach(f => {
const fhex = f[0]
const fb58 = f[1]
const fhex = f[0];
const fb58 = f[1];
it('can decode ' + fb58, () => {
const buffer = base58.decode(fb58)
const actual = buffer.toString('hex')
const buffer = base58.decode(fb58);
const actual = buffer.toString('hex');
assert.strictEqual(actual, fhex)
})
assert.strictEqual(actual, fhex);
});
it('can encode ' + fhex, () => {
const buffer = Buffer.from(fhex, 'hex')
const actual = base58.encode(buffer)
const buffer = Buffer.from(fhex, 'hex');
const actual = base58.encode(buffer);
assert.strictEqual(actual, fb58)
})
})
})
assert.strictEqual(actual, fb58);
});
});
});
// base58KeysValid
describe('address.toBase58Check', () => {
const typeMap = {
'pubkey': 'pubKeyHash',
'script': 'scriptHash'
}
const typeMap: any = {
pubkey: 'pubKeyHash',
script: 'scriptHash',
};
base58KeysValid.forEach(f => {
const expected = f[0]
const hash = Buffer.from(f[1], 'hex')
const params = f[2]
const expected = f[0];
const hash = Buffer.from(f[1] as any, 'hex');
const params = f[2] as any;
if (params.isPrivkey) return
if (params.isPrivkey) return;
const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
const version = network[typeMap[params.addrType]]
const network: any = params.isTestnet
? bitcoin.networks.testnet
: bitcoin.networks.bitcoin;
const version = network[typeMap[params.addrType]];
it('can export ' + expected, () => {
assert.strictEqual(bitcoin.address.toBase58Check(hash, version), expected)
})
})
})
assert.strictEqual(
bitcoin.address.toBase58Check(hash, version),
expected,
);
});
});
});
// base58KeysInvalid
describe('address.fromBase58Check', () => {
const allowedNetworks = [
bitcoin.networks.bitcoin.pubkeyhash,
bitcoin.networks.bitcoin.scripthash,
bitcoin.networks.testnet.pubkeyhash,
bitcoin.networks.testnet.scripthash
]
bitcoin.networks.bitcoin.pubKeyHash,
bitcoin.networks.bitcoin.scriptHash,
bitcoin.networks.testnet.pubKeyHash,
bitcoin.networks.testnet.scriptHash,
];
base58KeysInvalid.forEach(f => {
const string = f[0]
const string = f[0];
it('throws on ' + string, () => {
assert.throws(() => {
const address = bitcoin.address.fromBase58Check(string)
const address = bitcoin.address.fromBase58Check(string);
assert.notStrictEqual(allowedNetworks.indexOf(address.version), -1, 'Invalid network')
}, /(Invalid (checksum|network))|(too (short|long))/)
})
})
})
assert.notStrictEqual(
allowedNetworks.indexOf(address.version),
-1,
'Invalid network',
);
}, /(Invalid (checksum|network))|(too (short|long))/);
});
});
});
// base58KeysValid
describe('ECPair', () => {
base58KeysValid.forEach(f => {
const string = f[0]
const hex = f[1]
const params = f[2]
const strng = f[0] as string;
const hex = f[1];
const params = f[2] as any;
if (!params.isPrivkey) return
if (!params.isPrivkey) return;
const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
const keyPair = bitcoin.ECPair.fromWIF(string, network)
const network = params.isTestnet
? bitcoin.networks.testnet
: bitcoin.networks.bitcoin;
const keyPair = bitcoin.ECPair.fromWIF(strng, network);
it('fromWIF imports ' + string, () => {
assert.strictEqual(keyPair.privateKey.toString('hex'), hex)
assert.strictEqual(keyPair.compressed, params.isCompressed)
})
it('fromWIF imports ' + strng, () => {
assert.strictEqual(keyPair.privateKey!.toString('hex'), hex);
assert.strictEqual(keyPair.compressed, params.isCompressed);
});
it('toWIF exports ' + hex + ' to ' + string, () => {
assert.strictEqual(keyPair.toWIF(), string)
})
})
})
it('toWIF exports ' + hex + ' to ' + strng, () => {
assert.strictEqual(keyPair.toWIF(), strng);
});
});
});
// base58KeysInvalid
describe('ECPair.fromWIF', () => {
const allowedNetworks = [
bitcoin.networks.bitcoin,
bitcoin.networks.testnet
]
bitcoin.networks.testnet,
];
base58KeysInvalid.forEach(f => {
const string = f[0]
const string = f[0];
it('throws on ' + string, () => {
assert.throws(() => {
bitcoin.ECPair.fromWIF(string, allowedNetworks)
}, /(Invalid|Unknown) (checksum|compression flag|network version|WIF length)/)
})
})
})
bitcoin.ECPair.fromWIF(string, allowedNetworks);
}, /(Invalid|Unknown) (checksum|compression flag|network version|WIF length)/);
});
});
});
describe('Block.fromHex', () => {
blocksValid.forEach(f => {
it('can parse ' + f.id, () => {
const block = bitcoin.Block.fromHex(f.hex)
const block = bitcoin.Block.fromHex(f.hex);
assert.strictEqual(block.getId(), f.id)
assert.strictEqual(block.transactions.length, f.transactions)
})
})
})
assert.strictEqual(block.getId(), f.id);
assert.strictEqual(block.transactions!.length, f.transactions);
});
});
});
// txValid
describe('Transaction.fromHex', () => {
txValid.forEach(f => {
// Objects that are only a single string are ignored
if (f.length === 1) return
if (f.length === 1) return;
const inputs = f[0]
const fhex = f[1]
const inputs = f[0];
const fhex = f[1];
// const verifyFlags = f[2] // TODO: do we need to test this?
it('can decode ' + fhex, () => {
const transaction = bitcoin.Transaction.fromHex(fhex)
const transaction = bitcoin.Transaction.fromHex(fhex as string);
transaction.ins.forEach((txIn, i) => {
const input = inputs[i]
const input = inputs[i];
// reverse because test data is reversed
const prevOutHash = Buffer.from(input[0], 'hex').reverse()
const prevOutIndex = input[1]
const prevOutHash = Buffer.from(input[0] as string, 'hex').reverse();
const prevOutIndex = input[1];
assert.deepStrictEqual(txIn.hash, prevOutHash)
assert.deepStrictEqual(txIn.hash, prevOutHash);
// we read UInt32, not Int32
assert.strictEqual(txIn.index & 0xffffffff, prevOutIndex)
})
})
})
})
assert.strictEqual(txIn.index & 0xffffffff, prevOutIndex);
});
});
});
});
// sighash
describe('Transaction', () => {
sigHash.forEach(f => {
// Objects that are only a single string are ignored
if (f.length === 1) return
if (f.length === 1) return;
const txHex = f[0]
const scriptHex = f[1]
const inIndex = f[2]
const hashType = f[3]
const expectedHash = f[4]
const txHex = f[0] as string;
const scriptHex = f[1] as string;
const inIndex = f[2] as number;
const hashType = f[3] as number;
const expectedHash = f[4];
const hashTypes = []
if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_NONE) hashTypes.push('SIGHASH_NONE')
else if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_SINGLE) hashTypes.push('SIGHASH_SINGLE')
else hashTypes.push('SIGHASH_ALL')
if (hashType & bitcoin.Transaction.SIGHASH_ANYONECANPAY) hashTypes.push('SIGHASH_ANYONECANPAY')
const hashTypes = [];
if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_NONE)
hashTypes.push('SIGHASH_NONE');
else if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_SINGLE)
hashTypes.push('SIGHASH_SINGLE');
else hashTypes.push('SIGHASH_ALL');
if (hashType & bitcoin.Transaction.SIGHASH_ANYONECANPAY)
hashTypes.push('SIGHASH_ANYONECANPAY');
const hashTypeName = hashTypes.join(' | ')
const hashTypeName = hashTypes.join(' | ');
it('should hash ' + txHex.slice(0, 40) + '... (' + hashTypeName + ')', () => {
const transaction = bitcoin.Transaction.fromHex(txHex)
assert.strictEqual(transaction.toHex(), txHex)
it(
'should hash ' + txHex.slice(0, 40) + '... (' + hashTypeName + ')',
() => {
const transaction = bitcoin.Transaction.fromHex(txHex);
assert.strictEqual(transaction.toHex(), txHex);
const script = Buffer.from(scriptHex, 'hex')
const scriptChunks = bitcoin.script.decompile(script)
assert.strictEqual(bitcoin.script.compile(scriptChunks).toString('hex'), scriptHex)
const script = Buffer.from(scriptHex, 'hex');
const scriptChunks = bitcoin.script.decompile(script);
assert.strictEqual(
bitcoin.script.compile(scriptChunks!).toString('hex'),
scriptHex,
);
const hash = transaction.hashForSignature(inIndex, script, hashType)
const hash = transaction.hashForSignature(inIndex, script, hashType);
// reverse because test data is reversed
assert.strictEqual(hash.reverse().toString('hex'), expectedHash)
})
})
})
assert.strictEqual(
(hash.reverse() as Buffer).toString('hex'),
expectedHash,
);
},
);
});
});
describe('script.signature.decode', () => {
sigCanonical.forEach(hex => {
const buffer = Buffer.from(hex, 'hex')
const buffer = Buffer.from(hex, 'hex');
it('can parse ' + hex, () => {
const parsed = bitcoin.script.signature.decode(buffer)
const actual = bitcoin.script.signature.encode(parsed.signature, parsed.hashType)
const parsed = bitcoin.script.signature.decode(buffer);
const actual = bitcoin.script.signature.encode(
parsed.signature,
parsed.hashType,
);
assert.strictEqual(actual.toString('hex'), hex)
})
})
assert.strictEqual(actual.toString('hex'), hex);
});
});
sigNoncanonical.forEach((hex, i) => {
if (i === 0) return
if (i % 2 !== 0) return
if (i === 0) return;
if (i % 2 !== 0) return;
const description = sigNoncanonical[i - 1].slice(0, -1)
const buffer = Buffer.from(hex, 'hex')
const description = sigNoncanonical[i - 1].slice(0, -1);
const buffer = Buffer.from(hex, 'hex');
it('throws on ' + description, () => {
assert.throws(() => {
bitcoin.script.signature.decode(buffer)
}, /Expected DER (integer|sequence)|(R|S) value (excessively padded|is negative)|(R|S|DER sequence) length is (zero|too short|too long|invalid)|Invalid hashType/)
})
})
})
})
bitcoin.script.signature.decode(buffer);
}, /Expected DER (integer|sequence)|(R|S) value (excessively padded|is negative)|(R|S|DER sequence) length is (zero|too short|too long|invalid)|Invalid hashType/);
});
});
});
});

View file

@ -1,157 +1,172 @@
const { describe, it, beforeEach } = require('mocha')
const assert = require('assert')
const Block = require('..').Block
import * as assert from 'assert';
import { beforeEach, describe, it } from 'mocha';
import { Block } from '..';
const fixtures = require('./fixtures/block')
import * as fixtures from './fixtures/block.json';
describe('Block', () => {
describe('version', () => {
it('should be interpreted as an int32le', () => {
const blockHex = 'ffffffff0000000000000000000000000000000000000000000000000000000000000000414141414141414141414141414141414141414141414141414141414141414101000000020000000300000000'
const block = Block.fromHex(blockHex)
assert.strictEqual(-1, block.version)
assert.strictEqual(1, block.timestamp)
})
})
const blockHex =
'ffffffff0000000000000000000000000000000000000000000000000000000000000000414141414141414141414141414141414141414141414141414141414141414101000000020000000300000000';
const block = Block.fromHex(blockHex);
assert.strictEqual(-1, block.version);
assert.strictEqual(1, block.timestamp);
});
});
describe('calculateTarget', () => {
fixtures.targets.forEach(f => {
it('returns ' + f.expected + ' for 0x' + f.bits, () => {
const bits = parseInt(f.bits, 16)
const bits = parseInt(f.bits, 16);
assert.strictEqual(Block.calculateTarget(bits).toString('hex'), f.expected)
})
})
})
assert.strictEqual(
Block.calculateTarget(bits).toString('hex'),
f.expected,
);
});
});
});
describe('fromBuffer/fromHex', () => {
fixtures.valid.forEach(f => {
it('imports ' + f.description, () => {
const block = Block.fromHex(f.hex)
const block = Block.fromHex(f.hex);
assert.strictEqual(block.version, f.version)
assert.strictEqual(block.prevHash.toString('hex'), f.prevHash)
assert.strictEqual(block.merkleRoot.toString('hex'), f.merkleRoot)
assert.strictEqual(block.version, f.version);
assert.strictEqual(block.prevHash!.toString('hex'), f.prevHash);
assert.strictEqual(block.merkleRoot!.toString('hex'), f.merkleRoot);
if (block.witnessCommit) {
assert.strictEqual(block.witnessCommit.toString('hex'), f.witnessCommit)
assert.strictEqual(
block.witnessCommit.toString('hex'),
f.witnessCommit,
);
}
assert.strictEqual(block.timestamp, f.timestamp)
assert.strictEqual(block.bits, f.bits)
assert.strictEqual(block.nonce, f.nonce)
assert.strictEqual(!block.transactions, f.hex.length === 160)
})
})
assert.strictEqual(block.timestamp, f.timestamp);
assert.strictEqual(block.bits, f.bits);
assert.strictEqual(block.nonce, f.nonce);
assert.strictEqual(!block.transactions, f.hex.length === 160);
});
});
fixtures.invalid.forEach(f => {
it('throws on ' + f.exception, () => {
assert.throws(() => {
Block.fromHex(f.hex)
}, new RegExp(f.exception))
})
})
})
Block.fromHex(f.hex);
}, new RegExp(f.exception));
});
});
});
describe('toBuffer/toHex', () => {
fixtures.valid.forEach(f => {
let block
let block: Block;
beforeEach(() => {
block = Block.fromHex(f.hex)
})
block = Block.fromHex(f.hex);
});
it('exports ' + f.description, () => {
assert.strictEqual(block.toHex(true), f.hex.slice(0, 160))
assert.strictEqual(block.toHex(), f.hex)
})
})
})
assert.strictEqual(block.toHex(true), f.hex.slice(0, 160));
assert.strictEqual(block.toHex(), f.hex);
});
});
});
describe('getHash/getId', () => {
fixtures.valid.forEach(f => {
let block
let block: Block;
beforeEach(() => {
block = Block.fromHex(f.hex)
})
block = Block.fromHex(f.hex);
});
it('returns ' + f.id + ' for ' + f.description, () => {
assert.strictEqual(block.getHash().toString('hex'), f.hash)
assert.strictEqual(block.getId(), f.id)
})
})
})
assert.strictEqual(block.getHash().toString('hex'), f.hash);
assert.strictEqual(block.getId(), f.id);
});
});
});
describe('getUTCDate', () => {
fixtures.valid.forEach(f => {
let block
let block: Block;
beforeEach(() => {
block = Block.fromHex(f.hex)
})
block = Block.fromHex(f.hex);
});
it('returns UTC date of ' + f.id, () => {
const utcDate = block.getUTCDate().getTime()
const utcDate = block.getUTCDate().getTime();
assert.strictEqual(utcDate, f.timestamp * 1e3)
})
})
})
assert.strictEqual(utcDate, f.timestamp * 1e3);
});
});
});
describe('calculateMerkleRoot', () => {
it('should throw on zero-length transaction array', () => {
assert.throws(() => {
Block.calculateMerkleRoot([])
}, /Cannot compute merkle root for zero transactions/)
})
Block.calculateMerkleRoot([]);
}, /Cannot compute merkle root for zero transactions/);
});
fixtures.valid.forEach(f => {
if (f.hex.length === 160) return
if (f.hex.length === 160) return;
let block
let block: Block;
beforeEach(() => {
block = Block.fromHex(f.hex)
})
block = Block.fromHex(f.hex);
});
it('returns ' + f.merkleRoot + ' for ' + f.id, () => {
assert.strictEqual(Block.calculateMerkleRoot(block.transactions).toString('hex'), f.merkleRoot)
})
assert.strictEqual(
Block.calculateMerkleRoot(block.transactions!).toString('hex'),
f.merkleRoot,
);
});
if (f.witnessCommit) {
it('returns witness commit ' + f.witnessCommit + ' for ' + f.id, () => {
assert.strictEqual(Block.calculateMerkleRoot(block.transactions, true).toString('hex'), f.witnessCommit)
})
assert.strictEqual(
Block.calculateMerkleRoot(block.transactions!, true).toString(
'hex',
),
f.witnessCommit,
);
});
}
})
})
});
});
describe('checkTxRoots', () => {
fixtures.valid.forEach(f => {
if (f.hex.length === 160) return
if (f.hex.length === 160) return;
let block
let block: Block;
beforeEach(() => {
block = Block.fromHex(f.hex)
})
block = Block.fromHex(f.hex);
});
it('returns ' + f.valid + ' for ' + f.id, () => {
assert.strictEqual(block.checkTxRoots(), true)
})
})
})
assert.strictEqual(block.checkTxRoots(), true);
});
});
});
describe('checkProofOfWork', () => {
fixtures.valid.forEach(f => {
let block
let block: Block;
beforeEach(() => {
block = Block.fromHex(f.hex)
})
block = Block.fromHex(f.hex);
});
it('returns ' + f.valid + ' for ' + f.id, () => {
assert.strictEqual(block.checkProofOfWork(), f.valid)
})
})
})
})
assert.strictEqual(block.checkProofOfWork(), f.valid);
});
});
});
});

View file

@ -1,49 +1,49 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bufferutils = require('../src/bufferutils')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bufferutils from '../src/bufferutils';
const fixtures = require('./fixtures/bufferutils.json')
import * as fixtures from './fixtures/bufferutils.json';
describe('bufferutils', () => {
describe('readUInt64LE', () => {
fixtures.valid.forEach(f => {
it('decodes ' + f.hex, () => {
const buffer = Buffer.from(f.hex, 'hex')
const number = bufferutils.readUInt64LE(buffer, 0)
const buffer = Buffer.from(f.hex, 'hex');
const number = bufferutils.readUInt64LE(buffer, 0);
assert.strictEqual(number, f.dec)
})
})
assert.strictEqual(number, f.dec);
});
});
fixtures.invalid.readUInt64LE.forEach(f => {
it('throws on ' + f.description, () => {
const buffer = Buffer.from(f.hex, 'hex')
const buffer = Buffer.from(f.hex, 'hex');
assert.throws(() => {
bufferutils.readUInt64LE(buffer, 0)
}, new RegExp(f.exception))
})
})
})
bufferutils.readUInt64LE(buffer, 0);
}, new RegExp(f.exception));
});
});
});
describe('writeUInt64LE', () => {
fixtures.valid.forEach(f => {
it('encodes ' + f.dec, () => {
const buffer = Buffer.alloc(8, 0)
const buffer = Buffer.alloc(8, 0);
bufferutils.writeUInt64LE(buffer, f.dec, 0)
assert.strictEqual(buffer.toString('hex'), f.hex)
})
})
bufferutils.writeUInt64LE(buffer, f.dec, 0);
assert.strictEqual(buffer.toString('hex'), f.hex);
});
});
fixtures.invalid.readUInt64LE.forEach(f => {
it('throws on ' + f.description, () => {
const buffer = Buffer.alloc(8, 0)
const buffer = Buffer.alloc(8, 0);
assert.throws(() => {
bufferutils.writeUInt64LE(buffer, f.dec, 0)
}, new RegExp(f.exception))
})
})
})
})
bufferutils.writeUInt64LE(buffer, f.dec, 0);
}, new RegExp(f.exception));
});
});
});
});

View file

@ -1,18 +1,18 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bscript = require('../src/script')
const classify = require('../src/classify')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bscript from '../src/script';
import * as classify from '../src/classify';
const fixtures = require('./fixtures/templates.json')
import * as fixtures from './fixtures/templates.json';
const multisig = require('../src/templates/multisig')
const nullData = require('../src/templates/nulldata')
const pubKey = require('../src/templates/pubkey')
const pubKeyHash = require('../src/templates/pubkeyhash')
const scriptHash = require('../src/templates/scripthash')
const witnessPubKeyHash = require('../src/templates/witnesspubkeyhash')
const witnessScriptHash = require('../src/templates/witnessscripthash')
const witnessCommitment = require('../src/templates/witnesscommitment')
import * as multisig from '../src/templates/multisig';
import * as nullData from '../src/templates/nulldata';
import * as pubKey from '../src/templates/pubkey';
import * as pubKeyHash from '../src/templates/pubkeyhash';
import * as scriptHash from '../src/templates/scripthash';
import * as witnessPubKeyHash from '../src/templates/witnesspubkeyhash';
import * as witnessScriptHash from '../src/templates/witnessscripthash';
import * as witnessCommitment from '../src/templates/witnesscommitment';
const tmap = {
pubKey,
@ -22,49 +22,48 @@ const tmap = {
witnessScriptHash,
multisig,
nullData,
witnessCommitment
}
witnessCommitment,
};
describe('classify', () => {
describe('input', () => {
fixtures.valid.forEach(f => {
if (!f.input) return
if (!f.input) return;
it('classifies ' + f.input + ' as ' + f.type, () => {
const input = bscript.fromASM(f.input)
const type = classify.input(input)
const input = bscript.fromASM(f.input);
const type = classify.input(input);
assert.strictEqual(type, f.type)
})
})
assert.strictEqual(type, f.type);
});
});
fixtures.valid.forEach(f => {
if (!f.input) return
if (!f.typeIncomplete) return
if (!f.input) return;
if (!f.typeIncomplete) return;
it('classifies incomplete ' + f.input + ' as ' + f.typeIncomplete, () => {
const input = bscript.fromASM(f.input)
const type = classify.input(input, true)
const input = bscript.fromASM(f.input);
const type = classify.input(input, true);
assert.strictEqual(type, f.typeIncomplete)
})
})
})
assert.strictEqual(type, f.typeIncomplete);
});
});
});
describe('classifyOutput', () => {
fixtures.valid.forEach(f => {
if (!f.output) return
if (!f.output) return;
it('classifies ' + f.output + ' as ' + f.type, () => {
const output = bscript.fromASM(f.output)
const type = classify.output(output)
const output = bscript.fromASM(f.output);
const type = classify.output(output);
assert.strictEqual(type, f.type)
})
})
})
;[
assert.strictEqual(type, f.type);
});
});
});
[
'pubKey',
'pubKeyHash',
'scriptHash',
@ -72,85 +71,110 @@ describe('classify', () => {
'witnessScriptHash',
'multisig',
'nullData',
'witnessCommitment'
'witnessCommitment',
].forEach(name => {
const inputType = tmap[name].input
const outputType = tmap[name].output
const inputType = (tmap as any)[name].input;
const outputType = (tmap as any)[name].output;
describe(name + '.input.check', () => {
fixtures.valid.forEach(f => {
if (name.toLowerCase() === classify.types.P2WPKH) return
if (name.toLowerCase() === classify.types.P2WSH) return
const expected = name.toLowerCase() === f.type.toLowerCase()
if (name.toLowerCase() === classify.types.P2WPKH) return;
if (name.toLowerCase() === classify.types.P2WSH) return;
const expected = name.toLowerCase() === f.type.toLowerCase();
if (inputType && f.input) {
const input = bscript.fromASM(f.input)
const input = bscript.fromASM(f.input);
it('returns ' + expected + ' for ' + f.input, () => {
assert.strictEqual(inputType.check(input), expected)
})
assert.strictEqual(inputType.check(input), expected);
});
if (f.typeIncomplete) {
const expectedIncomplete = name.toLowerCase() === f.typeIncomplete
const expectedIncomplete = name.toLowerCase() === f.typeIncomplete;
it('returns ' + expected + ' for ' + f.input, () => {
assert.strictEqual(inputType.check(input, true), expectedIncomplete)
})
assert.strictEqual(
inputType.check(input, true),
expectedIncomplete,
);
});
}
}
})
});
if (!(fixtures.invalid[name])) return
if (!(fixtures.invalid as any)[name]) return;
fixtures.invalid[name].inputs.forEach(f => {
if (!f.input && !f.inputHex) return
(fixtures.invalid as any)[name].inputs.forEach((f: any) => {
if (!f.input && !f.inputHex) return;
it('returns false for ' + f.description + ' (' + (f.input || f.inputHex) + ')', () => {
let input
it(
'returns false for ' +
f.description +
' (' +
(f.input || f.inputHex) +
')',
() => {
let input;
if (f.input) {
input = bscript.fromASM(f.input)
input = bscript.fromASM(f.input);
} else {
input = Buffer.from(f.inputHex, 'hex')
input = Buffer.from(f.inputHex, 'hex');
}
assert.strictEqual(inputType.check(input), false)
})
})
})
assert.strictEqual(inputType.check(input), false);
},
);
});
});
describe(name + '.output.check', () => {
fixtures.valid.forEach(f => {
const expected = name.toLowerCase() === f.type
const expected = name.toLowerCase() === f.type;
if (outputType && f.output) {
it('returns ' + expected + ' for ' + f.output, () => {
const output = bscript.fromASM(f.output)
const output = bscript.fromASM(f.output);
if (name.toLowerCase() === 'nulldata' && f.type === classify.types.WITNESS_COMMITMENT) return
if (name.toLowerCase() === 'witnesscommitment' && f.type === classify.types.NULLDATA) return
assert.strictEqual(outputType.check(output), expected)
})
if (
name.toLowerCase() === 'nulldata' &&
f.type === classify.types.WITNESS_COMMITMENT
)
return;
if (
name.toLowerCase() === 'witnesscommitment' &&
f.type === classify.types.NULLDATA
)
return;
assert.strictEqual(outputType.check(output), expected);
});
}
})
});
if (!(fixtures.invalid[name])) return
if (!(fixtures.invalid as any)[name]) return;
fixtures.invalid[name].outputs.forEach(f => {
if (!f.output && !f.outputHex) return
(fixtures.invalid as any)[name].outputs.forEach((f: any) => {
if (!f.output && !f.outputHex) return;
it('returns false for ' + f.description + ' (' + (f.output || f.outputHex) + ')', () => {
let output
it(
'returns false for ' +
f.description +
' (' +
(f.output || f.outputHex) +
')',
() => {
let output;
if (f.output) {
output = bscript.fromASM(f.output)
output = bscript.fromASM(f.output);
} else {
output = Buffer.from(f.outputHex, 'hex')
output = Buffer.from(f.outputHex, 'hex');
}
assert.strictEqual(outputType.check(output), false)
})
})
})
})
})
assert.strictEqual(outputType.check(output), false);
},
);
});
});
});
});

View file

@ -1,23 +1,22 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bcrypto = require('../src/crypto')
const fixtures = require('./fixtures/crypto')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import { crypto as bcrypto } from '..';
import * as fixtures from './fixtures/crypto.json';
describe('crypto', () => {
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => {
describe(algorithm, () => {
fixtures.forEach(f => {
const fn = bcrypto[algorithm]
const expected = f[algorithm]
const fn = (bcrypto as any)[algorithm];
const expected = (f as any)[algorithm];
it('returns ' + expected + ' for ' + f.hex, () => {
const data = Buffer.from(f.hex, 'hex')
const actual = fn(data).toString('hex')
const data = Buffer.from(f.hex, 'hex');
const actual = fn(data).toString('hex');
assert.strictEqual(actual, expected)
})
})
})
})
})
assert.strictEqual(actual, expected);
});
});
});
});
});

View file

@ -1,284 +1,334 @@
const { describe, it, beforeEach } = require('mocha')
const assert = require('assert')
const proxyquire = require('proxyquire')
const hoodwink = require('hoodwink')
import * as assert from 'assert';
import { beforeEach, describe, it } from 'mocha';
import * as proxyquire from 'proxyquire';
import { ECPair, ECPairInterface, networks as NETWORKS } from '..';
import * as fixtures from './fixtures/ecpair.json';
const hoodwink = require('hoodwink');
const tinysecp = require('tiny-secp256k1');
const ECPair = require('../src/ecpair')
const tinysecp = require('tiny-secp256k1')
const fixtures = require('./fixtures/ecpair.json')
const NETWORKS = require('../src/networks')
const NETWORKS_LIST = [] // Object.values(NETWORKS)
for (let networkName in NETWORKS) {
NETWORKS_LIST.push(NETWORKS[networkName])
}
const ZERO = Buffer.alloc(32, 0)
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
const GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex')
const GROUP_ORDER_LESS_1 = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', 'hex')
const NETWORKS_LIST = Object.values(NETWORKS);
const ZERO = Buffer.alloc(32, 0);
const ONE = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex',
);
const GROUP_ORDER = Buffer.from(
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
'hex',
);
const GROUP_ORDER_LESS_1 = Buffer.from(
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
'hex',
);
describe('ECPair', () => {
describe('getPublicKey', () => {
let keyPair
let keyPair: ECPairInterface;
beforeEach(() => {
keyPair = ECPair.fromPrivateKey(ONE)
})
keyPair = ECPair.fromPrivateKey(ONE);
});
it('calls pointFromScalar lazily', hoodwink(() => {
assert.strictEqual(keyPair.__Q, undefined)
it(
'calls pointFromScalar lazily',
hoodwink(() => {
assert.strictEqual((keyPair as any).__Q, undefined);
// .publicKey forces the memoization
assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
assert.strictEqual(keyPair.__Q.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
}))
})
assert.strictEqual(
keyPair.publicKey.toString('hex'),
'0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
);
assert.strictEqual(
(keyPair as any).__Q.toString('hex'),
'0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
);
}),
);
});
describe('fromPrivateKey', () => {
it('defaults to compressed', () => {
const keyPair = ECPair.fromPrivateKey(ONE)
const keyPair = ECPair.fromPrivateKey(ONE);
assert.strictEqual(keyPair.compressed, true)
})
assert.strictEqual(keyPair.compressed, true);
});
it('supports the uncompressed option', () => {
const keyPair = ECPair.fromPrivateKey(ONE, {
compressed: false
})
compressed: false,
});
assert.strictEqual(keyPair.compressed, false)
})
assert.strictEqual(keyPair.compressed, false);
});
it('supports the network option', () => {
const keyPair = ECPair.fromPrivateKey(ONE, {
compressed: false,
network: NETWORKS.testnet
})
network: NETWORKS.testnet,
});
assert.strictEqual(keyPair.network, NETWORKS.testnet)
})
assert.strictEqual(keyPair.network, NETWORKS.testnet);
});
fixtures.valid.forEach(f => {
it('derives public key for ' + f.WIF, () => {
const d = Buffer.from(f.d, 'hex')
const d = Buffer.from(f.d, 'hex');
const keyPair = ECPair.fromPrivateKey(d, {
compressed: f.compressed
})
compressed: f.compressed,
});
assert.strictEqual(keyPair.publicKey.toString('hex'), f.Q)
})
})
assert.strictEqual(keyPair.publicKey.toString('hex'), f.Q);
});
});
fixtures.invalid.fromPrivateKey.forEach(f => {
it('throws ' + f.exception, () => {
const d = Buffer.from(f.d, 'hex')
const d = Buffer.from(f.d, 'hex');
assert.throws(() => {
ECPair.fromPrivateKey(d, f.options)
}, new RegExp(f.exception))
})
})
})
ECPair.fromPrivateKey(d, (f as any).options);
}, new RegExp(f.exception));
});
});
});
describe('fromPublicKey', () => {
fixtures.invalid.fromPublicKey.forEach(f => {
it('throws ' + f.exception, () => {
const Q = Buffer.from(f.Q, 'hex')
const Q = Buffer.from(f.Q, 'hex');
assert.throws(() => {
ECPair.fromPublicKey(Q, f.options)
}, new RegExp(f.exception))
})
})
})
ECPair.fromPublicKey(Q, (f as any).options);
}, new RegExp(f.exception));
});
});
});
describe('fromWIF', () => {
fixtures.valid.forEach(f => {
it('imports ' + f.WIF + ' (' + f.network + ')', () => {
const network = NETWORKS[f.network]
const keyPair = ECPair.fromWIF(f.WIF, network)
const network = (NETWORKS as any)[f.network];
const keyPair = ECPair.fromWIF(f.WIF, network);
assert.strictEqual(keyPair.privateKey.toString('hex'), f.d)
assert.strictEqual(keyPair.compressed, f.compressed)
assert.strictEqual(keyPair.network, network)
})
})
assert.strictEqual(keyPair.privateKey!.toString('hex'), f.d);
assert.strictEqual(keyPair.compressed, f.compressed);
assert.strictEqual(keyPair.network, network);
});
});
fixtures.valid.forEach(f => {
it('imports ' + f.WIF + ' (via list of networks)', () => {
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST);
assert.strictEqual(keyPair.privateKey.toString('hex'), f.d)
assert.strictEqual(keyPair.compressed, f.compressed)
assert.strictEqual(keyPair.network, NETWORKS[f.network])
})
})
assert.strictEqual(keyPair.privateKey!.toString('hex'), f.d);
assert.strictEqual(keyPair.compressed, f.compressed);
assert.strictEqual(keyPair.network, (NETWORKS as any)[f.network]);
});
});
fixtures.invalid.fromWIF.forEach(f => {
it('throws on ' + f.WIF, () => {
assert.throws(() => {
const networks = f.network ? NETWORKS[f.network] : NETWORKS_LIST
const networks = f.network
? (NETWORKS as any)[f.network]
: NETWORKS_LIST;
ECPair.fromWIF(f.WIF, networks)
}, new RegExp(f.exception))
})
})
})
ECPair.fromWIF(f.WIF, networks);
}, new RegExp(f.exception));
});
});
});
describe('toWIF', () => {
fixtures.valid.forEach(f => {
it('exports ' + f.WIF, () => {
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
const result = keyPair.toWIF()
assert.strictEqual(result, f.WIF)
})
})
})
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST);
const result = keyPair.toWIF();
assert.strictEqual(result, f.WIF);
});
});
});
describe('makeRandom', () => {
const d = Buffer.alloc(32, 4)
const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv'
const d = Buffer.alloc(32, 4);
const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv';
describe('uses randombytes RNG', () => {
it('generates a ECPair', () => {
const stub = { randombytes: () => { return d } }
const ProxiedECPair = proxyquire('../src/ecpair', stub)
const stub = {
randombytes: (): Buffer => {
return d;
},
};
const ProxiedECPair = proxyquire('../src/ecpair', stub);
const keyPair = ProxiedECPair.makeRandom()
assert.strictEqual(keyPair.toWIF(), exWIF)
})
})
const keyPair = ProxiedECPair.makeRandom();
assert.strictEqual(keyPair.toWIF(), exWIF);
});
});
it('allows a custom RNG to be used', () => {
const keyPair = ECPair.makeRandom({
rng: size => { return d.slice(0, size) }
})
rng: (size): Buffer => {
return d.slice(0, size);
},
});
assert.strictEqual(keyPair.toWIF(), exWIF)
})
assert.strictEqual(keyPair.toWIF(), exWIF);
});
it('retains the same defaults as ECPair constructor', () => {
const keyPair = ECPair.makeRandom()
const keyPair = ECPair.makeRandom();
assert.strictEqual(keyPair.compressed, true)
assert.strictEqual(keyPair.network, NETWORKS.bitcoin)
})
assert.strictEqual(keyPair.compressed, true);
assert.strictEqual(keyPair.network, NETWORKS.bitcoin);
});
it('supports the options parameter', () => {
const keyPair = ECPair.makeRandom({
compressed: false,
network: NETWORKS.testnet
})
network: NETWORKS.testnet,
});
assert.strictEqual(keyPair.compressed, false)
assert.strictEqual(keyPair.network, NETWORKS.testnet)
})
assert.strictEqual(keyPair.compressed, false);
assert.strictEqual(keyPair.network, NETWORKS.testnet);
});
it('throws if d is bad length', () => {
function rng () {
return Buffer.alloc(28)
function rng(): Buffer {
return Buffer.alloc(28);
}
assert.throws(() => {
ECPair.makeRandom({ rng: rng })
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 28\)/)
})
ECPair.makeRandom({ rng });
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 28\)/);
});
it('loops until d is within interval [1, n) : 1', hoodwink(function () {
it(
'loops until d is within interval [1, n) : 1',
hoodwink(function(this: any): void {
const rng = this.stub(() => {
if (rng.calls === 0) return ZERO // 0
return ONE // >0
}, 2)
if (rng.calls === 0) return ZERO; // 0
return ONE; // >0
}, 2);
ECPair.makeRandom({ rng: rng })
}))
ECPair.makeRandom({ rng });
}),
);
it('loops until d is within interval [1, n) : n - 1', hoodwink(function () {
it(
'loops until d is within interval [1, n) : n - 1',
hoodwink(function(this: any): void {
const rng = this.stub(() => {
if (rng.calls === 0) return ZERO // <1
if (rng.calls === 1) return GROUP_ORDER // >n-1
return GROUP_ORDER_LESS_1 // n-1
}, 3)
if (rng.calls === 0) return ZERO; // <1
if (rng.calls === 1) return GROUP_ORDER; // >n-1
return GROUP_ORDER_LESS_1; // n-1
}, 3);
ECPair.makeRandom({ rng: rng })
}))
})
ECPair.makeRandom({ rng });
}),
);
});
describe('.network', () => {
fixtures.valid.forEach(f => {
it('returns ' + f.network + ' for ' + f.WIF, () => {
const network = NETWORKS[f.network]
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
const network = (NETWORKS as any)[f.network];
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST);
assert.strictEqual(keyPair.network, network)
})
})
})
assert.strictEqual(keyPair.network, network);
});
});
});
describe('tinysecp wrappers', () => {
let keyPair
let hash
let signature
let keyPair: ECPairInterface;
let hash: Buffer;
let signature: Buffer;
beforeEach(() => {
keyPair = ECPair.makeRandom()
hash = ZERO
signature = Buffer.alloc(64, 1)
})
keyPair = ECPair.makeRandom();
hash = ZERO;
signature = Buffer.alloc(64, 1);
});
describe('signing', () => {
it('wraps tinysecp.sign', hoodwink(function () {
this.mock(tinysecp, 'sign', (h, d) => {
assert.strictEqual(h, hash)
assert.strictEqual(d, keyPair.privateKey)
return signature
}, 1)
it(
'wraps tinysecp.sign',
hoodwink(function(this: any): void {
this.mock(
tinysecp,
'sign',
(h: any, d: any) => {
assert.strictEqual(h, hash);
assert.strictEqual(d, keyPair.privateKey);
return signature;
},
1,
);
assert.strictEqual(keyPair.sign(hash), signature)
}))
assert.strictEqual(keyPair.sign(hash), signature);
}),
);
it('throws if no private key is found', () => {
delete keyPair.__D
delete (keyPair as any).__D;
assert.throws(() => {
keyPair.sign(hash)
}, /Missing private key/)
})
})
keyPair.sign(hash);
}, /Missing private key/);
});
});
describe('verify', () => {
it('wraps tinysecp.verify', hoodwink(function () {
this.mock(tinysecp, 'verify', (h, q, s) => {
assert.strictEqual(h, hash)
assert.strictEqual(q, keyPair.publicKey)
assert.strictEqual(s, signature)
return true
}, 1)
it(
'wraps tinysecp.verify',
hoodwink(function(this: any): void {
this.mock(
tinysecp,
'verify',
(h: any, q: any, s: any) => {
assert.strictEqual(h, hash);
assert.strictEqual(q, keyPair.publicKey);
assert.strictEqual(s, signature);
return true;
},
1,
);
assert.strictEqual(keyPair.verify(hash, signature), true)
}))
})
})
assert.strictEqual(keyPair.verify(hash, signature), true);
}),
);
});
});
describe('optional low R signing', () => {
const sig = Buffer.from('95a6619140fca3366f1d3b013b0367c4f86e39508a50fdce' +
const sig = Buffer.from(
'95a6619140fca3366f1d3b013b0367c4f86e39508a50fdce' +
'e5245fbb8bd60aa6086449e28cf15387cf9f85100bfd0838624ca96759e59f65c10a00' +
'16b86f5229', 'hex')
const sigLowR = Buffer.from('6a2660c226e8055afad317eeba918a304be79208d505' +
'16b86f5229',
'hex',
);
const sigLowR = Buffer.from(
'6a2660c226e8055afad317eeba918a304be79208d505' +
'3bc5ea4a5e4c5892b4a061c717c5284ae5202d721c0e49b4717b79966280906b1d3b52' +
'95d1fdde963c35', 'hex')
const lowRKeyPair = ECPair.fromWIF('L3nThUzbAwpUiBAjR5zCu66ybXSPMr2zZ3ikp' +
'ScpTPiYTxBynfZu')
const dataToSign = Buffer.from('b6c5c548a7f6164c8aa7af5350901626ebd69f9ae' +
'2c1ecf8871f5088ec204cfe', 'hex')
'95d1fdde963c35',
'hex',
);
const lowRKeyPair = ECPair.fromWIF(
'L3nThUzbAwpUiBAjR5zCu66ybXSPMr2zZ3ikp' + 'ScpTPiYTxBynfZu',
);
const dataToSign = Buffer.from(
'b6c5c548a7f6164c8aa7af5350901626ebd69f9ae' + '2c1ecf8871f5088ec204cfe',
'hex',
);
it('signs with normal R by default', () => {
const signed = lowRKeyPair.sign(dataToSign)
assert.deepStrictEqual(sig, signed)
})
const signed = lowRKeyPair.sign(dataToSign);
assert.deepStrictEqual(sig, signed);
});
it('signs with low R when true is passed', () => {
const signed = lowRKeyPair.sign(dataToSign, true)
assert.deepStrictEqual(sigLowR, signed)
})
})
})
const signed = lowRKeyPair.sign(dataToSign, true);
assert.deepStrictEqual(sigLowR, signed);
});
});
});

View file

@ -1,8 +0,0 @@
const { RegtestUtils } = require('regtest-client')
const APIPASS = process.env.APIPASS || 'satoshi'
const APIURL = process.env.APIURL || 'https://regtest.bitbank.cc/1'
const regtestUtils = new RegtestUtils({ APIPASS, APIURL })
module.exports = regtestUtils;

View file

@ -0,0 +1,6 @@
import { RegtestUtils } from 'regtest-client';
const APIPASS = process.env.APIPASS || 'satoshi';
const APIURL = process.env.APIURL || 'https://regtest.bitbank.cc/1';
export const regtestUtils = new RegtestUtils({ APIPASS, APIURL });

View file

@ -1,117 +1,137 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const dhttp = require('./_regtest').dhttp
const TESTNET = bitcoin.networks.testnet
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
const dhttp = regtestUtils.dhttp;
const TESTNET = bitcoin.networks.testnet;
describe('bitcoinjs-lib (addresses)', () => {
it('can generate a random address [and support the retrieval of transactions for that address (via 3PBP)', async () => {
const keyPair = bitcoin.ECPair.makeRandom()
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
const keyPair = bitcoin.ECPair.makeRandom();
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey });
// bitcoin P2PKH addresses start with a '1'
assert.strictEqual(address.startsWith('1'), true)
assert.strictEqual(address!.startsWith('1'), true);
const result = await dhttp({
method: 'GET',
url: 'https://blockchain.info/rawaddr/' + address
})
url: 'https://blockchain.info/rawaddr/' + address,
});
// random private keys [probably!] have no transactions
assert.strictEqual(result.n_tx, 0)
assert.strictEqual(result.total_received, 0)
assert.strictEqual(result.total_sent, 0)
})
assert.strictEqual((result as any).n_tx, 0);
assert.strictEqual((result as any).total_received, 0);
assert.strictEqual((result as any).total_sent, 0);
});
it('can import an address via WIF', () => {
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
const keyPair = bitcoin.ECPair.fromWIF(
'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn',
);
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey });
assert.strictEqual(address, '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH')
})
assert.strictEqual(address, '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH');
});
it('can generate a P2SH, pay-to-multisig (2-of-3) address', () => {
const pubkeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9'
].map((hex) => Buffer.from(hex, 'hex'))
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9',
].map(hex => Buffer.from(hex, 'hex'));
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys })
})
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys }),
});
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
})
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7');
});
it('can generate a SegWit address', () => {
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
const keyPair = bitcoin.ECPair.fromWIF(
'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn',
);
const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey });
assert.strictEqual(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4')
})
assert.strictEqual(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4');
});
it('can generate a SegWit address (via P2SH)', () => {
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
const keyPair = bitcoin.ECPair.fromWIF(
'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn',
);
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
})
redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }),
});
assert.strictEqual(address, '3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN')
})
assert.strictEqual(address, '3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN');
});
it('can generate a P2WSH (SegWit), pay-to-multisig (3-of-4) address', () => {
const pubkeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
'023e4740d0ba639e28963f3476157b7cf2fb7c6fdf4254f97099cf8670b505ea59',
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9'
].map((hex) => Buffer.from(hex, 'hex'))
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9',
].map(hex => Buffer.from(hex, 'hex'));
const { address } = bitcoin.payments.p2wsh({
redeem: bitcoin.payments.p2ms({ m: 3, pubkeys })
})
redeem: bitcoin.payments.p2ms({ m: 3, pubkeys }),
});
assert.strictEqual(address, 'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul')
})
assert.strictEqual(
address,
'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul',
);
});
it('can generate a P2SH(P2WSH(...)), pay-to-multisig (2-of-2) address', () => {
const pubkeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9'
].map((hex) => Buffer.from(hex, 'hex'))
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
].map(hex => Buffer.from(hex, 'hex'));
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wsh({
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys })
})
})
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys }),
}),
});
assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN')
})
assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN');
});
// examples using other network information
it('can generate a Testnet address', () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: TESTNET })
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: TESTNET })
const keyPair = bitcoin.ECPair.makeRandom({ network: TESTNET });
const { address } = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
network: TESTNET,
});
// bitcoin testnet P2PKH addresses start with a 'm' or 'n'
assert.strictEqual(address.startsWith('m') || address.startsWith('n'), true)
})
assert.strictEqual(
address!.startsWith('m') || address!.startsWith('n'),
true,
);
});
it('can generate a Litecoin address', () => {
// WARNING: although possible, bitcoinjs is NOT necessarily compatible with Litecoin
const LITECOIN = {
messagePrefix: '\x19Litecoin Signed Message:\n',
bech32: 'ltc',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
private: 0x019d9cfe,
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0
}
wif: 0xb0,
};
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN })
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: LITECOIN })
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN });
const { address } = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
network: LITECOIN,
});
assert.strictEqual(address.startsWith('L'), true)
})
})
assert.strictEqual(address!.startsWith('L'), true);
});
});

View file

@ -1,101 +1,151 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bip32 = require('bip32')
const bip39 = require('bip39')
const bitcoin = require('../../')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bip32 from 'bip32';
import * as bip39 from 'bip39';
import * as bitcoin from '../..';
function getAddress (node, network) {
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address
function getAddress(node: any, network?: any): string {
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address!;
}
describe('bitcoinjs-lib (BIP32)', () => {
it('can import a BIP32 testnet xpriv and export to WIF', () => {
const xpriv = 'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK'
const node = bip32.fromBase58(xpriv, bitcoin.networks.testnet)
const xpriv =
'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK';
const node = bip32.fromBase58(xpriv, bitcoin.networks.testnet);
assert.strictEqual(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7')
})
assert.strictEqual(
node.toWIF(),
'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7',
);
});
it('can export a BIP32 xpriv, then import it', () => {
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
const seed = bip39.mnemonicToSeed(mnemonic)
const node = bip32.fromSeed(seed)
const string = node.toBase58()
const restored = bip32.fromBase58(string)
const mnemonic =
'praise you muffin lion enable neck grocery crumble super myself license ghost';
const seed = bip39.mnemonicToSeedSync(mnemonic);
const node = bip32.fromSeed(seed);
const string = node.toBase58();
const restored = bip32.fromBase58(string);
assert.strictEqual(getAddress(node), getAddress(restored)) // same public key
assert.strictEqual(node.toWIF(), restored.toWIF()) // same private key
})
assert.strictEqual(getAddress(node), getAddress(restored)); // same public key
assert.strictEqual(node.toWIF(), restored.toWIF()); // same private key
});
it('can export a BIP32 xpub', () => {
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
const seed = bip39.mnemonicToSeed(mnemonic)
const node = bip32.fromSeed(seed)
const string = node.neutered().toBase58()
const mnemonic =
'praise you muffin lion enable neck grocery crumble super myself license ghost';
const seed = bip39.mnemonicToSeedSync(mnemonic);
const node = bip32.fromSeed(seed);
const string = node.neutered().toBase58();
assert.strictEqual(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n')
})
assert.strictEqual(
string,
'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n',
);
});
it('can create a BIP32, bitcoin, account 0, external address', () => {
const path = "m/0'/0/0"
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
const path = "m/0'/0/0";
const root = bip32.fromSeed(
Buffer.from(
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
'hex',
),
);
const child1 = root.derivePath(path)
const child1 = root.derivePath(path);
// option 2, manually
const child1b = root.deriveHardened(0)
.derive(0)
const child1b = root
.deriveHardened(0)
.derive(0)
.derive(0);
assert.strictEqual(getAddress(child1), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
assert.strictEqual(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
})
assert.strictEqual(
getAddress(child1),
'1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7',
);
assert.strictEqual(
getAddress(child1b),
'1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7',
);
});
it('can create a BIP44, bitcoin, account 0, external address', () => {
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
const root = bip32.fromSeed(
Buffer.from(
'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
'hex',
),
);
const child1 = root.derivePath("m/44'/0'/0'/0/0")
const child1 = root.derivePath("m/44'/0'/0'/0/0");
// option 2, manually
const child1b = root.deriveHardened(44)
const child1b = root
.deriveHardened(44)
.deriveHardened(0)
.deriveHardened(0)
.derive(0)
.derive(0)
.derive(0);
assert.strictEqual(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
assert.strictEqual(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
})
assert.strictEqual(
getAddress(child1),
'12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au',
);
assert.strictEqual(
getAddress(child1b),
'12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au',
);
});
it('can create a BIP49, bitcoin testnet, account 0, external address', () => {
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
const seed = bip39.mnemonicToSeed(mnemonic)
const root = bip32.fromSeed(seed)
const mnemonic =
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const seed = bip39.mnemonicToSeedSync(mnemonic);
const root = bip32.fromSeed(seed);
const path = "m/49'/1'/0'/0/0"
const child = root.derivePath(path)
const path = "m/49'/1'/0'/0/0";
const child = root.derivePath(path);
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }),
network: bitcoin.networks.testnet
})
assert.strictEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2')
})
redeem: bitcoin.payments.p2wpkh({
pubkey: child.publicKey,
network: bitcoin.networks.testnet,
}),
network: bitcoin.networks.testnet,
});
assert.strictEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2');
});
it('can use BIP39 to generate BIP32 addresses', () => {
// var mnemonic = bip39.generateMnemonic()
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
assert(bip39.validateMnemonic(mnemonic))
const mnemonic =
'praise you muffin lion enable neck grocery crumble super myself license ghost';
assert(bip39.validateMnemonic(mnemonic));
const seed = bip39.mnemonicToSeed(mnemonic)
const root = bip32.fromSeed(seed)
const seed = bip39.mnemonicToSeedSync(mnemonic);
const root = bip32.fromSeed(seed);
// receive addresses
assert.strictEqual(getAddress(root.derivePath("m/0'/0/0")), '1AVQHbGuES57wD68AJi7Gcobc3RZrfYWTC')
assert.strictEqual(getAddress(root.derivePath("m/0'/0/1")), '1Ad6nsmqDzbQo5a822C9bkvAfrYv9mc1JL')
assert.strictEqual(
getAddress(root.derivePath("m/0'/0/0")),
'1AVQHbGuES57wD68AJi7Gcobc3RZrfYWTC',
);
assert.strictEqual(
getAddress(root.derivePath("m/0'/0/1")),
'1Ad6nsmqDzbQo5a822C9bkvAfrYv9mc1JL',
);
// change addresses
assert.strictEqual(getAddress(root.derivePath("m/0'/1/0")), '1349KVc5NgedaK7DvuD4xDFxL86QN1Hvdn')
assert.strictEqual(getAddress(root.derivePath("m/0'/1/1")), '1EAvj4edpsWcSer3duybAd4KiR4bCJW5J6')
})
})
assert.strictEqual(
getAddress(root.derivePath("m/0'/1/0")),
'1349KVc5NgedaK7DvuD4xDFxL86QN1Hvdn',
);
assert.strictEqual(
getAddress(root.derivePath("m/0'/1/1")),
'1EAvj4edpsWcSer3duybAd4KiR4bCJW5J6',
);
});
});

View file

@ -1,22 +1,21 @@
'use strict'
const { describe, it } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bitcoin from '../..';
describe('bitcoinjs-lib (blocks)', () => {
it('can extract a height from a CoinBase transaction', () => {
// from 00000000000000000097669cdca131f24d40c4cc7d80eaa65967a2d09acf6ce6
const txHex = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000'
const tx = bitcoin.Transaction.fromHex(txHex)
const txHex =
'010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000';
const tx = bitcoin.Transaction.fromHex(txHex);
assert.strictEqual(tx.ins.length, 1)
const script = tx.ins[0].script
assert.strictEqual(tx.ins.length, 1);
const script = tx.ins[0].script;
// bitcoin.script.decompile(script) // returns [] :(
assert.strictEqual(script[0], 0x03)
const heightBuffer = script.slice(1, 4)
const height = bitcoin.script.number.decode(heightBuffer)
assert.strictEqual(height, 498303)
})
})
assert.strictEqual(script[0], 0x03);
const heightBuffer = script.slice(1, 4);
const height = bitcoin.script.number.decode(heightBuffer);
assert.strictEqual(height, 498303);
});
});

View file

@ -1,198 +1,219 @@
const { describe, it, before } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const regtestUtils = require('./_regtest')
const regtest = regtestUtils.network
const bip65 = require('bip65')
import * as assert from 'assert';
import { before, describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
const regtest = regtestUtils.network;
const bip65 = require('bip65');
const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
console.warn = () => {} // Silence the Deprecation Warning
const alice = bitcoin.ECPair.fromWIF(
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
regtest,
);
const bob = bitcoin.ECPair.fromWIF(
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x',
regtest,
);
console.warn = () => {}; // Silence the Deprecation Warning
describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
// force update MTP
before(async () => {
await regtestUtils.mine(11)
})
await regtestUtils.mine(11);
});
const hashType = bitcoin.Transaction.SIGHASH_ALL
const hashType = bitcoin.Transaction.SIGHASH_ALL;
function cltvCheckSigOutput (aQ, bQ, lockTime) {
return bitcoin.script.compile([
bitcoin.opcodes.OP_IF,
bitcoin.script.number.encode(lockTime),
bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
bitcoin.opcodes.OP_DROP,
bitcoin.opcodes.OP_ELSE,
bQ.publicKey,
bitcoin.opcodes.OP_CHECKSIGVERIFY,
bitcoin.opcodes.OP_ENDIF,
aQ.publicKey,
bitcoin.opcodes.OP_CHECKSIG
])
type keyPair = { publicKey: Buffer };
function cltvCheckSigOutput(aQ: keyPair, bQ: keyPair, lockTime: number) {
return bitcoin.script.fromASM(
`
OP_IF
${bitcoin.script.number.encode(lockTime).toString('hex')}
OP_CHECKLOCKTIMEVERIFY
OP_DROP
OP_ELSE
${bQ.publicKey.toString('hex')}
OP_CHECKSIGVERIFY
OP_ENDIF
${aQ.publicKey.toString('hex')}
OP_CHECKSIG
`
.trim()
.replace(/\s+/g, ' '),
);
}
function utcNow() {
return Math.floor(Date.now() / 1000)
return Math.floor(Date.now() / 1000);
}
// expiry past, {Alice's signature} OP_TRUE
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)', async () => {
// 3 hours ago
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
const lockTime = bip65.encode({ utc: utcNow() - 3600 * 3 });
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
const { address } = bitcoin.payments.p2sh({
redeem: { output: redeemScript, network: regtest },
network: regtest,
});
// fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address, 1e5)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
const unspent = await regtestUtils.faucet(address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest);
txb.setLockTime(lockTime);
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
bitcoin.opcodes.OP_TRUE,
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
output: redeemScript,
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
value: 7e4,
});
});
// expiry will pass, {Alice's signature} OP_TRUE
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', async () => {
const height = await regtestUtils.height()
const height = await regtestUtils.height();
// 5 blocks from now
const lockTime = bip65.encode({ blocks: height + 5 })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
const lockTime = bip65.encode({ blocks: height + 5 });
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
const { address } = bitcoin.payments.p2sh({
redeem: { output: redeemScript, network: regtest },
network: regtest,
});
// fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address, 1e5)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
const unspent = await regtestUtils.faucet(address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest);
txb.setLockTime(lockTime);
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
bitcoin.opcodes.OP_TRUE,
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
output: redeemScript,
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
await regtestUtils.mine(5)
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.mine(5);
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
value: 7e4,
});
});
// expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE
it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', async () => {
// two hours ago
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 2) })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
const lockTime = bip65.encode({ utc: utcNow() - 3600 * 2 });
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
const { address } = bitcoin.payments.p2sh({
redeem: { output: redeemScript, network: regtest },
network: regtest,
});
// fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address, 2e5)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
const unspent = await regtestUtils.faucet(address!, 2e5);
const txb = new bitcoin.TransactionBuilder(regtest);
txb.setLockTime(lockTime);
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4)
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4);
// {Alice's signature} {Bob's signature} OP_FALSE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_FALSE
bitcoin.opcodes.OP_FALSE,
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
output: redeemScript,
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 8e4
})
})
value: 8e4,
});
});
// expiry in the future, {Alice's signature} OP_TRUE
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', async () => {
// two hours from now
const lockTime = bip65.encode({ utc: utcNow() + (3600 * 2) })
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
const lockTime = bip65.encode({ utc: utcNow() + 3600 * 2 });
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime);
const { address } = bitcoin.payments.p2sh({
redeem: { output: redeemScript, network: regtest },
network: regtest,
});
// fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address, 2e4)
const txb = new bitcoin.TransactionBuilder(regtest)
txb.setLockTime(lockTime)
const unspent = await regtestUtils.faucet(address!, 2e4);
const txb = new bitcoin.TransactionBuilder(regtest);
txb.setLockTime(lockTime);
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
bitcoin.opcodes.OP_TRUE,
]),
output: redeemScript
}
}).input
tx.setInputScript(0, redeemScriptSig)
output: redeemScript,
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
await regtestUtils.broadcast(tx.toHex()).catch(err => {
assert.throws(() => {
if (err) throw err
}, /Error: non-final \(code 64\)/)
})
})
})
if (err) throw err;
}, /Error: non-final \(code 64\)/);
});
});
});

View file

@ -1,27 +1,41 @@
const { describe, it, before } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const regtestUtils = require('./_regtest')
const regtest = regtestUtils.network
const bip68 = require('bip68')
import * as assert from 'assert';
import { before, describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
const regtest = regtestUtils.network;
const bip68 = require('bip68');
const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
const charles = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf', regtest)
const dave = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest)
console.warn = () => {} // Silence the Deprecation Warning
const alice = bitcoin.ECPair.fromWIF(
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
regtest,
);
const bob = bitcoin.ECPair.fromWIF(
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x',
regtest,
);
const charles = bitcoin.ECPair.fromWIF(
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf',
regtest,
);
const dave = bitcoin.ECPair.fromWIF(
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx',
regtest,
);
console.warn = () => {}; // Silence the Deprecation Warning
describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// force update MTP
before(async () => {
await regtestUtils.mine(11)
})
await regtestUtils.mine(11);
});
const hashType = bitcoin.Transaction.SIGHASH_ALL
const hashType = bitcoin.Transaction.SIGHASH_ALL;
type keyPair = { publicKey: Buffer };
// IF MTP (from when confirmed) > seconds, _alice can redeem
function csvCheckSigOutput (_alice, _bob, sequence) {
return bitcoin.script.fromASM(`
function csvCheckSigOutput(_alice: keyPair, _bob: keyPair, sequence: number) {
return bitcoin.script.fromASM(
`
OP_IF
${bitcoin.script.number.encode(sequence).toString('hex')}
OP_CHECKSEQUENCEVERIFY
@ -32,7 +46,10 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
OP_ENDIF
${_alice.publicKey.toString('hex')}
OP_CHECKSIG
`.trim().replace(/\s+/g, ' '))
`
.trim()
.replace(/\s+/g, ' '),
);
}
// 2 of 3 multisig of _bob, _charles, _dave,
@ -41,8 +58,16 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// Ref: https://github.com/bitcoinbook/bitcoinbook/blob/f8b883dcd4e3d1b9adf40fed59b7e898fbd9241f/ch07.asciidoc#complex-script-example
// Note: bitcoinjs-lib will not offer specific support for problems with
// advanced script usages such as below. Use at your own risk.
function complexCsvOutput (_alice, _bob, _charles, _dave, sequence1, sequence2) {
return bitcoin.script.fromASM(`
function complexCsvOutput(
_alice: keyPair,
_bob: keyPair,
_charles: keyPair,
_dave: keyPair,
sequence1: number,
sequence2: number,
) {
return bitcoin.script.fromASM(
`
OP_IF
OP_IF
OP_2
@ -66,253 +91,294 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
${_alice.publicKey.toString('hex')}
OP_CHECKSIG
OP_ENDIF
`.trim().replace(/\s+/g, ' '))
`
.trim()
.replace(/\s+/g, ' '),
);
}
// expiry will pass, {Alice's signature} OP_TRUE
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)', async () => {
// 5 blocks from now
const sequence = bip68.encode({ blocks: 5 })
const sequence = bip68.encode({ blocks: 5 });
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: csvCheckSigOutput(alice, bob, sequence)
output: csvCheckSigOutput(alice, bob, sequence),
},
network: regtest
})
network: regtest,
});
// fund the P2SH(CSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, sequence);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(
0,
p2sh.redeem!.output!,
hashType,
);
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
output: p2sh.redeem!.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
bitcoin.opcodes.OP_TRUE,
]),
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
await regtestUtils.mine(10)
await regtestUtils.mine(10);
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
value: 7e4,
});
});
// expiry in the future, {Alice's signature} OP_TRUE
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', async () => {
// two hours after confirmation
const sequence = bip68.encode({ seconds: 7168 })
const sequence = bip68.encode({ seconds: 7168 });
const p2sh = bitcoin.payments.p2sh({
network: regtest,
redeem: {
output: csvCheckSigOutput(alice, bob, sequence)
}
})
output: csvCheckSigOutput(alice, bob, sequence),
},
});
// fund the P2SH(CSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 2e4)
const unspent = await regtestUtils.faucet(p2sh.address!, 2e4);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, sequence);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
// {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(
0,
p2sh.redeem!.output!,
hashType,
);
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
output: p2sh.redeem!.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
bitcoin.opcodes.OP_TRUE,
]),
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
await regtestUtils.broadcast(tx.toHex()).catch(err => {
assert.throws(() => {
if (err) throw err
}, /Error: non-BIP68-final \(code 64\)/)
})
})
if (err) throw err;
}, /Error: non-BIP68-final \(code 64\)/);
});
});
// Check first combination of complex CSV, 2 of 3
it('can create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)', async () => {
const height = await regtestUtils.height()
// 2 blocks from now
const sequence1 = bip68.encode({ blocks: 2 })
const sequence1 = bip68.encode({ blocks: 2 });
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const sequence2 = bip68.encode({ blocks: 5 });
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
output: complexCsvOutput(
alice,
bob,
charles,
dave,
sequence1,
sequence2,
),
},
network: regtest
})
network: regtest,
});
// fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(
0,
p2sh.redeem!.output!,
hashType,
);
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
output: p2sh.redeem!.output,
input: bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.script.signature.encode(charles.sign(signatureHash), hashType),
bitcoin.script.signature.encode(
charles.sign(signatureHash),
hashType,
),
bitcoin.opcodes.OP_TRUE,
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
bitcoin.opcodes.OP_TRUE,
]),
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
value: 7e4,
});
});
// Check first combination of complex CSV, mediator + 1 of 3 after 2 blocks
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)', async () => {
const height = await regtestUtils.height()
// 2 blocks from now
const sequence1 = bip68.encode({ blocks: 2 })
const sequence1 = bip68.encode({ blocks: 2 });
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const sequence2 = bip68.encode({ blocks: 5 });
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
output: complexCsvOutput(
alice,
bob,
charles,
dave,
sequence1,
sequence2,
),
},
network: regtest
})
network: regtest,
});
// fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence1) // Set sequence1 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, sequence1); // Set sequence1 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(
0,
p2sh.redeem!.output!,
hashType,
);
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
output: p2sh.redeem!.output,
input: bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_0,
bitcoin.opcodes.OP_TRUE
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
bitcoin.opcodes.OP_TRUE,
]),
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
// Wait 2 blocks
await regtestUtils.mine(2)
await regtestUtils.mine(2);
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
value: 7e4,
});
});
// Check first combination of complex CSV, mediator after 5 blocks
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)', async () => {
const height = await regtestUtils.height()
// 2 blocks from now
const sequence1 = bip68.encode({ blocks: 2 })
const sequence1 = bip68.encode({ blocks: 2 });
// 5 blocks from now
const sequence2 = bip68.encode({ blocks: 5 })
const sequence2 = bip68.encode({ blocks: 5 });
const p2sh = bitcoin.payments.p2sh({
redeem: {
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
output: complexCsvOutput(
alice,
bob,
charles,
dave,
sequence1,
sequence2,
),
},
network: regtest
})
network: regtest,
});
// fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, sequence2) // Set sequence2 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, sequence2); // Set sequence2 for input
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4);
// {Alice mediator sig} OP_FALSE
const tx = txb.buildIncomplete()
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(
0,
p2sh.redeem!.output!,
hashType,
);
const redeemScriptSig = bitcoin.payments.p2sh({
network: regtest,
redeem: {
network: regtest,
output: p2sh.redeem.output,
output: p2sh.redeem!.output,
input: bitcoin.script.compile([
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
bitcoin.opcodes.OP_0
])
}
}).input
tx.setInputScript(0, redeemScriptSig)
bitcoin.opcodes.OP_0,
]),
},
}).input;
tx.setInputScript(0, redeemScriptSig!);
// Wait 5 blocks
await regtestUtils.mine(5)
await regtestUtils.mine(5);
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 7e4
})
})
})
value: 7e4,
});
});
});

View file

@ -1,23 +1,27 @@
const bitcoin = require('../../')
const { describe, it } = require('mocha')
const regtestUtils = require('./_regtest')
const NETWORK = regtestUtils.network
import { describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
const NETWORK = regtestUtils.network;
const keyPairs = [
bitcoin.ECPair.makeRandom({ network: NETWORK }),
bitcoin.ECPair.makeRandom({ network: NETWORK })
]
console.warn = () => {} // Silence the Deprecation Warning
bitcoin.ECPair.makeRandom({ network: NETWORK }),
];
console.warn = () => {}; // Silence the Deprecation Warning
async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4)
async function buildAndSign(
depends: any,
prevOutput: any,
redeemScript: any,
witnessScript: any,
) {
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4);
const txb = new bitcoin.TransactionBuilder(NETWORK)
txb.addInput(unspent.txId, unspent.vout, null, prevOutput)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
const txb = new bitcoin.TransactionBuilder(NETWORK);
txb.addInput(unspent.txId, unspent.vout, undefined, prevOutput);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
const posType = depends.prevOutScriptType
const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh'
const posType = depends.prevOutScriptType;
const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh';
if (depends.signatures) {
keyPairs.forEach(keyPair => {
@ -28,8 +32,8 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
redeemScript,
witnessValue: needsValue ? unspent.value : undefined,
witnessScript,
})
})
});
});
} else if (depends.signature) {
txb.sign({
prevOutScriptType: posType,
@ -38,52 +42,94 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
redeemScript,
witnessValue: needsValue ? unspent.value : undefined,
witnessScript,
})
});
}
return regtestUtils.broadcast(txb.build().toHex())
return regtestUtils.broadcast(txb.build().toHex());
}
;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
const fixtures = require('../fixtures/' + k)
const { depends } = fixtures.dynamic
const fn = bitcoin.payments[k]
['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
const fixtures = require('../fixtures/' + k);
const { depends } = fixtures.dynamic;
const fn: any = (bitcoin.payments as any)[k];
const base = {}
if (depends.pubkey) base.pubkey = keyPairs[0].publicKey
if (depends.pubkeys) base.pubkeys = keyPairs.map(x => x.publicKey)
if (depends.m) base.m = base.pubkeys.length
const base: any = {};
if (depends.pubkey) base.pubkey = keyPairs[0].publicKey;
if (depends.pubkeys) base.pubkeys = keyPairs.map(x => x.publicKey);
if (depends.m) base.m = base.pubkeys.length;
const { output } = fn(base)
if (!output) throw new TypeError('Missing output')
const { output } = fn(base);
if (!output) throw new TypeError('Missing output');
describe('bitcoinjs-lib (payments - ' + k + ')', () => {
it('can broadcast as an output, and be spent as an input', async () => {
Object.assign(depends, { prevOutScriptType: k })
await buildAndSign(depends, output, undefined, undefined)
})
Object.assign(depends, { prevOutScriptType: k });
await buildAndSign(depends, output, undefined, undefined);
});
it('can (as P2SH(' + k + ')) broadcast as an output, and be spent as an input', async () => {
const p2sh = bitcoin.payments.p2sh({ redeem: { output }, network: NETWORK })
Object.assign(depends, { prevOutScriptType: 'p2sh-' + k })
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, undefined)
})
it(
'can (as P2SH(' +
k +
')) broadcast as an output, and be spent as an input',
async () => {
const p2sh = bitcoin.payments.p2sh({
redeem: { output },
network: NETWORK,
});
Object.assign(depends, { prevOutScriptType: 'p2sh-' + k });
await buildAndSign(
depends,
p2sh.output,
p2sh.redeem!.output,
undefined,
);
},
);
// NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail
if (k === 'p2wpkh') return
if (k === 'p2wpkh') return;
it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', async () => {
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
Object.assign(depends, { prevOutScriptType: 'p2wsh-' + k })
await buildAndSign(depends, p2wsh.output, undefined, p2wsh.redeem.output)
})
it(
'can (as P2WSH(' +
k +
')) broadcast as an output, and be spent as an input',
async () => {
const p2wsh = bitcoin.payments.p2wsh({
redeem: { output },
network: NETWORK,
});
Object.assign(depends, { prevOutScriptType: 'p2wsh-' + k });
await buildAndSign(
depends,
p2wsh.output,
undefined,
p2wsh.redeem!.output,
);
},
);
it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', async () => {
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK })
it(
'can (as P2SH(P2WSH(' +
k +
'))) broadcast as an output, and be spent as an input',
async () => {
const p2wsh = bitcoin.payments.p2wsh({
redeem: { output },
network: NETWORK,
});
const p2sh = bitcoin.payments.p2sh({
redeem: { output: p2wsh.output },
network: NETWORK,
});
Object.assign(depends, { prevOutScriptType: 'p2sh-p2wsh-' + k })
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output)
})
})
})
Object.assign(depends, { prevOutScriptType: 'p2sh-p2wsh-' + k });
await buildAndSign(
depends,
p2sh.output,
p2sh.redeem!.output,
p2wsh.redeem!.output,
);
},
);
});
});

View file

@ -1,9 +1,9 @@
const { describe, it } = require('mocha');
const assert = require('assert');
const bitcoin = require('../../');
const bip32 = require('bip32');
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
import * as bip32 from 'bip32';
const rng = require('randombytes');
const regtestUtils = require('./_regtest');
const regtest = regtestUtils.network;
// See bottom of file for some helper functions used to make the payment objects needed.
@ -174,7 +174,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData1)
.addOutput({
script: embed.output,
script: embed.output!,
value: 1000,
})
.addOutput({
@ -350,7 +350,12 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
// For learning purposes, ignore this test.
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const p2wpkh = createPayment('p2wpkh');
const inputData = await getInputData(5e4, p2wpkh.payment, false, 'noredeem');
const inputData = await getInputData(
5e4,
p2wpkh.payment,
false,
'noredeem',
);
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
@ -486,7 +491,12 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
// For learning purposes, ignore this test.
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh-p2wsh');
const inputData = await getInputData(
5e4,
p2sh.payment,
false,
'p2sh-p2wsh',
);
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
@ -528,9 +538,9 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
masterFingerprint,
path,
pubkey,
}
]
}
},
],
};
const p2wpkh = createPayment('p2wpkh', [childNode]);
const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem');
{
@ -539,7 +549,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
}
// You can add extra attributes for updateData into the addInput(s) object(s)
Object.assign(inputData, updateData)
Object.assign(inputData, updateData);
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
@ -551,7 +561,10 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
.signInputHD(0, hdRoot); // must sign with root!!!
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(psbt.validateSignaturesOfInput(0, childNode.publicKey), true);
assert.strictEqual(
psbt.validateSignaturesOfInput(0, childNode.publicKey),
true,
);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
@ -568,18 +581,18 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
});
});
function createPayment(_type, myKeys, network) {
function createPayment(_type: string, myKeys?: any[], network?: any) {
network = network || regtest;
const splitType = _type.split('-').reverse();
const isMultisig = splitType[0].slice(0, 4) === 'p2ms';
const keys = myKeys || [];
let m;
let m: number | undefined;
if (isMultisig) {
const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/);
m = parseInt(match[1]);
let n = parseInt(match[2]);
m = parseInt(match![1]);
let n = parseInt(match![2]);
if (keys.length > 0 && keys.length !== n) {
throw new Error('Need n keys for multisig')
throw new Error('Need n keys for multisig');
}
while (!myKeys && n > 1) {
keys.push(bitcoin.ECPair.makeRandom({ network }));
@ -588,7 +601,7 @@ function createPayment(_type, myKeys, network) {
}
if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network }));
let payment;
let payment: any;
splitType.forEach(type => {
if (type.slice(0, 4) === 'p2ms') {
payment = bitcoin.payments.p2ms({
@ -597,12 +610,12 @@ function createPayment(_type, myKeys, network) {
network,
});
} else if (['p2sh', 'p2wsh'].indexOf(type) > -1) {
payment = bitcoin.payments[type]({
payment = (bitcoin.payments as any)[type]({
redeem: payment,
network,
});
} else {
payment = bitcoin.payments[type]({
payment = (bitcoin.payments as any)[type]({
pubkey: keys[0].publicKey,
network,
});
@ -615,13 +628,18 @@ function createPayment(_type, myKeys, network) {
};
}
function getWitnessUtxo(out) {
function getWitnessUtxo(out: any) {
delete out.address;
out.script = Buffer.from(out.script, 'hex');
return out;
}
async function getInputData(amount, payment, isSegwit, redeemType) {
async function getInputData(
amount: number,
payment: any,
isSegwit: boolean,
redeemType: string,
) {
const unspent = await regtestUtils.faucetComplex(payment.output, amount);
const utx = await regtestUtils.fetch(unspent.txId);
// for non segwit inputs, you must pass the full transaction buffer
@ -629,7 +647,7 @@ async function getInputData(amount, payment, isSegwit, redeemType) {
// for segwit inputs, you only need the output script and value as an object.
const witnessUtxo = getWitnessUtxo(utx.outs[unspent.vout]);
const mixin = isSegwit ? { witnessUtxo } : { nonWitnessUtxo };
const mixin2 = {};
const mixin2: any = {};
switch (redeemType) {
case 'p2sh':
mixin2.redeemScript = payment.redeem.output;

View file

@ -1,361 +1,421 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bitcoin = require('../../')
const regtestUtils = require('./_regtest')
const regtest = regtestUtils.network
console.warn = () => {} // Silence the Deprecation Warning
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
const regtest = regtestUtils.network;
console.warn = () => {}; // Silence the Deprecation Warning
function rng() {
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64')
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64');
}
describe('bitcoinjs-lib (transactions)', () => {
it('can create a 1-to-1 Transaction', () => {
const alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
const txb = new bitcoin.TransactionBuilder()
const alice = bitcoin.ECPair.fromWIF(
'L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy',
);
const txb = new bitcoin.TransactionBuilder();
txb.setVersion(1)
txb.addInput('61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', 0) // Alice's previous transaction output, has 15000 satoshis
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000)
txb.setVersion(1);
txb.addInput(
'61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d',
0,
); // Alice's previous transaction output, has 15000 satoshis
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000);
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair: alice
})
keyPair: alice,
});
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
assert.strictEqual(txb.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000')
})
assert.strictEqual(
txb.build().toHex(),
'01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000',
);
});
it('can create a 2-to-2 Transaction', () => {
const alice = bitcoin.ECPair.fromWIF('L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1')
const bob = bitcoin.ECPair.fromWIF('KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z')
const alice = bitcoin.ECPair.fromWIF(
'L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1',
);
const bob = bitcoin.ECPair.fromWIF(
'KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z',
);
const txb = new bitcoin.TransactionBuilder()
txb.setVersion(1)
txb.addInput('b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c', 6) // Alice's previous transaction output, has 200000 satoshis
txb.addInput('7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730', 0) // Bob's previous transaction output, has 300000 satoshis
txb.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000)
txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000)
const txb = new bitcoin.TransactionBuilder();
txb.setVersion(1);
txb.addInput(
'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c',
6,
); // Alice's previous transaction output, has 200000 satoshis
txb.addInput(
'7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730',
0,
); // Bob's previous transaction output, has 300000 satoshis
txb.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000);
txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000);
// (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 1,
keyPair: bob
}) // Bob signs his input, which was the second input (1th)
keyPair: bob,
}); // Bob signs his input, which was the second input (1th)
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair: alice
}) // Alice signs her input, which was the first input (0th)
keyPair: alice,
}); // Alice signs her input, which was the first input (0th)
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
assert.strictEqual(txb.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000')
})
assert.strictEqual(
txb.build().toHex(),
'01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000',
);
});
it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest })
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest })
const aliceChange = bitcoin.ECPair.makeRandom({ network: regtest, rng: rng })
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest });
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest });
const aliceChange = bitcoin.ECPair.makeRandom({
network: regtest,
rng: rng,
});
const alice1pkh = bitcoin.payments.p2pkh({ pubkey: alice1.publicKey, network: regtest })
const alice2pkh = bitcoin.payments.p2pkh({ pubkey: alice2.publicKey, network: regtest })
const aliceCpkh = bitcoin.payments.p2pkh({ pubkey: aliceChange.publicKey, network: regtest })
const alice1pkh = bitcoin.payments.p2pkh({
pubkey: alice1.publicKey,
network: regtest,
});
const alice2pkh = bitcoin.payments.p2pkh({
pubkey: alice2.publicKey,
network: regtest,
});
const aliceCpkh = bitcoin.payments.p2pkh({
pubkey: aliceChange.publicKey,
network: regtest,
});
// give Alice 2 unspent outputs
const unspent0 = await regtestUtils.faucet(alice1pkh.address, 5e4)
const unspent0 = await regtestUtils.faucet(alice1pkh.address!, 5e4);
const unspent1 = await regtestUtils.faucet(alice2pkh.address, 7e4)
const unspent1 = await regtestUtils.faucet(alice2pkh.address!, 7e4);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent0.txId, unspent0.vout) // alice1 unspent
txb.addInput(unspent1.txId, unspent1.vout) // alice2 unspent
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4) // the actual "spend"
txb.addOutput(aliceCpkh.address, 1e4) // Alice's change
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent0.txId, unspent0.vout); // alice1 unspent
txb.addInput(unspent1.txId, unspent1.vout); // alice2 unspent
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4); // the actual "spend"
txb.addOutput(aliceCpkh.address!, 1e4); // Alice's change
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
// Alice signs each input with the respective private keys
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair: alice1
})
keyPair: alice1,
});
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 1,
keyPair: alice2
})
keyPair: alice2,
});
// build and broadcast our RegTest network
await regtestUtils.broadcast(txb.build().toHex())
await regtestUtils.broadcast(txb.build().toHex());
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
})
});
it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: regtest })
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
const p2pkh = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
network: regtest,
});
const unspent = await regtestUtils.faucet(p2pkh.address, 2e5)
const unspent = await regtestUtils.faucet(p2pkh.address!, 2e5);
const txb = new bitcoin.TransactionBuilder(regtest)
const data = Buffer.from('bitcoinjs-lib', 'utf8')
const embed = bitcoin.payments.embed({ data: [data] })
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(embed.output, 1000)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5)
const txb = new bitcoin.TransactionBuilder(regtest);
const data = Buffer.from('bitcoinjs-lib', 'utf8');
const embed = bitcoin.payments.embed({ data: [data] });
txb.addInput(unspent.txId, unspent.vout);
txb.addOutput(embed.output!, 1000);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5);
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
});
// build and broadcast to the RegTest network
await regtestUtils.broadcast(txb.build().toHex())
})
await regtestUtils.broadcast(txb.build().toHex());
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => {
const keyPairs = [
bitcoin.ECPair.makeRandom({ network: regtest }),
bitcoin.ECPair.makeRandom({ network: regtest }),
bitcoin.ECPair.makeRandom({ network: regtest }),
bitcoin.ECPair.makeRandom({ network: regtest })
]
const pubkeys = keyPairs.map(x => x.publicKey)
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network: regtest })
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest })
bitcoin.ECPair.makeRandom({ network: regtest }),
];
const pubkeys = keyPairs.map(x => x.publicKey);
const p2ms = bitcoin.payments.p2ms({
m: 2,
pubkeys: pubkeys,
network: regtest,
});
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest });
const unspent = await regtestUtils.faucet(p2sh.address, 2e4)
const unspent = await regtestUtils.faucet(p2sh.address!, 2e4);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4);
txb.sign({
prevOutScriptType: 'p2sh-p2ms',
vin: 0,
keyPair: keyPairs[0],
redeemScript: p2sh.redeem.output,
})
redeemScript: p2sh.redeem!.output,
});
txb.sign({
prevOutScriptType: 'p2sh-p2ms',
vin: 0,
keyPair: keyPairs[2],
redeemScript: p2sh.redeem.output,
})
const tx = txb.build()
redeemScript: p2sh.redeem!.output,
});
const tx = txb.build();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 1e4
})
})
value: 1e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest })
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
const p2wpkh = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
network: regtest,
});
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest });
const unspent = await regtestUtils.faucet(p2sh.address, 5e4)
const unspent = await regtestUtils.faucet(p2sh.address!, 5e4);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
txb.sign({
prevOutScriptType: 'p2sh-p2wpkh',
vin: 0,
keyPair: keyPair,
redeemScript: p2sh.redeem.output,
redeemScript: p2sh.redeem!.output,
witnessValue: unspent.value,
})
});
const tx = txb.build()
const tx = txb.build();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
})
})
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
const p2wpkh = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
network: regtest,
});
const unspent = await regtestUtils.faucetComplex(p2wpkh.output, 5e4)
const unspent = await regtestUtils.faucetComplex(p2wpkh.output!, 5e4);
// XXX: build the Transaction w/ a P2WPKH input
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, undefined, p2wpkh.output); // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
txb.sign({
prevOutScriptType: 'p2wpkh',
vin: 0,
keyPair: keyPair,
witnessValue: unspent.value,
}) // NOTE: no redeem script
const tx = txb.build()
}); // NOTE: no redeem script
const tx = txb.build();
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
})
})
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
const p2pk = bitcoin.payments.p2pk({ pubkey: keyPair.publicKey, network: regtest })
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest })
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest });
const p2pk = bitcoin.payments.p2pk({
pubkey: keyPair.publicKey,
network: regtest,
});
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest });
const unspent = await regtestUtils.faucetComplex(p2wsh.output, 5e4)
const unspent = await regtestUtils.faucetComplex(p2wsh.output!, 5e4);
// XXX: build the Transaction w/ a P2WSH input
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, undefined, p2wsh.output); // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
txb.sign({
prevOutScriptType: 'p2wsh-p2pk',
vin: 0,
keyPair: keyPair,
witnessValue: 5e4,
witnessScript: p2wsh.redeem.output,
}) // NOTE: provide a witnessScript!
const tx = txb.build()
witnessScript: p2wsh.redeem!.output,
}); // NOTE: provide a witnessScript!
const tx = txb.build();
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4
})
})
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
const keyPairs = [
bitcoin.ECPair.makeRandom({ network: regtest }),
bitcoin.ECPair.makeRandom({ network: regtest }),
bitcoin.ECPair.makeRandom({ network: regtest }),
bitcoin.ECPair.makeRandom({ network: regtest })
]
const pubkeys = keyPairs.map(x => x.publicKey)
bitcoin.ECPair.makeRandom({ network: regtest }),
];
const pubkeys = keyPairs.map(x => x.publicKey);
const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest })
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest })
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest })
const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest });
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest });
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest });
const unspent = await regtestUtils.faucet(p2sh.address, 6e4)
const unspent = await regtestUtils.faucet(p2sh.address!, 6e4);
const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, null, p2sh.output)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4)
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, undefined, p2sh.output);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4);
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[0],
redeemScript: p2sh.redeem.output,
redeemScript: p2sh.redeem!.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem.output,
})
witnessScript: p2wsh.redeem!.output,
});
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[2],
redeemScript: p2sh.redeem.output,
redeemScript: p2sh.redeem!.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem.output,
})
witnessScript: p2wsh.redeem!.output,
});
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[3],
redeemScript: p2sh.redeem.output,
redeemScript: p2sh.redeem!.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem.output,
})
witnessScript: p2wsh.redeem!.output,
});
const tx = txb.build()
const tx = txb.build();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex())
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 3e4
})
})
value: 3e4,
});
});
it('can verify Transaction (P2PKH) signatures', () => {
const txHex = '010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700'
const txHex =
'010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700';
const keyPairs = [
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d',
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a',
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f'
].map(q => { return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')) })
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f',
].map(q => {
return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex'));
});
const tx = bitcoin.Transaction.fromHex(txHex)
const tx = bitcoin.Transaction.fromHex(txHex);
tx.ins.forEach((input, i) => {
const keyPair = keyPairs[i]
const keyPair = keyPairs[i];
const p2pkh = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
input: input.script
})
input: input.script,
});
const ss = bitcoin.script.signature.decode(p2pkh.signature)
const hash = tx.hashForSignature(i, p2pkh.output, ss.hashType)
const ss = bitcoin.script.signature.decode(p2pkh.signature!);
const hash = tx.hashForSignature(i, p2pkh.output!, ss.hashType);
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
})
})
assert.strictEqual(keyPair.verify(hash, ss.signature), true);
});
});
it('can verify Transaction (P2SH(P2WPKH)) signatures', () => {
const utxos = {
'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': {
value: 50000
}
}
value: 50000,
},
};
const txHex = '02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000'
const tx = bitcoin.Transaction.fromHex(txHex)
const txHex =
'02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000';
const tx = bitcoin.Transaction.fromHex(txHex);
tx.ins.forEach((input, i) => {
const txId = Buffer.from(input.hash).reverse().toString('hex')
const utxo = utxos[`${txId}:${i}`]
if (!utxo) throw new Error('Missing utxo')
const txId = (Buffer.from(input.hash).reverse() as Buffer).toString(
'hex',
);
const utxo = (utxos as any)[`${txId}:${i}`];
if (!utxo) throw new Error('Missing utxo');
const p2sh = bitcoin.payments.p2sh({
input: input.script,
witness: input.witness
})
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem)
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }) // because P2WPKH is annoying
witness: input.witness,
});
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem!);
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }); // because P2WPKH is annoying
const ss = bitcoin.script.signature.decode(p2wpkh.signature)
const hash = tx.hashForWitnessV0(i, p2pkh.output, utxo.value, ss.hashType)
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey) // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
const ss = bitcoin.script.signature.decode(p2wpkh.signature!);
const hash = tx.hashForWitnessV0(
i,
p2pkh.output!,
utxo.value,
ss.hashType,
);
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey!); // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
})
})
})
assert.strictEqual(keyPair.verify(hash, ss.signature), true);
});
});
});

View file

@ -1,49 +1,55 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const u = require('./payments.utils')
;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(p => {
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as u from './payments.utils';
import { PaymentCreator } from '../src/payments';
['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(p => {
describe(p, () => {
let fn
let payment = require('../src/payments/' + p)
let fn: PaymentCreator;
let payment = require('../src/payments/' + p);
if (p === 'embed') {
fn = payment.p2data
fn = payment.p2data;
} else {
fn = payment[p]
fn = payment[p];
}
const fixtures = require('./fixtures/' + p)
const fixtures = require('./fixtures/' + p);
fixtures.valid.forEach((f, i) => {
fixtures.valid.forEach((f: any) => {
it(f.description + ' as expected', () => {
const args = u.preform(f.arguments)
const actual = fn(args, f.options)
const args = u.preform(f.arguments);
const actual = fn(args, f.options);
u.equate(actual, f.expected, f.arguments)
})
u.equate(actual, f.expected, f.arguments);
});
it(f.description + ' as expected (no validation)', () => {
const args = u.preform(f.arguments)
const actual = fn(args, Object.assign({}, f.options, {
validate: false
}))
const args = u.preform(f.arguments);
const actual = fn(
args,
Object.assign({}, f.options, {
validate: false,
}),
);
u.equate(actual, f.expected, f.arguments)
})
})
u.equate(actual, f.expected, f.arguments);
});
});
fixtures.invalid.forEach(f => {
it('throws ' + f.exception + (f.description ? ('for ' + f.description) : ''), () => {
const args = u.preform(f.arguments)
fixtures.invalid.forEach((f: any) => {
it(
'throws ' + f.exception + (f.description ? 'for ' + f.description : ''),
() => {
const args = u.preform(f.arguments);
assert.throws(() => {
fn(args, f.options)
}, new RegExp(f.exception))
})
})
fn(args, f.options);
}, new RegExp(f.exception));
},
);
});
if (p === 'p2sh') {
const p2wsh = require('../src/payments/p2wsh').p2wsh
const p2pk = require('../src/payments/p2pk').p2pk
const p2wsh = require('../src/payments/p2wsh').p2wsh;
const p2pk = require('../src/payments/p2pk').p2pk;
it('properly assembles nested p2wsh with names', () => {
const actual = fn({
redeem: p2wsh({
@ -51,42 +57,57 @@ const u = require('./payments.utils')
pubkey: Buffer.from(
'03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058',
'hex',
)
})
})
})
assert.strictEqual(actual.address, '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw')
assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk')
assert.strictEqual(actual.redeem.name, 'p2wsh-p2pk')
assert.strictEqual(actual.redeem.redeem.name, 'p2pk')
})
),
}),
}),
});
assert.strictEqual(
actual.address,
'3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw',
);
assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk');
assert.strictEqual(actual.redeem!.name, 'p2wsh-p2pk');
assert.strictEqual(actual.redeem!.redeem!.name, 'p2pk');
});
}
// cross-verify dynamically too
if (!fixtures.dynamic) return
const { depends, details } = fixtures.dynamic
if (!fixtures.dynamic) return;
const { depends, details } = fixtures.dynamic;
details.forEach(f => {
const detail = u.preform(f)
const disabled = {}
if (f.disabled) f.disabled.forEach(k => { disabled[k] = true })
details.forEach((f: any) => {
const detail = u.preform(f);
const disabled: any = {};
if (f.disabled)
f.disabled.forEach((k: string) => {
disabled[k] = true;
});
for (let key in depends) {
if (key in disabled) continue
const dependencies = depends[key]
if (key in disabled) continue;
const dependencies = depends[key];
dependencies.forEach(dependency => {
if (!Array.isArray(dependency)) dependency = [dependency]
dependencies.forEach((dependency: any) => {
if (!Array.isArray(dependency)) dependency = [dependency];
const args = {}
dependency.forEach(d => { u.from(d, detail, args) })
const expected = u.from(key, detail)
const args = {};
dependency.forEach((d: any) => {
u.from(d, detail, args);
});
const expected = u.from(key, detail);
it(f.description + ', ' + key + ' derives from ' + JSON.stringify(dependency), () => {
u.equate(fn(args), expected)
})
})
it(
f.description +
', ' +
key +
' derives from ' +
JSON.stringify(dependency),
() => {
u.equate(fn(args), expected);
},
);
});
}
})
})
})
});
});
});

View file

@ -1,134 +0,0 @@
const t = require('assert')
const bscript = require('../src/script')
const BNETWORKS = require('../src/networks')
function tryHex (x) {
if (Buffer.isBuffer(x)) return x.toString('hex')
if (Array.isArray(x)) return x.map(tryHex)
return x
}
function fromHex (x) {
if (typeof x === 'string') return Buffer.from(x, 'hex')
if (Array.isArray(x)) return x.map(fromHex)
return x
}
function tryASM (x) {
if (Buffer.isBuffer(x)) return bscript.toASM(x)
return x
}
function asmToBuffer (x) {
if (x === '') return Buffer.alloc(0)
return bscript.fromASM(x)
}
function carryOver (a, b) {
for (let k in b) {
if (k in a && k === 'redeem') {
carryOver(a[k], b[k])
continue
}
// don't, the value was specified
if (k in a) continue
// otherwise, expect match
a[k] = b[k]
}
}
function equateBase (a, b, context) {
if ('output' in b) t.strictEqual(tryASM(a.output), tryASM(b.output), `Inequal ${context}output`)
if ('input' in b) t.strictEqual(tryASM(a.input), tryASM(b.input), `Inequal ${context}input`)
if ('witness' in b) t.deepStrictEqual(tryHex(a.witness), tryHex(b.witness), `Inequal ${context}witness`)
}
function equate (a, b, args) {
b = Object.assign({}, b)
carryOver(b, args)
// by null, we mean 'undefined', but JSON
if (b.input === null) b.input = undefined
if (b.output === null) b.output = undefined
if (b.witness === null) b.witness = undefined
if (b.redeem) {
if (b.redeem.input === null) b.redeem.input = undefined
if (b.redeem.output === null) b.redeem.output = undefined
if (b.redeem.witness === null) b.redeem.witness = undefined
}
equateBase(a, b, '')
if (b.redeem) equateBase(a.redeem, b.redeem, 'redeem.')
if (b.network) t.deepStrictEqual(a.network, BNETWORKS[b.network], 'Inequal *.network')
// contextual
if (b.signature === null) b.signature = undefined
if (b.signatures === null) b.signatures = undefined
if ('address' in b) t.strictEqual(a.address, b.address, 'Inequal *.address')
if ('hash' in b) t.strictEqual(tryHex(a.hash), tryHex(b.hash), 'Inequal *.hash')
if ('pubkey' in b) t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey')
if ('signature' in b) t.strictEqual(tryHex(a.signature), tryHex(b.signature), 'Inequal signature')
if ('m' in b) t.strictEqual(a.m, b.m, 'Inequal *.m')
if ('n' in b) t.strictEqual(a.n, b.n, 'Inequal *.n')
if ('pubkeys' in b) t.deepStrictEqual(tryHex(a.pubkeys), tryHex(b.pubkeys), 'Inequal *.pubkeys')
if ('signatures' in b) t.deepStrictEqual(tryHex(a.signatures), tryHex(b.signatures), 'Inequal *.signatures')
if ('data' in b) t.deepStrictEqual(tryHex(a.data), tryHex(b.data), 'Inequal *.data')
}
function preform (x) {
x = Object.assign({}, x)
if (x.network) x.network = BNETWORKS[x.network]
if (typeof x.inputHex === 'string') {
x.input = Buffer.from(x.inputHex, 'hex')
delete x.inputHex
}
if (typeof x.outputHex === 'string') {
x.output = Buffer.from(x.outputHex, 'hex')
delete x.outputHex
}
if (typeof x.output === 'string') x.output = asmToBuffer(x.output)
if (typeof x.input === 'string') x.input = asmToBuffer(x.input)
if (Array.isArray(x.witness)) x.witness = x.witness.map(fromHex)
if (x.data) x.data = x.data.map(fromHex)
if (x.hash) x.hash = Buffer.from(x.hash, 'hex')
if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex')
if (x.signature) x.signature = Buffer.from(x.signature, 'hex')
if (x.pubkeys) x.pubkeys = x.pubkeys.map(fromHex)
if (x.signatures) x.signatures = x.signatures.map(y => { return Number.isFinite(y) ? y : Buffer.from(y, 'hex') })
if (x.redeem) {
x.redeem = Object.assign({}, x.redeem)
if (typeof x.redeem.input === 'string') x.redeem.input = asmToBuffer(x.redeem.input)
if (typeof x.redeem.output === 'string') x.redeem.output = asmToBuffer(x.redeem.output)
if (Array.isArray(x.redeem.witness)) x.redeem.witness = x.redeem.witness.map(fromHex)
if (x.redeem.network) x.redeem.network = BNETWORKS[x.redeem.network]
}
return x
}
function from (path, object, result) {
path = path.split('.')
result = result || {}
let r = result
path.forEach((k, i) => {
if (i < path.length - 1) {
r[k] = r[k] || {}
// recurse
r = r[k]
object = object[k]
} else {
r[k] = object[k]
}
})
return result
}
module.exports = {
from,
equate,
preform
}

169
test/payments.utils.ts Normal file
View file

@ -0,0 +1,169 @@
import * as t from 'assert';
import * as bscript from '../src/script';
import * as BNETWORKS from '../src/networks';
function tryHex(x: Buffer | Buffer[]): string | string[] {
if (Buffer.isBuffer(x)) return x.toString('hex');
if (Array.isArray(x)) return x.map(tryHex) as string[];
return x;
}
function fromHex(x: string | string[]): Buffer | Buffer[] {
if (typeof x === 'string') return Buffer.from(x, 'hex');
if (Array.isArray(x)) return x.map(fromHex) as Buffer[];
return x;
}
function tryASM(x: Buffer): string {
if (Buffer.isBuffer(x)) return bscript.toASM(x);
return x;
}
function asmToBuffer(x: string) {
if (x === '') return Buffer.alloc(0);
return bscript.fromASM(x);
}
function carryOver(a: any, b: any) {
for (let k in b) {
if (k in a && k === 'redeem') {
carryOver(a[k], b[k]);
continue;
}
// don't, the value was specified
if (k in a) continue;
// otherwise, expect match
a[k] = b[k];
}
}
function equateBase(a: any, b: any, context: string) {
if ('output' in b)
t.strictEqual(
tryASM(a.output),
tryASM(b.output),
`Inequal ${context}output`,
);
if ('input' in b)
t.strictEqual(tryASM(a.input), tryASM(b.input), `Inequal ${context}input`);
if ('witness' in b)
t.deepStrictEqual(
tryHex(a.witness),
tryHex(b.witness),
`Inequal ${context}witness`,
);
}
export function equate(a: any, b: any, args?: any) {
b = Object.assign({}, b);
carryOver(b, args);
// by null, we mean 'undefined', but JSON
if (b.input === null) b.input = undefined;
if (b.output === null) b.output = undefined;
if (b.witness === null) b.witness = undefined;
if (b.redeem) {
if (b.redeem.input === null) b.redeem.input = undefined;
if (b.redeem.output === null) b.redeem.output = undefined;
if (b.redeem.witness === null) b.redeem.witness = undefined;
}
equateBase(a, b, '');
if (b.redeem) equateBase(a.redeem, b.redeem, 'redeem.');
if (b.network)
t.deepStrictEqual(
a.network,
(BNETWORKS as any)[b.network],
'Inequal *.network',
);
// contextual
if (b.signature === null) b.signature = undefined;
if (b.signatures === null) b.signatures = undefined;
if ('address' in b) t.strictEqual(a.address, b.address, 'Inequal *.address');
if ('hash' in b)
t.strictEqual(tryHex(a.hash), tryHex(b.hash), 'Inequal *.hash');
if ('pubkey' in b)
t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey');
if ('signature' in b)
t.strictEqual(
tryHex(a.signature),
tryHex(b.signature),
'Inequal signature',
);
if ('m' in b) t.strictEqual(a.m, b.m, 'Inequal *.m');
if ('n' in b) t.strictEqual(a.n, b.n, 'Inequal *.n');
if ('pubkeys' in b)
t.deepStrictEqual(
tryHex(a.pubkeys),
tryHex(b.pubkeys),
'Inequal *.pubkeys',
);
if ('signatures' in b)
t.deepStrictEqual(
tryHex(a.signatures),
tryHex(b.signatures),
'Inequal *.signatures',
);
if ('data' in b)
t.deepStrictEqual(tryHex(a.data), tryHex(b.data), 'Inequal *.data');
}
export function preform(x: any) {
x = Object.assign({}, x);
if (x.network) x.network = (BNETWORKS as any)[x.network];
if (typeof x.inputHex === 'string') {
x.input = Buffer.from(x.inputHex, 'hex');
delete x.inputHex;
}
if (typeof x.outputHex === 'string') {
x.output = Buffer.from(x.outputHex, 'hex');
delete x.outputHex;
}
if (typeof x.output === 'string') x.output = asmToBuffer(x.output);
if (typeof x.input === 'string') x.input = asmToBuffer(x.input);
if (Array.isArray(x.witness)) x.witness = x.witness.map(fromHex);
if (x.data) x.data = x.data.map(fromHex);
if (x.hash) x.hash = Buffer.from(x.hash, 'hex');
if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex');
if (x.signature) x.signature = Buffer.from(x.signature, 'hex');
if (x.pubkeys) x.pubkeys = x.pubkeys.map(fromHex);
if (x.signatures)
x.signatures = x.signatures.map((y: any) => {
return Number.isFinite(y) ? y : Buffer.from(y, 'hex');
});
if (x.redeem) {
x.redeem = Object.assign({}, x.redeem);
if (typeof x.redeem.input === 'string')
x.redeem.input = asmToBuffer(x.redeem.input);
if (typeof x.redeem.output === 'string')
x.redeem.output = asmToBuffer(x.redeem.output);
if (Array.isArray(x.redeem.witness))
x.redeem.witness = x.redeem.witness.map(fromHex);
if (x.redeem.network)
x.redeem.network = (BNETWORKS as any)[x.redeem.network];
}
return x;
}
export function from(path: string, object: any, result?: any) {
const paths = path.split('.');
result = result || {};
let r = result;
paths.forEach((k, i) => {
if (i < paths.length - 1) {
r[k] = r[k] || {};
// recurse
r = r[k];
object = object[k];
} else {
r[k] = object[k];
}
});
return result;
}

File diff suppressed because it is too large Load diff

View file

@ -1,157 +1,176 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bscript = require('../src/script')
const minimalData = require('minimaldata')
const fixtures = require('./fixtures/script.json')
const fixtures2 = require('./fixtures/templates.json')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bscript from '../src/script';
import * as fixtures from './fixtures/script.json';
import * as fixtures2 from './fixtures/templates.json';
const minimalData = require('minimaldata');
describe('script', () => {
// TODO
describe('isCanonicalPubKey', () => {
it('rejects if not provided a Buffer', () => {
assert.strictEqual(false, bscript.isCanonicalPubKey(0))
})
assert.strictEqual(false, bscript.isCanonicalPubKey(0 as any));
});
it('rejects smaller than 33', () => {
for (var i = 0; i < 33; i++) {
assert.strictEqual(false, bscript.isCanonicalPubKey(Buffer.from('', i)))
assert.strictEqual(
false,
bscript.isCanonicalPubKey(Buffer.allocUnsafe(i)),
);
}
})
})
describe.skip('isCanonicalScriptSignature', () => {
})
});
});
describe.skip('isCanonicalScriptSignature', () => {});
describe('fromASM/toASM', () => {
fixtures.valid.forEach(f => {
it('encodes/decodes ' + f.asm, () => {
const script = bscript.fromASM(f.asm)
assert.strictEqual(bscript.toASM(script), f.asm)
})
})
const script = bscript.fromASM(f.asm);
assert.strictEqual(bscript.toASM(script), f.asm);
});
});
fixtures.invalid.fromASM.forEach(f => {
it('throws ' + f.description, () => {
assert.throws(() => {
bscript.fromASM(f.script)
}, new RegExp(f.description))
})
})
})
bscript.fromASM(f.script);
}, new RegExp(f.description));
});
});
});
describe('fromASM/toASM (templates)', () => {
fixtures2.valid.forEach(f => {
if (f.inputHex) {
const ih = bscript.toASM(Buffer.from(f.inputHex, 'hex'))
const ih = bscript.toASM(Buffer.from(f.inputHex, 'hex'));
it('encodes/decodes ' + ih, () => {
const script = bscript.fromASM(f.input)
assert.strictEqual(script.toString('hex'), f.inputHex)
assert.strictEqual(bscript.toASM(script), f.input)
})
const script = bscript.fromASM(f.input);
assert.strictEqual(script.toString('hex'), f.inputHex);
assert.strictEqual(bscript.toASM(script), f.input);
});
}
if (f.outputHex) {
it('encodes/decodes ' + f.output, () => {
const script = bscript.fromASM(f.output)
assert.strictEqual(script.toString('hex'), f.outputHex)
assert.strictEqual(bscript.toASM(script), f.output)
})
const script = bscript.fromASM(f.output);
assert.strictEqual(script.toString('hex'), f.outputHex);
assert.strictEqual(bscript.toASM(script), f.output);
});
}
})
})
});
});
describe('isPushOnly', () => {
fixtures.valid.forEach(f => {
it('returns ' + !!f.stack + ' for ' + f.asm, () => {
const script = bscript.fromASM(f.asm)
const chunks = bscript.decompile(script)
const script = bscript.fromASM(f.asm);
const chunks = bscript.decompile(script);
assert.strictEqual(bscript.isPushOnly(chunks), !!f.stack)
})
})
})
assert.strictEqual(bscript.isPushOnly(chunks!), !!f.stack);
});
});
});
describe('toStack', () => {
fixtures.valid.forEach(f => {
it('returns ' + !!f.stack + ' for ' + f.asm, () => {
if (!f.stack || !f.asm) return
if (!f.stack || !f.asm) return;
const script = bscript.fromASM(f.asm)
const script = bscript.fromASM(f.asm);
const stack = bscript.toStack(script)
assert.deepStrictEqual(stack.map(x => {
return x.toString('hex')
}), f.stack)
const stack = bscript.toStack(script);
assert.deepStrictEqual(
stack.map(x => {
return x.toString('hex');
}),
f.stack,
);
assert.strictEqual(bscript.toASM(bscript.compile(stack)), f.asm, 'should rebuild same script from stack')
})
})
})
assert.strictEqual(
bscript.toASM(bscript.compile(stack)),
f.asm,
'should rebuild same script from stack',
);
});
});
});
describe('compile (via fromASM)', () => {
fixtures.valid.forEach(f => {
it('(' + f.type + ') compiles ' + f.asm, () => {
const scriptSig = bscript.fromASM(f.asm)
it('compiles ' + f.asm, () => {
const scriptSig = bscript.fromASM(f.asm);
assert.strictEqual(scriptSig.toString('hex'), f.script)
assert.strictEqual(scriptSig.toString('hex'), f.script);
if (f.nonstandard) {
const scriptSigNS = bscript.fromASM(f.nonstandard.scriptSig)
const scriptSigNS = bscript.fromASM(f.nonstandard.scriptSig);
assert.strictEqual(scriptSigNS.toString('hex'), f.script)
assert.strictEqual(scriptSigNS.toString('hex'), f.script);
}
})
})
})
});
});
});
describe('decompile', () => {
fixtures.valid.forEach(f => {
it('decompiles ' + f.asm, () => {
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'))
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'));
assert.strictEqual(bscript.compile(chunks).toString('hex'), f.script)
assert.strictEqual(bscript.toASM(chunks), f.asm)
assert.strictEqual(bscript.compile(chunks!).toString('hex'), f.script);
assert.strictEqual(bscript.toASM(chunks!), f.asm);
if (f.nonstandard) {
const chunksNS = bscript.decompile(Buffer.from(f.nonstandard.scriptSigHex, 'hex'))
const chunksNS = bscript.decompile(
Buffer.from(f.nonstandard.scriptSigHex, 'hex'),
);
assert.strictEqual(bscript.compile(chunksNS).toString('hex'), f.script)
assert.strictEqual(
bscript.compile(chunksNS!).toString('hex'),
f.script,
);
// toASM converts verbatim, only `compile` transforms the script to a minimalpush compliant script
assert.strictEqual(bscript.toASM(chunksNS), f.nonstandard.scriptSig)
assert.strictEqual(bscript.toASM(chunksNS!), f.nonstandard.scriptSig);
}
})
})
});
});
fixtures.invalid.decompile.forEach(f => {
it('fails to decompile ' + f.script + ', because "' + f.description + '"', () => {
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'))
it(
'fails to decompile ' + f.script + ', because "' + f.description + '"',
() => {
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'));
assert.strictEqual(chunks, null)
})
})
})
assert.strictEqual(chunks, null);
},
);
});
});
describe('SCRIPT_VERIFY_MINIMALDATA policy', () => {
fixtures.valid.forEach(f => {
it('compliant for ' + f.type + ' scriptSig ' + f.asm, () => {
const script = Buffer.from(f.script, 'hex')
it('compliant for scriptSig ' + f.asm, () => {
const script = Buffer.from(f.script, 'hex');
assert(minimalData(script))
})
})
assert(minimalData(script));
});
});
function testEncodingForSize (i) {
function testEncodingForSize(i: number) {
it('compliant for data PUSH of length ' + i, () => {
const buffer = Buffer.alloc(i)
const script = bscript.compile([buffer])
const buffer = Buffer.alloc(i);
const script = bscript.compile([buffer]);
assert(minimalData(script), 'Failed for ' + i + ' length script: ' + script.toString('hex'))
})
assert(
minimalData(script),
'Failed for ' + i + ' length script: ' + script.toString('hex'),
);
});
}
for (var i = 0; i < 520; ++i) {
testEncodingForSize(i)
testEncodingForSize(i);
}
})
})
});
});

View file

@ -1,26 +1,26 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const scriptNumber = require('../src/script_number')
const fixtures = require('./fixtures/script_number.json')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as scriptNumber from '../src/script_number';
import * as fixtures from './fixtures/script_number.json';
describe('script-number', () => {
describe('decode', () => {
fixtures.forEach(f => {
it(f.hex + ' returns ' + f.number, () => {
const actual = scriptNumber.decode(Buffer.from(f.hex, 'hex'), f.bytes)
const actual = scriptNumber.decode(Buffer.from(f.hex, 'hex'), f.bytes);
assert.strictEqual(actual, f.number)
})
})
})
assert.strictEqual(actual, f.number);
});
});
});
describe('encode', () => {
fixtures.forEach(f => {
it(f.number + ' returns ' + f.hex, () => {
const actual = scriptNumber.encode(f.number)
const actual = scriptNumber.encode(f.number);
assert.strictEqual(actual.toString('hex'), f.hex)
})
})
})
})
assert.strictEqual(actual.toString('hex'), f.hex);
});
});
});
});

View file

@ -1,64 +1,63 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const bscriptSig = require('../src/script').signature
const Buffer = require('safe-buffer').Buffer
const fixtures = require('./fixtures/signature.json')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import { signature as bscriptSig } from '../src/script';
import * as fixtures from './fixtures/signature.json';
describe('Script Signatures', () => {
function fromRaw (signature) {
return Buffer.concat([
Buffer.from(signature.r, 'hex'),
Buffer.from(signature.s, 'hex')
], 64)
function fromRaw(signature: { r: string; s: string }) {
return Buffer.concat(
[Buffer.from(signature.r, 'hex'), Buffer.from(signature.s, 'hex')],
64,
);
}
function toRaw (signature) {
function toRaw(signature: Buffer) {
return {
r: signature.slice(0, 32).toString('hex'),
s: signature.slice(32, 64).toString('hex')
}
s: signature.slice(32, 64).toString('hex'),
};
}
describe('encode', () => {
fixtures.valid.forEach(f => {
it('encodes ' + f.hex, () => {
const buffer = bscriptSig.encode(fromRaw(f.raw), f.hashType)
const buffer = bscriptSig.encode(fromRaw(f.raw), f.hashType);
assert.strictEqual(buffer.toString('hex'), f.hex)
})
})
assert.strictEqual(buffer.toString('hex'), f.hex);
});
});
fixtures.invalid.forEach(f => {
if (!f.raw) return
if (!f.raw) return;
it('throws ' + f.exception, () => {
const signature = fromRaw(f.raw)
const signature = fromRaw(f.raw);
assert.throws(() => {
bscriptSig.encode(signature, f.hashType)
}, new RegExp(f.exception))
})
})
})
bscriptSig.encode(signature, f.hashType);
}, new RegExp(f.exception));
});
});
});
describe('decode', () => {
fixtures.valid.forEach(f => {
it('decodes ' + f.hex, () => {
const decode = bscriptSig.decode(Buffer.from(f.hex, 'hex'))
const decode = bscriptSig.decode(Buffer.from(f.hex, 'hex'));
assert.deepStrictEqual(toRaw(decode.signature), f.raw)
assert.strictEqual(decode.hashType, f.hashType)
})
})
assert.deepStrictEqual(toRaw(decode.signature), f.raw);
assert.strictEqual(decode.hashType, f.hashType);
});
});
fixtures.invalid.forEach(f => {
it('throws on ' + f.hex, () => {
const buffer = Buffer.from(f.hex, 'hex')
const buffer = Buffer.from(f.hex, 'hex');
assert.throws(() => {
bscriptSig.decode(buffer)
}, new RegExp(f.exception))
})
})
})
})
bscriptSig.decode(buffer);
}, new RegExp(f.exception));
});
});
});
});

View file

@ -1,288 +1,338 @@
const { describe, it, beforeEach } = require('mocha')
const assert = require('assert')
const bscript = require('../src/script')
const fixtures = require('./fixtures/transaction')
const Transaction = require('..').Transaction
import * as assert from 'assert';
import { beforeEach, describe, it } from 'mocha';
import { Transaction } from '..';
import * as bscript from '../src/script';
import * as fixtures from './fixtures/transaction.json';
describe('Transaction', () => {
function fromRaw (raw, noWitness) {
const tx = new Transaction()
tx.version = raw.version
tx.locktime = raw.locktime
function fromRaw(raw: any, noWitness?: boolean) {
const tx = new Transaction();
tx.version = raw.version;
tx.locktime = raw.locktime;
raw.ins.forEach((txIn, i) => {
const txHash = Buffer.from(txIn.hash, 'hex')
let scriptSig
raw.ins.forEach((txIn: any, i: number) => {
const txHash = Buffer.from(txIn.hash, 'hex');
let scriptSig;
if (txIn.data) {
scriptSig = Buffer.from(txIn.data, 'hex')
scriptSig = Buffer.from(txIn.data, 'hex');
} else if (txIn.script) {
scriptSig = bscript.fromASM(txIn.script)
scriptSig = bscript.fromASM(txIn.script);
}
tx.addInput(txHash, txIn.index, txIn.sequence, scriptSig)
tx.addInput(txHash, txIn.index, txIn.sequence, scriptSig);
if (!noWitness && txIn.witness) {
const witness = txIn.witness.map(x => {
return Buffer.from(x, 'hex')
})
const witness = txIn.witness.map((x: string) => {
return Buffer.from(x, 'hex');
});
tx.setWitness(i, witness)
tx.setWitness(i, witness);
}
})
});
raw.outs.forEach(txOut => {
let script
raw.outs.forEach((txOut: any) => {
let script: Buffer;
if (txOut.data) {
script = Buffer.from(txOut.data, 'hex')
script = Buffer.from(txOut.data, 'hex');
} else if (txOut.script) {
script = bscript.fromASM(txOut.script)
script = bscript.fromASM(txOut.script);
}
tx.addOutput(script, txOut.value)
})
tx.addOutput(script!, txOut.value);
});
return tx
return tx;
}
describe('fromBuffer/fromHex', () => {
function importExport (f) {
const id = f.id || f.hash
const txHex = f.hex || f.txHex
function importExport(f: any) {
const id = f.id || f.hash;
const txHex = f.hex || f.txHex;
it('imports ' + f.description + ' (' + id + ')', () => {
const actual = Transaction.fromHex(txHex)
const actual = Transaction.fromHex(txHex);
assert.strictEqual(actual.toHex(), txHex)
})
assert.strictEqual(actual.toHex(), txHex);
});
if (f.whex) {
it('imports ' + f.description + ' (' + id + ') as witness', () => {
const actual = Transaction.fromHex(f.whex)
const actual = Transaction.fromHex(f.whex);
assert.strictEqual(actual.toHex(), f.whex)
})
assert.strictEqual(actual.toHex(), f.whex);
});
}
}
fixtures.valid.forEach(importExport)
fixtures.hashForSignature.forEach(importExport)
fixtures.hashForWitnessV0.forEach(importExport)
fixtures.valid.forEach(importExport);
fixtures.hashForSignature.forEach(importExport);
fixtures.hashForWitnessV0.forEach(importExport);
fixtures.invalid.fromBuffer.forEach(f => {
it('throws on ' + f.exception, () => {
assert.throws(() => {
Transaction.fromHex(f.hex)
}, new RegExp(f.exception))
})
})
Transaction.fromHex(f.hex);
}, new RegExp(f.exception));
});
});
it('.version should be interpreted as an int32le', () => {
const txHex = 'ffffffff0000ffffffff'
const tx = Transaction.fromHex(txHex)
assert.strictEqual(-1, tx.version)
assert.strictEqual(0xffffffff, tx.locktime)
})
})
const txHex = 'ffffffff0000ffffffff';
const tx = Transaction.fromHex(txHex);
assert.strictEqual(-1, tx.version);
assert.strictEqual(0xffffffff, tx.locktime);
});
});
describe('toBuffer/toHex', () => {
fixtures.valid.forEach(f => {
it('exports ' + f.description + ' (' + f.id + ')', () => {
const actual = fromRaw(f.raw, true)
assert.strictEqual(actual.toHex(), f.hex)
})
const actual = fromRaw(f.raw, true);
assert.strictEqual(actual.toHex(), f.hex);
});
if (f.whex) {
it('exports ' + f.description + ' (' + f.id + ') as witness', () => {
const wactual = fromRaw(f.raw)
assert.strictEqual(wactual.toHex(), f.whex)
})
const wactual = fromRaw(f.raw);
assert.strictEqual(wactual.toHex(), f.whex);
});
}
})
});
it('accepts target Buffer and offset parameters', () => {
const f = fixtures.valid[0]
const actual = fromRaw(f.raw)
const byteLength = actual.byteLength()
const f = fixtures.valid[0];
const actual = fromRaw(f.raw);
const byteLength = actual.byteLength();
const target = Buffer.alloc(byteLength * 2)
const a = actual.toBuffer(target, 0)
const b = actual.toBuffer(target, byteLength)
const target = Buffer.alloc(byteLength * 2);
const a = actual.toBuffer(target, 0);
const b = actual.toBuffer(target, byteLength);
assert.strictEqual(a.length, byteLength)
assert.strictEqual(b.length, byteLength)
assert.strictEqual(a.toString('hex'), f.hex)
assert.strictEqual(b.toString('hex'), f.hex)
assert.deepStrictEqual(a, b)
assert.deepStrictEqual(a, target.slice(0, byteLength))
assert.deepStrictEqual(b, target.slice(byteLength))
})
})
assert.strictEqual(a.length, byteLength);
assert.strictEqual(b.length, byteLength);
assert.strictEqual(a.toString('hex'), f.hex);
assert.strictEqual(b.toString('hex'), f.hex);
assert.deepStrictEqual(a, b);
assert.deepStrictEqual(a, target.slice(0, byteLength));
assert.deepStrictEqual(b, target.slice(byteLength));
});
});
describe('hasWitnesses', () => {
fixtures.valid.forEach(f => {
it('detects if the transaction has witnesses: ' + (f.whex ? 'true' : 'false'), () => {
assert.strictEqual(Transaction.fromHex(f.whex ? f.whex : f.hex).hasWitnesses(), !!f.whex)
})
})
})
it(
'detects if the transaction has witnesses: ' +
(f.whex ? 'true' : 'false'),
() => {
assert.strictEqual(
Transaction.fromHex(f.whex ? f.whex : f.hex).hasWitnesses(),
!!f.whex,
);
},
);
});
});
describe('weight/virtualSize', () => {
it('computes virtual size', () => {
fixtures.valid.forEach(f => {
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex)
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex);
assert.strictEqual(transaction.virtualSize(), f.virtualSize)
})
})
assert.strictEqual(transaction.virtualSize(), f.virtualSize);
});
});
it('computes weight', () => {
fixtures.valid.forEach(f => {
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex)
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex);
assert.strictEqual(transaction.weight(), f.weight)
})
})
})
assert.strictEqual(transaction.weight(), f.weight);
});
});
});
describe('addInput', () => {
let prevTxHash
let prevTxHash: Buffer;
beforeEach(() => {
prevTxHash = Buffer.from('ffffffff00ffff000000000000000000000000000000000000000000101010ff', 'hex')
})
prevTxHash = Buffer.from(
'ffffffff00ffff000000000000000000000000000000000000000000101010ff',
'hex',
);
});
it('returns an index', () => {
const tx = new Transaction()
assert.strictEqual(tx.addInput(prevTxHash, 0), 0)
assert.strictEqual(tx.addInput(prevTxHash, 0), 1)
})
const tx = new Transaction();
assert.strictEqual(tx.addInput(prevTxHash, 0), 0);
assert.strictEqual(tx.addInput(prevTxHash, 0), 1);
});
it('defaults to empty script, witness and 0xffffffff SEQUENCE number', () => {
const tx = new Transaction()
tx.addInput(prevTxHash, 0)
const tx = new Transaction();
tx.addInput(prevTxHash, 0);
assert.strictEqual(tx.ins[0].script.length, 0)
assert.strictEqual(tx.ins[0].witness.length, 0)
assert.strictEqual(tx.ins[0].sequence, 0xffffffff)
})
assert.strictEqual(tx.ins[0].script.length, 0);
assert.strictEqual(tx.ins[0].witness.length, 0);
assert.strictEqual(tx.ins[0].sequence, 0xffffffff);
});
fixtures.invalid.addInput.forEach(f => {
it('throws on ' + f.exception, () => {
const tx = new Transaction()
const hash = Buffer.from(f.hash, 'hex')
const tx = new Transaction();
const hash = Buffer.from(f.hash, 'hex');
assert.throws(() => {
tx.addInput(hash, f.index)
}, new RegExp(f.exception))
})
})
})
tx.addInput(hash, f.index);
}, new RegExp(f.exception));
});
});
});
describe('addOutput', () => {
it('returns an index', () => {
const tx = new Transaction()
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 0)
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 1)
})
})
const tx = new Transaction();
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 0);
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 1);
});
});
describe('clone', () => {
fixtures.valid.forEach(f => {
let actual
let expected
let actual: Transaction;
let expected: Transaction;
beforeEach(() => {
expected = Transaction.fromHex(f.hex)
actual = expected.clone()
})
expected = Transaction.fromHex(f.hex);
actual = expected.clone();
});
it('should have value equality', () => {
assert.deepStrictEqual(actual, expected)
})
assert.deepStrictEqual(actual, expected);
});
it('should not have reference equality', () => {
assert.notStrictEqual(actual, expected)
})
})
})
assert.notStrictEqual(actual, expected);
});
});
});
describe('getHash/getId', () => {
function verify (f) {
function verify(f: any) {
it('should return the id for ' + f.id + '(' + f.description + ')', () => {
const tx = Transaction.fromHex(f.whex || f.hex)
const tx = Transaction.fromHex(f.whex || f.hex);
assert.strictEqual(tx.getHash().toString('hex'), f.hash)
assert.strictEqual(tx.getId(), f.id)
})
assert.strictEqual(tx.getHash().toString('hex'), f.hash);
assert.strictEqual(tx.getId(), f.id);
});
}
fixtures.valid.forEach(verify)
})
fixtures.valid.forEach(verify);
});
describe('isCoinbase', () => {
function verify (f) {
it('should return ' + f.coinbase + ' for ' + f.id + '(' + f.description + ')', () => {
const tx = Transaction.fromHex(f.hex)
function verify(f: any) {
it(
'should return ' +
f.coinbase +
' for ' +
f.id +
'(' +
f.description +
')',
() => {
const tx = Transaction.fromHex(f.hex);
assert.strictEqual(tx.isCoinbase(), f.coinbase)
})
assert.strictEqual(tx.isCoinbase(), f.coinbase);
},
);
}
fixtures.valid.forEach(verify)
})
fixtures.valid.forEach(verify);
});
describe('hashForSignature', () => {
it('does not use Witness serialization', () => {
const randScript = Buffer.from('6a', 'hex')
const randScript = Buffer.from('6a', 'hex');
const tx = new Transaction()
tx.addInput(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), 0)
tx.addOutput(randScript, 5000000000)
const tx = new Transaction();
tx.addInput(
Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex',
),
0,
);
tx.addOutput(randScript, 5000000000);
const original = tx.__toBuffer
tx.__toBuffer = (a, b, c) => {
if (c !== false) throw new Error('hashForSignature MUST pass false')
const original = (tx as any).__toBuffer;
(tx as any).__toBuffer = function(
this: Transaction,
a: any,
b: any,
c: any,
): any {
if (c !== false) throw new Error('hashForSignature MUST pass false');
return original.call(this, a, b, c)
}
return original.call(this, a, b, c);
};
assert.throws(() => {
tx.__toBuffer(undefined, undefined, true)
}, /hashForSignature MUST pass false/)
(tx as any).__toBuffer(undefined, undefined, true);
}, /hashForSignature MUST pass false/);
// assert hashForSignature does not pass false
assert.doesNotThrow(() => {
tx.hashForSignature(0, randScript, 1)
})
})
tx.hashForSignature(0, randScript, 1);
});
});
fixtures.hashForSignature.forEach(f => {
it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : f.script), () => {
const tx = Transaction.fromHex(f.txHex)
const script = bscript.fromASM(f.script)
it(
'should return ' +
f.hash +
' for ' +
(f.description ? 'case "' + f.description + '"' : f.script),
() => {
const tx = Transaction.fromHex(f.txHex);
const script = bscript.fromASM(f.script);
assert.strictEqual(tx.hashForSignature(f.inIndex, script, f.type).toString('hex'), f.hash)
})
})
})
assert.strictEqual(
tx.hashForSignature(f.inIndex, script, f.type).toString('hex'),
f.hash,
);
},
);
});
});
describe('hashForWitnessV0', () => {
fixtures.hashForWitnessV0.forEach(f => {
it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : ''), () => {
const tx = Transaction.fromHex(f.txHex)
const script = bscript.fromASM(f.script)
it(
'should return ' +
f.hash +
' for ' +
(f.description ? 'case "' + f.description + '"' : ''),
() => {
const tx = Transaction.fromHex(f.txHex);
const script = bscript.fromASM(f.script);
assert.strictEqual(tx.hashForWitnessV0(f.inIndex, script, f.value, f.type).toString('hex'), f.hash)
})
})
})
assert.strictEqual(
tx
.hashForWitnessV0(f.inIndex, script, f.value, f.type)
.toString('hex'),
f.hash,
);
},
);
});
});
describe('setWitness', () => {
it('only accepts a a witness stack (Array of Buffers)', () => {
assert.throws(() => {
(new Transaction()).setWitness(0, 'foobar')
}, /Expected property "1" of type \[Buffer], got String "foobar"/)
})
})
})
(new Transaction().setWitness as any)(0, 'foobar');
}, /Expected property "1" of type \[Buffer], got String "foobar"/);
});
});
});

File diff suppressed because it is too large Load diff

5
test/ts-node-register.js Normal file
View file

@ -0,0 +1,5 @@
// This file is required to run mocha tests on the TS files directly
require("ts-node").register({
project: "test/tsconfig.json",
});

40
test/tsconfig.json Normal file
View file

@ -0,0 +1,40 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"outDir": "../",
"declaration": false,
"rootDir": "../",
"rootDirs": [
"../src",
"../types"
],
"types": [
"node",
"mocha"
],
"allowJs": false,
"resolveJsonModule": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"esModuleInterop": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"baseUrl": ".",
"paths": {
"../src/*": ["../ts_src/*"]
}
},
"include": [
"./**/*.ts"
],
"exclude": [
"../ts_src/**/*.ts"
]
}

View file

@ -1,38 +1,44 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const types = require('../src/types')
const typeforce = require('typeforce')
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as types from '../src/types';
const typeforce = require('typeforce');
describe('types', () => {
describe('Buffer Hash160/Hash256', () => {
const buffer20byte = Buffer.alloc(20)
const buffer32byte = Buffer.alloc(32)
const buffer20byte = Buffer.alloc(20);
const buffer32byte = Buffer.alloc(32);
it('return true for valid size', () => {
assert(types.Hash160bit(buffer20byte))
assert(types.Hash256bit(buffer32byte))
})
assert(types.Hash160bit(buffer20byte));
assert(types.Hash256bit(buffer32byte));
});
it('return true for oneOf', () => {
assert.doesNotThrow(() => {
typeforce(types.oneOf(types.Hash160bit, types.Hash256bit), buffer32byte)
})
typeforce(
types.oneOf(types.Hash160bit, types.Hash256bit),
buffer32byte,
);
});
assert.doesNotThrow(() => {
typeforce(types.oneOf(types.Hash256bit, types.Hash160bit), buffer32byte)
})
})
typeforce(
types.oneOf(types.Hash256bit, types.Hash160bit),
buffer32byte,
);
});
});
it('throws for invalid size', () => {
assert.throws(() => {
types.Hash160bit(buffer32byte)
}, /Expected Buffer\(Length: 20\), got Buffer\(Length: 32\)/)
types.Hash160bit(buffer32byte);
}, /Expected Buffer\(Length: 20\), got Buffer\(Length: 32\)/);
assert.throws(() => {
types.Hash256bit(buffer20byte)
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 20\)/)
})
})
types.Hash256bit(buffer20byte);
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 20\)/);
});
});
describe('Satoshi', () => {
[
@ -41,11 +47,11 @@ describe('types', () => {
{ value: 1, result: true },
{ value: 20999999 * 1e8, result: true },
{ value: 21000000 * 1e8, result: true },
{ value: 21000001 * 1e8, result: false }
{ value: 21000001 * 1e8, result: false },
].forEach(f => {
it('returns ' + f.result + ' for valid for ' + f.value, () => {
assert.strictEqual(types.Satoshi(f.value), f.result)
})
})
})
})
assert.strictEqual(types.Satoshi(f.value), f.result);
});
});
});
});

View file

@ -148,7 +148,7 @@ export class Block {
return anyTxHasWitness(this.transactions!);
}
byteLength(headersOnly: boolean): number {
byteLength(headersOnly?: boolean): number {
if (headersOnly || !this.transactions) return 80;
return (
@ -174,7 +174,7 @@ export class Block {
}
// TODO: buffer, offset compatibility
toBuffer(headersOnly: boolean): Buffer {
toBuffer(headersOnly?: boolean): Buffer {
const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
let offset: number = 0;
@ -213,7 +213,7 @@ export class Block {
return buffer;
}
toHex(headersOnly: boolean): string {
toHex(headersOnly?: boolean): string {
return this.toBuffer(headersOnly).toString('hex');
}

View file

@ -38,7 +38,7 @@ function classifyOutput(script: Buffer): string {
return types.NONSTANDARD;
}
function classifyInput(script: Buffer, allowIncomplete: boolean): string {
function classifyInput(script: Buffer, allowIncomplete?: boolean): string {
// XXX: optimization, below functions .decompile before use
const chunks = decompile(script);
if (!chunks) throw new TypeError('Invalid script');
@ -51,7 +51,7 @@ function classifyInput(script: Buffer, allowIncomplete: boolean): string {
return types.NONSTANDARD;
}
function classifyWitness(script: Buffer[], allowIncomplete: boolean): string {
function classifyWitness(script: Buffer[], allowIncomplete?: boolean): string {
// XXX: optimization, below functions .decompile before use
const chunks = decompile(script);
if (!chunks) throw new TypeError('Invalid script');

View file

@ -17,6 +17,12 @@ export { TransactionBuilder } from './transaction_builder';
export { BIP32Interface } from 'bip32';
export { ECPairInterface, Signer, SignerAsync } from './ecpair';
export { Network } from './networks';
export { Payment, PaymentOpts, Stack, StackElement } from './payments';
export {
Payment,
PaymentCreator,
PaymentOpts,
Stack,
StackElement,
} from './payments';
export { OpCode } from './script';
export { Input as TxInput, Output as TxOutput } from './transaction';

View file

@ -25,6 +25,8 @@ export interface Payment {
witness?: Buffer[];
}
export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
export type PaymentFunction = () => Payment;
export interface PaymentOpts {

View file

@ -16,7 +16,7 @@
"no-bitwise": false,
"no-console": false,
"no-empty": [true, "allow-empty-catch"],
"no-implicit-dependencies": true,
"no-implicit-dependencies": [true, "dev"],
"no-return-await": true,
"no-var-requires": false,
"no-unused-expression": false,

6
types/block.d.ts vendored
View file

@ -16,12 +16,12 @@ export declare class Block {
getWitnessCommit(): Buffer | null;
hasWitnessCommit(): boolean;
hasWitness(): boolean;
byteLength(headersOnly: boolean): number;
byteLength(headersOnly?: boolean): number;
getHash(): Buffer;
getId(): string;
getUTCDate(): Date;
toBuffer(headersOnly: boolean): Buffer;
toHex(headersOnly: boolean): string;
toBuffer(headersOnly?: boolean): Buffer;
toHex(headersOnly?: boolean): string;
checkTxRoots(): boolean;
checkProofOfWork(): boolean;
private __checkMerkleRoot;

4
types/classify.d.ts vendored
View file

@ -11,6 +11,6 @@ declare const types: {
WITNESS_COMMITMENT: string;
};
declare function classifyOutput(script: Buffer): string;
declare function classifyInput(script: Buffer, allowIncomplete: boolean): string;
declare function classifyWitness(script: Buffer[], allowIncomplete: boolean): string;
declare function classifyInput(script: Buffer, allowIncomplete?: boolean): string;
declare function classifyWitness(script: Buffer[], allowIncomplete?: boolean): string;
export { classifyInput as input, classifyOutput as output, classifyWitness as witness, types, };

2
types/index.d.ts vendored
View file

@ -14,6 +14,6 @@ export { TransactionBuilder } from './transaction_builder';
export { BIP32Interface } from 'bip32';
export { ECPairInterface, Signer, SignerAsync } from './ecpair';
export { Network } from './networks';
export { Payment, PaymentOpts, Stack, StackElement } from './payments';
export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments';
export { OpCode } from './script';
export { Input as TxInput, Output as TxOutput } from './transaction';

View file

@ -24,6 +24,7 @@ export interface Payment {
redeem?: Payment;
witness?: Buffer[];
}
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
export declare type PaymentFunction = () => Payment;
export interface PaymentOpts {
validate?: boolean;