Merge pull request #1416 from bitcoinjs/signTxb

Migrate to stricter type checks during sign
This commit is contained in:
Jonathan Underwood 2019-06-20 23:02:15 +09:00 committed by GitHub
commit 8bbe7c7178
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1969 additions and 747 deletions

View file

@ -13,6 +13,33 @@ const transaction_1 = require('./transaction');
const types = require('./types'); const types = require('./types');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const SCRIPT_TYPES = classify.types; const SCRIPT_TYPES = classify.types;
const PREVOUT_TYPES = new Set([
// Raw
'p2pkh',
'p2pk',
'p2wpkh',
'p2ms',
// P2SH wrapped
'p2sh-p2pkh',
'p2sh-p2pk',
'p2sh-p2wpkh',
'p2sh-p2ms',
// P2WSH wrapped
'p2wsh-p2pkh',
'p2wsh-p2pk',
'p2wsh-p2ms',
// P2SH-P2WSH wrapper
'p2sh-p2wsh-p2pkh',
'p2sh-p2wsh-p2pk',
'p2sh-p2wsh-p2ms',
]);
function tfMessage(type, value, message) {
try {
typeforce(type, value);
} catch (err) {
throw new Error(message);
}
}
function txIsString(tx) { function txIsString(tx) {
return typeof tx === 'string' || tx instanceof String; return typeof tx === 'string' || tx instanceof String;
} }
@ -118,74 +145,29 @@ class TransactionBuilder {
buildIncomplete() { buildIncomplete() {
return this.__build(true); return this.__build(true);
} }
sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { sign(
// TODO: remove keyPair.network matching in 4.0.0 signParams,
if (keyPair.network && keyPair.network !== this.network) keyPair,
throw new TypeError('Inconsistent network');
if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin);
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL;
if (this.__needsOutputs(hashType))
throw new Error('Transaction needs outputs');
const input = this.__INPUTS[vin];
// if redeemScript was previously provided, enforce consistency
if (
input.redeemScript !== undefined &&
redeemScript &&
!input.redeemScript.equals(redeemScript)
) {
throw new Error('Inconsistent redeemScript');
}
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey();
if (!canSign(input)) {
if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue)
throw new Error('Input did not match witnessValue');
typeforce(types.Satoshi, witnessValue);
input.value = witnessValue;
}
if (!canSign(input)) {
const prepared = prepareInput(
input,
ourPubKey,
redeemScript, redeemScript,
hashType,
witnessValue,
witnessScript, witnessScript,
); ) {
// updates inline trySign(
Object.assign(input, prepared); getSigningData(
} this.network,
if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); this.__INPUTS,
} this.__needsOutputs.bind(this),
// ready to sign this.__TX,
let signatureHash; signParams,
if (input.hasWitness) { keyPair,
signatureHash = this.__TX.hashForWitnessV0( redeemScript,
vin,
input.signScript,
input.value,
hashType, hashType,
witnessValue,
witnessScript,
this.__USE_LOW_R,
),
); );
} else {
signatureHash = this.__TX.hashForSignature(
vin,
input.signScript,
hashType,
);
}
// enforce in order signing of public keys
const signed = input.pubkeys.some((pubKey, i) => {
if (!ourPubKey.equals(pubKey)) return false;
if (input.signatures[i]) throw new Error('Signature already exists');
// TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error(
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
);
}
const signature = keyPair.sign(signatureHash, this.__USE_LOW_R);
input.signatures[i] = bscript.signature.encode(signature, hashType);
return true;
});
if (!signed) throw new Error('Key pair cannot sign for this input');
} }
__addInputUnsafe(txHash, vout, options) { __addInputUnsafe(txHash, vout, options) {
if (transaction_1.Transaction.isCoinbaseHash(txHash)) { if (transaction_1.Transaction.isCoinbaseHash(txHash)) {
@ -746,3 +728,331 @@ function canSign(input) {
function signatureHashType(buffer) { function signatureHashType(buffer) {
return buffer.readUInt8(buffer.length - 1); return buffer.readUInt8(buffer.length - 1);
} }
function checkSignArgs(inputs, signParams) {
if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) {
throw new TypeError(
`Unknown prevOutScriptType "${signParams.prevOutScriptType}"`,
);
}
tfMessage(
typeforce.Number,
signParams.vin,
`sign must include vin parameter as Number (input index)`,
);
tfMessage(
types.Signer,
signParams.keyPair,
`sign must include keyPair parameter as Signer interface`,
);
tfMessage(
typeforce.maybe(typeforce.Number),
signParams.hashType,
`sign hashType parameter must be a number`,
);
const prevOutType = (inputs[signParams.vin] || []).prevOutType;
const posType = signParams.prevOutScriptType;
switch (posType) {
case 'p2pkh':
if (prevOutType && prevOutType !== 'pubkeyhash') {
throw new TypeError(
`input #${signParams.vin} is not of type p2pkh: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2pk':
if (prevOutType && prevOutType !== 'pubkey') {
throw new TypeError(
`input #${signParams.vin} is not of type p2pk: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2wpkh':
if (prevOutType && prevOutType !== 'witnesspubkeyhash') {
throw new TypeError(
`input #${signParams.vin} is not of type p2wpkh: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessValue`,
);
break;
case 'p2ms':
if (prevOutType && prevOutType !== 'multisig') {
throw new TypeError(
`input #${signParams.vin} is not of type p2ms: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2sh-p2wpkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type p2sh-p2wpkh: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.Buffer,
signParams.redeemScript,
`${posType} requires redeemScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessValue`,
);
break;
case 'p2sh-p2ms':
case 'p2sh-p2pk':
case 'p2sh-p2pkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.Buffer,
signParams.redeemScript,
`${posType} requires redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2wsh-p2ms':
case 'p2wsh-p2pk':
case 'p2wsh-p2pkh':
if (prevOutType && prevOutType !== 'witnessscripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`,
);
}
tfMessage(
typeforce.Buffer,
signParams.witnessScript,
`${posType} requires witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessValue`,
);
break;
case 'p2sh-p2wsh-p2ms':
case 'p2sh-p2wsh-p2pk':
case 'p2sh-p2wsh-p2pkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`,
);
}
tfMessage(
typeforce.Buffer,
signParams.witnessScript,
`${posType} requires witnessScript`,
);
tfMessage(
typeforce.Buffer,
signParams.redeemScript,
`${posType} requires witnessScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessScript`,
);
break;
}
}
function trySign({
input,
ourPubKey,
keyPair,
signatureHash,
hashType,
useLowR,
}) {
// enforce in order signing of public keys
let signed = false;
for (const [i, pubKey] of input.pubkeys.entries()) {
if (!ourPubKey.equals(pubKey)) continue;
if (input.signatures[i]) throw new Error('Signature already exists');
// TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error(
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
);
}
const signature = keyPair.sign(signatureHash, useLowR);
input.signatures[i] = bscript.signature.encode(signature, hashType);
signed = true;
}
if (!signed) throw new Error('Key pair cannot sign for this input');
}
function getSigningData(
network,
inputs,
needsOutputs,
tx,
signParams,
keyPair,
redeemScript,
hashType,
witnessValue,
witnessScript,
useLowR,
) {
let vin;
if (typeof signParams === 'number') {
console.warn(
'DEPRECATED: TransactionBuilder sign method arguments ' +
'will change in v6, please use the TxbSignArg interface',
);
vin = signParams;
} else if (typeof signParams === 'object') {
checkSignArgs(inputs, signParams);
({
vin,
keyPair,
redeemScript,
hashType,
witnessValue,
witnessScript,
} = signParams);
} else {
throw new TypeError(
'TransactionBuilder sign first arg must be TxbSignArg or number',
);
}
if (keyPair === undefined) {
throw new Error('sign requires keypair');
}
// TODO: remove keyPair.network matching in 4.0.0
if (keyPair.network && keyPair.network !== network)
throw new TypeError('Inconsistent network');
if (!inputs[vin]) throw new Error('No input at index: ' + vin);
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL;
if (needsOutputs(hashType)) throw new Error('Transaction needs outputs');
const input = inputs[vin];
// if redeemScript was previously provided, enforce consistency
if (
input.redeemScript !== undefined &&
redeemScript &&
!input.redeemScript.equals(redeemScript)
) {
throw new Error('Inconsistent redeemScript');
}
const ourPubKey =
keyPair.publicKey || (keyPair.getPublicKey && keyPair.getPublicKey());
if (!canSign(input)) {
if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue)
throw new Error('Input did not match witnessValue');
typeforce(types.Satoshi, witnessValue);
input.value = witnessValue;
}
if (!canSign(input)) {
const prepared = prepareInput(
input,
ourPubKey,
redeemScript,
witnessScript,
);
// updates inline
Object.assign(input, prepared);
}
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
}
// ready to sign
let signatureHash;
if (input.hasWitness) {
signatureHash = tx.hashForWitnessV0(
vin,
input.signScript,
input.value,
hashType,
);
} else {
signatureHash = tx.hashForSignature(vin, input.signScript, hashType);
}
return {
input,
ourPubKey,
keyPair,
signatureHash,
hashType,
useLowR: !!useLowR,
};
}

View file

@ -13,6 +13,14 @@ exports.BIP32Path = BIP32Path;
BIP32Path.toJSON = () => { BIP32Path.toJSON = () => {
return 'BIP32 derivation path'; return 'BIP32 derivation path';
}; };
function Signer(obj) {
return (
(typeforce.Buffer(obj.publicKey) ||
typeof obj.getPublicKey === 'function') &&
typeof obj.sign === 'function'
);
}
exports.Signer = Signer;
const SATOSHI_MAX = 21 * 1e14; const SATOSHI_MAX = 21 * 1e14;
function Satoshi(value) { function Satoshi(value) {
return typeforce.UInt53(value) && value <= SATOSHI_MAX; return typeforce.UInt53(value) && value <= SATOSHI_MAX;

File diff suppressed because it is too large Load diff

View file

@ -92,7 +92,11 @@ async function faucetComplex (output, value) {
const txvb = new bitcoin.TransactionBuilder(NETWORK) const txvb = new bitcoin.TransactionBuilder(NETWORK)
txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output) txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output)
txvb.addOutput(output, value) txvb.addOutput(output, value)
txvb.sign(0, keyPair) txvb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair
})
const txv = txvb.build() const txv = txvb.build()
await broadcast(txv.toHex()) await broadcast(txv.toHex())

View file

@ -15,12 +15,29 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
txb.addInput(unspent.txId, unspent.vout, null, prevOutput) txb.addInput(unspent.txId, unspent.vout, null, prevOutput)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
const posType = depends.prevOutScriptType
const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh'
if (depends.signatures) { if (depends.signatures) {
keyPairs.forEach(keyPair => { keyPairs.forEach(keyPair => {
txb.sign(0, keyPair, redeemScript, null, unspent.value, witnessScript) txb.sign({
prevOutScriptType: posType,
vin: 0,
keyPair,
redeemScript,
witnessValue: needsValue ? unspent.value : undefined,
witnessScript,
})
}) })
} else if (depends.signature) { } else if (depends.signature) {
txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript) txb.sign({
prevOutScriptType: posType,
vin: 0,
keyPair: keyPairs[0],
redeemScript,
witnessValue: needsValue ? unspent.value : undefined,
witnessScript,
})
} }
return regtestUtils.broadcast(txb.build().toHex()) return regtestUtils.broadcast(txb.build().toHex())
@ -41,12 +58,14 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
describe('bitcoinjs-lib (payments - ' + k + ')', () => { describe('bitcoinjs-lib (payments - ' + k + ')', () => {
it('can broadcast as an output, and be spent as an input', async () => { it('can broadcast as an output, and be spent as an input', async () => {
await buildAndSign(depends, output, null, null) 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 () => { 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 }) const p2sh = bitcoin.payments.p2sh({ redeem: { output }, network: NETWORK })
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, null) Object.assign(depends, { prevOutScriptType: 'p2sh-' + k })
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, undefined)
}) })
// NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail // NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail
@ -54,13 +73,15 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', async () => { 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 }) const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
await buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output) 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 () => { 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 p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.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) await buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output)
}) })
}) })

View file

@ -18,7 +18,11 @@ describe('bitcoinjs-lib (transactions)', () => {
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000) txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000)
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee // (in)15000 - (out)12000 = (fee)3000, this is the miner fee
txb.sign(0, alice) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair: alice
})
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below // 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')
@ -36,8 +40,16 @@ describe('bitcoinjs-lib (transactions)', () => {
txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000) txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000)
// (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee // (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee
txb.sign(1, bob) // Bob signs his input, which was the second input (1th) txb.sign({
txb.sign(0, alice) // Alice signs her input, which was the first input (0th) prevOutScriptType: 'p2pkh',
vin: 1,
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)
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below // 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')
@ -65,8 +77,16 @@ describe('bitcoinjs-lib (transactions)', () => {
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
// Alice signs each input with the respective private keys // Alice signs each input with the respective private keys
txb.sign(0, alice1) txb.sign({
txb.sign(1, alice2) prevOutScriptType: 'p2pkh',
vin: 0,
keyPair: alice1
})
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 1,
keyPair: alice2
})
// build and broadcast our RegTest network // build and broadcast our RegTest network
await regtestUtils.broadcast(txb.build().toHex()) await regtestUtils.broadcast(txb.build().toHex())
@ -85,7 +105,11 @@ describe('bitcoinjs-lib (transactions)', () => {
txb.addInput(unspent.txId, unspent.vout) txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(embed.output, 1000) txb.addOutput(embed.output, 1000)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
// build and broadcast to the RegTest network // build and broadcast to the RegTest network
await regtestUtils.broadcast(txb.build().toHex()) await regtestUtils.broadcast(txb.build().toHex())
@ -108,8 +132,18 @@ describe('bitcoinjs-lib (transactions)', () => {
txb.addInput(unspent.txId, unspent.vout) txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
txb.sign(0, keyPairs[0], p2sh.redeem.output) txb.sign({
txb.sign(0, keyPairs[2], p2sh.redeem.output) prevOutScriptType: 'p2sh-p2ms',
vin: 0,
keyPair: keyPairs[0],
redeemScript: p2sh.redeem.output,
})
txb.sign({
prevOutScriptType: 'p2sh-p2ms',
vin: 0,
keyPair: keyPairs[2],
redeemScript: p2sh.redeem.output,
})
const tx = txb.build() const tx = txb.build()
// build and broadcast to the Bitcoin RegTest network // build and broadcast to the Bitcoin RegTest network
@ -133,7 +167,13 @@ describe('bitcoinjs-lib (transactions)', () => {
const txb = new bitcoin.TransactionBuilder(regtest) const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout) txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
txb.sign(0, keyPair, p2sh.redeem.output, null, unspent.value) txb.sign({
prevOutScriptType: 'p2sh-p2wpkh',
vin: 0,
keyPair: keyPair,
redeemScript: p2sh.redeem.output,
witnessValue: unspent.value,
})
const tx = txb.build() const tx = txb.build()
@ -158,7 +198,12 @@ describe('bitcoinjs-lib (transactions)', () => {
const txb = new bitcoin.TransactionBuilder(regtest) const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript! txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
txb.sign(0, keyPair, null, null, unspent.value) // NOTE: no redeem script txb.sign({
prevOutScriptType: 'p2wpkh',
vin: 0,
keyPair: keyPair,
witnessValue: unspent.value,
}) // NOTE: no redeem script
const tx = txb.build() const tx = txb.build()
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network // build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
@ -183,7 +228,13 @@ describe('bitcoinjs-lib (transactions)', () => {
const txb = new bitcoin.TransactionBuilder(regtest) const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript! txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
txb.sign(0, keyPair, null, null, 5e4, p2wsh.redeem.output) // NOTE: provide a witnessScript! txb.sign({
prevOutScriptType: 'p2wsh-p2pk',
vin: 0,
keyPair: keyPair,
witnessValue: 5e4,
witnessScript: p2wsh.redeem.output,
}) // NOTE: provide a witnessScript!
const tx = txb.build() const tx = txb.build()
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network // build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network
@ -215,9 +266,30 @@ describe('bitcoinjs-lib (transactions)', () => {
const txb = new bitcoin.TransactionBuilder(regtest) const txb = new bitcoin.TransactionBuilder(regtest)
txb.addInput(unspent.txId, unspent.vout, null, p2sh.output) txb.addInput(unspent.txId, unspent.vout, null, p2sh.output)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4)
txb.sign(0, keyPairs[0], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output) txb.sign({
txb.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output) prevOutScriptType: 'p2sh-p2wsh-p2ms',
txb.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output) vin: 0,
keyPair: keyPairs[0],
redeemScript: p2sh.redeem.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem.output,
})
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[2],
redeemScript: p2sh.redeem.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem.output,
})
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[3],
redeemScript: p2sh.redeem.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem.output,
})
const tx = txb.build() const tx = txb.build()

View file

@ -11,7 +11,7 @@ const NETWORKS = require('../src/networks')
const fixtures = require('./fixtures/transaction_builder') const fixtures = require('./fixtures/transaction_builder')
function constructSign (f, txb) { function constructSign (f, txb, useOldSignArgs) {
const network = NETWORKS[f.network] const network = NETWORKS[f.network]
const stages = f.stages && f.stages.concat() const stages = f.stages && f.stages.concat()
@ -21,21 +21,36 @@ function constructSign (f, txb) {
const keyPair = ECPair.fromWIF(sign.keyPair, network) const keyPair = ECPair.fromWIF(sign.keyPair, network)
let redeemScript let redeemScript
let witnessScript let witnessScript
let value let witnessValue
if (sign.redeemScript) { if (sign.redeemScript) {
redeemScript = bscript.fromASM(sign.redeemScript) redeemScript = bscript.fromASM(sign.redeemScript)
} }
if (sign.value) { if (sign.value) {
value = sign.value witnessValue = sign.value
} }
if (sign.witnessScript) { if (sign.witnessScript) {
witnessScript = bscript.fromASM(sign.witnessScript) witnessScript = bscript.fromASM(sign.witnessScript)
} }
txb.sign(index, keyPair, redeemScript, sign.hashType, value, witnessScript) if (useOldSignArgs) {
// DEPRECATED: v6 will remove this interface
txb.sign(index, keyPair, redeemScript, sign.hashType, witnessValue, witnessScript)
} else {
// prevOutScriptType is required, see /ts_src/transaction_builder.ts
// The PREVOUT_TYPES constant is a Set with all possible values.
txb.sign({
prevOutScriptType: sign.prevOutScriptType,
vin: index,
keyPair,
redeemScript,
hashType: sign.hashType,
witnessValue,
witnessScript,
})
}
if (sign.stage) { if (sign.stage) {
const tx = txb.buildIncomplete() const tx = txb.buildIncomplete()
@ -48,7 +63,7 @@ function constructSign (f, txb) {
return txb return txb
} }
function construct (f, dontSign) { function construct (f, dontSign, useOldSignArgs) {
const network = NETWORKS[f.network] const network = NETWORKS[f.network]
const txb = new TransactionBuilder(network) const txb = new TransactionBuilder(network)
@ -84,10 +99,20 @@ function construct (f, dontSign) {
}) })
if (dontSign) return txb if (dontSign) return txb
return constructSign(f, txb) return constructSign(f, txb, useOldSignArgs)
} }
describe('TransactionBuilder', () => { // TODO: Remove loop in v6
for (const useOldSignArgs of [ false, true ]) {
// Search for "useOldSignArgs"
// to find the second part of this console.warn replace
let consoleWarn;
if (useOldSignArgs) {
consoleWarn = console.warn;
// Silence console.warn during these tests
console.warn = () => undefined;
}
describe(`TransactionBuilder: useOldSignArgs === ${useOldSignArgs}`, () => {
// constants // constants
const keyPair = ECPair.fromPrivateKey(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')) const keyPair = ECPair.fromPrivateKey(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'))
const scripts = [ const scripts = [
@ -149,7 +174,7 @@ describe('TransactionBuilder', () => {
assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSig) assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSig)
}) })
constructSign(f, txb) constructSign(f, txb, useOldSignArgs)
const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build()
txAfter.ins.forEach((input, i) => { txAfter.ins.forEach((input, i) => {
@ -232,7 +257,11 @@ describe('TransactionBuilder', () => {
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => {
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
txb.addOutput(scripts[0], 1000) txb.addOutput(scripts[0], 1000)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
assert.throws(() => { assert.throws(() => {
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
@ -274,26 +303,46 @@ describe('TransactionBuilder', () => {
it('add second output after signed first input with SIGHASH_NONE', () => { it('add second output after signed first input with SIGHASH_NONE', () => {
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
txb.addOutput(scripts[0], 2000) txb.addOutput(scripts[0], 2000)
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
hashType: Transaction.SIGHASH_NONE,
})
assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) assert.strictEqual(txb.addOutput(scripts[1], 9000), 1)
}) })
it('add first output after signed first input with SIGHASH_NONE', () => { it('add first output after signed first input with SIGHASH_NONE', () => {
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
hashType: Transaction.SIGHASH_NONE,
})
assert.strictEqual(txb.addOutput(scripts[0], 2000), 0) assert.strictEqual(txb.addOutput(scripts[0], 2000), 0)
}) })
it('add second output after signed first input with SIGHASH_SINGLE', () => { it('add second output after signed first input with SIGHASH_SINGLE', () => {
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
txb.addOutput(scripts[0], 2000) txb.addOutput(scripts[0], 2000)
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
hashType: Transaction.SIGHASH_SINGLE,
})
assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) assert.strictEqual(txb.addOutput(scripts[1], 9000), 1)
}) })
it('add first output after signed first input with SIGHASH_SINGLE', () => { it('add first output after signed first input with SIGHASH_SINGLE', () => {
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
hashType: Transaction.SIGHASH_SINGLE,
})
assert.throws(() => { assert.throws(() => {
txb.addOutput(scripts[0], 2000) txb.addOutput(scripts[0], 2000)
}, /No, this would invalidate signatures/) }, /No, this would invalidate signatures/)
@ -302,7 +351,11 @@ describe('TransactionBuilder', () => {
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => {
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
txb.addOutput(scripts[0], 2000) txb.addOutput(scripts[0], 2000)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
assert.throws(() => { assert.throws(() => {
txb.addOutput(scripts[1], 9000) txb.addOutput(scripts[1], 9000)
@ -315,7 +368,11 @@ describe('TransactionBuilder', () => {
const txb = new TransactionBuilder() const txb = new TransactionBuilder()
txb.addInput(txHash, 0) txb.addInput(txHash, 0)
txb.addOutput(scripts[0], 100) txb.addOutput(scripts[0], 100)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
assert.throws(() => { assert.throws(() => {
txb.setLockTime(65535) txb.setLockTime(65535)
@ -334,7 +391,11 @@ describe('TransactionBuilder', () => {
txb.setVersion(1) txb.setVersion(1)
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
txb.addOutput('1111111111111111111114oLvT2', 100000) txb.addOutput('1111111111111111111114oLvT2', 100000)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
}) })
@ -343,7 +404,11 @@ describe('TransactionBuilder', () => {
txb.setVersion(1) txb.setVersion(1)
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
txb.addOutput('1111111111111111111114oLvT2', 100000) txb.addOutput('1111111111111111111114oLvT2', 100000)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
// high R // high R
assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb4941f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb4941f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
@ -352,11 +417,58 @@ describe('TransactionBuilder', () => {
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
txb.addOutput('1111111111111111111114oLvT2', 100000) txb.addOutput('1111111111111111111114oLvT2', 100000)
txb.setLowR() txb.setLowR()
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
// low R // low R
assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
}) })
it('fails when missing required arguments', () => {
let txb = new TransactionBuilder()
txb.setVersion(1)
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
txb.addOutput('1111111111111111111114oLvT2', 100000)
assert.throws(() => {
txb.sign()
}, /TransactionBuilder sign first arg must be TxbSignArg or number/)
assert.throws(() => {
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 1,
keyPair,
})
}, /No input at index: 1/)
assert.throws(() => {
txb.sign({
prevOutScriptType: 'p2pkh',
keyPair,
})
}, /sign must include vin parameter as Number \(input index\)/)
assert.throws(() => {
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair: {},
})
}, /sign must include keyPair parameter as Signer interface/)
assert.throws(() => {
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
hashType: 'string',
})
}, /sign hashType parameter must be a number/)
if (useOldSignArgs) {
assert.throws(() => {
txb.sign(0)
}, /sign requires keypair/)
}
})
fixtures.invalid.sign.forEach(f => { fixtures.invalid.sign.forEach(f => {
it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => { it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => {
const txb = construct(f, true) const txb = construct(f, true)
@ -379,11 +491,27 @@ describe('TransactionBuilder', () => {
if (sign.throws) { if (sign.throws) {
assert.throws(() => { assert.throws(() => {
txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript) txb.sign({
prevOutScriptType: sign.prevOutScriptType,
vin: index,
keyPair: keyPair2,
redeemScript,
hashType: sign.hashType,
witnessValue: sign.value,
witnessScript,
})
}, new RegExp(f.exception)) }, new RegExp(f.exception))
threw = true threw = true
} else { } else {
txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript) txb.sign({
prevOutScriptType: sign.prevOutScriptType,
vin: index,
keyPair: keyPair2,
redeemScript,
hashType: sign.hashType,
witnessValue: sign.value,
witnessScript,
})
} }
}) })
}) })
@ -396,7 +524,7 @@ describe('TransactionBuilder', () => {
describe('build', () => { describe('build', () => {
fixtures.valid.build.forEach(f => { fixtures.valid.build.forEach(f => {
it('builds "' + f.description + '"', () => { it('builds "' + f.description + '"', () => {
const txb = construct(f) const txb = construct(f, undefined, useOldSignArgs)
const tx = f.incomplete ? txb.buildIncomplete() : txb.build() const tx = f.incomplete ? txb.buildIncomplete() : txb.build()
assert.strictEqual(tx.toHex(), f.txHex) assert.strictEqual(tx.toHex(), f.txHex)
@ -412,7 +540,7 @@ describe('TransactionBuilder', () => {
if (f.txHex) { if (f.txHex) {
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
} else { } else {
txb = construct(f) txb = construct(f, undefined, useOldSignArgs)
} }
txb.build() txb.build()
@ -427,7 +555,7 @@ describe('TransactionBuilder', () => {
if (f.txHex) { if (f.txHex) {
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
} else { } else {
txb = construct(f) txb = construct(f, undefined, useOldSignArgs)
} }
txb.buildIncomplete() txb.buildIncomplete()
@ -439,7 +567,7 @@ describe('TransactionBuilder', () => {
if (f.txHex) { if (f.txHex) {
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
} else { } else {
txb = construct(f) txb = construct(f, undefined, useOldSignArgs)
} }
txb.buildIncomplete() txb.buildIncomplete()
@ -516,7 +644,13 @@ describe('TransactionBuilder', () => {
} }
const keyPair2 = ECPair.fromWIF(sign.keyPair, network) const keyPair2 = ECPair.fromWIF(sign.keyPair, network)
txb.sign(i, keyPair2, redeemScript, sign.hashType) txb.sign({
prevOutScriptType: sign.prevOutScriptType,
vin: i,
keyPair: keyPair2,
redeemScript,
hashType: sign.hashType,
})
// update the tx // update the tx
tx = txb.buildIncomplete() tx = txb.buildIncomplete()
@ -571,7 +705,14 @@ describe('TransactionBuilder', () => {
txb.setVersion(1) txb.setVersion(1)
txb.addInput('a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529', 1, 0xffffffff, scriptPubKey) txb.addInput('a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529', 1, 0xffffffff, scriptPubKey)
txb.addOutput(scriptPubKey, 99000) txb.addOutput(scriptPubKey, 99000)
txb.sign(0, keyPair, redeemScript, null, 100000, witnessScript) txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair,
redeemScript,
witnessValue: 100000,
witnessScript,
})
// 2-of-2 signed only once // 2-of-2 signed only once
const tx = txb.buildIncomplete() const tx = txb.buildIncomplete()
@ -596,7 +737,12 @@ describe('TransactionBuilder', () => {
const txb = TransactionBuilder.fromTransaction(tx, NETWORKS.testnet) const txb = TransactionBuilder.fromTransaction(tx, NETWORKS.testnet)
const keyPair2 = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network) const keyPair2 = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network)
txb.sign(0, keyPair2, redeemScript) txb.sign({
prevOutScriptType: 'p2sh-p2ms',
vin: 0,
keyPair: keyPair2,
redeemScript,
})
const tx2 = txb.build() const tx2 = txb.build()
assert.strictEqual(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9') assert.strictEqual(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9')
@ -613,16 +759,29 @@ describe('TransactionBuilder', () => {
// sign, as expected // sign, as expected
txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
const txId = txb.build().getId() const txId = txb.build().getId()
assert.strictEqual(txId, '54f097315acbaedb92a95455da3368eb45981cdae5ffbc387a9afc872c0f29b3') assert.strictEqual(txId, '54f097315acbaedb92a95455da3368eb45981cdae5ffbc387a9afc872c0f29b3')
// and, repeat // and, repeat
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(incomplete)) txb = TransactionBuilder.fromTransaction(Transaction.fromHex(incomplete))
txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
txb.sign(0, keyPair) txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair,
})
const txId2 = txb.build().getId() const txId2 = txb.build().getId()
assert.strictEqual(txId, txId2) assert.strictEqual(txId, txId2)
// TODO: Remove me in v6
if (useOldSignArgs) {
console.warn = consoleWarn;
}
}) })
}) })
}) })
}

View file

@ -19,15 +19,26 @@ interface ECPairOptions {
rng?(arg0: number): Buffer; rng?(arg0: number): Buffer;
} }
export interface ECPairInterface { export interface Signer {
publicKey: Buffer;
network?: Network;
sign(hash: Buffer, lowR?: boolean): Buffer;
getPublicKey?(): Buffer;
}
export interface SignerAsync {
publicKey: Buffer;
network?: Network;
sign(hash: Buffer, lowR?: boolean): Promise<Buffer>;
getPublicKey?(): Buffer;
}
export interface ECPairInterface extends Signer {
compressed: boolean; compressed: boolean;
network: Network; network: Network;
publicKey: Buffer;
privateKey?: Buffer; privateKey?: Buffer;
toWIF(): string; toWIF(): string;
sign(hash: Buffer, lowR?: boolean): Buffer;
verify(hash: Buffer, signature: Buffer): boolean; verify(hash: Buffer, signature: Buffer): boolean;
getPublicKey?(): Buffer;
} }
class ECPair implements ECPairInterface { class ECPair implements ECPairInterface {

View file

@ -14,7 +14,7 @@ export { Transaction } from './transaction';
export { TransactionBuilder } from './transaction_builder'; export { TransactionBuilder } from './transaction_builder';
export { BIP32Interface } from 'bip32'; export { BIP32Interface } from 'bip32';
export { ECPairInterface } from './ecpair'; export { ECPairInterface, Signer, SignerAsync } from './ecpair';
export { Network } from './networks'; export { Network } from './networks';
export { Payment, PaymentOpts, Stack, StackElement } from './payments'; export { Payment, PaymentOpts, Stack, StackElement } from './payments';
export { OpCode } from './script'; export { OpCode } from './script';

View file

@ -2,7 +2,7 @@ import * as baddress from './address';
import { reverseBuffer } from './bufferutils'; import { reverseBuffer } from './bufferutils';
import * as classify from './classify'; import * as classify from './classify';
import * as bcrypto from './crypto'; import * as bcrypto from './crypto';
import { ECPairInterface } from './ecpair'; import { Signer } from './ecpair';
import * as ECPair from './ecpair'; import * as ECPair from './ecpair';
import { Network } from './networks'; import { Network } from './networks';
import * as networks from './networks'; import * as networks from './networks';
@ -16,6 +16,27 @@ const typeforce = require('typeforce');
const SCRIPT_TYPES = classify.types; const SCRIPT_TYPES = classify.types;
const PREVOUT_TYPES: Set<string> = new Set([
// Raw
'p2pkh',
'p2pk',
'p2wpkh',
'p2ms',
// P2SH wrapped
'p2sh-p2pkh',
'p2sh-p2pk',
'p2sh-p2wpkh',
'p2sh-p2ms',
// P2WSH wrapped
'p2wsh-p2pkh',
'p2wsh-p2pk',
'p2wsh-p2ms',
// P2SH-P2WSH wrapper
'p2sh-p2wsh-p2pkh',
'p2sh-p2wsh-p2pk',
'p2sh-p2wsh-p2ms',
]);
type MaybeBuffer = Buffer | undefined; type MaybeBuffer = Buffer | undefined;
type TxbSignatures = Buffer[] | MaybeBuffer[]; type TxbSignatures = Buffer[] | MaybeBuffer[];
type TxbPubkeys = MaybeBuffer[]; type TxbPubkeys = MaybeBuffer[];
@ -50,6 +71,24 @@ interface TxbOutput {
maxSignatures?: number; maxSignatures?: number;
} }
interface TxbSignArg {
prevOutScriptType: string;
vin: number;
keyPair: Signer;
redeemScript?: Buffer;
hashType?: number;
witnessValue?: number;
witnessScript?: Buffer;
}
function tfMessage(type: any, value: any, message: string): void {
try {
typeforce(type, value);
} catch (err) {
throw new Error(message);
}
}
function txIsString(tx: Buffer | string | Transaction): tx is string { function txIsString(tx: Buffer | string | Transaction): tx is string {
return typeof tx === 'string' || tx instanceof String; return typeof tx === 'string' || tx instanceof String;
} }
@ -197,92 +236,28 @@ export class TransactionBuilder {
} }
sign( sign(
vin: number, signParams: number | TxbSignArg,
keyPair: ECPairInterface, keyPair?: Signer,
redeemScript?: Buffer, redeemScript?: Buffer,
hashType?: number, hashType?: number,
witnessValue?: number, witnessValue?: number,
witnessScript?: Buffer, witnessScript?: Buffer,
): void { ): void {
// TODO: remove keyPair.network matching in 4.0.0 trySign(
if (keyPair.network && keyPair.network !== this.network) getSigningData(
throw new TypeError('Inconsistent network'); this.network,
if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin); this.__INPUTS,
this.__needsOutputs.bind(this),
hashType = hashType || Transaction.SIGHASH_ALL; this.__TX,
if (this.__needsOutputs(hashType)) signParams,
throw new Error('Transaction needs outputs'); keyPair,
const input = this.__INPUTS[vin];
// if redeemScript was previously provided, enforce consistency
if (
input.redeemScript !== undefined &&
redeemScript &&
!input.redeemScript.equals(redeemScript)
) {
throw new Error('Inconsistent redeemScript');
}
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey!();
if (!canSign(input)) {
if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue)
throw new Error('Input did not match witnessValue');
typeforce(types.Satoshi, witnessValue);
input.value = witnessValue;
}
if (!canSign(input)) {
const prepared = prepareInput(
input,
ourPubKey,
redeemScript, redeemScript,
hashType,
witnessValue,
witnessScript, witnessScript,
this.__USE_LOW_R,
),
); );
// updates inline
Object.assign(input, prepared);
}
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
}
// ready to sign
let signatureHash: Buffer;
if (input.hasWitness) {
signatureHash = this.__TX.hashForWitnessV0(
vin,
input.signScript as Buffer,
input.value as number,
hashType,
);
} else {
signatureHash = this.__TX.hashForSignature(
vin,
input.signScript as Buffer,
hashType,
);
}
// enforce in order signing of public keys
const signed = input.pubkeys!.some((pubKey, i) => {
if (!ourPubKey.equals(pubKey!)) return false;
if (input.signatures![i]) throw new Error('Signature already exists');
// TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error(
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
);
}
const signature = keyPair.sign(signatureHash, this.__USE_LOW_R);
input.signatures![i] = bscript.signature.encode(signature, hashType!);
return true;
});
if (!signed) throw new Error('Key pair cannot sign for this input');
} }
private __addInputUnsafe( private __addInputUnsafe(
@ -976,3 +951,361 @@ function canSign(input: TxbInput): boolean {
function signatureHashType(buffer: Buffer): number { function signatureHashType(buffer: Buffer): number {
return buffer.readUInt8(buffer.length - 1); return buffer.readUInt8(buffer.length - 1);
} }
function checkSignArgs(inputs: TxbInput[], signParams: TxbSignArg): void {
if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) {
throw new TypeError(
`Unknown prevOutScriptType "${signParams.prevOutScriptType}"`,
);
}
tfMessage(
typeforce.Number,
signParams.vin,
`sign must include vin parameter as Number (input index)`,
);
tfMessage(
types.Signer,
signParams.keyPair,
`sign must include keyPair parameter as Signer interface`,
);
tfMessage(
typeforce.maybe(typeforce.Number),
signParams.hashType,
`sign hashType parameter must be a number`,
);
const prevOutType = (inputs[signParams.vin] || []).prevOutType;
const posType = signParams.prevOutScriptType;
switch (posType) {
case 'p2pkh':
if (prevOutType && prevOutType !== 'pubkeyhash') {
throw new TypeError(
`input #${signParams.vin} is not of type p2pkh: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2pk':
if (prevOutType && prevOutType !== 'pubkey') {
throw new TypeError(
`input #${signParams.vin} is not of type p2pk: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2wpkh':
if (prevOutType && prevOutType !== 'witnesspubkeyhash') {
throw new TypeError(
`input #${signParams.vin} is not of type p2wpkh: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessValue`,
);
break;
case 'p2ms':
if (prevOutType && prevOutType !== 'multisig') {
throw new TypeError(
`input #${signParams.vin} is not of type p2ms: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2sh-p2wpkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type p2sh-p2wpkh: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.Buffer,
signParams.redeemScript,
`${posType} requires redeemScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessValue`,
);
break;
case 'p2sh-p2ms':
case 'p2sh-p2pk':
case 'p2sh-p2pkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`,
);
}
tfMessage(
typeforce.value(undefined),
signParams.witnessScript,
`${posType} requires NO witnessScript`,
);
tfMessage(
typeforce.Buffer,
signParams.redeemScript,
`${posType} requires redeemScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.witnessValue,
`${posType} requires NO witnessValue`,
);
break;
case 'p2wsh-p2ms':
case 'p2wsh-p2pk':
case 'p2wsh-p2pkh':
if (prevOutType && prevOutType !== 'witnessscripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`,
);
}
tfMessage(
typeforce.Buffer,
signParams.witnessScript,
`${posType} requires witnessScript`,
);
tfMessage(
typeforce.value(undefined),
signParams.redeemScript,
`${posType} requires NO redeemScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessValue`,
);
break;
case 'p2sh-p2wsh-p2ms':
case 'p2sh-p2wsh-p2pk':
case 'p2sh-p2wsh-p2pkh':
if (prevOutType && prevOutType !== 'scripthash') {
throw new TypeError(
`input #${signParams.vin} is not of type ${posType}: ${prevOutType}`,
);
}
tfMessage(
typeforce.Buffer,
signParams.witnessScript,
`${posType} requires witnessScript`,
);
tfMessage(
typeforce.Buffer,
signParams.redeemScript,
`${posType} requires witnessScript`,
);
tfMessage(
types.Satoshi,
signParams.witnessValue,
`${posType} requires witnessScript`,
);
break;
}
}
function trySign({
input,
ourPubKey,
keyPair,
signatureHash,
hashType,
useLowR,
}: SigningData): void {
// enforce in order signing of public keys
let signed = false;
for (const [i, pubKey] of input.pubkeys!.entries()) {
if (!ourPubKey.equals(pubKey!)) continue;
if (input.signatures![i]) throw new Error('Signature already exists');
// TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error(
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
);
}
const signature = keyPair.sign(signatureHash, useLowR);
input.signatures![i] = bscript.signature.encode(signature, hashType);
signed = true;
}
if (!signed) throw new Error('Key pair cannot sign for this input');
}
interface SigningData {
input: TxbInput;
ourPubKey: Buffer;
keyPair: Signer;
signatureHash: Buffer;
hashType: number;
useLowR: boolean;
}
type HashTypeCheck = (hashType: number) => boolean;
function getSigningData(
network: Network,
inputs: TxbInput[],
needsOutputs: HashTypeCheck,
tx: Transaction,
signParams: number | TxbSignArg,
keyPair?: Signer,
redeemScript?: Buffer,
hashType?: number,
witnessValue?: number,
witnessScript?: Buffer,
useLowR?: boolean,
): SigningData {
let vin: number;
if (typeof signParams === 'number') {
console.warn(
'DEPRECATED: TransactionBuilder sign method arguments ' +
'will change in v6, please use the TxbSignArg interface',
);
vin = signParams;
} else if (typeof signParams === 'object') {
checkSignArgs(inputs, signParams);
({
vin,
keyPair,
redeemScript,
hashType,
witnessValue,
witnessScript,
} = signParams);
} else {
throw new TypeError(
'TransactionBuilder sign first arg must be TxbSignArg or number',
);
}
if (keyPair === undefined) {
throw new Error('sign requires keypair');
}
// TODO: remove keyPair.network matching in 4.0.0
if (keyPair.network && keyPair.network !== network)
throw new TypeError('Inconsistent network');
if (!inputs[vin]) throw new Error('No input at index: ' + vin);
hashType = hashType || Transaction.SIGHASH_ALL;
if (needsOutputs(hashType)) throw new Error('Transaction needs outputs');
const input = inputs[vin];
// if redeemScript was previously provided, enforce consistency
if (
input.redeemScript !== undefined &&
redeemScript &&
!input.redeemScript.equals(redeemScript)
) {
throw new Error('Inconsistent redeemScript');
}
const ourPubKey =
keyPair.publicKey || (keyPair.getPublicKey && keyPair.getPublicKey());
if (!canSign(input)) {
if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue)
throw new Error('Input did not match witnessValue');
typeforce(types.Satoshi, witnessValue);
input.value = witnessValue;
}
if (!canSign(input)) {
const prepared = prepareInput(
input,
ourPubKey,
redeemScript,
witnessScript,
);
// updates inline
Object.assign(input, prepared);
}
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
}
// ready to sign
let signatureHash: Buffer;
if (input.hasWitness) {
signatureHash = tx.hashForWitnessV0(
vin,
input.signScript as Buffer,
input.value as number,
hashType,
);
} else {
signatureHash = tx.hashForSignature(
vin,
input.signScript as Buffer,
hashType,
);
}
return {
input,
ourPubKey,
keyPair,
signatureHash,
hashType,
useLowR: !!useLowR,
};
}

View file

@ -12,6 +12,14 @@ BIP32Path.toJSON = (): string => {
return 'BIP32 derivation path'; return 'BIP32 derivation path';
}; };
export function Signer(obj: any): boolean {
return (
(typeforce.Buffer(obj.publicKey) ||
typeof obj.getPublicKey === 'function') &&
typeof obj.sign === 'function'
);
}
const SATOSHI_MAX: number = 21 * 1e14; const SATOSHI_MAX: number = 21 * 1e14;
export function Satoshi(value: number): boolean { export function Satoshi(value: number): boolean {
return typeforce.UInt53(value) && value <= SATOSHI_MAX; return typeforce.UInt53(value) && value <= SATOSHI_MAX;

17
types/ecpair.d.ts vendored
View file

@ -5,15 +5,24 @@ interface ECPairOptions {
network?: Network; network?: Network;
rng?(arg0: number): Buffer; rng?(arg0: number): Buffer;
} }
export interface ECPairInterface { export interface Signer {
publicKey: Buffer;
network?: Network;
sign(hash: Buffer, lowR?: boolean): Buffer;
getPublicKey?(): Buffer;
}
export interface SignerAsync {
publicKey: Buffer;
network?: Network;
sign(hash: Buffer, lowR?: boolean): Promise<Buffer>;
getPublicKey?(): Buffer;
}
export interface ECPairInterface extends Signer {
compressed: boolean; compressed: boolean;
network: Network; network: Network;
publicKey: Buffer;
privateKey?: Buffer; privateKey?: Buffer;
toWIF(): string; toWIF(): string;
sign(hash: Buffer, lowR?: boolean): Buffer;
verify(hash: Buffer, signature: Buffer): boolean; verify(hash: Buffer, signature: Buffer): boolean;
getPublicKey?(): Buffer;
} }
declare class ECPair implements ECPairInterface { declare class ECPair implements ECPairInterface {
private __D?; private __D?;

2
types/index.d.ts vendored
View file

@ -11,7 +11,7 @@ export { OPS as opcodes } from './script';
export { Transaction } from './transaction'; export { Transaction } from './transaction';
export { TransactionBuilder } from './transaction_builder'; export { TransactionBuilder } from './transaction_builder';
export { BIP32Interface } from 'bip32'; export { BIP32Interface } from 'bip32';
export { ECPairInterface } from './ecpair'; export { ECPairInterface, Signer, SignerAsync } from './ecpair';
export { Network } from './networks'; export { Network } from './networks';
export { Payment, PaymentOpts, Stack, StackElement } from './payments'; export { Payment, PaymentOpts, Stack, StackElement } from './payments';
export { OpCode } from './script'; export { OpCode } from './script';

View file

@ -1,7 +1,16 @@
/// <reference types="node" /> /// <reference types="node" />
import { ECPairInterface } from './ecpair'; import { Signer } from './ecpair';
import { Network } from './networks'; import { Network } from './networks';
import { Transaction } from './transaction'; import { Transaction } from './transaction';
interface TxbSignArg {
prevOutScriptType: string;
vin: number;
keyPair: Signer;
redeemScript?: Buffer;
hashType?: number;
witnessValue?: number;
witnessScript?: Buffer;
}
export declare class TransactionBuilder { export declare class TransactionBuilder {
network: Network; network: Network;
maximumFeeRate: number; maximumFeeRate: number;
@ -18,7 +27,7 @@ export declare class TransactionBuilder {
addOutput(scriptPubKey: string | Buffer, value: number): number; addOutput(scriptPubKey: string | Buffer, value: number): number;
build(): Transaction; build(): Transaction;
buildIncomplete(): Transaction; buildIncomplete(): Transaction;
sign(vin: number, keyPair: ECPairInterface, redeemScript?: Buffer, hashType?: number, witnessValue?: number, witnessScript?: Buffer): void; sign(signParams: number | TxbSignArg, keyPair?: Signer, redeemScript?: Buffer, hashType?: number, witnessValue?: number, witnessScript?: Buffer): void;
private __addInputUnsafe; private __addInputUnsafe;
private __build; private __build;
private __canModifyInputs; private __canModifyInputs;
@ -26,3 +35,4 @@ export declare class TransactionBuilder {
private __canModifyOutputs; private __canModifyOutputs;
private __overMaximumFees; private __overMaximumFees;
} }
export {};

1
types/types.d.ts vendored
View file

@ -3,6 +3,7 @@ export declare function BIP32Path(value: string): boolean;
export declare namespace BIP32Path { export declare namespace BIP32Path {
var toJSON: () => string; var toJSON: () => string;
} }
export declare function Signer(obj: any): boolean;
export declare function Satoshi(value: number): boolean; export declare function Satoshi(value: number): boolean;
export declare const ECPoint: any; export declare const ECPoint: any;
export declare const Network: any; export declare const Network: any;