Merge pull request #637 from bitcoinjs/txbb
TransactionBuilder: major internal refactor
This commit is contained in:
commit
1d0f48b03e
8 changed files with 470 additions and 401 deletions
|
@ -15,15 +15,6 @@ function fromBase58Check (address) {
|
||||||
return { hash: hash, version: version }
|
return { hash: hash, version: version }
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromOutputScript (scriptPubKey, network) {
|
|
||||||
network = network || networks.bitcoin
|
|
||||||
|
|
||||||
if (bscript.isPubKeyHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(3, 23), network.pubKeyHash)
|
|
||||||
if (bscript.isScriptHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(2, 22), network.scriptHash)
|
|
||||||
|
|
||||||
throw new Error(bscript.toASM(scriptPubKey) + ' has no matching Address')
|
|
||||||
}
|
|
||||||
|
|
||||||
function toBase58Check (hash, version) {
|
function toBase58Check (hash, version) {
|
||||||
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments)
|
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments)
|
||||||
|
|
||||||
|
@ -34,6 +25,15 @@ function toBase58Check (hash, version) {
|
||||||
return bs58check.encode(payload)
|
return bs58check.encode(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fromOutputScript (scriptPubKey, network) {
|
||||||
|
network = network || networks.bitcoin
|
||||||
|
|
||||||
|
if (bscript.isPubKeyHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(3, 23), network.pubKeyHash)
|
||||||
|
if (bscript.isScriptHashOutput(scriptPubKey)) return toBase58Check(bscript.compile(scriptPubKey).slice(2, 22), network.scriptHash)
|
||||||
|
|
||||||
|
throw new Error(bscript.toASM(scriptPubKey) + ' has no matching Address')
|
||||||
|
}
|
||||||
|
|
||||||
function toOutputScript (address, network) {
|
function toOutputScript (address, network) {
|
||||||
network = network || networks.bitcoin
|
network = network || networks.bitcoin
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
var createHash = require('create-hash')
|
var createHash = require('create-hash')
|
||||||
|
|
||||||
function hash160 (buffer) {
|
|
||||||
return ripemd160(sha256(buffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
function hash256 (buffer) {
|
|
||||||
return sha256(sha256(buffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
function ripemd160 (buffer) {
|
function ripemd160 (buffer) {
|
||||||
return createHash('rmd160').update(buffer).digest()
|
return createHash('rmd160').update(buffer).digest()
|
||||||
}
|
}
|
||||||
|
@ -20,6 +12,14 @@ function sha256 (buffer) {
|
||||||
return createHash('sha256').update(buffer).digest()
|
return createHash('sha256').update(buffer).digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hash160 (buffer) {
|
||||||
|
return ripemd160(sha256(buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash256 (buffer) {
|
||||||
|
return sha256(sha256(buffer))
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
hash160: hash160,
|
hash160: hash160,
|
||||||
hash256: hash256,
|
hash256: hash256,
|
||||||
|
|
|
@ -15,32 +15,6 @@ var REVERSE_OPS = (function () {
|
||||||
|
|
||||||
var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
||||||
|
|
||||||
function toASM (chunks) {
|
|
||||||
if (Buffer.isBuffer(chunks)) {
|
|
||||||
chunks = decompile(chunks)
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks.map(function (chunk) {
|
|
||||||
// data?
|
|
||||||
if (Buffer.isBuffer(chunk)) return chunk.toString('hex')
|
|
||||||
|
|
||||||
// opcode!
|
|
||||||
return REVERSE_OPS[chunk]
|
|
||||||
}).join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromASM (asm) {
|
|
||||||
typeforce(types.String, asm)
|
|
||||||
|
|
||||||
return compile(asm.split(' ').map(function (chunkStr) {
|
|
||||||
// opcode?
|
|
||||||
if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
|
|
||||||
|
|
||||||
// data!
|
|
||||||
return new Buffer(chunkStr, 'hex')
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
function compile (chunks) {
|
function compile (chunks) {
|
||||||
// TODO: remove me
|
// TODO: remove me
|
||||||
if (Buffer.isBuffer(chunks)) return chunks
|
if (Buffer.isBuffer(chunks)) return chunks
|
||||||
|
@ -118,6 +92,32 @@ function decompile (buffer) {
|
||||||
return chunks
|
return chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toASM (chunks) {
|
||||||
|
if (Buffer.isBuffer(chunks)) {
|
||||||
|
chunks = decompile(chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks.map(function (chunk) {
|
||||||
|
// data?
|
||||||
|
if (Buffer.isBuffer(chunk)) return chunk.toString('hex')
|
||||||
|
|
||||||
|
// opcode!
|
||||||
|
return REVERSE_OPS[chunk]
|
||||||
|
}).join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromASM (asm) {
|
||||||
|
typeforce(types.String, asm)
|
||||||
|
|
||||||
|
return compile(asm.split(' ').map(function (chunkStr) {
|
||||||
|
// opcode?
|
||||||
|
if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
|
||||||
|
|
||||||
|
// data!
|
||||||
|
return new Buffer(chunkStr, 'hex')
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
function isCanonicalPubKey (buffer) {
|
function isCanonicalPubKey (buffer) {
|
||||||
if (!Buffer.isBuffer(buffer)) return false
|
if (!Buffer.isBuffer(buffer)) return false
|
||||||
if (buffer.length < 33) return false
|
if (buffer.length < 33) return false
|
||||||
|
@ -133,13 +133,6 @@ function isCanonicalPubKey (buffer) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCanonicalSignature (buffer) {
|
|
||||||
if (!Buffer.isBuffer(buffer)) return false
|
|
||||||
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
|
|
||||||
|
|
||||||
return bip66.check(buffer.slice(0, -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDefinedHashType (hashType) {
|
function isDefinedHashType (hashType) {
|
||||||
var hashTypeMod = hashType & ~0x80
|
var hashTypeMod = hashType & ~0x80
|
||||||
|
|
||||||
|
@ -147,6 +140,13 @@ function isDefinedHashType (hashType) {
|
||||||
return hashTypeMod > 0x00 && hashTypeMod < 0x04
|
return hashTypeMod > 0x00 && hashTypeMod < 0x04
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCanonicalSignature (buffer) {
|
||||||
|
if (!Buffer.isBuffer(buffer)) return false
|
||||||
|
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
|
||||||
|
|
||||||
|
return bip66.check(buffer.slice(0, -1))
|
||||||
|
}
|
||||||
|
|
||||||
function isPubKeyHashInput (script) {
|
function isPubKeyHashInput (script) {
|
||||||
var chunks = decompile(script)
|
var chunks = decompile(script)
|
||||||
|
|
||||||
|
|
|
@ -12,154 +12,106 @@ var ECPair = require('./ecpair')
|
||||||
var ECSignature = require('./ecsignature')
|
var ECSignature = require('./ecsignature')
|
||||||
var Transaction = require('./transaction')
|
var Transaction = require('./transaction')
|
||||||
|
|
||||||
// re-orders signatures to match pubKeys, fills undefined otherwise
|
// inspects a scriptSig w/ optional provided redeemScript and derives
|
||||||
function fixMSSignatures (transaction, vin, pubKeys, signatures, prevOutScript, hashType, skipPubKey) {
|
// all necessary input information as required by TransactionBuilder
|
||||||
// maintain a local copy of unmatched signatures
|
function expandInput (scriptSig, redeemScript) {
|
||||||
var unmatched = signatures.slice()
|
var scriptSigChunks = bscript.decompile(scriptSig)
|
||||||
var signatureHash
|
var scriptSigType = bscript.classifyInput(scriptSigChunks, true)
|
||||||
|
|
||||||
return pubKeys.map(function (pubKey) {
|
var hashType, pubKeys, signatures, prevOutScript
|
||||||
// skip optionally provided pubKey
|
|
||||||
if (skipPubKey && bufferEquals(skipPubKey, pubKey)) return undefined
|
|
||||||
|
|
||||||
var matched
|
switch (scriptSigType) {
|
||||||
var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey)
|
case 'scripthash':
|
||||||
|
// FIXME: maybe depth limit instead, how possible is this anyway?
|
||||||
|
if (redeemScript) throw new Error('Recursive P2SH script')
|
||||||
|
|
||||||
// check for a matching signature
|
var redeemScriptSig = scriptSigChunks.slice(0, -1)
|
||||||
unmatched.some(function (signature, i) {
|
redeemScript = scriptSigChunks[scriptSigChunks.length - 1]
|
||||||
// skip if undefined || OP_0
|
|
||||||
if (!signature) return false
|
|
||||||
|
|
||||||
if (!signatureHash) {
|
var result = expandInput(redeemScriptSig, redeemScript)
|
||||||
signatureHash = transaction.hashForSignature(vin, prevOutScript, hashType)
|
result.redeemScript = redeemScript
|
||||||
}
|
result.redeemScriptType = result.prevOutType
|
||||||
if (!keyPair2.verify(signatureHash, signature)) return false
|
result.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
|
||||||
|
result.prevOutType = 'scripthash'
|
||||||
|
return result
|
||||||
|
|
||||||
// remove matched signature from unmatched
|
|
||||||
unmatched[i] = undefined
|
|
||||||
matched = signature
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return matched || undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractInput (transaction, txIn, vin) {
|
|
||||||
if (txIn.script.length === 0) return {}
|
|
||||||
|
|
||||||
var scriptSigChunks = bscript.decompile(txIn.script)
|
|
||||||
var prevOutType = bscript.classifyInput(scriptSigChunks, true)
|
|
||||||
|
|
||||||
function processScript (scriptType, scriptSigChunks, redeemScriptChunks) {
|
|
||||||
// ensure chunks are decompiled
|
|
||||||
scriptSigChunks = bscript.decompile(scriptSigChunks)
|
|
||||||
redeemScriptChunks = redeemScriptChunks ? bscript.decompile(redeemScriptChunks) : undefined
|
|
||||||
|
|
||||||
var hashType, pubKeys, signatures, prevOutScript, redeemScript, redeemScriptType, result, parsed
|
|
||||||
|
|
||||||
switch (scriptType) {
|
|
||||||
case 'scripthash':
|
|
||||||
redeemScript = scriptSigChunks.slice(-1)[0]
|
|
||||||
scriptSigChunks = bscript.compile(scriptSigChunks.slice(0, -1))
|
|
||||||
|
|
||||||
redeemScriptType = bscript.classifyInput(scriptSigChunks, true)
|
|
||||||
prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
|
|
||||||
|
|
||||||
result = processScript(redeemScriptType, scriptSigChunks, bscript.decompile(redeemScript))
|
|
||||||
|
|
||||||
result.prevOutScript = prevOutScript
|
|
||||||
result.redeemScript = redeemScript
|
|
||||||
result.redeemScriptType = redeemScriptType
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
case 'pubkeyhash':
|
|
||||||
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0])
|
|
||||||
hashType = parsed.hashType
|
|
||||||
pubKeys = scriptSigChunks.slice(1)
|
|
||||||
signatures = [parsed.signature]
|
|
||||||
prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'pubkey':
|
|
||||||
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0])
|
|
||||||
hashType = parsed.hashType
|
|
||||||
signatures = [parsed.signature]
|
|
||||||
|
|
||||||
if (redeemScriptChunks) {
|
|
||||||
pubKeys = redeemScriptChunks.slice(0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'multisig':
|
|
||||||
signatures = scriptSigChunks.slice(1).map(function (chunk) {
|
|
||||||
if (chunk === ops.OP_0) return undefined
|
|
||||||
|
|
||||||
parsed = ECSignature.parseScriptSignature(chunk)
|
|
||||||
hashType = parsed.hashType
|
|
||||||
|
|
||||||
return parsed.signature
|
|
||||||
})
|
|
||||||
|
|
||||||
if (redeemScriptChunks) {
|
|
||||||
pubKeys = redeemScriptChunks.slice(1, -2)
|
|
||||||
|
|
||||||
if (pubKeys.length !== signatures.length) {
|
|
||||||
signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, bscript.compile(redeemScriptChunks), hashType, redeemScript)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
hashType: hashType,
|
|
||||||
pubKeys: pubKeys,
|
|
||||||
signatures: signatures,
|
|
||||||
prevOutScript: prevOutScript,
|
|
||||||
redeemScript: redeemScript,
|
|
||||||
redeemScriptType: redeemScriptType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract hashType, pubKeys, signatures and prevOutScript
|
|
||||||
var result = processScript(prevOutType, scriptSigChunks)
|
|
||||||
|
|
||||||
return {
|
|
||||||
hashType: result.hashType,
|
|
||||||
prevOutScript: result.prevOutScript,
|
|
||||||
prevOutType: prevOutType,
|
|
||||||
pubKeys: result.pubKeys,
|
|
||||||
redeemScript: result.redeemScript,
|
|
||||||
redeemScriptType: result.redeemScriptType,
|
|
||||||
signatures: result.signatures
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractFromOutputScript (outputScript, kpPubKey) {
|
|
||||||
var scriptType = bscript.classifyOutput(outputScript)
|
|
||||||
var outputScriptChunks = bscript.decompile(outputScript)
|
|
||||||
|
|
||||||
var pubKeys
|
|
||||||
switch (scriptType) {
|
|
||||||
case 'pubkeyhash':
|
case 'pubkeyhash':
|
||||||
var pkh1 = outputScriptChunks[2]
|
// if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
|
||||||
var pkh2 = bcrypto.hash160(kpPubKey)
|
var s = ECSignature.parseScriptSignature(scriptSigChunks[0])
|
||||||
|
hashType = s.hashType
|
||||||
|
pubKeys = scriptSigChunks.slice(1)
|
||||||
|
signatures = [s.signature]
|
||||||
|
|
||||||
if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input')
|
if (redeemScript) break
|
||||||
pubKeys = [kpPubKey]
|
|
||||||
|
prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'pubkey':
|
case 'pubkey':
|
||||||
pubKeys = outputScriptChunks.slice(0, 1)
|
if (redeemScript) {
|
||||||
|
pubKeys = bscript.decompile(redeemScript).slice(0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ss = ECSignature.parseScriptSignature(scriptSigChunks[0])
|
||||||
|
hashType = ss.hashType
|
||||||
|
signatures = [ss.signature]
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'multisig':
|
case 'multisig':
|
||||||
pubKeys = outputScriptChunks.slice(1, -2)
|
if (redeemScript) {
|
||||||
|
pubKeys = bscript.decompile(redeemScript).slice(1, -2)
|
||||||
|
}
|
||||||
|
|
||||||
|
signatures = scriptSigChunks.slice(1).map(function (chunk) {
|
||||||
|
if (chunk === ops.OP_0) return undefined
|
||||||
|
|
||||||
|
var sss = ECSignature.parseScriptSignature(chunk)
|
||||||
|
|
||||||
|
if (hashType !== undefined) {
|
||||||
|
if (sss.hashType !== hashType) throw new Error('Inconsistent hashType')
|
||||||
|
} else {
|
||||||
|
hashType = sss.hashType
|
||||||
|
}
|
||||||
|
|
||||||
|
return sss.signature
|
||||||
|
})
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hashType: hashType,
|
||||||
|
pubKeys: pubKeys,
|
||||||
|
signatures: signatures,
|
||||||
|
prevOutScript: prevOutScript,
|
||||||
|
prevOutType: scriptSigType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandOutput (script, ourPubKey) {
|
||||||
|
typeforce(types.Buffer, script)
|
||||||
|
|
||||||
|
var scriptChunks = bscript.decompile(script)
|
||||||
|
var scriptType = bscript.classifyOutput(script)
|
||||||
|
|
||||||
|
var pubKeys = []
|
||||||
|
|
||||||
|
switch (scriptType) {
|
||||||
|
// does our hash160(pubKey) match the output scripts?
|
||||||
|
case 'pubkeyhash':
|
||||||
|
if (!ourPubKey) break
|
||||||
|
|
||||||
|
var pkh1 = scriptChunks[2]
|
||||||
|
var pkh2 = bcrypto.hash160(ourPubKey)
|
||||||
|
if (bufferEquals(pkh1, pkh2)) pubKeys = [ourPubKey]
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'pubkey':
|
||||||
|
pubKeys = scriptChunks.slice(0, 1)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'multisig':
|
||||||
|
pubKeys = scriptChunks.slice(1, -2)
|
||||||
break
|
break
|
||||||
|
|
||||||
default: return
|
default: return
|
||||||
|
@ -167,10 +119,146 @@ function extractFromOutputScript (outputScript, kpPubKey) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pubKeys: pubKeys,
|
pubKeys: pubKeys,
|
||||||
scriptType: scriptType
|
scriptType: scriptType,
|
||||||
|
signatures: pubKeys.map(function () { return undefined })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildInput (input, scriptType, allowIncomplete) {
|
||||||
|
var signatures = input.signatures
|
||||||
|
var scriptSig
|
||||||
|
|
||||||
|
switch (scriptType) {
|
||||||
|
case 'pubkeyhash':
|
||||||
|
// remove blank signatures
|
||||||
|
signatures = signatures.filter(function (x) { return x })
|
||||||
|
|
||||||
|
if (signatures.length < 1) throw new Error('Not enough signatures provided')
|
||||||
|
if (signatures.length > 1) throw new Error('Too many signatures provided')
|
||||||
|
|
||||||
|
var pkhSignature = signatures[0].toScriptSignature(input.hashType)
|
||||||
|
scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0])
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'pubkey':
|
||||||
|
// remove blank signatures
|
||||||
|
signatures = signatures.filter(function (x) { return x })
|
||||||
|
|
||||||
|
if (signatures.length < 1) throw new Error('Not enough signatures provided')
|
||||||
|
if (signatures.length > 1) throw new Error('Too many signatures provided')
|
||||||
|
|
||||||
|
var pkSignature = signatures[0].toScriptSignature(input.hashType)
|
||||||
|
scriptSig = bscript.pubKeyInput(pkSignature)
|
||||||
|
break
|
||||||
|
|
||||||
|
// ref https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/script/sign.cpp#L232
|
||||||
|
case 'multisig':
|
||||||
|
signatures = signatures.map(function (signature) {
|
||||||
|
return signature && signature.toScriptSignature(input.hashType)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allowIncomplete) {
|
||||||
|
// fill in blanks with OP_0
|
||||||
|
for (var i = 0; i < signatures.length; ++i) {
|
||||||
|
signatures[i] = signatures[i] || ops.OP_0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// remove blank signatures
|
||||||
|
signatures = signatures.filter(function (x) { return x })
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptSig = bscript.multisigInput(signatures, allowIncomplete ? undefined : input.redeemScript)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap as scriptHash if necessary
|
||||||
|
if (input.prevOutType === 'scripthash') {
|
||||||
|
scriptSig = bscript.scriptHashInput(scriptSig, input.redeemScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptSig
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareInput (input, kpPubKey, redeemScript, hashType) {
|
||||||
|
if (redeemScript) {
|
||||||
|
var redeemScriptHash = bcrypto.hash160(redeemScript)
|
||||||
|
|
||||||
|
// if redeemScript exists, it is pay-to-scriptHash
|
||||||
|
// if we have a prevOutScript, enforce hash160(redeemScriptequality) to the redeemScript
|
||||||
|
if (input.prevOutType) {
|
||||||
|
if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH')
|
||||||
|
|
||||||
|
var prevOutScriptScriptHash = bscript.decompile(input.prevOutScript)[1]
|
||||||
|
if (!bufferEquals(prevOutScriptScriptHash, redeemScriptHash)) throw new Error('Inconsistent hash160(RedeemScript)')
|
||||||
|
|
||||||
|
// or, we don't have a prevOutScript, so generate a P2SH script
|
||||||
|
} else {
|
||||||
|
input.prevOutScript = bscript.scriptHashOutput(redeemScriptHash)
|
||||||
|
input.prevOutType = 'scripthash'
|
||||||
|
}
|
||||||
|
|
||||||
|
var expanded = expandOutput(redeemScript, kpPubKey)
|
||||||
|
if (!expanded) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"')
|
||||||
|
|
||||||
|
input.pubKeys = expanded.pubKeys
|
||||||
|
input.redeemScript = redeemScript
|
||||||
|
input.redeemScriptType = expanded.scriptType
|
||||||
|
input.signatures = expanded.signatures
|
||||||
|
|
||||||
|
// maybe we have some prior knowledge
|
||||||
|
} else if (input.prevOutType) {
|
||||||
|
// pay-to-scriptHash is not possible without a redeemScript
|
||||||
|
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript')
|
||||||
|
|
||||||
|
// throw if we can't sign with it
|
||||||
|
if (!input.pubKeys || !input.signatures) throw new Error(input.prevOutType + ' not supported')
|
||||||
|
|
||||||
|
// no prior knowledge, assume pubKeyHash
|
||||||
|
} else {
|
||||||
|
input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey))
|
||||||
|
input.prevOutType = 'pubkeyhash'
|
||||||
|
|
||||||
|
input.pubKeys = [kpPubKey]
|
||||||
|
input.signatures = [undefined]
|
||||||
|
}
|
||||||
|
|
||||||
|
input.hashType = hashType
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixMultisigOrder (input, transaction, vin) {
|
||||||
|
var hashScriptType = input.redeemScriptType || input.prevOutType
|
||||||
|
if (hashScriptType !== 'multisig') throw new TypeError('Expected multisig input')
|
||||||
|
|
||||||
|
var hashType = input.hashType || Transaction.SIGHASH_ALL
|
||||||
|
var hashScript = input.redeemScript || input.prevOutScript
|
||||||
|
|
||||||
|
// maintain a local copy of unmatched signatures
|
||||||
|
var unmatched = input.signatures.concat()
|
||||||
|
var signatureHash = transaction.hashForSignature(vin, hashScript, hashType)
|
||||||
|
|
||||||
|
input.signatures = input.pubKeys.map(function (pubKey, y) {
|
||||||
|
var keyPair = ECPair.fromPublicKeyBuffer(pubKey)
|
||||||
|
var match
|
||||||
|
|
||||||
|
// check for a signature
|
||||||
|
unmatched.some(function (signature, i) {
|
||||||
|
// skip if undefined || OP_0
|
||||||
|
if (!signature) return false
|
||||||
|
|
||||||
|
// skip if signature does not match pubKey
|
||||||
|
if (!keyPair.verify(signatureHash, signature)) return false
|
||||||
|
|
||||||
|
// remove matched signature from unmatched
|
||||||
|
unmatched[i] = undefined
|
||||||
|
match = signature
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return match || undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function TransactionBuilder (network) {
|
function TransactionBuilder (network) {
|
||||||
this.prevTxMap = {}
|
this.prevTxMap = {}
|
||||||
this.network = network || networks.bitcoin
|
this.network = network || networks.bitcoin
|
||||||
|
@ -204,34 +292,40 @@ TransactionBuilder.prototype.setVersion = function (version) {
|
||||||
TransactionBuilder.fromTransaction = function (transaction, network) {
|
TransactionBuilder.fromTransaction = function (transaction, network) {
|
||||||
var txb = new TransactionBuilder(network)
|
var txb = new TransactionBuilder(network)
|
||||||
|
|
||||||
// Copy other transaction fields
|
// Copy transaction fields
|
||||||
txb.tx.version = transaction.version
|
txb.setVersion(transaction.version)
|
||||||
txb.tx.locktime = transaction.locktime
|
txb.setLockTime(transaction.locktime)
|
||||||
|
|
||||||
// Extract/add inputs
|
// Copy outputs (done first to avoid signature invalidation)
|
||||||
transaction.ins.forEach(function (txIn) {
|
|
||||||
txb.addInput(txIn.hash, txIn.index, txIn.sequence)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Extract/add outputs
|
|
||||||
transaction.outs.forEach(function (txOut) {
|
transaction.outs.forEach(function (txOut) {
|
||||||
txb.addOutput(txOut.script, txOut.value)
|
txb.addOutput(txOut.script, txOut.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Extract/add signatures
|
// Copy inputs
|
||||||
txb.inputs = transaction.ins.map(function (txIn, vin) {
|
transaction.ins.forEach(function (txIn) {
|
||||||
// TODO: verify whether extractInput is sane with coinbase scripts
|
txb.__addInputUnsafe(txIn.hash, txIn.index, txIn.sequence, txIn.script)
|
||||||
if (Transaction.isCoinbaseHash(txIn.hash)) {
|
})
|
||||||
throw new Error('coinbase inputs not supported')
|
|
||||||
}
|
|
||||||
|
|
||||||
return extractInput(transaction, txIn, vin)
|
// fix some things not possible through the public API
|
||||||
|
txb.inputs.forEach(function (input, i) {
|
||||||
|
// attempt to fix any multisig inputs if they exist
|
||||||
|
if ((input.redeemScriptType || input.prevOutType) === 'multisig') {
|
||||||
|
// pubKeys will only exist for 'multisig' if a redeemScript was found
|
||||||
|
if (!input.pubKeys || !input.signatures) return
|
||||||
|
if (input.pubKeys.length === input.signatures.length) return
|
||||||
|
|
||||||
|
fixMultisigOrder(input, transaction, i)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return txb
|
return txb
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
|
TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
|
||||||
|
if (!this.__canModifyInputs()) {
|
||||||
|
throw new Error('No, this would invalidate signatures')
|
||||||
|
}
|
||||||
|
|
||||||
// is it a hex string?
|
// is it a hex string?
|
||||||
if (typeof txHash === 'string') {
|
if (typeof txHash === 'string') {
|
||||||
// transaction hashs's are displayed in reverse order, un-reverse it
|
// transaction hashs's are displayed in reverse order, un-reverse it
|
||||||
|
@ -243,73 +337,50 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu
|
||||||
txHash = txHash.getHash()
|
txHash = txHash.getHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.__addInputUnsafe(txHash, vout, sequence, null, prevOutScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence, scriptSig, prevOutScript) {
|
||||||
|
if (Transaction.isCoinbaseHash(txHash)) {
|
||||||
|
throw new Error('coinbase inputs not supported')
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevTxOut = txHash.toString('hex') + ':' + vout
|
||||||
|
if (this.prevTxMap[prevTxOut]) throw new Error('Duplicate TxOut: ' + prevTxOut)
|
||||||
|
|
||||||
var input = {}
|
var input = {}
|
||||||
if (prevOutScript) {
|
|
||||||
|
// derive what we can from the scriptSig
|
||||||
|
if (scriptSig) {
|
||||||
|
input = expandInput(scriptSig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// derive what we can from the previous transactions output script
|
||||||
|
if (!input.prevOutScript && prevOutScript) {
|
||||||
var prevOutScriptChunks = bscript.decompile(prevOutScript)
|
var prevOutScriptChunks = bscript.decompile(prevOutScript)
|
||||||
var prevOutType = bscript.classifyOutput(prevOutScriptChunks)
|
var prevOutType = bscript.classifyOutput(prevOutScriptChunks)
|
||||||
|
|
||||||
// if we can, extract pubKey information
|
if (!input.pubKeys && !input.signatures) {
|
||||||
switch (prevOutType) {
|
var expanded = expandOutput(prevOutScript)
|
||||||
case 'multisig':
|
if (expanded) {
|
||||||
input.pubKeys = prevOutScriptChunks.slice(1, -2)
|
input.pubKeys = expanded.pubKeys
|
||||||
input.signatures = input.pubKeys.map(function () { return undefined })
|
input.signatures = expanded.signatures
|
||||||
|
}
|
||||||
break
|
|
||||||
|
|
||||||
case 'pubkey':
|
|
||||||
input.pubKeys = prevOutScriptChunks.slice(0, 1)
|
|
||||||
input.signatures = [undefined]
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevOutType !== 'scripthash') {
|
|
||||||
input.scriptType = prevOutType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.prevOutScript = prevOutScript
|
input.prevOutScript = prevOutScript
|
||||||
input.prevOutType = prevOutType
|
input.prevOutType = prevOutType
|
||||||
}
|
}
|
||||||
|
|
||||||
// if signatures exist, adding inputs is only acceptable if SIGHASH_ANYONECANPAY is used
|
var vin = this.tx.addInput(txHash, vout, sequence, scriptSig)
|
||||||
// throw if any signatures *didn't* use SIGHASH_ANYONECANPAY
|
|
||||||
if (!this.inputs.every(function (otherInput) {
|
|
||||||
// no signature
|
|
||||||
if (otherInput.hashType === undefined) return true
|
|
||||||
|
|
||||||
return otherInput.hashType & Transaction.SIGHASH_ANYONECANPAY
|
|
||||||
})) {
|
|
||||||
throw new Error('No, this would invalidate signatures')
|
|
||||||
}
|
|
||||||
|
|
||||||
var prevTxOut = txHash.toString('hex') + ':' + vout
|
|
||||||
if (this.prevTxMap[prevTxOut]) throw new Error('Duplicate TxOut: ' + prevTxOut)
|
|
||||||
|
|
||||||
var vin = this.tx.addInput(txHash, vout, sequence)
|
|
||||||
this.inputs[vin] = input
|
this.inputs[vin] = input
|
||||||
this.prevTxMap[prevTxOut] = vin
|
this.prevTxMap[prevTxOut] = true
|
||||||
|
|
||||||
return vin
|
return vin
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
|
TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
|
||||||
var nOutputs = this.tx.outs.length
|
if (!this.__canModifyOutputs()) {
|
||||||
|
|
||||||
// if signatures exist, adding outputs is only acceptable if SIGHASH_NONE or SIGHASH_SINGLE is used
|
|
||||||
// throws if any signatures didn't use SIGHASH_NONE|SIGHASH_SINGLE
|
|
||||||
if (!this.inputs.every(function (input, index) {
|
|
||||||
// no signature
|
|
||||||
if (input.hashType === undefined) return true
|
|
||||||
|
|
||||||
var hashTypeMod = input.hashType & 0x1f
|
|
||||||
if (hashTypeMod === Transaction.SIGHASH_NONE) return true
|
|
||||||
if (hashTypeMod === Transaction.SIGHASH_SINGLE) {
|
|
||||||
// account for SIGHASH_SINGLE signing of a non-existing output, aka the "SIGHASH_SINGLE" bug
|
|
||||||
return index < nOutputs
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
})) {
|
|
||||||
throw new Error('No, this would invalidate signatures')
|
throw new Error('No, this would invalidate signatures')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,48 +405,6 @@ var canBuildTypes = {
|
||||||
'pubkeyhash': true
|
'pubkeyhash': true
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildFromInputData (input, scriptType, parentType, redeemScript, allowIncomplete) {
|
|
||||||
var scriptSig
|
|
||||||
|
|
||||||
switch (scriptType) {
|
|
||||||
case 'pubkeyhash':
|
|
||||||
var pkhSignature = input.signatures[0].toScriptSignature(input.hashType)
|
|
||||||
scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0])
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'pubkey':
|
|
||||||
var pkSignature = input.signatures[0].toScriptSignature(input.hashType)
|
|
||||||
scriptSig = bscript.pubKeyInput(pkSignature)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'multisig':
|
|
||||||
var msSignatures = input.signatures.map(function (signature) {
|
|
||||||
return signature && signature.toScriptSignature(input.hashType)
|
|
||||||
})
|
|
||||||
|
|
||||||
// fill in blanks with OP_0
|
|
||||||
if (allowIncomplete) {
|
|
||||||
for (var i = 0; i < msSignatures.length; ++i) {
|
|
||||||
msSignatures[i] = msSignatures[i] || ops.OP_0
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove blank signatures
|
|
||||||
} else {
|
|
||||||
msSignatures = msSignatures.filter(function (x) { return x })
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptSig = bscript.multisigInput(msSignatures, allowIncomplete ? undefined : redeemScript)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap as scriptHash if necessary
|
|
||||||
if (parentType === 'scripthash') {
|
|
||||||
scriptSig = bscript.scriptHashInput(scriptSig, redeemScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
return scriptSig
|
|
||||||
}
|
|
||||||
|
|
||||||
TransactionBuilder.prototype.__build = function (allowIncomplete) {
|
TransactionBuilder.prototype.__build = function (allowIncomplete) {
|
||||||
if (!allowIncomplete) {
|
if (!allowIncomplete) {
|
||||||
if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
|
if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
|
||||||
|
@ -385,48 +414,43 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
|
||||||
var tx = this.tx.clone()
|
var tx = this.tx.clone()
|
||||||
|
|
||||||
// Create script signatures from inputs
|
// Create script signatures from inputs
|
||||||
this.inputs.forEach(function (input, index) {
|
this.inputs.forEach(function (input, i) {
|
||||||
var scriptType = input.redeemScriptType || input.prevOutType
|
var scriptType = input.redeemScriptType || input.prevOutType
|
||||||
var scriptSig
|
|
||||||
|
|
||||||
if (!allowIncomplete) {
|
if (!allowIncomplete) {
|
||||||
if (!scriptType) throw new Error('Transaction is not complete')
|
if (!scriptType) throw new Error('Transaction is not complete')
|
||||||
if (!canBuildTypes[scriptType]) throw new Error(scriptType + ' not supported')
|
if (!canBuildTypes[scriptType]) throw new Error(scriptType + ' not supported')
|
||||||
|
|
||||||
// XXX: only relevant to types that need signatures
|
// FIXME: only relevant to types that need signatures
|
||||||
if (!input.signatures) throw new Error('Transaction is missing signatures')
|
if (!input.signatures) throw new Error('Transaction is missing signatures')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.signatures) {
|
// FIXME: only relevant to types that need signatures
|
||||||
scriptSig = buildFromInputData(input, scriptType, input.prevOutType, input.redeemScript, allowIncomplete)
|
// skip if no scriptSig exists
|
||||||
}
|
if (!input.signatures) return
|
||||||
|
|
||||||
// did we build a scriptSig? Buffer('') is allowed
|
// build a scriptSig
|
||||||
if (scriptSig) {
|
var scriptSig = buildInput(input, scriptType, allowIncomplete)
|
||||||
tx.setInputScript(index, scriptSig)
|
tx.setInputScript(i, scriptSig)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hashType) {
|
TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType) {
|
||||||
if (keyPair.network !== this.network) throw new Error('Inconsistent network')
|
if (keyPair.network !== this.network) throw new Error('Inconsistent network')
|
||||||
if (!this.inputs[index]) throw new Error('No input at index: ' + index)
|
if (!this.inputs[vin]) throw new Error('No input at index: ' + vin)
|
||||||
hashType = hashType || Transaction.SIGHASH_ALL
|
hashType = hashType || Transaction.SIGHASH_ALL
|
||||||
|
|
||||||
var input = this.inputs[index]
|
var input = this.inputs[vin]
|
||||||
var canSign = input.hashType &&
|
var canSign = input.hashType !== undefined &&
|
||||||
input.prevOutScript &&
|
input.prevOutScript !== undefined &&
|
||||||
input.prevOutType &&
|
input.pubKeys !== undefined &&
|
||||||
input.pubKeys &&
|
input.signatures !== undefined &&
|
||||||
input.redeemScriptType &&
|
|
||||||
input.signatures &&
|
|
||||||
input.signatures.length === input.pubKeys.length
|
input.signatures.length === input.pubKeys.length
|
||||||
|
|
||||||
var kpPubKey = keyPair.getPublicKeyBuffer()
|
var kpPubKey = keyPair.getPublicKeyBuffer()
|
||||||
|
|
||||||
// are we ready to sign?
|
|
||||||
if (canSign) {
|
if (canSign) {
|
||||||
// if redeemScript was provided, enforce consistency
|
// if redeemScript was provided, enforce consistency
|
||||||
if (redeemScript) {
|
if (redeemScript) {
|
||||||
|
@ -434,56 +458,13 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.hashType !== hashType) throw new Error('Inconsistent hashType')
|
if (input.hashType !== hashType) throw new Error('Inconsistent hashType')
|
||||||
|
|
||||||
// no? prepare
|
|
||||||
} else {
|
} else {
|
||||||
// must be pay-to-scriptHash?
|
prepareInput(input, kpPubKey, redeemScript, hashType)
|
||||||
if (redeemScript) {
|
|
||||||
// if we have a prevOutScript, enforce scriptHash equality to the redeemScript
|
|
||||||
if (input.prevOutScript) {
|
|
||||||
if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH')
|
|
||||||
|
|
||||||
var scriptHash = bscript.decompile(input.prevOutScript)[1]
|
|
||||||
if (!bufferEquals(scriptHash, bcrypto.hash160(redeemScript))) throw new Error('RedeemScript does not match ' + scriptHash.toString('hex'))
|
|
||||||
}
|
|
||||||
|
|
||||||
var extracted = extractFromOutputScript(redeemScript, kpPubKey)
|
|
||||||
if (!extracted) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"')
|
|
||||||
|
|
||||||
// if we don't have a prevOutScript, generate a P2SH script
|
|
||||||
if (!input.prevOutScript) {
|
|
||||||
input.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
|
|
||||||
input.prevOutType = 'scripthash'
|
|
||||||
}
|
|
||||||
|
|
||||||
input.pubKeys = extracted.pubKeys
|
|
||||||
input.redeemScript = redeemScript
|
|
||||||
input.redeemScriptType = extracted.scriptType
|
|
||||||
input.signatures = extracted.pubKeys.map(function () { return undefined })
|
|
||||||
} else {
|
|
||||||
// pay-to-scriptHash is not possible without a redeemScript
|
|
||||||
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript')
|
|
||||||
|
|
||||||
// if we don't have a scriptType, assume pubKeyHash otherwise
|
|
||||||
if (!input.scriptType) {
|
|
||||||
input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey))
|
|
||||||
input.prevOutType = 'pubkeyhash'
|
|
||||||
|
|
||||||
input.pubKeys = [kpPubKey]
|
|
||||||
input.scriptType = input.prevOutType
|
|
||||||
input.signatures = [undefined]
|
|
||||||
} else {
|
|
||||||
// throw if we can't sign with it
|
|
||||||
if (!input.pubKeys || !input.signatures) throw new Error(input.scriptType + ' not supported')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input.hashType = hashType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ready to sign?
|
// ready to sign
|
||||||
var signatureScript = input.redeemScript || input.prevOutScript
|
var hashScript = input.redeemScript || input.prevOutScript
|
||||||
var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType)
|
var signatureHash = this.tx.hashForSignature(vin, hashScript, hashType)
|
||||||
|
|
||||||
// enforce in order signing of public keys
|
// enforce in order signing of public keys
|
||||||
var valid = input.pubKeys.some(function (pubKey, i) {
|
var valid = input.pubKeys.some(function (pubKey, i) {
|
||||||
|
@ -491,11 +472,42 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash
|
||||||
if (input.signatures[i]) throw new Error('Signature already exists')
|
if (input.signatures[i]) throw new Error('Signature already exists')
|
||||||
|
|
||||||
input.signatures[i] = keyPair.sign(signatureHash)
|
input.signatures[i] = keyPair.sign(signatureHash)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!valid) throw new Error('Key pair cannot sign for this input')
|
if (!valid) throw new Error('Key pair cannot sign for this input')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransactionBuilder.prototype.__canModifyInputs = function () {
|
||||||
|
return this.inputs.every(function (otherInput) {
|
||||||
|
// no signature
|
||||||
|
if (otherInput.hashType === undefined) return true
|
||||||
|
|
||||||
|
// if SIGHASH_ANYONECANPAY is set, signatures would not
|
||||||
|
// be invalidated by more inputs
|
||||||
|
return otherInput.hashType & Transaction.SIGHASH_ANYONECANPAY
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionBuilder.prototype.__canModifyOutputs = function () {
|
||||||
|
var nInputs = this.tx.ins.length
|
||||||
|
var nOutputs = this.tx.outs.length
|
||||||
|
|
||||||
|
return this.inputs.every(function (input, i) {
|
||||||
|
// any signatures?
|
||||||
|
if (input.hashType === undefined) return true
|
||||||
|
|
||||||
|
var hashTypeMod = input.hashType & 0x1f
|
||||||
|
if (hashTypeMod === Transaction.SIGHASH_NONE) return true
|
||||||
|
if (hashTypeMod === Transaction.SIGHASH_SINGLE) {
|
||||||
|
// if SIGHASH_SINGLE is set, and nInputs > nOutputs
|
||||||
|
// some signatures would be invalidated by the addition
|
||||||
|
// of more outputs
|
||||||
|
return nInputs <= nOutputs
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = TransactionBuilder
|
module.exports = TransactionBuilder
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var typeforce = require('typeforce')
|
var typeforce = require('typeforce')
|
||||||
|
|
||||||
function nBuffer (value, n) {
|
function nBuffer (value, n) {
|
||||||
typeforce(types.Buffer, value)
|
typeforce(typeforce.Buffer, value)
|
||||||
if (value.length !== n) throw new typeforce.TfTypeError('Expected ' + (n * 8) + '-bit Buffer, got ' + (value.length * 8) + '-bit Buffer')
|
if (value.length !== n) throw new typeforce.TfTypeError('Expected ' + (n * 8) + '-bit Buffer, got ' + (value.length * 8) + '-bit Buffer')
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
27
test/fixtures/transaction_builder.json
vendored
27
test/fixtures/transaction_builder.json
vendored
|
@ -632,8 +632,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Incomplete transaction w/ prevTxScript defined",
|
"description": "Incomplete transaction w/ prevTxScript defined",
|
||||||
"exception": "Transaction is missing signatures",
|
"exception": "Not enough signatures provided",
|
||||||
"alwaysThrows": true,
|
"incomplete": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
@ -658,6 +658,29 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Duplicate transaction outs",
|
||||||
|
"exception": "Duplicate TxOut: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff:0",
|
||||||
|
"incomplete": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"vout": 0,
|
||||||
|
"signs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"vout": 0,
|
||||||
|
"signs": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
"value": 1000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Complete transaction w/ non-standard inputs",
|
"description": "Complete transaction w/ non-standard inputs",
|
||||||
"exception": "nonstandard not supported",
|
"exception": "nonstandard not supported",
|
||||||
|
|
|
@ -11,7 +11,7 @@ var bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLw
|
||||||
describe('bitcoinjs-lib (CLTV)', function () {
|
describe('bitcoinjs-lib (CLTV)', function () {
|
||||||
var hashType = bitcoin.Transaction.SIGHASH_ALL
|
var hashType = bitcoin.Transaction.SIGHASH_ALL
|
||||||
|
|
||||||
function cltvCheckSigInput (aQ, bQ, utcSeconds) {
|
function cltvCheckSigOutput (aQ, bQ, utcSeconds) {
|
||||||
return bitcoin.script.compile([
|
return bitcoin.script.compile([
|
||||||
bitcoin.opcodes.OP_IF,
|
bitcoin.opcodes.OP_IF,
|
||||||
bitcoin.script.number.encode(utcSeconds),
|
bitcoin.script.number.encode(utcSeconds),
|
||||||
|
@ -38,7 +38,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
|
||||||
|
|
||||||
// three hours ago
|
// three hours ago
|
||||||
var timeUtc = utcNow() - (3600 * 3)
|
var timeUtc = utcNow() - (3600 * 3)
|
||||||
var redeemScript = cltvCheckSigInput(alice, bob, timeUtc)
|
var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
|
||||||
var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
|
var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
|
||||||
var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
|
var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
|
||||||
|
|
||||||
// two hours ago
|
// two hours ago
|
||||||
var timeUtc = utcNow() - (3600 * 2)
|
var timeUtc = utcNow() - (3600 * 2)
|
||||||
var redeemScript = cltvCheckSigInput(alice, bob, timeUtc)
|
var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
|
||||||
var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
|
var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
|
||||||
var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
|
var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
|
||||||
|
|
||||||
// two hours from now
|
// two hours from now
|
||||||
var timeUtc = utcNow() + (3600 * 2)
|
var timeUtc = utcNow() + (3600 * 2)
|
||||||
var redeemScript = cltvCheckSigInput(alice, bob, timeUtc)
|
var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
|
||||||
var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
|
var scriptPubKey = bitcoin.script.scriptHashOutput(bitcoin.crypto.hash160(redeemScript))
|
||||||
var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
|
var address = bitcoin.address.fromOutputScript(scriptPubKey, network)
|
||||||
|
|
||||||
|
|
|
@ -62,11 +62,6 @@ describe('TransactionBuilder', function () {
|
||||||
return baddress.toOutputScript(x)
|
return baddress.toOutputScript(x)
|
||||||
})
|
})
|
||||||
var txHash = new Buffer('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex')
|
var txHash = new Buffer('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex')
|
||||||
var txb
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
txb = new TransactionBuilder()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('fromTransaction', function () {
|
describe('fromTransaction', function () {
|
||||||
fixtures.valid.build.forEach(function (f) {
|
fixtures.valid.build.forEach(function (f) {
|
||||||
|
@ -108,7 +103,7 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.fromTransaction.forEach(function (f) {
|
fixtures.invalid.fromTransaction.forEach(function (f) {
|
||||||
it('throws on ' + f.exception, function () {
|
it('throws ' + f.exception, function () {
|
||||||
var tx = Transaction.fromHex(f.txHex)
|
var tx = Transaction.fromHex(f.txHex)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
|
@ -119,6 +114,11 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addInput', function () {
|
describe('addInput', function () {
|
||||||
|
var txb
|
||||||
|
beforeEach(function () {
|
||||||
|
txb = new TransactionBuilder()
|
||||||
|
})
|
||||||
|
|
||||||
it('accepts a txHash, index [and sequence number]', function () {
|
it('accepts a txHash, index [and sequence number]', function () {
|
||||||
var vin = txb.addInput(txHash, 1, 54)
|
var vin = txb.addInput(txHash, 1, 54)
|
||||||
assert.strictEqual(vin, 0)
|
assert.strictEqual(vin, 0)
|
||||||
|
@ -172,6 +172,11 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addOutput', function () {
|
describe('addOutput', function () {
|
||||||
|
var txb
|
||||||
|
beforeEach(function () {
|
||||||
|
txb = new TransactionBuilder()
|
||||||
|
})
|
||||||
|
|
||||||
it('accepts an address string and value', function () {
|
it('accepts an address string and value', function () {
|
||||||
var vout = txb.addOutput(keyPair.getAddress(), 1000)
|
var vout = txb.addOutput(keyPair.getAddress(), 1000)
|
||||||
assert.strictEqual(vout, 0)
|
assert.strictEqual(vout, 0)
|
||||||
|
@ -237,6 +242,7 @@ describe('TransactionBuilder', function () {
|
||||||
|
|
||||||
describe('setLockTime', function () {
|
describe('setLockTime', function () {
|
||||||
it('throws if if there exist any scriptSigs', function () {
|
it('throws if if there exist any scriptSigs', function () {
|
||||||
|
var txb = new TransactionBuilder()
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
|
|
||||||
|
@ -249,7 +255,7 @@ describe('TransactionBuilder', function () {
|
||||||
describe('sign', function () {
|
describe('sign', function () {
|
||||||
fixtures.invalid.sign.forEach(function (f) {
|
fixtures.invalid.sign.forEach(function (f) {
|
||||||
it('throws on ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), function () {
|
it('throws on ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), function () {
|
||||||
txb = construct(f, false)
|
var txb = construct(f, false)
|
||||||
|
|
||||||
f.inputs.forEach(function (input, index) {
|
f.inputs.forEach(function (input, index) {
|
||||||
input.signs.forEach(function (sign) {
|
input.signs.forEach(function (sign) {
|
||||||
|
@ -277,33 +283,55 @@ describe('TransactionBuilder', function () {
|
||||||
describe('build', function () {
|
describe('build', function () {
|
||||||
fixtures.valid.build.forEach(function (f) {
|
fixtures.valid.build.forEach(function (f) {
|
||||||
it('builds "' + f.description + '"', function () {
|
it('builds "' + f.description + '"', function () {
|
||||||
txb = construct(f)
|
var txb = construct(f)
|
||||||
|
|
||||||
var tx = txb.build()
|
var tx = txb.build()
|
||||||
|
|
||||||
assert.strictEqual(tx.toHex(), f.txHex)
|
assert.strictEqual(tx.toHex(), f.txHex)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: remove duplicate test code
|
||||||
fixtures.invalid.build.forEach(function (f) {
|
fixtures.invalid.build.forEach(function (f) {
|
||||||
describe('for ' + (f.description || f.exception), function () {
|
describe('for ' + (f.description || f.exception), function () {
|
||||||
beforeEach(function () {
|
it('throws ' + f.exception, function () {
|
||||||
if (f.txHex) {
|
|
||||||
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
|
||||||
} else {
|
|
||||||
txb = construct(f)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws', function () {
|
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
|
var txb
|
||||||
|
if (f.txHex) {
|
||||||
|
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
||||||
|
} else {
|
||||||
|
txb = construct(f)
|
||||||
|
}
|
||||||
|
|
||||||
txb.build()
|
txb.build()
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
|
|
||||||
if (f.alwaysThrows) return
|
// if throws on incomplete too, enforce that
|
||||||
it("doesn't throw if building incomplete", function () {
|
if (f.incomplete) {
|
||||||
txb.buildIncomplete()
|
it('throws ' + f.exception, function () {
|
||||||
})
|
assert.throws(function () {
|
||||||
|
var txb
|
||||||
|
if (f.txHex) {
|
||||||
|
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
||||||
|
} else {
|
||||||
|
txb = construct(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
txb.buildIncomplete()
|
||||||
|
}, new RegExp(f.exception))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
it('does not throw if buildIncomplete', function () {
|
||||||
|
var txb
|
||||||
|
if (f.txHex) {
|
||||||
|
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
||||||
|
} else {
|
||||||
|
txb = construct(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
txb.buildIncomplete()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -311,8 +339,7 @@ describe('TransactionBuilder', function () {
|
||||||
describe('multisig', function () {
|
describe('multisig', function () {
|
||||||
fixtures.valid.multisig.forEach(function (f) {
|
fixtures.valid.multisig.forEach(function (f) {
|
||||||
it(f.description, function () {
|
it(f.description, function () {
|
||||||
txb = construct(f, false)
|
var txb = construct(f, false)
|
||||||
|
|
||||||
var tx
|
var tx
|
||||||
var network = NETWORKS[f.network]
|
var network = NETWORKS[f.network]
|
||||||
|
|
||||||
|
@ -358,19 +385,26 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('multisig edge case', function () {
|
describe('multisig edge case', function () {
|
||||||
|
var network = NETWORKS.testnet
|
||||||
|
|
||||||
it('should handle badly pre-filled OP_0s', function () {
|
it('should handle badly pre-filled OP_0s', function () {
|
||||||
var lameTx = Transaction.fromHex('0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd16010000483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000')
|
// OP_0 is used where a signature is missing
|
||||||
var network = NETWORKS.testnet
|
var redeemScripSig = bscript.fromASM('OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
|
||||||
|
|
||||||
txb = TransactionBuilder.fromTransaction(lameTx, network)
|
|
||||||
|
|
||||||
var redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG')
|
var redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG')
|
||||||
|
|
||||||
|
var tx = new Transaction()
|
||||||
|
tx.addInput(new Buffer('cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f07149', 'hex'), 0, undefined, redeemScripSig)
|
||||||
|
tx.addOutput(new Buffer('76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac', 'hex'), 1000)
|
||||||
|
|
||||||
|
// now import the Transaction
|
||||||
|
var txb = TransactionBuilder.fromTransaction(tx, NETWORKS.testnet)
|
||||||
|
|
||||||
var keyPair = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network)
|
var keyPair = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network)
|
||||||
txb.sign(0, keyPair, redeemScript)
|
txb.sign(0, keyPair, redeemScript)
|
||||||
|
|
||||||
var tx = txb.build()
|
var tx2 = txb.build()
|
||||||
assert.equal(tx.toHex(), '0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd5e0100483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01483045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af0014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000')
|
assert.equal(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9')
|
||||||
|
assert.equal(bscript.toASM(tx2.ins[0].script), 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue