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') describe('Bitcoin-core', () => { // base58EncodeDecode describe('base58', () => { base58EncodeDecode.forEach(f => { const fhex = f[0] const fb58 = f[1] it('can decode ' + fb58, () => { const buffer = base58.decode(fb58) const actual = buffer.toString('hex') assert.strictEqual(actual, fhex) }) it('can encode ' + fhex, () => { const buffer = Buffer.from(fhex, 'hex') const actual = base58.encode(buffer) assert.strictEqual(actual, fb58) }) }) }) // base58KeysValid describe('address.toBase58Check', () => { const typeMap = { 'pubkey': 'pubKeyHash', 'script': 'scriptHash' } base58KeysValid.forEach(f => { const expected = f[0] const hash = Buffer.from(f[1], 'hex') const params = f[2] if (params.isPrivkey) return const network = 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) }) }) }) // base58KeysInvalid describe('address.fromBase58Check', () => { const allowedNetworks = [ bitcoin.networks.bitcoin.pubkeyhash, bitcoin.networks.bitcoin.scripthash, bitcoin.networks.testnet.pubkeyhash, bitcoin.networks.testnet.scripthash ] base58KeysInvalid.forEach(f => { const string = f[0] it('throws on ' + string, () => { assert.throws(() => { const address = bitcoin.address.fromBase58Check(string) 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] if (!params.isPrivkey) return const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin const keyPair = bitcoin.ECPair.fromWIF(string, network) it('fromWIF imports ' + string, () => { assert.strictEqual(keyPair.privateKey.toString('hex'), hex) assert.strictEqual(keyPair.compressed, params.isCompressed) }) it('toWIF exports ' + hex + ' to ' + string, () => { assert.strictEqual(keyPair.toWIF(), string) }) }) }) // base58KeysInvalid describe('ECPair.fromWIF', () => { const allowedNetworks = [ bitcoin.networks.bitcoin, bitcoin.networks.testnet ] base58KeysInvalid.forEach(f => { const string = f[0] it('throws on ' + string, () => { assert.throws(() => { 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) 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 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) transaction.ins.forEach((txIn, i) => { const input = inputs[i] // reverse because test data is reversed const prevOutHash = Buffer.from(input[0], 'hex').reverse() const prevOutIndex = input[1] assert.deepStrictEqual(txIn.hash, prevOutHash) // we read UInt32, not Int32 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 const txHex = f[0] const scriptHex = f[1] const inIndex = f[2] const hashType = f[3] 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 hashTypeName = hashTypes.join(' | ') 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 hash = transaction.hashForSignature(inIndex, script, hashType) // reverse because test data is reversed assert.strictEqual(hash.reverse().toString('hex'), expectedHash) }) }) }) describe('script.signature.decode', () => { sigCanonical.forEach(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) assert.strictEqual(actual.toString('hex'), hex) }) }) sigNoncanonical.forEach((hex, i) => { if (i === 0) return if (i % 2 !== 0) return 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/) }) }) }) })