Transaction/Script: bitcoin network no longer implied
A Transaction (and its subsequent scripts) do not carry any network specific information in the Bitcoin protocol. Therefore they can not (without further context) produce the network specific constants for the generation of the base58 Addresses. As TransactionOut.address is used heavily throughout Wallet and other areas of the library, this could not be entirely removed without a large number of changes. For now, TransactionOut.address is only defined in the case of Tx.addOutput being used directly: Transaction.addOutput(address, value)
This commit is contained in:
parent
4207a0df99
commit
708aa03390
6 changed files with 47 additions and 142 deletions
|
@ -1,8 +1,6 @@
|
||||||
var assert = require('assert')
|
|
||||||
var Address = require('./address')
|
var Address = require('./address')
|
||||||
|
var assert = require('assert')
|
||||||
var crypto = require('./crypto')
|
var crypto = require('./crypto')
|
||||||
var convert = require('./convert')
|
|
||||||
var networks = require('./networks')
|
|
||||||
var Opcode = require('./opcode')
|
var Opcode = require('./opcode')
|
||||||
|
|
||||||
function Script(data) {
|
function Script(data) {
|
||||||
|
@ -202,18 +200,6 @@ Script.prototype.toScriptHash = function() {
|
||||||
return crypto.hash160(this.buffer)
|
return crypto.hash160(this.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
Script.prototype.getToAddress = function(network) {
|
|
||||||
network = network || networks.bitcoin
|
|
||||||
|
|
||||||
return Address.fromScriptPubKey(this, network)
|
|
||||||
}
|
|
||||||
|
|
||||||
Script.prototype.getFromAddress = function(version) {
|
|
||||||
version = version || networks.bitcoin.pubKeyHash
|
|
||||||
|
|
||||||
return new Address(this.simpleInHash(), version)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare the script to known templates of scriptSig.
|
* Compare the script to known templates of scriptSig.
|
||||||
*
|
*
|
||||||
|
@ -259,58 +245,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.
|
* Add an op code to the script.
|
||||||
*/
|
*/
|
||||||
|
@ -347,22 +281,6 @@ Script.prototype.writeBytes = function(data) {
|
||||||
this.chunks.push(data)
|
this.chunks.push(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an output for an address
|
|
||||||
*/
|
|
||||||
Script.createScriptPubKey = function(address, network) {
|
|
||||||
assert(address instanceof Address)
|
|
||||||
network = network || networks.bitcoin
|
|
||||||
|
|
||||||
if (address.version === network.pubKeyHash) {
|
|
||||||
return Script.createPubKeyHashScriptPubKey(address.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.strictEqual(address.version, network.scriptHash, 'Unknown address type')
|
|
||||||
|
|
||||||
return Script.createP2SHScriptPubKey(address.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
||||||
Script.createPubKeyHashScriptPubKey = function(hash) {
|
Script.createPubKeyHashScriptPubKey = function(hash) {
|
||||||
var script = new Script()
|
var script = new Script()
|
||||||
|
|
|
@ -9,7 +9,6 @@ var convert = require('./convert')
|
||||||
var crypto = require('./crypto')
|
var crypto = require('./crypto')
|
||||||
var ECKey = require('./eckey').ECKey
|
var ECKey = require('./eckey').ECKey
|
||||||
var ecdsa = require('./ecdsa')
|
var ecdsa = require('./ecdsa')
|
||||||
var networks = require('./networks')
|
|
||||||
|
|
||||||
var Transaction = function (doc) {
|
var Transaction = function (doc) {
|
||||||
if (!(this instanceof Transaction)) { return new Transaction(doc) }
|
if (!(this instanceof Transaction)) { return new Transaction(doc) }
|
||||||
|
@ -90,11 +89,10 @@ Transaction.prototype.addInput = function (tx, outIndex) {
|
||||||
* i) An existing TransactionOut object
|
* i) An existing TransactionOut object
|
||||||
* ii) An address object or a string address, and a value
|
* ii) An address object or a string address, and a value
|
||||||
* iii) An address:value string
|
* iii) An address:value string
|
||||||
* iv) Either ii), iii) with an optional network argument
|
|
||||||
*
|
*
|
||||||
* FIXME: This is a bit convoluted
|
* FIXME: This is a bit convoluted
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.addOutput = function (address, value, network) {
|
Transaction.prototype.addOutput = function (address, value) {
|
||||||
if (arguments[0] instanceof TransactionOut) {
|
if (arguments[0] instanceof TransactionOut) {
|
||||||
this.outs.push(arguments[0])
|
this.outs.push(arguments[0])
|
||||||
return
|
return
|
||||||
|
@ -105,19 +103,15 @@ Transaction.prototype.addOutput = function (address, value, network) {
|
||||||
var args = arguments[0].split(':')
|
var args = arguments[0].split(':')
|
||||||
address = args[0]
|
address = args[0]
|
||||||
value = parseInt(args[1])
|
value = parseInt(args[1])
|
||||||
|
|
||||||
network = arguments[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
address = Address.fromBase58Check(address)
|
address = Address.fromBase58Check(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
network = network || networks.bitcoin
|
|
||||||
|
|
||||||
this.outs.push(new TransactionOut({
|
this.outs.push(new TransactionOut({
|
||||||
value: value,
|
value: value,
|
||||||
script: Script.createScriptPubKey(address, network),
|
script: address.toScriptPubKey(),
|
||||||
network: network
|
address: address // TODO: Remove me
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,18 +358,14 @@ Transaction.deserialize = function(buffer) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs a standard output at some index with the given key
|
* 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)
|
assert(key instanceof ECKey)
|
||||||
network = network || networks.bitcoin
|
|
||||||
|
|
||||||
var address = key.pub.getAddress(network.pubKeyHash)
|
var script = key.pub.getAddress().toScriptPubKey()
|
||||||
|
|
||||||
// FIXME: Assumed prior TX was pay-to-pubkey-hash
|
|
||||||
var script = Script.createScriptPubKey(address, network)
|
|
||||||
var signature = this.signScriptSig(index, script, key, type)
|
var signature = this.signScriptSig(index, script, key, type)
|
||||||
|
|
||||||
|
// FIXME: Assumed prior TX was pay-to-pubkey-hash
|
||||||
var scriptSig = Script.createPubKeyHashScriptSig(signature, key.pub)
|
var scriptSig = Script.createPubKeyHashScriptSig(signature, key.pub)
|
||||||
this.setScriptSig(index, scriptSig)
|
this.setScriptSig(index, scriptSig)
|
||||||
}
|
}
|
||||||
|
@ -445,10 +435,7 @@ function TransactionOut(data) {
|
||||||
this.value = data.value
|
this.value = data.value
|
||||||
this.address = data.address
|
this.address = data.address
|
||||||
|
|
||||||
var network = data.network || networks.bitcoin
|
if (data.address) this.address = data.address
|
||||||
if (this.script.buffer.length > 0) {
|
|
||||||
this.address = this.script.getToAddress(network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionOut.prototype.clone = function() {
|
TransactionOut.prototype.clone = function() {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
var Address = require('./address')
|
||||||
var convert = require('./convert')
|
var convert = require('./convert')
|
||||||
var Transaction = require('./transaction').Transaction
|
|
||||||
var HDNode = require('./hdwallet.js')
|
var HDNode = require('./hdwallet.js')
|
||||||
var rng = require('secure-random')
|
|
||||||
var networks = require('./networks')
|
var networks = require('./networks')
|
||||||
|
var rng = require('secure-random')
|
||||||
|
var Transaction = require('./transaction').Transaction
|
||||||
|
|
||||||
function Wallet(seed, options) {
|
function Wallet(seed, options) {
|
||||||
if (!(this instanceof Wallet)) { return new 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.newMasterKey(seed, network)
|
||||||
|
|
||||||
|
|
||||||
this.generateAddress = function() {
|
this.generateAddress = function() {
|
||||||
var key = externalAccount.derive(this.addresses.length)
|
var key = externalAccount.derive(this.addresses.length)
|
||||||
this.addresses.push(key.getAddress().toString())
|
this.addresses.push(key.getAddress().toString())
|
||||||
|
@ -150,9 +150,17 @@ function Wallet(seed, options) {
|
||||||
var txhash = tx.getHash()
|
var txhash = tx.getHash()
|
||||||
|
|
||||||
tx.outs.forEach(function(txOut, i){
|
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)) {
|
if (isMyAddress(address)) {
|
||||||
var output = txhash+':'+i
|
var output = txhash + ':' + i
|
||||||
|
|
||||||
me.outputs[output] = {
|
me.outputs[output] = {
|
||||||
receive: output,
|
receive: output,
|
||||||
value: txOut.value,
|
value: txOut.value,
|
||||||
|
@ -165,7 +173,7 @@ function Wallet(seed, options) {
|
||||||
var op = txIn.outpoint
|
var op = txIn.outpoint
|
||||||
var o = me.outputs[op.hash+':'+op.index]
|
var o = me.outputs[op.hash+':'+op.index]
|
||||||
if (o) {
|
if (o) {
|
||||||
o.spend = txhash+':'+i
|
o.spend = txhash + ':' +i
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -174,7 +182,7 @@ function Wallet(seed, options) {
|
||||||
checkDust(value)
|
checkDust(value)
|
||||||
|
|
||||||
var tx = new Transaction()
|
var tx = new Transaction()
|
||||||
tx.addOutput(to, value, networks[network])
|
tx.addOutput(to, value)
|
||||||
|
|
||||||
var utxo = getCandidateOutputs(value)
|
var utxo = getCandidateOutputs(value)
|
||||||
var totalInValue = 0
|
var totalInValue = 0
|
||||||
|
@ -190,7 +198,7 @@ function Wallet(seed, options) {
|
||||||
|
|
||||||
var change = totalInValue - value - fee
|
var change = totalInValue - value - fee
|
||||||
if(change > 0 && !isDust(change)) {
|
if(change > 0 && !isDust(change)) {
|
||||||
tx.addOutput(changeAddress || getChangeAddress(), change, networks[network])
|
tx.addOutput(changeAddress || getChangeAddress(), change)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -246,7 +254,7 @@ function Wallet(seed, options) {
|
||||||
|
|
||||||
function estimateFeePadChangeOutput(tx){
|
function estimateFeePadChangeOutput(tx){
|
||||||
var tmpTx = tx.clone()
|
var tmpTx = tx.clone()
|
||||||
tmpTx.addOutput(getChangeAddress(), 0, networks[network])
|
tmpTx.addOutput(getChangeAddress(), 0)
|
||||||
return tmpTx.estimateFee()
|
return tmpTx.estimateFee()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +274,7 @@ function Wallet(seed, options) {
|
||||||
tx.ins.forEach(function(inp,i) {
|
tx.ins.forEach(function(inp,i) {
|
||||||
var output = me.outputs[inp.outpoint.hash + ':' + inp.outpoint.index]
|
var output = me.outputs[inp.outpoint.hash + ':' + inp.outpoint.index]
|
||||||
if (output) {
|
if (output) {
|
||||||
tx.sign(i, me.getPrivateKeyForAddress(output.address), false, networks[network])
|
tx.sign(i, me.getPrivateKeyForAddress(output.address), false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return tx
|
return tx
|
||||||
|
|
|
@ -116,25 +116,6 @@ describe('Script', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getToAddress', function() {
|
|
||||||
it('works for p2sh type output', function() {
|
|
||||||
var script = Script.fromHex(p2shScriptPubKey)
|
|
||||||
assert.equal(script.getToAddress().toString(), '3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('works for pubkey type output', function() {
|
|
||||||
var script = Script.fromHex(pubKeyScriptPubKey)
|
|
||||||
assert.equal(script.getToAddress().toString(), '19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getFromAddress', function() {
|
|
||||||
it('works for address type input', function() {
|
|
||||||
var script = Script.fromHex(addressScriptSig)
|
|
||||||
assert.equal(script.getFromAddress().toString(), '1BBjuhF2jHxu7tPinyQGCuaNhEs6f5u59u')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('2-of-3 Multi-Signature', function() {
|
describe('2-of-3 Multi-Signature', function() {
|
||||||
var pubKeys
|
var pubKeys
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,7 @@ describe('Transaction', function() {
|
||||||
it('supports alternative networks', function(){
|
it('supports alternative networks', function(){
|
||||||
var addr = 'mkHJaNR7uuwRG1JrmTZsV4MszaTKjCBvCR'
|
var addr = 'mkHJaNR7uuwRG1JrmTZsV4MszaTKjCBvCR'
|
||||||
|
|
||||||
tx.addOutput(addr, 40000, networks.testnet)
|
tx.addOutput(addr, 40000)
|
||||||
verifyTransactionOut()
|
verifyTransactionOut()
|
||||||
|
|
||||||
assert.equal(tx.outs[0].address.toString(), addr)
|
assert.equal(tx.outs[0].address.toString(), addr)
|
||||||
|
@ -250,7 +250,7 @@ describe('Transaction', 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)
|
||||||
tx.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1, networks.testnet)
|
tx.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1)
|
||||||
|
|
||||||
var privKeys = [
|
var privKeys = [
|
||||||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf',
|
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf',
|
||||||
|
|
|
@ -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 HDNode = require('../src/hdwallet.js')
|
||||||
var T = require('../src/transaction.js')
|
var T = require('../src/transaction.js')
|
||||||
var Transaction = T.Transaction
|
var Transaction = T.Transaction
|
||||||
var TransactionOut = T.TransactionOut
|
var TransactionOut = T.TransactionOut
|
||||||
var Script = require('../src/script.js')
|
var Script = require('../src/script.js')
|
||||||
var assert = require('assert')
|
var Wallet = require('../src/wallet.js')
|
||||||
var sinon = require('sinon')
|
|
||||||
var crypto = require('../').crypto
|
|
||||||
|
|
||||||
var fixtureTxes = require('./fixtures/mainnet_tx')
|
var fixtureTxes = require('./fixtures/mainnet_tx')
|
||||||
var fixtureTx1Hex = fixtureTxes.prevTx
|
var fixtureTx1Hex = fixtureTxes.prevTx
|
||||||
|
@ -308,17 +310,24 @@ describe('Wallet', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('processTx', function(){
|
describe('processTx', function(){
|
||||||
|
var addresses
|
||||||
var tx
|
var tx
|
||||||
|
|
||||||
beforeEach(function(){
|
beforeEach(function(){
|
||||||
|
addresses = [
|
||||||
|
'115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi',
|
||||||
|
'1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd',
|
||||||
|
'1BBjuhF2jHxu7tPinyQGCuaNhEs6f5u59u'
|
||||||
|
]
|
||||||
|
|
||||||
tx = Transaction.deserialize(fixtureTx1Hex)
|
tx = Transaction.deserialize(fixtureTx1Hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){
|
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(){
|
it("works for receive address", function(){
|
||||||
var totalOuts = outputCount()
|
var totalOuts = outputCount()
|
||||||
wallet.addresses = [tx.outs[0].address.toString()]
|
|
||||||
|
|
||||||
|
wallet.addresses = [addresses[0]]
|
||||||
wallet.processTx(tx)
|
wallet.processTx(tx)
|
||||||
|
|
||||||
assert.equal(outputCount(), totalOuts + 1)
|
assert.equal(outputCount(), totalOuts + 1)
|
||||||
|
@ -327,7 +336,7 @@ describe('Wallet', function() {
|
||||||
|
|
||||||
it("works for change address", function(){
|
it("works for change address", function(){
|
||||||
var totalOuts = outputCount()
|
var totalOuts = outputCount()
|
||||||
wallet.changeAddresses = [tx.outs[1].address.toString()]
|
wallet.changeAddresses = [addresses[1]]
|
||||||
|
|
||||||
wallet.processTx(tx)
|
wallet.processTx(tx)
|
||||||
|
|
||||||
|
@ -345,13 +354,15 @@ describe('Wallet', function() {
|
||||||
var output = wallet.outputs[key]
|
var output = wallet.outputs[key]
|
||||||
assert.equal(output.receive, key)
|
assert.equal(output.receive, key)
|
||||||
assert.equal(output.value, txOut.value)
|
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(){
|
describe("when tx ins outpoint contains a known txhash:i, the corresponding 'output' gets updated", function(){
|
||||||
beforeEach(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)
|
wallet.processTx(tx)
|
||||||
|
|
||||||
tx = Transaction.deserialize(fixtureTx2Hex)
|
tx = Transaction.deserialize(fixtureTx2Hex)
|
||||||
|
|
Loading…
Add table
Reference in a new issue