|
|
@ -2,7 +2,6 @@ var BigInteger = require('./jsbn/jsbn'); |
|
|
|
var Script = require('./script'); |
|
|
|
var util = require('./util'); |
|
|
|
var convert = require('./convert'); |
|
|
|
var Wallet = require('./wallet'); |
|
|
|
var ECKey = require('./eckey').ECKey; |
|
|
|
var ECDSA = require('./ecdsa'); |
|
|
|
var Address = require('./address'); |
|
|
@ -15,8 +14,7 @@ var Transaction = function (doc) { |
|
|
|
this.locktime = 0; |
|
|
|
this.ins = []; |
|
|
|
this.outs = []; |
|
|
|
this.timestamp = null; |
|
|
|
this.block = null; |
|
|
|
this.defaultSequence = [255, 255, 255, 255] // 0xFFFFFFFF
|
|
|
|
|
|
|
|
if (doc) { |
|
|
|
if (typeof doc == "string" || Array.isArray(doc)) { |
|
|
@ -35,27 +33,11 @@ var Transaction = function (doc) { |
|
|
|
this.addOutput(new TransactionOut(doc.outs[i])); |
|
|
|
} |
|
|
|
} |
|
|
|
if (doc.timestamp) this.timestamp = doc.timestamp; |
|
|
|
if (doc.block) this.block = doc.block; |
|
|
|
|
|
|
|
this.hash = this.hash || this.getHash() |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Turn transaction data into Transaction objects. |
|
|
|
* |
|
|
|
* Takes an array of plain JavaScript objects containing transaction data and |
|
|
|
* returns an array of Transaction objects. |
|
|
|
*/ |
|
|
|
Transaction.objectify = function (txs) { |
|
|
|
var objs = []; |
|
|
|
for (var i = 0; i < txs.length; i++) { |
|
|
|
objs.push(new Transaction(txs[i])); |
|
|
|
} |
|
|
|
return objs; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a new txin. |
|
|
|
* |
|
|
@ -85,7 +67,7 @@ Transaction.prototype.addInput = function (tx, outIndex) { |
|
|
|
index: outIndex |
|
|
|
}, |
|
|
|
script: new Script(), |
|
|
|
sequence: 4294967295 |
|
|
|
sequence: this.defaultSequence |
|
|
|
})); |
|
|
|
} |
|
|
|
}; |
|
|
@ -138,7 +120,7 @@ Transaction.prototype.serialize = function () { |
|
|
|
var scriptBytes = txin.script.buffer; |
|
|
|
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length)); |
|
|
|
buffer = buffer.concat(scriptBytes); |
|
|
|
buffer = buffer.concat(convert.numToBytes(parseInt(txin.sequence),4)); |
|
|
|
buffer = buffer.concat(txin.sequence); |
|
|
|
} |
|
|
|
buffer = buffer.concat(convert.numToVarInt(this.outs.length)); |
|
|
|
for (var i = 0; i < this.outs.length; i++) { |
|
|
@ -246,169 +228,6 @@ Transaction.prototype.clone = function () |
|
|
|
return newTx; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Analyze how this transaction affects a wallet. |
|
|
|
* |
|
|
|
* Returns an object with properties 'impact', 'type' and 'addr'. |
|
|
|
* |
|
|
|
* 'impact' is an object, see Transaction#calcImpact. |
|
|
|
* |
|
|
|
* 'type' can be one of the following: |
|
|
|
* |
|
|
|
* recv: |
|
|
|
* This is an incoming transaction, the wallet received money. |
|
|
|
* 'addr' contains the first address in the wallet that receives money |
|
|
|
* from this transaction. |
|
|
|
* |
|
|
|
* self: |
|
|
|
* This is an internal transaction, money was sent within the wallet. |
|
|
|
* 'addr' is undefined. |
|
|
|
* |
|
|
|
* sent: |
|
|
|
* This is an outgoing transaction, money was sent out from the wallet. |
|
|
|
* 'addr' contains the first external address, i.e. the recipient. |
|
|
|
* |
|
|
|
* other: |
|
|
|
* This method was unable to detect what the transaction does. Either it |
|
|
|
*/ |
|
|
|
Transaction.prototype.analyze = function (wallet) { |
|
|
|
if (!(wallet instanceof Wallet)) return null; |
|
|
|
|
|
|
|
var allFromMe = true, |
|
|
|
allToMe = true, |
|
|
|
firstRecvHash = null, |
|
|
|
firstMeRecvHash = null, |
|
|
|
firstSendHash = null; |
|
|
|
|
|
|
|
for (var i = this.outs.length-1; i >= 0; i--) { |
|
|
|
var txout = this.outs[i]; |
|
|
|
var hash = txout.script.simpleOutPubKeyHash(); |
|
|
|
if (!wallet.hasHash(hash)) { |
|
|
|
allToMe = false; |
|
|
|
} else { |
|
|
|
firstMeRecvHash = hash; |
|
|
|
} |
|
|
|
firstRecvHash = hash; |
|
|
|
} |
|
|
|
for (var i = this.ins.length-1; i >= 0; i--) { |
|
|
|
var txin = this.ins[i]; |
|
|
|
firstSendHash = txin.script.simpleInPubKeyHash(); |
|
|
|
if (!wallet.hasHash(firstSendHash)) { |
|
|
|
allFromMe = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var impact = this.calcImpact(wallet); |
|
|
|
|
|
|
|
var analysis = {}; |
|
|
|
|
|
|
|
analysis.impact = impact; |
|
|
|
|
|
|
|
if (impact.sign > 0 && impact.value > 0) { |
|
|
|
analysis.type = 'recv'; |
|
|
|
analysis.addr = new Address(firstMeRecvHash); |
|
|
|
} else if (allFromMe && allToMe) { |
|
|
|
analysis.type = 'self'; |
|
|
|
} else if (allFromMe) { |
|
|
|
analysis.type = 'sent'; |
|
|
|
// TODO: Right now, firstRecvHash is the first output, which - if the
|
|
|
|
// transaction was not generated by this library could be the
|
|
|
|
// change address.
|
|
|
|
analysis.addr = new Address(firstRecvHash); |
|
|
|
} else { |
|
|
|
analysis.type = "other"; |
|
|
|
} |
|
|
|
|
|
|
|
return analysis; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Get a human-readable version of the data returned by Transaction#analyze. |
|
|
|
* |
|
|
|
* This is merely a convenience function. Clients should consider implementing |
|
|
|
* this themselves based on their UI, I18N, etc. |
|
|
|
*/ |
|
|
|
Transaction.prototype.getDescription = function (wallet) { |
|
|
|
var analysis = this.analyze(wallet); |
|
|
|
|
|
|
|
if (!analysis) return ""; |
|
|
|
|
|
|
|
switch (analysis.type) { |
|
|
|
case 'recv': |
|
|
|
return "Received with "+analysis.addr; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'sent': |
|
|
|
return "Payment to "+analysis.addr; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'self': |
|
|
|
return "Payment to yourself"; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'other': |
|
|
|
default: |
|
|
|
return ""; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the total amount of a transaction's outputs. |
|
|
|
*/ |
|
|
|
Transaction.prototype.getTotalOutValue = function () { |
|
|
|
return this.outs.reduce(function(t,o) { return t + o.value },0); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Old name for Transaction#getTotalOutValue. |
|
|
|
* |
|
|
|
* @deprecated |
|
|
|
*/ |
|
|
|
Transaction.prototype.getTotalValue = Transaction.prototype.getTotalOutValue; |
|
|
|
|
|
|
|
/** |
|
|
|
* Calculates the impact a transaction has on this wallet. |
|
|
|
* |
|
|
|
* Based on the its public keys, the wallet will calculate the |
|
|
|
* credit or debit of this transaction. |
|
|
|
* |
|
|
|
* It will return an object with two properties: |
|
|
|
* - sign: 1 or -1 depending on sign of the calculated impact. |
|
|
|
* - value: amount of calculated impact |
|
|
|
* |
|
|
|
* @returns Object Impact on wallet |
|
|
|
*/ |
|
|
|
Transaction.prototype.calcImpact = function (wallet) { |
|
|
|
if (!(wallet instanceof Wallet)) return 0; |
|
|
|
|
|
|
|
// Calculate credit to us from all outputs
|
|
|
|
var valueOut = this.outs.filter(function(o) { |
|
|
|
return wallet.hasHash(convert.bytesToHex(o.script.simpleOutPubKeyHash())); |
|
|
|
}) |
|
|
|
.reduce(function(t,o) { return t+o.value },0); |
|
|
|
|
|
|
|
var valueIn = this.ins.filter(function(i) { |
|
|
|
return wallet.hasHash(convert.bytesToHex(i.script.simpleInPubKeyHash())) |
|
|
|
&& wallet.txIndex[i.outpoint.hash]; |
|
|
|
}) |
|
|
|
.reduce(function(t,i) { |
|
|
|
return t + wallet.txIndex[i.outpoint.hash].outs[i.outpoint.index].value |
|
|
|
},0); |
|
|
|
|
|
|
|
if (valueOut > valueIn) { |
|
|
|
return { |
|
|
|
sign: 1, |
|
|
|
value: valueOut - valueIn |
|
|
|
}; |
|
|
|
} else { |
|
|
|
return { |
|
|
|
sign: -1, |
|
|
|
value: valueIn - valueOut |
|
|
|
}; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Converts a serialized transaction into a transaction object |
|
|
|
*/ |
|
|
@ -451,7 +270,7 @@ Transaction.deserialize = function(buffer) { |
|
|
|
index: readAsInt(4) |
|
|
|
}, |
|
|
|
script: new Script(readVarString()), |
|
|
|
sequence: readAsInt(4) |
|
|
|
sequence: readBytes(4) |
|
|
|
}); |
|
|
|
} |
|
|
|
var outs = readVarInt(); |
|
|
@ -473,6 +292,9 @@ Transaction.deserialize = function(buffer) { |
|
|
|
Transaction.prototype.sign = function(index, key, type) { |
|
|
|
type = type || SIGHASH_ALL; |
|
|
|
key = new ECKey(key); |
|
|
|
|
|
|
|
// TODO: getPub is slow, sha256ripe160 probably is too.
|
|
|
|
// This could be sped up a lot by providing these as inputs.
|
|
|
|
var pub = key.getPub().export('bytes'), |
|
|
|
hash160 = util.sha256ripe160(pub), |
|
|
|
script = Script.createOutputScript(new Address(hash160)), |
|
|
@ -533,7 +355,6 @@ Transaction.prototype.validateSig = function(index, script, sig, pub) { |
|
|
|
convert.coerceToBytes(pub)); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var TransactionIn = function (data) { |
|
|
|
if (typeof data == "string") |
|
|
|
this.outpoint = { hash: data.split(':')[0], index: data.split(':')[1] } |
|
|
@ -549,7 +370,7 @@ var TransactionIn = function (data) { |
|
|
|
else |
|
|
|
this.script = new Script(data.script) |
|
|
|
|
|
|
|
this.sequence = data.sequence || 4294967295; |
|
|
|
this.sequence = data.sequence || this.defaultSequence |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionIn.prototype.clone = function () { |
|
|
|