Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions src/scripts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var assert = require('assert')
var typeForce = require('typeforce')
var ops = require('./opcodes')
var typeForce = require('typeforce')

var ecurve = require('ecurve')
var curve = ecurve.getCurveByName('secp256k1')
Expand Down Expand Up @@ -63,7 +63,7 @@ function isPubKeyOutput(script) {
script.chunks[1] === ops.OP_CHECKSIG
}

function isScriptHashInput(script) {
function isScriptHashInput(script, allowIncomplete) {
if (script.chunks.length < 2) return false
var lastChunk = script.chunks[script.chunks.length - 1]

Expand All @@ -72,7 +72,7 @@ function isScriptHashInput(script) {
var scriptSig = Script.fromChunks(script.chunks.slice(0, -1))
var scriptPubKey = Script.fromBuffer(lastChunk)

return classifyInput(scriptSig) === classifyOutput(scriptPubKey)
return classifyInput(scriptSig, allowIncomplete) === classifyOutput(scriptPubKey)
}

function isScriptHashOutput(script) {
Expand All @@ -83,9 +83,19 @@ function isScriptHashOutput(script) {
script.chunks[2] === ops.OP_EQUAL
}

function isMultisigInput(script) {
return script.chunks[0] === ops.OP_0 &&
script.chunks.slice(1).every(isCanonicalSignature)
// allowIncomplete is to account for combining signatures
// See https://github.com/bitcoin/bitcoin/blob/f425050546644a36b0b8e0eb2f6934a3e0f6f80f/src/script/sign.cpp#L195-L197
function isMultisigInput(script, allowIncomplete) {
if (script.chunks.length < 2) return false
if (script.chunks[0] !== ops.OP_0) return false

if (allowIncomplete) {
return script.chunks.slice(1).every(function(chunk) {
return chunk === ops.OP_0 || isCanonicalSignature(chunk)
})
}

return script.chunks.slice(1).every(isCanonicalSignature)
}

function isMultisigOutput(script) {
Expand Down Expand Up @@ -134,15 +144,15 @@ function classifyOutput(script) {
return 'nonstandard'
}

function classifyInput(script) {
function classifyInput(script, allowIncomplete) {
typeForce('Script', script)

if (isPubKeyHashInput(script)) {
return 'pubkeyhash'
} else if (isScriptHashInput(script)) {
return 'scripthash'
} else if (isMultisigInput(script)) {
} else if (isMultisigInput(script, allowIncomplete)) {
return 'multisig'
} else if (isScriptHashInput(script, allowIncomplete)) {
return 'scripthash'
} else if (isPubKeyInput(script)) {
return 'pubkey'
}
Expand Down Expand Up @@ -234,8 +244,13 @@ function multisigInput(signatures, scriptPubKey) {
var m = mOp - (ops.OP_1 - 1)
var n = nOp - (ops.OP_1 - 1)

assert(signatures.length >= m, 'Not enough signatures provided')
assert(signatures.length <= n, 'Too many signatures provided')
var count = 0
signatures.forEach(function(signature) {
count += (signature !== ops.OP_0)
})

assert(count >= m, 'Not enough signatures provided')
assert(count <= n, 'Too many signatures provided')
}

return Script.fromChunks([].concat(ops.OP_0, signatures))
Expand Down
18 changes: 9 additions & 9 deletions src/transaction_builder.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var assert = require('assert')
var ops = require('./opcodes')
var scripts = require('./scripts')

var ECPubKey = require('./ecpubkey')
Expand Down Expand Up @@ -44,23 +45,23 @@ TransactionBuilder.fromTransaction = function(transaction) {

var redeemScript
var scriptSig = txIn.script
var scriptType = scripts.classifyInput(scriptSig)
var scriptType = scripts.classifyInput(scriptSig, true)

// Re-classify if P2SH
if (scriptType === 'scripthash') {
redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0])
scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1))

scriptType = scripts.classifyInput(scriptSig)
scriptType = scripts.classifyInput(scriptSig, true)
assert.equal(scripts.classifyOutput(redeemScript), scriptType, 'Non-matching scriptSig and scriptPubKey in input')
}

// Extract hashType, pubKeys and signatures
var hashType, pubKeys, signatures
var hashType, parsed, pubKeys, signatures

switch (scriptType) {
case 'pubkeyhash':
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
var pubKey = ECPubKey.fromBuffer(scriptSig.chunks[1])

hashType = parsed.hashType
Expand All @@ -70,10 +71,9 @@ TransactionBuilder.fromTransaction = function(transaction) {
break

case 'multisig':
var scriptSigs = scriptSig.chunks.slice(1) // ignore OP_0
var parsed = scriptSigs.map(function(scriptSig) {
return ECSignature.parseScriptSignature(scriptSig)
})
parsed = scriptSig.chunks.slice(1).filter(function(chunk) {
return chunk !== ops.OP_0
}).map(ECSignature.parseScriptSignature)

hashType = parsed[0].hashType
pubKeys = []
Expand All @@ -82,7 +82,7 @@ TransactionBuilder.fromTransaction = function(transaction) {
break

case 'pubkey':
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])

hashType = parsed.hashType
pubKeys = []
Expand Down
57 changes: 57 additions & 0 deletions test/fixtures/scripts.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,51 @@
"type": "nulldata",
"data": "deadffffffffffffffffffffffffffffffffbeef",
"scriptPubKey": "OP_RETURN deadffffffffffffffffffffffffffffffffbeef"
},
{
"type": "nonstandard",
"typeIncomplete": "multisig",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this type is used when the allowIncomplete flag is true

"pubKeys": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340",
"024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34"
],
"signatures": [
null,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null is re-interpreted as OP_0 as seen on line 148.

"3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901",
"3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01"
],
"scriptSig": "OP_0 OP_0 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01"
},
{
"type": "nonstandard",
"typeIncomplete": "multisig",
"pubKeys": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340",
"024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34"
],
"signatures": [
null,
null,
null
],
"scriptSig": "OP_0 OP_0 OP_0 OP_0"
},
{
"type": "nonstandard",
"typeIncomplete": "scripthash",
"pubKeys": [
"0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
"04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a"
],
"signatures": [
null,
"30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801"
],
"redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG",
"redeemScriptSig": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801",
"scriptSig": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae"
}
],
"invalid": {
Expand Down Expand Up @@ -121,6 +166,18 @@
}
],
"multisigInput": [
{
"description": "Not enough signatures provided",
"type": "multisig",
"pubKeys": [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340"
],
"signatures": [
null,
null
]
},
{
"exception": "Not enough signatures provided",
"pubKeys": [
Expand Down
27 changes: 25 additions & 2 deletions test/scripts.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var assert = require('assert')
var ops = require('../src/opcodes')
var scripts = require('../src/scripts')

var ECPubKey = require('../src/ecpubkey')
Expand All @@ -22,6 +23,18 @@ describe('Scripts', function() {
assert.equal(type, f.type)
})
})

fixtures.valid.forEach(function(f) {
if (!f.scriptSig) return
if (!f.typeIncomplete) return

it('classifies incomplete ' + f.scriptSig + ' as ' + f.typeIncomplete, function() {
var script = Script.fromASM(f.scriptSig)
var type = scripts.classifyInput(script, true)

assert.equal(type, f.typeIncomplete)
})
})
})

describe('classifyOutput', function() {
Expand Down Expand Up @@ -51,6 +64,16 @@ describe('Scripts', function() {

assert.equal(inputFn(script), expected)
})

if (f.typeIncomplete) {
var expectedIncomplete = type.toLowerCase() === f.typeIncomplete

it('returns ' + expected + ' for ' + f.scriptSig, function() {
var script = Script.fromASM(f.scriptSig)

assert.equal(inputFn(script, true), expectedIncomplete)
})
}
}
})
})
Expand Down Expand Up @@ -131,7 +154,7 @@ describe('Scripts', function() {

it('returns ' + f.scriptSig, function() {
var signatures = f.signatures.map(function(signature) {
return new Buffer(signature, 'hex')
return signature ? new Buffer(signature, 'hex') : ops.OP_0
})

var scriptSig = scripts.multisigInput(signatures)
Expand All @@ -145,7 +168,7 @@ describe('Scripts', function() {

it('throws on ' + f.exception, function() {
var signatures = f.signatures.map(function(signature) {
return new Buffer(signature, 'hex')
return signature ? new Buffer(signature, 'hex') : ops.OP_0
})

assert.throws(function() {
Expand Down