Merge branch 'scriptclean'

Conflicts:
	test/wallet.js
This commit is contained in:
Wei Lu 2014-06-14 10:30:42 +08:00
commit 8e00eb6855
20 changed files with 500 additions and 587 deletions

View file

@ -1,7 +1,7 @@
var assert = require('assert') var assert = require('assert')
var base58check = require('./base58check') var base58check = require('./base58check')
var networks = require('./networks') var networks = require('./networks')
var Script = require('./script') var scripts = require('./scripts')
function findScriptTypeByVersion(queryVersion) { function findScriptTypeByVersion(queryVersion) {
for (var networkName in networks) { for (var networkName in networks) {
@ -35,17 +35,16 @@ Address.fromBase58Check = function(string) {
return new Address(hash, version) return new Address(hash, version)
} }
Address.fromScriptPubKey = function(script, network) { Address.fromOutputScript = function(script, network) {
network = network || networks.bitcoin network = network || networks.bitcoin
var type = script.getOutType() var type = scripts.classifyOutput(script)
if (type === 'pubkeyhash') { if (type === 'pubkeyhash') {
return new Address(new Buffer(script.chunks[2]), network.pubKeyHash) return new Address(script.chunks[2], network.pubkeyhash)
}
else if (type === 'scripthash') { } else if (type === 'scripthash') {
return new Address(new Buffer(script.chunks[1]), network.scriptHash) return new Address(script.chunks[1], network.scripthash)
} }
assert(false, type + ' has no matching Address') assert(false, type + ' has no matching Address')
@ -60,18 +59,18 @@ Address.prototype.toBase58Check = function () {
return base58check.encode(payload) return base58check.encode(payload)
} }
Address.prototype.toScriptPubKey = function() { Address.prototype.toOutputScript = function() {
var scriptType = findScriptTypeByVersion(this.version) var scriptType = findScriptTypeByVersion(this.version)
if (scriptType === 'pubKeyHash') { if (scriptType === 'pubkeyhash') {
return Script.createPubKeyHashScriptPubKey(this.hash) return scripts.pubKeyHashOutput(this.hash)
} else if (scriptType === 'scripthash') {
return scripts.scriptHashOutput(this.hash)
} }
else if (scriptType === 'scriptHash') { assert(false, this.toString() + ' has no matching Script')
return Script.createP2SHScriptPubKey(this.hash)
}
assert(false, this.toString() + ' has no matching script')
} }
Address.prototype.toString = Address.prototype.toBase58Check Address.prototype.toString = Address.prototype.toBase58Check

View file

@ -32,7 +32,7 @@ ECPubKey.fromHex = function(hex) {
ECPubKey.prototype.getAddress = function(network) { ECPubKey.prototype.getAddress = function(network) {
network = network || networks.bitcoin network = network || networks.bitcoin
return new Address(crypto.hash160(this.toBuffer()), network.pubKeyHash) return new Address(crypto.hash160(this.toBuffer()), network.pubkeyhash)
} }
ECPubKey.prototype.verify = function(hash, signature) { ECPubKey.prototype.verify = function(hash, signature) {

View file

@ -14,6 +14,7 @@ module.exports = {
opcodes: require('./opcodes'), opcodes: require('./opcodes'),
HDNode: require('./hdnode'), HDNode: require('./hdnode'),
Script: require('./script'), Script: require('./script'),
scripts: require('./scripts'),
Transaction: T.Transaction, Transaction: T.Transaction,
TransactionIn: T.TransactionIn, TransactionIn: T.TransactionIn,
TransactionOut: T.TransactionOut, TransactionOut: T.TransactionOut,

View file

@ -13,7 +13,7 @@ var ecurve = require('ecurve')
var ecparams = ecurve.getCurveByName('secp256k1') var ecparams = ecurve.getCurveByName('secp256k1')
function magicHash(message, network) { function magicHash(message, network) {
var magicPrefix = new Buffer(network.magicPrefix) var magicPrefix = new Buffer(network.magicprefix)
var messageBuffer = new Buffer(message) var messageBuffer = new Buffer(message)
var lengthBuffer = new Buffer(bufferutils.varIntSize(messageBuffer.length)) var lengthBuffer = new Buffer(bufferutils.varIntSize(messageBuffer.length))
bufferutils.writeVarInt(lengthBuffer, messageBuffer.length, 0) bufferutils.writeVarInt(lengthBuffer, messageBuffer.length, 0)

View file

@ -2,43 +2,43 @@
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
module.exports = { module.exports = {
bitcoin: { bitcoin: {
magicPrefix: '\x18Bitcoin Signed Message:\n', magicprefix: '\x18Bitcoin Signed Message:\n',
bip32: { bip32: {
public: 0x0488b21e, public: 0x0488b21e,
private: 0x0488ade4 private: 0x0488ade4
}, },
pubKeyHash: 0x00, pubkeyhash: 0x00,
scriptHash: 0x05, scripthash: 0x05,
wif: 0x80 wif: 0x80
}, },
dogecoin: { dogecoin: {
magicPrefix: '\x19Dogecoin Signed Message:\n', magicprefix: '\x19Dogecoin Signed Message:\n',
bip32: { bip32: {
public: 0x02facafd, public: 0x02facafd,
private: 0x02fac398 private: 0x02fac398
}, },
pubKeyHash: 0x1e, pubkeyhash: 0x1e,
scriptHash: 0x16, scripthash: 0x16,
wif: 0x9e wif: 0x9e
}, },
litecoin: { litecoin: {
magicPrefix: '\x19Litecoin Signed Message:\n', magicprefix: '\x19Litecoin Signed Message:\n',
bip32: { bip32: {
public: 0x019da462, public: 0x019da462,
private: 0x019d9cfe private: 0x019d9cfe
}, },
pubKeyHash: 0x30, pubkeyhash: 0x30,
scriptHash: 0x05, scripthash: 0x05,
wif: 0xb0 wif: 0xb0
}, },
testnet: { testnet: {
magicPrefix: '\x18Bitcoin Signed Message:\n', magicprefix: '\x18Bitcoin Signed Message:\n',
bip32: { bip32: {
public: 0x043587cf, public: 0x043587cf,
private: 0x04358394 private: 0x04358394
}, },
pubKeyHash: 0x6f, pubkeyhash: 0x6f,
scriptHash: 0xc4, scripthash: 0xc4,
wif: 0xef wif: 0xef
} }
} }

View file

@ -3,399 +3,61 @@ var bufferutils = require('./bufferutils')
var crypto = require('./crypto') var crypto = require('./crypto')
var opcodes = require('./opcodes') var opcodes = require('./opcodes')
function Script(data) { function Script(buffer, chunks) {
data = data || [] assert(Buffer.isBuffer(buffer), 'Expected Buffer, got ' + buffer)
assert(Array.isArray(data), 'Expected Array, got ' + data) assert(Array.isArray(chunks), 'Expected Array, got ' + chunks)
this.buffer = data this.buffer = buffer
this.parse() this.chunks = chunks
} }
// Import operations // Import operations
Script.fromBuffer = function(buffer) { Script.fromBuffer = function(buffer) {
assert(Buffer.isBuffer(buffer)) // FIXME: transitionary var chunks = []
return new Script(Array.prototype.slice.call(buffer))
}
Script.fromHex = function(hex) {
return Script.fromBuffer(new Buffer(hex, 'hex'))
}
// Export operations
Script.prototype.toBuffer = function() {
return new Buffer(this.buffer)
}
Script.prototype.toHex = function() {
return this.toBuffer().toString('hex')
}
/**
* Update the parsed script representation.
*
* Each Script object stores the script in two formats. First as a raw byte
* array and second as an array of 'chunks', such as opcodes and pieces of
* data.
*
* This method updates the chunks cache. Normally this is called by the
* constructor and you don't need to worry about it. However, if you change
* the script buffer manually, you should update the chunks using this method.
*/
Script.prototype.parse = function() {
var self = this
this.chunks = []
// Cursor
var i = 0 var i = 0
// Read n bytes and store result as a chunk while (i < buffer.length) {
function readChunk(n) { var opcode = buffer.readUInt8(i)
self.chunks.push(self.buffer.slice(i, i + n))
i += n
}
while (i < this.buffer.length) { if ((opcode > opcodes.OP_0) && (opcode <= opcodes.OP_PUSHDATA4)) {
var opcode = this.buffer[i++] var d = bufferutils.readPushDataInt(buffer, i)
if (opcode >= 0xF0) { i += d.size
// Two byte opcode
opcode = (opcode << 8) | this.buffer[i++] var data = buffer.slice(i, i + d.number)
} i += d.number
chunks.push(data)
var len
if (opcode > 0 && opcode < opcodes.OP_PUSHDATA1) {
// Read some bytes of data, opcode value is the length of data
readChunk(opcode)
} else if (opcode == opcodes.OP_PUSHDATA1) {
len = this.buffer[i++]
readChunk(len)
} else if (opcode == opcodes.OP_PUSHDATA2) {
len = (this.buffer[i++] << 8) | this.buffer[i++]
readChunk(len)
} else if (opcode == opcodes.OP_PUSHDATA4) {
len = (this.buffer[i++] << 24) |
(this.buffer[i++] << 16) |
(this.buffer[i++] << 8) |
this.buffer[i++]
readChunk(len)
} else { } else {
this.chunks.push(opcode) chunks.push(opcode)
i += 1
} }
} }
}
return new Script(buffer, chunks)
/**
* Compare the script to known templates of scriptPubKey.
*
* This method will compare the script to a small number of standard script
* templates and return a string naming the detected type.
*
* Currently supported are:
* Pubkeyhash (address)
* Paying to a Bitcoin address which is the hash of a pubkey.
* OP_DUP OP_HASH160 [pubKeyHash] OP_EQUALVERIFY OP_CHECKSIG
*
* Pubkey
* Paying to a public key directly.
* [pubKey] OP_CHECKSIG
*
* Scripthash (P2SH)
* Paying to an address which is the hash of a script
* OP_HASH160 [Scripthash] OP_EQUAL
*
* Multisig
* Paying to multiple pubkeys and require a number of the signatures
* m [pubkey] [pubkey] [pubkey] n OP_CHECKMULTISIG
*
* Nulldata
* Provably prune-able outputs
* OP_RETURN [data]
*
* Nonstandard:
* Any other script (no template matched).
*
* https://github.com/bitcoin/bitcoin/blob/19e5b9d2dfcac4efadba636745485d9660fb1abe/src/script.cpp#L75
*/
Script.prototype.getOutType = function() {
if (isPubkeyhash.call(this)) {
return 'pubkeyhash'
} else if (isPubkey.call(this)) {
return 'pubkey'
} else if (isScripthash.call(this)) {
return 'scripthash'
} else if (isMultisig.call(this)) {
return 'multisig'
} else if (isNulldata.call(this)) {
return 'nulldata'
} else {
return 'nonstandard'
}
}
function isPubkeyhash() {
return this.chunks.length == 5 &&
this.chunks[0] == opcodes.OP_DUP &&
this.chunks[1] == opcodes.OP_HASH160 &&
Array.isArray(this.chunks[2]) &&
this.chunks[2].length === 20 &&
this.chunks[3] == opcodes.OP_EQUALVERIFY &&
this.chunks[4] == opcodes.OP_CHECKSIG
}
function isPubkey() {
return this.chunks.length === 2 &&
Array.isArray(this.chunks[0]) &&
this.chunks[1] === opcodes.OP_CHECKSIG
}
function isScripthash() {
return this.chunks[this.chunks.length - 1] == opcodes.OP_EQUAL &&
this.chunks[0] == opcodes.OP_HASH160 &&
Array.isArray(this.chunks[1]) &&
this.chunks[1].length === 20 &&
this.chunks.length == 3
}
function isMultisig() {
return this.chunks.length > 3 &&
// m is a smallint
isSmallIntOp(this.chunks[0]) &&
// n is a smallint
isSmallIntOp(this.chunks[this.chunks.length - 2]) &&
// n greater or equal to m
this.chunks[0] <= this.chunks[this.chunks.length - 2] &&
// n cannot be 0
this.chunks[this.chunks.length - 2] !== opcodes.OP_0 &&
// n is the size of chunk length minus 3 (m, n, OP_CHECKMULTISIG)
this.chunks.length - 3 === this.chunks[this.chunks.length - 2] - opcodes.OP_RESERVED &&
// last chunk is OP_CHECKMULTISIG
this.chunks[this.chunks.length - 1] == opcodes.OP_CHECKMULTISIG
}
function isNulldata() {
return this.chunks[0] === opcodes.OP_RETURN
}
function isSmallIntOp(opcode) {
return ((opcode == opcodes.OP_0) ||
((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16)))
}
Script.prototype.getHash = function() {
return crypto.hash160(new Buffer(this.buffer))
}
/**
* Compare the script to known templates of scriptSig.
*
* This method will compare the script to a small number of standard script
* templates and return a string naming the detected type.
*
* WARNING: Use this method with caution. It merely represents a heuristic
* based on common transaction formats. A non-standard transaction could
* very easily match one of these templates by accident.
*
* Currently supported are:
* Address:
* Paying to a Bitcoin address which is the hash of a pubkey.
* [sig] [pubKey]
*
* Pubkey:
* Paying to a public key directly.
* [sig]
*
* Multisig:
* Paying to M-of-N public keys.
*
* Nonstandard:
* Any other script (no template matched).
*/
Script.prototype.getInType = function() {
if (this.chunks.length == 1 &&
Array.isArray(this.chunks[0])) {
// Direct IP to IP transactions only have the signature in their scriptSig.
// TODO: We could also check that the length of the data is correct.
return 'pubkey'
} else if (this.chunks.length == 2 &&
Array.isArray(this.chunks[0]) &&
Array.isArray(this.chunks[1])) {
return 'pubkeyhash'
} else if (this.chunks[0] == opcodes.OP_0 &&
this.chunks.slice(1).reduce(function(t, chunk, i) {
return t && Array.isArray(chunk) && (chunk[0] == 48 || i == this.chunks.length - 1)
}, true)) {
return 'multisig'
} else {
return 'nonstandard'
}
}
/**
* Add an op code to the script.
*/
Script.prototype.writeOp = function(opcode) {
this.buffer.push(opcode)
this.chunks.push(opcode)
}
/**
* Add a data chunk to the script.
*/
Script.prototype.writeBytes = function(data) {
// FIXME: Script module doesn't support buffers yet
if (Buffer.isBuffer(data)) data = Array.prototype.slice.call(data);
assert(Array.isArray(data), 'Expected a byte array, got ' + data)
if (data.length < opcodes.OP_PUSHDATA1) {
this.buffer.push(data.length)
} else if (data.length <= 0xff) {
this.buffer.push(opcodes.OP_PUSHDATA1)
this.buffer.push(data.length)
} else if (data.length <= 0xffff) {
this.buffer.push(opcodes.OP_PUSHDATA2)
this.buffer.push(data.length & 0xff)
this.buffer.push((data.length >>> 8) & 0xff)
} else {
this.buffer.push(opcodes.OP_PUSHDATA4)
this.buffer.push(data.length & 0xff)
this.buffer.push((data.length >>> 8) & 0xff)
this.buffer.push((data.length >>> 16) & 0xff)
this.buffer.push((data.length >>> 24) & 0xff)
}
this.buffer = this.buffer.concat(data)
this.chunks.push(data)
}
// {pubKey} OP_CHECKSIG
Script.createPubKeyScriptPubKey = function(pubKey) {
var script = new Script()
script.writeBytes(pubKey.toBuffer())
script.writeOp(opcodes.OP_CHECKSIG)
return script
}
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
Script.createPubKeyHashScriptPubKey = function(hash) {
var script = new Script()
script.writeOp(opcodes.OP_DUP)
script.writeOp(opcodes.OP_HASH160)
script.writeBytes(hash)
script.writeOp(opcodes.OP_EQUALVERIFY)
script.writeOp(opcodes.OP_CHECKSIG)
return script
}
// OP_HASH160 {scriptHash} OP_EQUAL
Script.createP2SHScriptPubKey = function(hash) {
var script = new Script()
script.writeOp(opcodes.OP_HASH160)
script.writeBytes(hash)
script.writeOp(opcodes.OP_EQUAL)
return script
}
// m [pubKeys ...] n OP_CHECKMULTISIG
Script.createMultisigScriptPubKey = function(m, pubKeys) {
assert(Array.isArray(pubKeys), 'Expected Array, got ' + pubKeys)
assert(pubKeys.length >= m, 'Not enough pubKeys provided')
var script = new Script()
var n = pubKeys.length
script.writeOp((opcodes.OP_1 - 1) + m)
pubKeys.forEach(function(pubKey) {
script.writeBytes(pubKey.toBuffer())
})
script.writeOp((opcodes.OP_1 - 1) + n)
script.writeOp(opcodes.OP_CHECKMULTISIG)
return script
}
// {signature}
Script.createPubKeyScriptSig = function(signature) {
var script = new Script()
script.writeBytes(signature)
return script
}
// {signature} {pubKey}
Script.createPubKeyHashScriptSig = function(signature, pubKey) {
var script = new Script()
script.writeBytes(signature)
script.writeBytes(pubKey.toBuffer())
return script
}
// <scriptSig> {serialized scriptPubKey script}
Script.createP2SHScriptSig = function(scriptSig, scriptPubKey) {
var inScript = new Script(scriptSig.buffer)
inScript.writeBytes(scriptPubKey.buffer)
return inScript
}
// OP_0 [signatures ...]
Script.createMultisigScriptSig = function(signatures, scriptPubKey) {
if (scriptPubKey) {
assert(isMultisig.call(scriptPubKey))
var m = scriptPubKey.chunks[0]
var k = m - (opcodes.OP_1 - 1)
assert(k <= signatures.length, 'Not enough signatures provided')
}
var inScript = new Script()
inScript.writeOp(opcodes.OP_0)
signatures.map(function(sig) {
inScript.writeBytes(sig)
})
return inScript
}
Script.prototype.clone = function() {
return new Script(this.buffer)
} }
Script.fromChunks = function(chunks) { Script.fromChunks = function(chunks) {
assert(Array.isArray(chunks), 'Expected Array, got ' + chunks) assert(Array.isArray(chunks), 'Expected Array, got ' + chunks)
var bufferSize = chunks.reduce(function(accum, chunk) { var bufferSize = chunks.reduce(function(accum, chunk) {
var chunkSize = 1 if (Buffer.isBuffer(chunk)) {
return accum + bufferutils.pushDataSize(chunk.length) + chunk.length
// FIXME: transitionary
if (Array.isArray(chunk) || Buffer.isBuffer(chunk)) {
chunkSize = bufferutils.pushDataSize(chunk.length) + chunk.length
} }
return accum + chunkSize return accum + 1
}, 0.0) }, 0.0)
var buffer = new Buffer(bufferSize) var buffer = new Buffer(bufferSize)
var offset = 0 var offset = 0
chunks.forEach(function(chunk) { chunks.forEach(function(chunk) {
// FIXME: transitionary if (Buffer.isBuffer(chunk)) {
if (Array.isArray(chunk) || Buffer.isBuffer(chunk)) {
offset += bufferutils.writePushDataInt(buffer, chunk.length, offset) offset += bufferutils.writePushDataInt(buffer, chunk.length, offset)
// FIXME: transitionary chunk.copy(buffer, offset)
// chunk.copy(buffer, offset)
for (var i = 0; i < chunk.length; ++i) {
buffer[offset + i] = chunk[i]
}
offset += chunk.length offset += chunk.length
} else { } else {
@ -404,7 +66,20 @@ Script.fromChunks = function(chunks) {
} }
}) })
return Script.fromBuffer(buffer) assert.equal(offset, buffer.length, 'Could not decode chunks')
return new Script(buffer, chunks)
}
Script.fromHex = function(hex) {
return Script.fromBuffer(new Buffer(hex, 'hex'))
}
// Constants
Script.EMPTY = Script.fromChunks([])
// Operations
Script.prototype.getHash = function() {
return crypto.hash160(this.buffer)
} }
// FIXME: doesn't work for data chunks, maybe time to use buffertools.compare... // FIXME: doesn't work for data chunks, maybe time to use buffertools.compare...
@ -414,4 +89,13 @@ Script.prototype.without = function(needle) {
})) }))
} }
// Export operations
Script.prototype.toBuffer = function() {
return this.buffer
}
Script.prototype.toHex = function() {
return this.toBuffer().toString('hex')
}
module.exports = Script module.exports = Script

184
src/scripts.js Normal file
View file

@ -0,0 +1,184 @@
var assert = require('assert')
var opcodes = require('./opcodes')
var Script = require('./script')
function classifyOutput(script) {
assert(script instanceof Script, 'Expected Script, got ', script)
if (isPubkeyhash.call(script)) {
return 'pubkeyhash'
} else if (isPubkey.call(script)) {
return 'pubkey'
} else if (isScripthash.call(script)) {
return 'scripthash'
} else if (isMultisig.call(script)) {
return 'multisig'
} else if (isNulldata.call(script)) {
return 'nulldata'
} else {
return 'nonstandard'
}
}
function classifyInput(script) {
assert(script instanceof Script, 'Expected Script, got ', script)
if (script.chunks.length == 1 && Buffer.isBuffer(script.chunks[0])) {
return 'pubkey'
} else if (script.chunks.length == 2 && Buffer.isBuffer(script.chunks[0]) && Buffer.isBuffer(script.chunks[1])) {
return 'pubkeyhash'
} else if (script.chunks[0] == opcodes.OP_0 && script.chunks.slice(1).reduce(function(t, chunk, i) {
return t && Buffer.isBuffer(chunk) && (chunk[0] == 48 || i == script.chunks.length - 1)
}, true)) {
return 'multisig'
} else {
return 'nonstandard'
}
}
function isPubkeyhash() {
return this.chunks.length == 5 &&
this.chunks[0] == opcodes.OP_DUP &&
this.chunks[1] == opcodes.OP_HASH160 &&
Buffer.isBuffer(this.chunks[2]) &&
this.chunks[2].length === 20 &&
this.chunks[3] == opcodes.OP_EQUALVERIFY &&
this.chunks[4] == opcodes.OP_CHECKSIG
}
function isPubkey() {
return this.chunks.length === 2 &&
Buffer.isBuffer(this.chunks[0]) &&
this.chunks[1] === opcodes.OP_CHECKSIG
}
function isScripthash() {
return this.chunks[this.chunks.length - 1] == opcodes.OP_EQUAL &&
this.chunks[0] == opcodes.OP_HASH160 &&
Buffer.isBuffer(this.chunks[1]) &&
this.chunks[1].length === 20 &&
this.chunks.length == 3
}
function isMultisig() {
return this.chunks.length > 3 &&
// m is a smallint
isSmallIntOp(this.chunks[0]) &&
// n is a smallint
isSmallIntOp(this.chunks[this.chunks.length - 2]) &&
// n greater or equal to m
this.chunks[0] <= this.chunks[this.chunks.length - 2] &&
// n cannot be 0
this.chunks[this.chunks.length - 2] !== opcodes.OP_0 &&
// n is the size of chunk length minus 3 (m, n, OP_CHECKMULTISIG)
this.chunks.length - 3 === this.chunks[this.chunks.length - 2] - opcodes.OP_RESERVED &&
// last chunk is OP_CHECKMULTISIG
this.chunks[this.chunks.length - 1] == opcodes.OP_CHECKMULTISIG
}
function isNulldata() {
return this.chunks[0] === opcodes.OP_RETURN
}
function isSmallIntOp(opcode) {
return ((opcode == opcodes.OP_0) || ((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16)))
}
// Standard Script Templates
// {pubKey} OP_CHECKSIG
function pubKeyOutput(pubKey) {
return Script.fromChunks([
pubKey.toBuffer(),
opcodes.OP_CHECKSIG
])
}
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
function pubKeyHashOutput(hash) {
assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash)
return Script.fromChunks([
opcodes.OP_DUP,
opcodes.OP_HASH160,
hash,
opcodes.OP_EQUALVERIFY,
opcodes.OP_CHECKSIG
])
}
// OP_HASH160 {scriptHash} OP_EQUAL
function scriptHashOutput(hash) {
assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash)
return Script.fromChunks([
opcodes.OP_HASH160,
hash,
opcodes.OP_EQUAL
])
}
// m [pubKeys ...] n OP_CHECKMULTISIG
function multisigOutput(m, pubKeys) {
assert(Array.isArray(pubKeys), 'Expected Array, got ' + pubKeys)
assert(pubKeys.length >= m, 'Not enough pubKeys provided')
var pubKeyBuffers = pubKeys.map(function(pubKey) {
return pubKey.toBuffer()
})
var n = pubKeys.length
return Script.fromChunks([].concat(
(opcodes.OP_1 - 1) + m,
pubKeyBuffers,
(opcodes.OP_1 - 1) + n,
opcodes.OP_CHECKMULTISIG
))
}
// {signature}
function pubKeyInput(signature) {
assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature)
return Script.fromChunks([signature])
}
// {signature} {pubKey}
function pubKeyHashInput(signature, pubKey) {
assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature)
return Script.fromChunks([signature, pubKey.toBuffer()])
}
// <scriptSig> {serialized scriptPubKey script}
function scriptHashInput(scriptSig, scriptPubKey) {
return Script.fromChunks([].concat(
scriptSig.chunks,
scriptPubKey.toBuffer()
))
}
// OP_0 [signatures ...]
function multisigInput(signatures, scriptPubKey) {
if (scriptPubKey) {
assert(isMultisig.call(scriptPubKey))
var m = scriptPubKey.chunks[0]
var k = m - (opcodes.OP_1 - 1)
assert(k <= signatures.length, 'Not enough signatures provided')
}
return Script.fromChunks([].concat(opcodes.OP_0, signatures))
}
module.exports = {
classifyInput: classifyInput,
classifyOutput: classifyOutput,
multisigInput: multisigInput,
multisigOutput: multisigOutput,
pubKeyHashInput: pubKeyHashInput,
pubKeyHashOutput: pubKeyHashOutput,
pubKeyInput: pubKeyInput,
pubKeyOutput: pubKeyOutput,
scriptHashInput: scriptHashInput,
scriptHashOutput: scriptHashOutput
}

View file

@ -5,10 +5,11 @@ var bufferutils = require('./bufferutils')
var crypto = require('./crypto') var crypto = require('./crypto')
var ecdsa = require('./ecdsa') var ecdsa = require('./ecdsa')
var opcodes = require('./opcodes') var opcodes = require('./opcodes')
var scripts = require('./scripts')
var Address = require('./address') var Address = require('./address')
var Script = require('./script')
var ECKey = require('./eckey') var ECKey = require('./eckey')
var Script = require('./script')
var DEFAULT_SEQUENCE = 0xffffffff var DEFAULT_SEQUENCE = 0xffffffff
@ -76,7 +77,7 @@ Transaction.prototype.addInput = function (tx, outIndex) {
hash: hash, hash: hash,
index: outIndex index: outIndex
}, },
script: new Script() script: Script.EMPTY
})) }))
} }
@ -109,7 +110,7 @@ Transaction.prototype.addOutput = function (address, value) {
this.outs.push(new TransactionOut({ this.outs.push(new TransactionOut({
value: value, value: value,
script: address.toScriptPubKey(), script: address.toOutputScript(),
address: address // TODO: Remove me address: address // TODO: Remove me
})) }))
} }
@ -133,7 +134,6 @@ Transaction.prototype.toBuffer = function () {
var offset = 0 var offset = 0
function writeSlice(slice) { function writeSlice(slice) {
if (Array.isArray(slice)) slice = new Buffer(slice) // FIXME: Performance: transitionary only
slice.copy(buffer, offset) slice.copy(buffer, offset)
offset += slice.length offset += slice.length
} }
@ -196,17 +196,17 @@ var SIGHASH_ANYONECANPAY = 0x80
* hashType, serializes and finally hashes the result. This hash can then be * hashType, serializes and finally hashes the result. This hash can then be
* used to sign the transaction input in question. * used to sign the transaction input in question.
*/ */
Transaction.prototype.hashForSignature = function(scriptPubKey, inIndex, hashType) { Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashType) {
assert(inIndex >= 0, 'Invalid vin index') assert(inIndex >= 0, 'Invalid vin index')
assert(inIndex < this.ins.length, 'Invalid vin index') assert(inIndex < this.ins.length, 'Invalid vin index')
assert(scriptPubKey instanceof Script, 'Invalid Script object') assert(prevOutScript instanceof Script, 'Invalid Script object')
var txTmp = this.clone() var txTmp = this.clone()
var hashScript = scriptPubKey.without(opcodes.OP_CODESEPARATOR) var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR)
// Blank out other inputs' signatures // Blank out other inputs' signatures
txTmp.ins.forEach(function(txin) { txTmp.ins.forEach(function(txin) {
txin.script = new Script() txin.script = Script.EMPTY
}) })
txTmp.ins[inIndex].script = hashScript txTmp.ins[inIndex].script = hashScript
@ -239,8 +239,7 @@ Transaction.prototype.getHash = function () {
return buffer.toString('hex') return buffer.toString('hex')
} }
Transaction.prototype.clone = function () Transaction.prototype.clone = function () {
{
var newTx = new Transaction() var newTx = new Transaction()
newTx.version = this.version newTx.version = this.version
newTx.locktime = this.locktime newTx.locktime = this.locktime
@ -337,24 +336,22 @@ Transaction.fromHex = function(hex) {
} }
/** /**
* Signs a standard output at some index with the given key * Signs a pubKeyHash output at some index with the given key
*/ */
Transaction.prototype.sign = function(index, key, type) { Transaction.prototype.sign = function(index, key, type) {
assert(key instanceof ECKey) var prevOutScript = key.pub.getAddress().toOutputScript()
var signature = this.signInput(index, prevOutScript, key, type)
var script = key.pub.getAddress().toScriptPubKey()
var signature = this.signScriptSig(index, script, key, type)
// FIXME: Assumed prior TX was pay-to-pubkey-hash // FIXME: Assumed prior TX was pay-to-pubkey-hash
var scriptSig = Script.createPubKeyHashScriptSig(signature, key.pub) var scriptSig = scripts.pubKeyHashInput(signature, key.pub)
this.setScriptSig(index, scriptSig) this.setInputScript(index, scriptSig)
} }
Transaction.prototype.signScriptSig = function(index, scriptPubKey, key, type) { Transaction.prototype.signInput = function(index, prevOutScript, key, type) {
type = type || SIGHASH_ALL type = type || SIGHASH_ALL
assert(key instanceof ECKey, 'Invalid private key') assert(key instanceof ECKey, 'Invalid private key')
var hash = this.hashForSignature(scriptPubKey, index, type) var hash = this.hashForSignature(prevOutScript, index, type)
var signature = key.sign(hash) var signature = key.sign(hash)
var DERencoded = ecdsa.serializeSig(signature) var DERencoded = ecdsa.serializeSig(signature)
@ -364,11 +361,12 @@ Transaction.prototype.signScriptSig = function(index, scriptPubKey, key, type) {
]) ])
} }
Transaction.prototype.setScriptSig = function(index, script) { Transaction.prototype.setInputScript = function(index, script) {
this.ins[index].script = script this.ins[index].script = script
} }
Transaction.prototype.validateSig = function(index, script, pub, DERsig) { // FIXME: should probably be validateInput(index, pub)
Transaction.prototype.validateInput = function(index, script, pub, DERsig) {
var type = DERsig.readUInt8(DERsig.length - 1) var type = DERsig.readUInt8(DERsig.length - 1)
DERsig = DERsig.slice(0, -1) DERsig = DERsig.slice(0, -1)
@ -410,7 +408,7 @@ TransactionIn.prototype.clone = function () {
hash: this.outpoint.hash, hash: this.outpoint.hash,
index: this.outpoint.index index: this.outpoint.index
}, },
script: this.script.clone(), script: this.script,
sequence: this.sequence sequence: this.sequence
}) })
} }
@ -425,7 +423,7 @@ function TransactionOut(data) {
TransactionOut.prototype.clone = function() { TransactionOut.prototype.clone = function() {
return new TransactionOut({ return new TransactionOut({
script: this.script.clone(), script: this.script,
value: this.value, value: this.value,
address: this.address address: this.address
}) })

View file

@ -152,7 +152,7 @@ function Wallet(seed, network) {
var address var address
try { try {
address = Address.fromScriptPubKey(txOut.script, network).toString() address = Address.fromOutputScript(txOut.script, network).toString()
} catch(e) { } catch(e) {
if (!(e.message.match(/has no matching Address/))) throw e if (!(e.message.match(/has no matching Address/))) throw e
} }

View file

@ -1,6 +1,7 @@
var assert = require('assert') var assert = require('assert')
var Address = require('../src/address')
var networks = require('../src/networks') var networks = require('../src/networks')
var Address = require('../src/address')
var Script = require('../src/script') var Script = require('../src/script')
var fixtures = require('./fixtures/address.json') var fixtures = require('./fixtures/address.json')
@ -37,23 +38,23 @@ describe('Address', function() {
}) })
}) })
describe('fromScriptPubKey', function() { describe('fromOutputScript', function() {
fixtures.valid.forEach(function(f) { fixtures.valid.forEach(function(f) {
it('imports ' + f.description + '(' + f.network + ') correctly', function() { it('imports ' + f.description + '(' + f.network + ') correctly', function() {
var script = Script.fromHex(f.script) var script = Script.fromHex(f.script)
var addr = Address.fromScriptPubKey(script, networks[f.network]) var addr = Address.fromOutputScript(script, networks[f.network])
assert.equal(addr.version, f.version) assert.equal(addr.version, f.version)
assert.equal(addr.hash.toString('hex'), f.hex) assert.equal(addr.hash.toString('hex'), f.hex)
}) })
}) })
fixtures.invalid.fromScriptPubKey.forEach(function(f) { fixtures.invalid.fromOutputScript.forEach(function(f) {
it('throws when ' + f.description, function() { it('throws when ' + f.description, function() {
var script = Script.fromHex(f.hex) var script = Script.fromHex(f.hex)
assert.throws(function() { assert.throws(function() {
Address.fromScriptPubKey(script) Address.fromOutputScript(script)
}, new RegExp(f.description)) }, new RegExp(f.description))
}) })
}) })
@ -70,22 +71,22 @@ describe('Address', function() {
}) })
}) })
describe('toScriptPubKey', function() { describe('toOutputScript', function() {
fixtures.valid.forEach(function(f) { fixtures.valid.forEach(function(f) {
it('imports ' + f.description + '(' + f.network + ') correctly', function() { it('imports ' + f.description + '(' + f.network + ') correctly', function() {
var addr = Address.fromBase58Check(f.base58check) var addr = Address.fromBase58Check(f.base58check)
var script = addr.toScriptPubKey() var script = addr.toOutputScript()
assert.equal(script.toHex(), f.script) assert.equal(script.toHex(), f.script)
}) })
}) })
fixtures.invalid.toScriptPubKey.forEach(function(f) { fixtures.invalid.toOutputScript.forEach(function(f) {
it('throws when ' + f.description, function() { it('throws when ' + f.description, function() {
var addr = new Address(new Buffer(f.hex, 'hex'), f.version) var addr = new Address(new Buffer(f.hex, 'hex'), f.version)
assert.throws(function() { assert.throws(function() {
addr.toScriptPubKey() addr.toOutputScript()
}, new RegExp(f.description)) }, new RegExp(f.description))
}) })
}) })

View file

@ -56,10 +56,10 @@ describe('Bitcoin-core', function() {
assert.equal(address.hash.toString('hex'), hex) assert.equal(address.hash.toString('hex'), hex)
if (params.addrType === 'pubkey') { if (params.addrType === 'pubkey') {
assert.equal(address.version, network.pubKeyHash) assert.equal(address.version, network.pubkeyhash)
} else if (params.addrType === 'script') { } else if (params.addrType === 'script') {
assert.equal(address.version, network.scriptHash) assert.equal(address.version, network.scripthash)
} }
}) })
}) })
@ -68,10 +68,10 @@ describe('Bitcoin-core', function() {
// base58_keys_invalid // base58_keys_invalid
describe('Address', function() { describe('Address', function() {
var allowedNetworks = [ var allowedNetworks = [
networks.bitcoin.pubKeyHash, networks.bitcoin.pubkeyhash,
networks.bitcoin.scriptHash, networks.bitcoin.scripthash,
networks.testnet.pubKeyHash, networks.testnet.pubkeyhash,
networks.testnet.scriptHash networks.testnet.scripthash
] ]
base58_keys_invalid.forEach(function(f) { base58_keys_invalid.forEach(function(f) {

View file

@ -72,7 +72,7 @@ describe('ECPubKey', function() {
var pubKey = new ECPubKey(Q) var pubKey = new ECPubKey(Q)
var address = pubKey.getAddress(networks.testnet) var address = pubKey.getAddress(networks.testnet)
assert.equal(address.version, networks.testnet.pubKeyHash) assert.equal(address.version, networks.testnet.pubkeyhash)
assert.equal(address.hash.toString('hex'), fixtures.compressed.hash160) assert.equal(address.hash.toString('hex'), fixtures.compressed.hash160)
}) })
}) })

View file

@ -46,7 +46,7 @@
"exception": "Invalid hash length" "exception": "Invalid hash length"
} }
], ],
"fromScriptPubKey": [ "fromOutputScript": [
{ {
"description": "pubkey has no matching Address", "description": "pubkey has no matching Address",
"hex": "21031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95ac" "hex": "21031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95ac"
@ -60,9 +60,9 @@
"hex": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" "hex": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474"
} }
], ],
"toScriptPubKey": [ "toOutputScript": [
{ {
"description": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE has no matching script", "description": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE has no matching Script",
"hex": "751e76e8199196d454941c45d1b3a323f1433bd6", "hex": "751e76e8199196d454941c45d1b3a323f1433bd6",
"version": 153 "version": 153
} }

View file

@ -33,6 +33,15 @@
"asm": "72 304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301 65 040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8", "asm": "72 304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301 65 040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8",
"scriptPubKey": false "scriptPubKey": false
}, },
{
"description": "pubKey scriptSig",
"hex": "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301",
"type": "pubkey",
"hash": "44d9982c3e79452e02ef5816976a0e20a0ec1cba",
"signature": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301",
"asm": "72 304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301",
"scriptPubKey": false
},
{ {
"description": "Valid multisig script", "description": "Valid multisig script",
"hex": "5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae", "hex": "5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae",
@ -41,6 +50,14 @@
"asm": "OP_TRUE 33 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 33 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG", "asm": "OP_TRUE 33 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 33 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG",
"scriptPubKey": true "scriptPubKey": true
}, },
{
"description": "mutisig scriptSig",
"hex": "0047304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101483045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601",
"type": "multisig",
"hash": "b1ef3ae2c77b356eff81049aad7dfd2eeb34c6f5",
"asm": "00 47 304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101 48 3045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601",
"scriptPubKey": false
},
{ {
"description": "OP_RETURN script", "description": "OP_RETURN script",
"hex":"6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", "hex":"6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474",

View file

@ -204,7 +204,7 @@ describe('HDNode', function() {
var hd = HDNode.fromBase58(f.master.base58) var hd = HDNode.fromBase58(f.master.base58)
hd.network = networks.testnet hd.network = networks.testnet
assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash) assert.equal(hd.getAddress().version, networks.testnet.pubkeyhash)
}) })
}) })

View file

@ -3,6 +3,7 @@ var assert = require('assert')
var bitcoin = require('../../') var bitcoin = require('../../')
var crypto = bitcoin.crypto var crypto = bitcoin.crypto
var networks = bitcoin.networks var networks = bitcoin.networks
var scripts = bitcoin.scripts
var Address = bitcoin.Address var Address = bitcoin.Address
var ECKey = bitcoin.ECKey var ECKey = bitcoin.ECKey
@ -28,10 +29,10 @@ describe('Bitcoin-js', function() {
var outputAmount = 1e4 var outputAmount = 1e4
var pubKeys = privKeys.map(function(eck) { return eck.pub }) var pubKeys = privKeys.map(function(eck) { return eck.pub })
var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys) var redeemScript = scripts.multisigOutput(2, pubKeys)
var scriptPubKey = Script.createP2SHScriptPubKey(redeemScript.getHash()) var scriptPubKey = scripts.scriptHashOutput(redeemScript.getHash())
var multisigAddress = Address.fromScriptPubKey(scriptPubKey, networks.testnet).toString() var multisigAddress = Address.fromOutputScript(scriptPubKey, networks.testnet).toString()
// Attempt to send funds to the source address, providing some unspents for later // Attempt to send funds to the source address, providing some unspents for later
helloblock.faucet.withdraw(multisigAddress, coldAmount, function(err) { helloblock.faucet.withdraw(multisigAddress, coldAmount, function(err) {
@ -54,12 +55,12 @@ describe('Bitcoin-js', function() {
tx.addOutput(targetAddress, spendAmount) tx.addOutput(targetAddress, spendAmount)
var signatures = privKeys.map(function(privKey) { var signatures = privKeys.map(function(privKey) {
return tx.signScriptSig(0, redeemScript, privKey) return tx.signInput(0, redeemScript, privKey)
}) })
var redeemScriptSig = Script.createMultisigScriptSig(signatures) var redeemScriptSig = scripts.multisigInput(signatures)
var scriptSig = Script.createP2SHScriptSig(redeemScriptSig, redeemScript) var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript)
tx.setScriptSig(0, scriptSig) tx.setInputScript(0, scriptSig)
// broadcast our transaction // broadcast our transaction
helloblock.transactions.propagate(tx.toHex(), function(err, resp, resource) { helloblock.transactions.propagate(tx.toHex(), function(err, resp, resource) {

View file

@ -1,29 +1,23 @@
var assert = require('assert') var assert = require('assert')
var crypto = require('../src/crypto')
var networks = require('../src/networks')
var opcodes = require('../src/opcodes') var opcodes = require('../src/opcodes')
var Address = require('../src/address')
var ECPubKey = require('../src/ecpubkey')
var Script = require('../src/script') var Script = require('../src/script')
var fixtures = require('./fixtures/script.json') var fixtures = require('./fixtures/script.json')
function b2h(b) { return new Buffer(b).toString('hex') }
function h2b(h) { return new Buffer(h, 'hex') }
describe('Script', function() { describe('Script', function() {
describe('constructor', function() { describe('constructor', function() {
it('works for a byte array', function() { it('accepts valid parameters', function() {
assert.ok(new Script([])) var buffer = new Buffer([1])
}) var chunks = [1]
var script = new Script(buffer, chunks)
it('works when nothing is passed in', function() { assert.equal(script.buffer, buffer)
assert.ok(new Script()) assert.equal(script.chunks, chunks)
}) })
it('throws an error when input is not an array', function() { it('throws an error when input is not an array', function() {
assert.throws(function(){ new Script({}) }, /Expected Array, got/) assert.throws(function(){ new Script({}) }, /Expected Buffer, got/)
}) })
}) })
@ -45,129 +39,18 @@ describe('Script', function() {
}) })
}) })
describe('getInType', function() {
fixtures.valid.forEach(function(f) {
if (!f.scriptPubKey) {
it('supports ' + f.description, function() {
var script = Script.fromHex(f.hex)
assert.equal(script.getInType(), f.type)
})
}
})
})
describe('getOutType', function() {
fixtures.valid.forEach(function(f) {
if (f.scriptPubKey) {
it('supports ' + f.description, function() {
var script = Script.fromHex(f.hex)
assert.equal(script.getOutType(), f.type)
})
}
})
})
describe('pay-to-pubKeyHash', function() {
it('matches the test data', function() {
// FIXME: bad
var f = fixtures.valid[2]
var address = Address.fromBase58Check('19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu')
var script = Script.createPubKeyHashScriptPubKey(address.hash)
assert.equal(script.toHex(), f.hex)
})
})
describe('pay-to-pubkey', function() {
it('matches the test data', function() {
// FIXME: bad
var f = fixtures.valid[0]
var pubKey = ECPubKey.fromHex(f.pubKey)
var script = Script.createPubKeyScriptPubKey(pubKey)
assert.equal(script.toHex(), f.hex)
})
})
describe('pay-to-scriptHash', function() {
it('matches the test data', function() {
// FIXME: bad
var f = fixtures.valid[1]
var address = Address.fromBase58Check('3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8')
var script = Script.createP2SHScriptPubKey(address.hash)
assert.equal(script.toHex(), f.hex)
})
})
describe('2-of-3 Multi-Signature scriptPubKey', function() {
var pubKeys
beforeEach(function() {
pubKeys = [
'02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f',
'02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f',
'036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19'
].map(ECPubKey.fromHex)
})
it('should create valid redeemScript', function() {
var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys)
var hash160 = crypto.hash160(new Buffer(redeemScript.buffer))
var multisigAddress = new Address(hash160, networks.bitcoin.scriptHash)
assert.equal(multisigAddress.toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v')
})
it('should throw on not enough pubKeys provided', function() {
assert.throws(function() {
Script.createMultisigScriptPubKey(4, pubKeys)
}, /Not enough pubKeys provided/)
})
})
describe('2-of-2 Multisig scriptSig', function() {
var pubKeys = [
'02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1',
'0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a'
].map(ECPubKey.fromHex)
var signatures = [
'304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801',
'3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501'
].map(h2b)
var expected = '0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae'
it('should create a valid P2SH multisig scriptSig', function() {
var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys)
var redeemScriptSig = Script.createMultisigScriptSig(signatures)
var scriptSig = Script.createP2SHScriptSig(redeemScriptSig, redeemScript)
assert.equal(b2h(scriptSig.buffer), expected)
})
it('should throw on not enough signatures', function() {
var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys)
assert.throws(function() {
Script.createMultisigScriptSig(signatures.slice(1), redeemScript)
}, /Not enough signatures provided/)
})
})
describe('fromChunks', function() { describe('fromChunks', function() {
it('should match expected behaviour', function() { it('should match expected behaviour', function() {
var hash = new Buffer(32) var hash = new Buffer(32)
hash.fill(0)
var script = Script.fromChunks([ var script = Script.fromChunks([
opcodes.OP_HASH160, opcodes.OP_HASH160,
hash, hash,
opcodes.OP_EQUAL opcodes.OP_EQUAL
]) ])
assert.deepEqual(script, Script.createP2SHScriptPubKey(hash)) assert.equal(script.toHex(), 'a920000000000000000000000000000000000000000000000000000000000000000087')
}) })
}) })

144
test/scripts.js Normal file
View file

@ -0,0 +1,144 @@
var assert = require('assert')
var crypto = require('../src/crypto')
var networks = require('../src/networks')
var scripts = require('../src/scripts')
var Address = require('../src/address')
var ECPubKey = require('../src/ecpubkey')
var Script = require('../src/script')
var fixtures = require('./fixtures/script.json')
function b2h(b) { return new Buffer(b).toString('hex') }
function h2b(h) { return new Buffer(h, 'hex') }
describe('Scripts', function() {
describe('classifyInput', function() {
fixtures.valid.forEach(function(f) {
if (f.scriptPubKey) return
it('supports ' + f.type, function() {
var script = Script.fromHex(f.hex)
var type = scripts.classifyInput(script)
assert.equal(type, f.type)
})
})
})
describe('classifyOutput', function() {
fixtures.valid.forEach(function(f) {
if (!f.scriptPubKey) return
it('supports ' + f.type, function() {
var script = Script.fromHex(f.hex)
var type = scripts.classifyOutput(script)
assert.equal(type, f.type)
})
})
})
// FIXME: bad
describe('pay-to-pubKeyHash', function() {
it('matches the test data', function() {
var f = fixtures.valid[2]
var address = Address.fromBase58Check('19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu')
var script = scripts.pubKeyHashOutput(address.hash)
assert.equal(script.toHex(), f.hex)
})
})
// FIXME: bad
describe('pay-to-pubkey', function() {
describe('input', function() {
it('matches the test data', function() {
var f = fixtures.valid[4]
var signature = new Buffer(f.signature, 'hex')
var script = scripts.pubKeyInput(signature)
assert.equal(script.toHex(), f.hex)
})
})
describe('output', function() {
it('matches the test data', function() {
var f = fixtures.valid[0]
var pubKey = ECPubKey.fromHex(f.pubKey)
var script = scripts.pubKeyOutput(pubKey)
assert.equal(script.toHex(), f.hex)
})
})
})
// FIXME: bad
describe('pay-to-scriptHash', function() {
it('matches the test data', function() {
var f = fixtures.valid[1]
var address = Address.fromBase58Check('3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8')
var script = scripts.scriptHashOutput(address.hash)
assert.equal(script.toHex(), f.hex)
})
})
// FIXME: bad
describe('2-of-3 Multi-Signature scriptPubKey', function() {
var pubKeys
beforeEach(function() {
pubKeys = [
'02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f',
'02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f',
'036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19'
].map(ECPubKey.fromHex)
})
it('should create valid redeemScript', function() {
var redeemScript = scripts.multisigOutput(2, pubKeys)
var hash160 = crypto.hash160(new Buffer(redeemScript.buffer))
var multisigAddress = new Address(hash160, networks.bitcoin.scripthash)
assert.equal(multisigAddress.toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v')
})
it('should throw on not enough pubKeys provided', function() {
assert.throws(function() {
scripts.multisigOutput(4, pubKeys)
}, /Not enough pubKeys provided/)
})
})
// FIXME: bad
describe('2-of-2 Multisig scriptSig', function() {
var pubKeys = [
'02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1',
'0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a'
].map(ECPubKey.fromHex)
var signatures = [
'304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801',
'3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501'
].map(h2b)
var expected = '0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae'
it('should create a valid P2SH multisig scriptSig', function() {
var redeemScript = scripts.multisigOutput(2, pubKeys)
var redeemInput = scripts.multisigInput(signatures)
var scriptSig = scripts.scriptHashInput(redeemInput, redeemScript)
assert.equal(b2h(scriptSig.buffer), expected)
})
it('should throw on not enough signatures', function() {
var redeemScript = scripts.multisigOutput(2, pubKeys)
assert.throws(function() {
scripts.multisigInput(signatures.slice(1), redeemScript)
}, /Not enough signatures provided/)
})
})
})

View file

@ -1,5 +1,6 @@
var assert = require('assert') var assert = require('assert')
var networks = require('../src/networks') var networks = require('../src/networks')
var scripts = require('../src/scripts')
var Address = require('../src/address') var Address = require('../src/address')
var ECKey = require('../src/eckey') var ECKey = require('../src/eckey')
@ -70,7 +71,7 @@ describe('Transaction', function() {
var output = tx.outs[0] var output = tx.outs[0]
assert.equal(output.value, 5000000000) assert.equal(output.value, 5000000000)
assert.deepEqual(output.script, Address.fromBase58Check('n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9').toScriptPubKey()) assert.deepEqual(output.script, Address.fromBase58Check('n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9').toOutputScript())
}) })
it('assigns hash to deserialized object', function(){ it('assigns hash to deserialized object', function(){
@ -131,7 +132,7 @@ describe('Transaction', function() {
verifyTransactionIn() verifyTransactionIn()
}) })
function verifyTransactionIn(){ function verifyTransactionIn() {
assert.equal(tx.ins.length, 1) assert.equal(tx.ins.length, 1)
var input = tx.ins[0] var input = tx.ins[0]
@ -140,7 +141,7 @@ describe('Transaction', function() {
assert.equal(input.outpoint.index, 0) assert.equal(input.outpoint.index, 0)
assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57") assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57")
assert.deepEqual(input.script.buffer, []) assert.equal(input.script, Script.EMPTY)
} }
}) })
@ -199,11 +200,11 @@ describe('Transaction', function() {
var script = prevTx.outs[0].script var script = prevTx.outs[0].script
var sig = new Buffer(tx.ins[0].script.chunks[0]) var sig = new Buffer(tx.ins[0].script.chunks[0])
assert.equal(tx.validateSig(0, script, key.pub, sig), true) assert.equal(tx.validateInput(0, script, key.pub, sig), true)
}) })
}) })
describe('validateSig', function(){ describe('validateInput', function(){
var validTx var validTx
beforeEach(function() { beforeEach(function() {
@ -215,7 +216,7 @@ describe('Transaction', function() {
var script = prevTx.outs[0].script var script = prevTx.outs[0].script
var sig = new Buffer(validTx.ins[0].script.chunks[0]) var sig = new Buffer(validTx.ins[0].script.chunks[0])
assert.equal(validTx.validateSig(0, script, key.pub, sig), true) assert.equal(validTx.validateInput(0, script, key.pub, sig), true)
}) })
}) })
@ -242,7 +243,7 @@ describe('Transaction', function() {
}) })
}) })
describe('signScriptSig', function() { describe('signInput', function() {
it('works for multi-sig redeem script', function() { it('works for multi-sig redeem script', function() {
var tx = new Transaction() var tx = new Transaction()
tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0) tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0)
@ -255,18 +256,18 @@ describe('Transaction', function() {
return ECKey.fromWIF(wif) return ECKey.fromWIF(wif)
}) })
var pubKeys = privKeys.map(function(eck) { return eck.pub }) var pubKeys = privKeys.map(function(eck) { return eck.pub })
var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys) var redeemScript = scripts.multisigOutput(2, pubKeys)
var signatures = privKeys.map(function(privKey) { var signatures = privKeys.map(function(privKey) {
return tx.signScriptSig(0, redeemScript, privKey) return tx.signInput(0, redeemScript, privKey)
}) })
var redeemScriptSig = Script.createMultisigScriptSig(signatures) var redeemScriptSig = scripts.multisigInput(signatures)
var scriptSig = Script.createP2SHScriptSig(redeemScriptSig, redeemScript) var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript)
tx.setScriptSig(0, scriptSig) tx.setInputScript(0, scriptSig)
signatures.forEach(function(sig, i){ signatures.forEach(function(sig, i){
assert(tx.validateSig(0, redeemScript, privKeys[i].pub, sig)) assert(tx.validateInput(0, redeemScript, privKeys[i].pub, sig))
}) })
var expected = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d600000000fd1b0100483045022100e5be20d440b2bbbc886161f9095fa6d0bca749a4e41d30064f30eb97adc7a1f5022061af132890d8e4e90fedff5e9365aeeb77021afd8ef1d5c114d575512e9a130a0147304402205054e38e9d7b5c10481b6b4991fde5704cd94d49e344406e3c2ce4d18a43bf8e022051d7ba8479865b53a48bee0cce86e89a25633af5b2918aa276859489e232f51c014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0101000000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000' var expected = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d600000000fd1b0100483045022100e5be20d440b2bbbc886161f9095fa6d0bca749a4e41d30064f30eb97adc7a1f5022061af132890d8e4e90fedff5e9365aeeb77021afd8ef1d5c114d575512e9a130a0147304402205054e38e9d7b5c10481b6b4991fde5704cd94d49e344406e3c2ce4d18a43bf8e022051d7ba8479865b53a48bee0cce86e89a25633af5b2918aa276859489e232f51c014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0101000000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000'

View file

@ -2,10 +2,10 @@ var assert = require('assert')
var crypto = require('../src/crypto') var crypto = require('../src/crypto')
var networks = require('../src/networks') var networks = require('../src/networks')
var sinon = require('sinon') var sinon = require('sinon')
var scripts = require('../src/scripts')
var Address = require('../src/address') var Address = require('../src/address')
var HDNode = require('../src/hdnode') var HDNode = require('../src/hdnode')
var Script = require('../src/script')
var Transaction = require('../src/transaction').Transaction var Transaction = require('../src/transaction').Transaction
var Wallet = require('../src/wallet') var Wallet = require('../src/wallet')
@ -266,7 +266,7 @@ describe('Wallet', function() {
it('does not fail on scripts with no corresponding Address', function() { it('does not fail on scripts with no corresponding Address', function() {
var pubKey = wallet.getPrivateKey(0).pub var pubKey = wallet.getPrivateKey(0).pub
var script = Script.createPubKeyScriptPubKey(pubKey) var script = scripts.pubKeyOutput(pubKey)
var tx2 = new Transaction() var tx2 = new Transaction()
tx2.addInput(fakeTxHash(1), 0) tx2.addInput(fakeTxHash(1), 0)
@ -346,7 +346,7 @@ describe('Wallet', function() {
assert.equal(output.value, txOut.value) assert.equal(output.value, txOut.value)
assert.equal(output.pending, pending) assert.equal(output.pending, pending)
var txOutAddress = Address.fromScriptPubKey(txOut.script).toString() var txOutAddress = Address.fromOutputScript(txOut.script).toString()
assert.equal(output.address, txOutAddress) assert.equal(output.address, txOutAddress)
} }
}) })