Browse Source

Merge pull request #173 from dcousens/scriptclean

Cleanup of network in Transaction/Script
hk-custom-address
Wei Lu 11 years ago
parent
commit
631f7257ef
  1. 55
      src/address.js
  2. 6
      src/eckey.js
  3. 12
      src/hdwallet.js
  4. 2
      src/index.js
  5. 0
      src/networks.js
  6. 188
      src/script.js
  7. 98
      src/transaction.js
  8. 28
      src/wallet.js
  9. 49
      test/address.js
  10. 39
      test/fixtures/address.js
  11. 54
      test/fixtures/script.js
  12. 6
      test/integration/p2sh.js
  13. 4
      test/message.js
  14. 124
      test/script.js
  15. 27
      test/transaction.js
  16. 27
      test/wallet.js

55
src/address.js

@ -1,5 +1,21 @@
var assert = require('assert')
var base58check = require('./base58check')
var networks = require('./networks')
var Script = require('./script')
function findScriptTypeByVersion(queryVersion) {
for (var networkName in networks) {
var network = networks[networkName]
for (var versionName in network) {
var version = network[versionName]
if (version === queryVersion) {
return versionName
}
}
}
}
function Address(hash, version) {
assert(Buffer.isBuffer(hash), 'First argument must be a Buffer')
@ -10,18 +26,55 @@ function Address(hash, version) {
this.version = version
}
Address.Error = function(message) {
this.name = 'AddressError'
this.message = message
}
Address.Error.prototype = new Error()
Address.Error.prototype.constructor = Address.Error
// Import functions
Address.fromBase58Check = function(string) {
var decode = base58check.decode(string)
return new Address(decode.payload, decode.version)
}
Address.prototype.fromString = Address.prototype.fromBase58Check
Address.fromScriptPubKey = function(script, network) {
network = network || networks.bitcoin
var type = script.getOutType()
if (type === 'pubkeyhash') {
return new Address(new Buffer(script.chunks[2]), network.pubKeyHash)
}
else if (type === 'scripthash') {
return new Address(new Buffer(script.chunks[1]), network.scriptHash)
}
throw new Address.Error(type + ' has no matching Address')
}
// Export functions
Address.prototype.toBase58Check = function () {
return base58check.encode(this.hash, this.version)
}
Address.prototype.toScriptPubKey = function() {
var scriptType = findScriptTypeByVersion(this.version)
if (scriptType === 'pubKeyHash') {
return Script.createPubKeyHashScriptPubKey(this.hash)
}
else if (scriptType === 'scriptHash') {
return Script.createP2SHScriptPubKey(this.hash)
}
throw new Address.Error(this + ' has no matching script')
}
Address.prototype.toString = Address.prototype.toBase58Check
module.exports = Address

6
src/eckey.js

@ -1,7 +1,7 @@
var assert = require('assert')
var base58check = require('./base58check')
var ecdsa = require('./ecdsa')
var network = require('./network')
var networks = require('./networks')
var secureRandom = require('secure-random')
var Address = require('./address')
@ -72,7 +72,7 @@ ECKey.prototype.toHex = function() {
}
ECKey.prototype.toWIF = function(version) {
version = version || network.bitcoin.wif
version = version || networks.bitcoin.wif
var buffer = this.toBuffer()
if (this.pub.compressed) {
@ -115,7 +115,7 @@ ECPubKey.prototype.verify = function(hash, sig) {
}
ECPubKey.prototype.getAddress = function(version) {
version = version || network.bitcoin.pubKeyHash
version = version || networks.bitcoin.pubKeyHash
return new Address(crypto.hash160(this.toBuffer()), version)
}

12
src/hdwallet.js

@ -8,7 +8,7 @@ var CJS = require('crypto-js')
var crypto = require('./crypto')
var ECKey = require('./eckey').ECKey
var ECPubKey = require('./eckey').ECPubKey
var Network = require('./network')
var networks = require('./networks')
var sec = require('./sec')
var ecparams = sec("secp256k1")
@ -27,7 +27,7 @@ function HDWallet(seed, networkString) {
this.chaincode = I.slice(32)
this.network = networkString || 'bitcoin'
if(!Network.hasOwnProperty(this.network)) {
if(!networks.hasOwnProperty(this.network)) {
throw new Error("Unknown network: " + this.network)
}
@ -70,8 +70,8 @@ HDWallet.fromBuffer = function(input) {
var version = input.readUInt32BE(0)
var type
for(var name in Network) {
var network = Network[name]
for(var name in networks) {
var network = networks[name]
for(var t in network.bip32) {
if (version != network.bip32[t]) continue
@ -128,7 +128,7 @@ HDWallet.prototype.getAddress = function() {
HDWallet.prototype.toBuffer = function(priv) {
// Version
var version = Network[this.network].bip32[priv ? 'priv' : 'pub']
var version = networks[this.network].bip32[priv ? 'priv' : 'pub']
var buffer = new Buffer(HDWallet.LENGTH)
// 4 bytes: version bytes
@ -245,7 +245,7 @@ HDWallet.prototype.derivePrivate = function(index) {
}
HDWallet.prototype.getKeyVersion = function() {
return Network[this.network].pubKeyHash
return networks[this.network].pubKeyHash
}
HDWallet.prototype.toString = HDWallet.prototype.toBase58

2
src/index.js

@ -22,6 +22,6 @@ module.exports = {
Transaction: T.Transaction,
TransactionIn: T.TransactionIn,
TransactionOut: T.TransactionOut,
network: require('./network'),
networks: require('./networks'),
Wallet: require('./wallet')
}

0
src/network.js → src/networks.js

188
src/script.js

@ -1,18 +1,17 @@
var assert = require('assert')
var Address = require('./address')
var assert = require('assert')
var crypto = require('./crypto')
var convert = require('./convert')
var Network = require('./network')
var Opcode = require('./opcode')
function Script(data) {
this.buffer = data || []
if(!Array.isArray(this.buffer)) {
throw new Error('expect Script to be initialized with Array, but got ' + data)
}
data = data || []
assert(Array.isArray(data), 'Expected Array, got ' + data)
this.buffer = data
this.parse()
}
// Import operations
Script.fromBuffer = function(buffer) {
assert(Buffer.isBuffer(buffer)) // FIXME: transitionary
@ -23,30 +22,13 @@ Script.fromHex = function(hex) {
return Script.fromBuffer(new Buffer(hex, 'hex'))
}
Script.fromPubKey = function(str) {
var script = new Script()
var s = str.split(' ')
for (var i in s) {
if (Opcode.map.hasOwnProperty(s[i])) {
script.writeOp(Opcode.map[s[i]])
} else {
script.writeBytes(convert.hexToBytes(s[i]))
}
}
return script
// Export operations
Script.prototype.toBuffer = function() {
return new Buffer(this.buffer)
}
Script.fromScriptSig = function(str) {
var script = new Script()
var s = str.split(' ')
for (var i in s) {
if (Opcode.map.hasOwnProperty(s[i])) {
script.writeOp(Opcode.map[s[i]])
} else {
script.writeBytes(convert.hexToBytes(s[i]))
}
}
return script
Script.prototype.toHex = function() {
return this.toBuffer().toString('hex')
}
/**
@ -202,40 +184,10 @@ function isSmallIntOp(opcode) {
((opcode >= Opcode.map.OP_1) && (opcode <= Opcode.map.OP_16)))
}
/**
* Returns the address corresponding to this output in hash160 form.
* Assumes strange scripts are P2SH
*/
Script.prototype.toScriptHash = function() {
if(isPubkeyhash.call(this)) {
return this.chunks[2]
}
if(isScripthash.call(this)) {
return crypto.hash160(this.buffer)
}
Script.prototype.getHash = function() {
return crypto.hash160(this.buffer)
}
Script.prototype.getToAddress = function(network) {
network = network || Network.bitcoin
if(isPubkeyhash.call(this)) {
return new Address(new Buffer(this.chunks[2]), network.pubKeyHash)
}
assert(isScripthash.call(this))
return new Address(new Buffer(this.chunks[1]), network.scriptHash)
}
Script.prototype.getFromAddress = function(version) {
version = version || Network.bitcoin.pubKeyHash
return new Address(this.simpleInHash(), version)
}
/**
* Compare the script to known templates of scriptSig.
*
@ -281,58 +233,6 @@ Script.prototype.getInType = function() {
}
}
/**
* Returns the affected public key for this input.
*
* This currently only works with payToPubKeyHash transactions. It will also
* work in the future for standard payToScriptHash transactions that use a
* single public key.
*
* However for multi-key and other complex transactions, this will only return
* one of the keys or raise an error. Therefore, it is recommended for indexing
* purposes to use Script#simpleInHash or Script#simpleOutHash instead.
*
* @deprecated
*/
Script.prototype.simpleInPubKey = function() {
switch (this.getInType()) {
case 'pubkeyhash':
return this.chunks[1]
case 'pubkey':
// TODO: Theoretically, we could recover the pubkey from the sig here.
// See https://bitcointalk.org/?topic=6430.0
throw new Error('Script does not contain pubkey')
default:
throw new Error('Encountered non-standard scriptSig')
}
}
/**
* Returns the affected address hash for this input.
*
* For standard transactions, this will return the hash of the pubKey that
* can spend this output.
*
* In the future, for standard payToScriptHash inputs, this will return the
* scriptHash.
*
* Note: This function provided for convenience. If you have the corresponding
* scriptPubKey available, you are urged to use Script#simpleOutHash instead
* as it is more reliable for non-standard payToScriptHash transactions.
*
* This method is useful for indexing transactions.
*/
Script.prototype.simpleInHash = function() {
return crypto.hash160(this.simpleInPubKey())
}
/**
* Old name for Script#simpleInHash.
*
* @deprecated
*/
Script.prototype.simpleInPubKeyHash = Script.prototype.simpleInHash
/**
* Add an op code to the script.
*/
@ -347,7 +247,7 @@ Script.prototype.writeOp = function(opcode) {
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), "Expect a byte array. Got" + data)
assert(Array.isArray(data), "Expected a byte array. Got " + data)
if (data.length < Opcode.map.OP_PUSHDATA1) {
this.buffer.push(data.length)
@ -369,48 +269,32 @@ Script.prototype.writeBytes = function(data) {
this.chunks.push(data)
}
/**
* Create an output for an address
*/
Script.createOutputScript = function(address, network) {
assert(address instanceof Address)
network = network || Network.bitcoin
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
Script.createPubKeyHashScriptPubKey = function(hash) {
var script = new Script()
// Standard pay-to-script-hash
if (address.version === network.scriptHash) {
script.writeOp(Opcode.map.OP_HASH160)
script.writeBytes(address.hash)
script.writeOp(Opcode.map.OP_EQUAL)
return script
}
assert.strictEqual(address.version, network.pubKeyHash, 'Unknown address type')
// Standard pay-to-pubkey-hash
script.writeOp(Opcode.map.OP_DUP)
script.writeOp(Opcode.map.OP_HASH160)
script.writeBytes(address.hash)
script.writeBytes(hash)
script.writeOp(Opcode.map.OP_EQUALVERIFY)
script.writeOp(Opcode.map.OP_CHECKSIG)
return script
}
/**
* Extract pubkeys from a multisig script
*/
// OP_HASH160 {scriptHash} OP_EQUAL
Script.createP2SHScriptPubKey = function(hash) {
var script = new Script()
Script.prototype.extractPubkeys = function() {
return this.chunks.filter(function(chunk) {
return(chunk[0] == 4 && chunk.length == 65 || chunk[0] < 4 && chunk.length == 33)
})
script.writeOp(Opcode.map.OP_HASH160)
script.writeBytes(hash)
script.writeOp(Opcode.map.OP_EQUAL)
return script
}
// m [pubKeys ...] n OP_CHECKMULTISIG
Script.createMultisigOutputScript = function(m, pubKeys) {
Script.createMultisigScriptPubKey = function(m, pubKeys) {
var script = new Script()
pubKeys = pubKeys.sort()
@ -433,7 +317,15 @@ Script.createPubKeyHashScriptSig = function(signature, pubKey) {
}
// OP_0 [signatures ...]
Script.createMultisigScriptSig = function(signatures) {
Script.createMultisigScriptSig = function(signatures, scriptPubKey) {
if (scriptPubKey) {
assert(isMultisig.call(scriptPubKey))
var m = scriptPubKey.chunks[0]
var k = m - (Opcode.map.OP_1 - 1)
assert(k <= signatures.length, 'Not enough signatures provided')
}
var inScript = new Script()
inScript.writeOp(Opcode.map.OP_0)
@ -451,18 +343,6 @@ Script.createP2SHScriptSig = function(scriptSig, scriptPubKey) {
return inScript
}
// [signatures ...] {m [pubKeys ...] n OP_CHECKSIG}
Script.createP2SHMultisigScriptSig = function(signatures, scriptPubKey) {
assert(isMultisig.call(scriptPubKey))
var m = scriptPubKey.chunks[0]
var k = m - (Opcode.map.OP_1 - 1)
assert(k <= signatures.length, 'Not enough signatures provided')
var scriptSig = Script.createMultisigScriptSig(signatures)
return Script.createP2SHScriptSig(scriptSig, scriptPubKey)
}
Script.prototype.clone = function() {
return new Script(this.buffer)
}

98
src/transaction.js

@ -9,7 +9,6 @@ var convert = require('./convert')
var crypto = require('./crypto')
var ECKey = require('./eckey').ECKey
var ecdsa = require('./ecdsa')
var Network = require('./network')
var Transaction = function (doc) {
if (!(this instanceof Transaction)) { return new Transaction(doc) }
@ -90,11 +89,10 @@ Transaction.prototype.addInput = function (tx, outIndex) {
* i) An existing TransactionOut object
* ii) An address object or a string address, and a value
* iii) An address:value string
* iv) Either ii), iii) with an optional network argument
*
* FIXME: This is a bit convoluted
*/
Transaction.prototype.addOutput = function (address, value, network) {
Transaction.prototype.addOutput = function (address, value) {
if (arguments[0] instanceof TransactionOut) {
this.outs.push(arguments[0])
return
@ -105,19 +103,15 @@ Transaction.prototype.addOutput = function (address, value, network) {
var args = arguments[0].split(':')
address = args[0]
value = parseInt(args[1])
network = arguments[1]
}
address = Address.fromBase58Check(address)
}
network = network || Network.bitcoin
this.outs.push(new TransactionOut({
value: value,
script: Script.createOutputScript(address, network),
network: network
script: address.toScriptPubKey(),
address: address // TODO: Remove me
}))
}
@ -364,56 +358,18 @@ Transaction.deserialize = function(buffer) {
/**
* Signs a standard output at some index with the given key
* FIXME: network support is ugly
*/
Transaction.prototype.sign = function(index, key, type, network) {
Transaction.prototype.sign = function(index, key, type) {
assert(key instanceof ECKey)
network = network || Network.bitcoin
var address = key.pub.getAddress(network.pubKeyHash)
// FIXME: Assumed prior TX was pay-to-pubkey-hash
var script = Script.createOutputScript(address, network)
var script = key.pub.getAddress().toScriptPubKey()
var signature = this.signScriptSig(index, script, key, type)
// FIXME: Assumed prior TX was pay-to-pubkey-hash
var scriptSig = Script.createPubKeyHashScriptSig(signature, key.pub)
this.setScriptSig(index, scriptSig)
}
// Takes outputs of the form [{ output: 'txhash:index', address: 'address' },...]
Transaction.prototype.signWithKeys = function(keys, outputs, type) {
type = type || SIGHASH_ALL
var addrdata = keys.map(function(key) {
assert(key instanceof ECKey)
return {
key: key,
address: key.getAddress().toString()
}
})
var hmap = {}
outputs.forEach(function(o) {
hmap[o.output] = o
})
for (var i = 0; i < this.ins.length; i++) {
var outpoint = this.ins[i].outpoint.hash + ':' + this.ins[i].outpoint.index
var histItem = hmap[outpoint]
if (!histItem) continue;
var thisInputAddrdata = addrdata.filter(function(a) {
return a.address == histItem.address
})
if (thisInputAddrdata.length === 0) continue;
this.sign(i,thisInputAddrdata[0].key)
}
}
Transaction.prototype.signScriptSig = function(index, script, key, type) {
type = type || SIGHASH_ALL
@ -458,14 +414,8 @@ var TransactionIn = function (data) {
this.outpoint = { hash: data.hash, index: data.index }
}
if (data.scriptSig) {
this.script = Script.fromScriptSig(data.scriptSig)
} else if (data.script) {
assert(data.script, 'Invalid TxIn parameters')
this.script = data.script
} else {
this.script = new Script(data.script)
}
this.sequence = data.sequence || this.defaultSequence
}
@ -480,38 +430,20 @@ TransactionIn.prototype.clone = function () {
})
}
// FIXME: Support for alternate networks
var TransactionOut = function (data) {
this.script =
data.script instanceof Script ? data.script.clone()
: Array.isArray(data.script) ? new Script(data.script)
: typeof data.script == "string" ? new Script(convert.hexToBytes(data.script))
: data.scriptPubKey ? Script.fromScriptSig(data.scriptPubKey)
: data.address ? Script.createOutputScript(data.address)
: new Script()
var network = data.network || Network.bitcoin
if (this.script.buffer.length > 0) {
this.address = this.script.getToAddress(network)
}
function TransactionOut(data) {
this.script = data.script
this.value = data.value
this.address = data.address
this.value =
Array.isArray(data.value) ? convert.bytesToNum(data.value)
: "string" == typeof data.value ? parseInt(data.value)
: data.value instanceof BigInteger ? parseInt(data.value.toString())
: data.value
if (data.address) this.address = data.address
}
TransactionOut.prototype.clone = function() {
var newTxout = new TransactionOut({
return new TransactionOut({
script: this.script.clone(),
value: this.value
value: this.value,
address: this.address
})
return newTxout
}
TransactionOut.prototype.scriptPubKey = function() {
return convert.bytesToHex(this.script.buffer)
}
module.exports = {

28
src/wallet.js

@ -1,8 +1,9 @@
var Address = require('./address')
var convert = require('./convert')
var Transaction = require('./transaction').Transaction
var HDNode = require('./hdwallet.js')
var networks = require('./networks')
var rng = require('secure-random')
var Network = require('./network')
var Transaction = require('./transaction').Transaction
function Wallet(seed, options) {
if (!(this instanceof Wallet)) { return new Wallet(seed, options); }
@ -42,7 +43,6 @@ function Wallet(seed, options) {
}
this.newMasterKey(seed, network)
this.generateAddress = function() {
var key = externalAccount.derive(this.addresses.length)
this.addresses.push(key.getAddress().toString())
@ -150,9 +150,17 @@ function Wallet(seed, options) {
var txhash = tx.getHash()
tx.outs.forEach(function(txOut, i){
var address = txOut.address.toString()
var address
try {
address = Address.fromScriptPubKey(txOut.script, networks[network]).toString()
} catch(e) {
if (!(e instanceof Address.Error)) throw e
}
if (isMyAddress(address)) {
var output = txhash+':'+i
var output = txhash + ':' + i
me.outputs[output] = {
receive: output,
value: txOut.value,
@ -165,7 +173,7 @@ function Wallet(seed, options) {
var op = txIn.outpoint
var o = me.outputs[op.hash+':'+op.index]
if (o) {
o.spend = txhash+':'+i
o.spend = txhash + ':' +i
}
})
}
@ -174,7 +182,7 @@ function Wallet(seed, options) {
checkDust(value)
var tx = new Transaction()
tx.addOutput(to, value, Network[network])
tx.addOutput(to, value)
var utxo = getCandidateOutputs(value)
var totalInValue = 0
@ -190,7 +198,7 @@ function Wallet(seed, options) {
var change = totalInValue - value - fee
if(change > 0 && !isDust(change)) {
tx.addOutput(changeAddress || getChangeAddress(), change, Network[network])
tx.addOutput(changeAddress || getChangeAddress(), change)
}
break
}
@ -246,7 +254,7 @@ function Wallet(seed, options) {
function estimateFeePadChangeOutput(tx){
var tmpTx = tx.clone()
tmpTx.addOutput(getChangeAddress(), 0, Network[network])
tmpTx.addOutput(getChangeAddress(), 0)
return tmpTx.estimateFee()
}
@ -266,7 +274,7 @@ function Wallet(seed, options) {
tx.ins.forEach(function(inp,i) {
var output = me.outputs[inp.outpoint.hash + ':' + inp.outpoint.index]
if (output) {
tx.sign(i, me.getPrivateKeyForAddress(output.address), false, Network[network])
tx.sign(i, me.getPrivateKeyForAddress(output.address), false)
}
})
return tx

49
test/address.js

@ -1,15 +1,17 @@
var assert = require('assert')
var Address = require('..').Address
var networks = require('..').networks
var Script = require('..').Script
var b58fixtures = require('./fixtures/base58')
var fixtures = require('./fixtures/address')
describe('Address', function() {
var bothVectors = fixtures.pubKeyHash.concat(fixtures.scriptHash)
function h2b(h) { return new Buffer(h, 'hex') }
describe('Address', function() {
describe('Constructor', function() {
it('does not mutate the input', function() {
bothVectors.forEach(function(f) {
fixtures.valid.forEach(function(f) {
var hash = new Buffer(f.hex, 'hex')
var addr = new Address(hash, f.version)
@ -28,8 +30,8 @@ describe('Address', function() {
})
})
bothVectors.forEach(function(f) {
it('imports ' + f.description + ' correctly', function() {
fixtures.valid.forEach(function(f) {
it('imports ' + f.description + '(' + f.network + ') correctly', function() {
var addr = Address.fromBase58Check(f.base58check)
assert.equal(addr.version, f.version)
@ -38,9 +40,21 @@ describe('Address', function() {
})
})
describe('fromScriptPubKey', function() {
fixtures.valid.forEach(function(f) {
it('imports ' + f.description + '(' + f.network + ') correctly', function() {
var script = Script.fromHex(f.script)
var addr = Address.fromScriptPubKey(script, networks[f.network])
assert.equal(addr.version, f.version)
assert.equal(addr.hash.toString('hex'), f.hex)
})
})
})
describe('toBase58Check', function() {
bothVectors.forEach(function(f) {
it('exports ' + f.description + ' correctly', function() {
fixtures.valid.forEach(function(f) {
it('exports ' + f.description + '(' + f.network + ') correctly', function() {
var addr = Address.fromBase58Check(f.base58check)
var result = addr.toBase58Check()
@ -48,4 +62,25 @@ describe('Address', function() {
})
})
})
describe('toScriptPubKey', function() {
fixtures.valid.forEach(function(f) {
it('imports ' + f.description + '(' + f.network + ') correctly', function() {
var addr = Address.fromBase58Check(f.base58check)
var script = addr.toScriptPubKey()
assert.equal(script.toHex(), f.script)
})
})
fixtures.invalid.toScriptPubKey.forEach(function(f) {
it('throws on ' + f.description, function() {
var addr = new Address(h2b(f.hex), f.version)
assert.throws(function() {
addr.toScriptPubKey()
})
})
})
})
})

39
test/fixtures/address.js

@ -1,30 +1,45 @@
module.exports = {
pubKeyHash: [
valid: [
{
description: 'pubKeyHash (bitcoin)',
description: 'pubKeyHash',
network: 'bitcoin',
version: 0,
hex: '751e76e8199196d454941c45d1b3a323f1433bd6',
base58check: '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH'
base58check: '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH',
script: '76a914751e76e8199196d454941c45d1b3a323f1433bd688ac'
},
{
description: 'pubKeyHash (testnet)',
description: 'pubKeyHash',
network: 'testnet',
version: 111,
hex: '751e76e8199196d454941c45d1b3a323f1433bd6',
base58check: 'mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r'
}
],
scriptHash: [
base58check: 'mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r',
script: '76a914751e76e8199196d454941c45d1b3a323f1433bd688ac'
},
{
description: 'scriptHash (bitcoin)',
description: 'scriptHash',
network: 'bitcoin',
version: 5,
hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6',
base58check: '3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr'
base58check: '3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr',
script: 'a914cd7b44d0b03f2d026d1e586d7ae18903b0d385f687'
},
{
description: 'scriptHash (testnet)',
description: 'scriptHash',
network: 'testnet',
version: 196,
hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6',
base58check: '2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w'
base58check: '2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w',
script: 'a914cd7b44d0b03f2d026d1e586d7ae18903b0d385f687'
}
],
invalid: {
toScriptPubKey: [
{
description: 'Unknown Address version',
version: 0x99,
hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6'
}
]
}
}

54
test/fixtures/script.js

@ -0,0 +1,54 @@
module.exports = {
valid: [
{
description: 'P2SH ScriptPubKey',
hex: 'a914e8c300c87986efa84c37c0519929019ef86eb5b487',
type: 'scripthash',
hash: '0ba47b56a573bab4b430ad6ed3ec79270e04b066',
scriptPubKey: true
},
{
description: 'PubKeyHash ScriptPubKey',
hex: '76a9145a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a888ac',
type: 'pubkeyhash',
hash: 'a5313f33d5c7b81674b35f7f3febc3522ef234db',
scriptPubKey: true
},
{
description: 'pubKeyHash scriptSig',
hex: '48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f30141040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8',
type: 'pubkeyhash',
hash: 'b9bac2a5c5c29bb27c382d41fa3d179c646c78fd',
scriptPubKey: false
},
{
description: 'Valid multisig script',
hex: '5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae',
type: 'multisig',
hash: 'f1c98f0b74ecabcf78ae20dfa224bb6666051fbe',
scriptPubKey: true
},
{
description: 'OP_RETURN script',
hex:'6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474',
type: 'nulldata',
hash: 'ec88f016655477663455fe6a8e83508c348ea145',
scriptPubKey: true
},
{
description: 'Non standard script',
hex: 'aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087',
type: 'nonstandard',
hash: '3823382e70d1930989813d3459988e0d7c2861d8',
scriptPubKey: true
},
{
description: 'Invalid multisig script',
asm: '0 0 0 OP_CHECKmulTISIG',
hex: '000000ae',
type: 'nonstandard',
hash: '62ede8963f9387544935f168745262f703dab1fb',
scriptPubKey: true
}
]
}

6
test/integration/p2sh.js

@ -5,7 +5,7 @@ var ECKey = require('../../src/eckey').ECKey;
var T = require('../../src/transaction');
var Transaction = T.Transaction;
var Script = require('../../src/script');
var network = require('../../src/network');
var networks = require('../../src/networks');
var crypto = require('../../src/crypto');
var helloblock = require('helloblock-js')({
@ -31,7 +31,7 @@ describe('p2sh', function() {
})
var redeemScript = Script.createMultisigOutputScript(2, pubKeyBuffers)
var hash160 = crypto.hash160(redeemScript.buffer)
var multisigAddress = new Address(hash160, network.testnet.scriptHash)
var multisigAddress = new Address(hash160, networks.testnet.scriptHash)
// Check what our target address's starting value is
var targetAddress = 'mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r';
@ -50,7 +50,7 @@ describe('p2sh', function() {
var tx = new Transaction()
var unspent = resource[0];
tx.addInput(unspent.txHash, unspent.index)
tx.addOutput(targetAddress, 100000, network.testnet)
tx.addOutput(targetAddress, 100000, networks.testnet)
var signatures = privKeys.map(function(privKey) {
return tx.signScriptSig(0, redeemScript, privKey)

4
test/message.js

@ -1,7 +1,7 @@
var assert = require('assert')
var ECKey = require('../src/eckey').ECKey
var Message = require('../').Message
var network = require('../').network
var networks = require('../').networks
var fixtures = require('./fixtures/message')
@ -86,7 +86,7 @@ describe('Message', function() {
var key = ECKey.makeRandom()
var sig = Message.sign(key, msg)
var addr = key.pub.getAddress(network.testnet.pubKeyHash)
var addr = key.pub.getAddress(networks.testnet.pubKeyHash)
assert(Message.verify(addr, sig, msg))
})
})

124
test/script.js

@ -1,34 +1,16 @@
var assert = require('assert')
var crypto = require('..').crypto
var network = require('..').network
var networks = require('..').networks
var Address = require('../src/address.js')
var Script = require('../src/script.js')
var fixtures = require('./fixtures/script')
function b2h(b) { return new Buffer(b).toString('hex') }
function h2b(h) { return new Buffer(h, 'hex') }
describe('Script', function() {
var p2shScriptPubKey, pubkeyScriptPubkey, addressScriptSig
beforeEach(function(){
p2shScriptPubKey = "a914e8c300c87986efa84c37c0519929019ef86eb5b487"
pubkeyScriptPubKey = "76a9145a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a888ac"
addressScriptSig = "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f30141040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8"
// txid: 09dd94f2c85262173da87a745a459007bb1eed6eeb6bfa238a0cd91a16cf7790
validMultisigScript = '5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae'
// txid: 5e9be7fb36ee49ce84bee4c8ef38ad0efc0608b78dae1c2c99075297ef527890
opreturnScript = '6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474'
// asm: "0 0 0 OP_CHECKMULTISIG"
invalidMultisigScript = '000000ae'
// txid: a4bfa8ab6435ae5f25dae9d89e4eb67dfa94283ca751f393c1ddc5a837bbc31b
nonStandardScript = 'aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087'
})
describe('constructor', function() {
it('works for a byte array', function() {
assert.ok(new Script([]))
@ -43,65 +25,69 @@ describe('Script', function() {
})
})
describe('getOutType', function() {
it('supports p2sh', function() {
var script = Script.fromHex(p2shScriptPubKey)
assert.equal(script.getOutType(), 'scripthash')
describe('fromHex/toHex', function() {
fixtures.valid.forEach(function(f) {
it('decodes/encodes ' + f.description, function() {
assert.equal(Script.fromHex(f.hex).toHex(), f.hex)
})
it('supports pubkeyhash', function() {
var script = Script.fromHex(pubkeyScriptPubKey)
assert.equal(script.getOutType(), 'pubkeyhash')
})
it('supports multisig', function() {
var script = Script.fromHex(validMultisigScript)
assert.equal(script.getOutType(), 'multisig')
})
it('supports null_data', function() {
var script = Script.fromHex(opreturnScript)
assert.equal(script.getOutType(), 'nulldata')
})
describe('getHash', function() {
it('matches the test vectors', function() {
fixtures.valid.forEach(function(f) {
var script = Script.fromHex(f.hex)
it('supports nonstandard script', function() {
var script = Script.fromHex(nonStandardScript)
assert.equal(script.getOutType(), 'nonstandard')
assert.equal(script.getHash().toString('hex'), f.hash)
})
it('identifies invalid multisig script as nonstandard', function() {
var script = Script.fromHex(invalidMultisigScript)
assert.equal(script.getOutType(), 'nonstandard')
})
})
describe('getInType', function() {
it('works for address', function() {
var script = Script.fromHex(addressScriptSig)
assert.equal(script.getInType(), 'pubkeyhash')
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('getToAddress', function() {
it('works for p2sh type output', function() {
var script = Script.fromHex(p2shScriptPubKey)
assert.equal(script.getToAddress().toString(), '3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8')
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() {
var address = Address.fromBase58Check('19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu')
var script = Script.createPubKeyHashScriptPubKey(address.hash)
it('works for pubkey type output', function() {
var script = Script.fromHex(pubkeyScriptPubKey)
assert.equal(script.getToAddress().toString(), '19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu')
// FIXME: not good TDD
assert.equal(script.toHex(), fixtures.valid[1].hex)
})
})
describe('getFromAddress', function() {
it('works for address type input', function() {
var script = Script.fromHex(addressScriptSig)
assert.equal(script.getFromAddress().toString(), '1BBjuhF2jHxu7tPinyQGCuaNhEs6f5u59u')
describe('pay-to-scriptHash', function() {
it('matches the test data', function() {
var hash = new Buffer('e8c300c87986efa84c37c0519929019ef86eb5b4', 'hex')
var script = Script.createP2SHScriptPubKey(hash)
// FIXME: not good TDD
assert.equal(script.toHex(), fixtures.valid[0].hex)
})
})
describe('2-of-3 Multi-Signature', function() {
describe('2-of-3 Multi-Signature scriptPubKey', function() {
var pubKeys
beforeEach(function() {
@ -113,10 +99,10 @@ describe('Script', function() {
})
it('should create valid redeemScript', function() {
var redeemScript = Script.createMultisigOutputScript(2, pubKeys)
var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys)
var hash160 = crypto.hash160(redeemScript.buffer)
var multisigAddress = new Address(hash160, network.bitcoin.scriptHash)
var multisigAddress = new Address(hash160, networks.bitcoin.scriptHash)
assert.equal(multisigAddress.toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v')
})
@ -134,10 +120,20 @@ describe('Script', function() {
var expected = '0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae'
it('should create a valid P2SH multisig scriptSig', function() {
var redeemScript = Script.createMultisigOutputScript(2, pubKeys)
var actual = Script.createP2SHMultisigScriptSig(signatures, redeemScript)
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.equal(b2h(actual.buffer), expected)
assert.throws(function() {
Script.createMultisigScriptSig(signatures.slice(1), redeemScript)
})
})
})
})

27
test/transaction.js

@ -2,11 +2,11 @@ var assert = require('assert')
var Address = require('../src/address')
var ECKey = require('../src/eckey').ECKey
var networks = require('..').networks
var T = require('../src/transaction')
var Transaction = T.Transaction
var TransactionOut = T.TransactionOut
var Script = require('../src/script')
var network = require('..').network
var fixtureTxes = require('./fixtures/mainnet_tx')
var fixtureTx1Hex = fixtureTxes.prevTx
@ -72,9 +72,7 @@ describe('Transaction', function() {
var output = tx.outs[0]
assert.equal(output.value, 5000000000)
assert.equal(b2h(output.script.toScriptHash()), "dd40dedd8f7e37466624c4dacc6362d8e7be23dd")
// assert.equal(output.address.toString(), "n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9")
// TODO: address is wrong because it's a testnet transaction. Transaction needs to support testnet
assert.deepEqual(output.script, Address.fromBase58Check('n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9').toScriptPubKey())
})
it('assigns hash to deserialized object', function(){
@ -176,7 +174,7 @@ describe('Transaction', function() {
it('supports alternative networks', function(){
var addr = 'mkHJaNR7uuwRG1JrmTZsV4MszaTKjCBvCR'
tx.addOutput(addr, 40000, network.testnet)
tx.addOutput(addr, 40000)
verifyTransactionOut()
assert.equal(tx.outs[0].address.toString(), addr)
@ -250,7 +248,7 @@ describe('Transaction', function() {
it('works for multi-sig redeem script', function() {
var tx = new Transaction()
tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0)
tx.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1, network.testnet)
tx.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1)
var privKeys = [
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf',
@ -260,13 +258,14 @@ describe('Transaction', function() {
})
var pubKeys = privKeys.map(function(eck) { return eck.pub })
var pubKeyBuffers = pubKeys.map(function(q) { return q.toBuffer() })
var redeemScript = Script.createMultisigOutputScript(2, pubKeyBuffers)
var redeemScript = Script.createMultisigScriptPubKey(2, pubKeyBuffers)
var signatures = privKeys.map(function(privKey) {
return tx.signScriptSig(0, redeemScript, privKey)
})
var scriptSig = Script.createP2SHMultisigScriptSig(signatures, redeemScript)
var redeemScriptSig = Script.createMultisigScriptSig(signatures)
var scriptSig = Script.createP2SHScriptSig(redeemScriptSig, redeemScript)
tx.setScriptSig(0, scriptSig)
signatures.forEach(function(sig, i){
@ -279,18 +278,6 @@ describe('Transaction', function() {
})
describe('TransactionOut', function() {
describe('scriptPubKey', function() {
it('returns hex string', function() {
var address = Address.fromBase58Check("1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv")
var txOut = new TransactionOut({
value: 50000,
script: Script.createOutputScript(address)
})
assert.equal(txOut.scriptPubKey(), "76a91468edf28474ee22f68dfe7e56e76c017c1701b84f88ac")
})
})
})
})

27
test/wallet.js

@ -1,12 +1,14 @@
var Wallet = require('../src/wallet.js')
var assert = require('assert')
var crypto = require('../').crypto
var sinon = require('sinon')
var Address = require('..').Address
var HDNode = require('../src/hdwallet.js')
var T = require('../src/transaction.js')
var Transaction = T.Transaction
var TransactionOut = T.TransactionOut
var Script = require('../src/script.js')
var assert = require('assert')
var sinon = require('sinon')
var crypto = require('../').crypto
var Wallet = require('../src/wallet.js')
var fixtureTxes = require('./fixtures/mainnet_tx')
var fixtureTx1Hex = fixtureTxes.prevTx
@ -308,17 +310,24 @@ describe('Wallet', function() {
})
describe('processTx', function(){
var addresses
var tx
beforeEach(function(){
addresses = [
'115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi',
'1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd',
'1BBjuhF2jHxu7tPinyQGCuaNhEs6f5u59u'
]
tx = Transaction.deserialize(fixtureTx1Hex)
})
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){
it("works for receive address", function(){
var totalOuts = outputCount()
wallet.addresses = [tx.outs[0].address.toString()]
wallet.addresses = [addresses[0]]
wallet.processTx(tx)
assert.equal(outputCount(), totalOuts + 1)
@ -327,7 +336,7 @@ describe('Wallet', function() {
it("works for change address", function(){
var totalOuts = outputCount()
wallet.changeAddresses = [tx.outs[1].address.toString()]
wallet.changeAddresses = [addresses[1]]
wallet.processTx(tx)
@ -345,13 +354,15 @@ describe('Wallet', function() {
var output = wallet.outputs[key]
assert.equal(output.receive, key)
assert.equal(output.value, txOut.value)
assert.equal(output.address, txOut.address)
var txOutAddress = Address.fromScriptPubKey(txOut.script).toString()
assert.equal(output.address, txOutAddress)
}
})
describe("when tx ins outpoint contains a known txhash:i, the corresponding 'output' gets updated", function(){
beforeEach(function(){
wallet.addresses = [tx.outs[0].address.toString()] // the address fixtureTx2 used as input
wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input
wallet.processTx(tx)
tx = Transaction.deserialize(fixtureTx2Hex)

Loading…
Cancel
Save