commit
b13271477c
23 changed files with 171 additions and 1627 deletions
|
@ -1,18 +0,0 @@
|
||||||
var bs58check = require('bs58check')
|
|
||||||
|
|
||||||
function decode () {
|
|
||||||
console.warn('bs58check will be removed in 2.0.0. require("bs58check") instead.')
|
|
||||||
|
|
||||||
return bs58check.decode.apply(undefined, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
function encode () {
|
|
||||||
console.warn('bs58check will be removed in 2.0.0. require("bs58check") instead.')
|
|
||||||
|
|
||||||
return bs58check.encode.apply(undefined, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
decode: decode,
|
|
||||||
encode: encode
|
|
||||||
}
|
|
|
@ -165,6 +165,16 @@ function varIntBuffer (i) {
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function equal (a, b) {
|
||||||
|
if (a.length !== b.length) return false
|
||||||
|
|
||||||
|
for (var i = 0; i < a.length; ++i) {
|
||||||
|
if (a[i] !== b[i]) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function reverse (buffer) {
|
function reverse (buffer) {
|
||||||
var buffer2 = new Buffer(buffer)
|
var buffer2 = new Buffer(buffer)
|
||||||
Array.prototype.reverse.call(buffer2)
|
Array.prototype.reverse.call(buffer2)
|
||||||
|
@ -172,6 +182,7 @@ function reverse (buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
equal: equal,
|
||||||
pushDataSize: pushDataSize,
|
pushDataSize: pushDataSize,
|
||||||
readPushDataInt: readPushDataInt,
|
readPushDataInt: readPushDataInt,
|
||||||
readUInt64LE: readUInt64LE,
|
readUInt64LE: readUInt64LE,
|
||||||
|
|
|
@ -20,23 +20,10 @@ function sha256 (buffer) {
|
||||||
return crypto.createHash('sha256').update(buffer).digest()
|
return crypto.createHash('sha256').update(buffer).digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Name not consistent with others
|
|
||||||
function HmacSHA256 (buffer, secret) {
|
|
||||||
console.warn('Hmac* functions are deprecated for removal in 2.0.0, use node crypto instead')
|
|
||||||
return crypto.createHmac('sha256', secret).update(buffer).digest()
|
|
||||||
}
|
|
||||||
|
|
||||||
function HmacSHA512 (buffer, secret) {
|
|
||||||
console.warn('Hmac* functions are deprecated for removal in 2.0.0, use node crypto instead')
|
|
||||||
return crypto.createHmac('sha512', secret).update(buffer).digest()
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ripemd160: ripemd160,
|
|
||||||
sha1: sha1,
|
|
||||||
sha256: sha256,
|
|
||||||
hash160: hash160,
|
hash160: hash160,
|
||||||
hash256: hash256,
|
hash256: hash256,
|
||||||
HmacSHA256: HmacSHA256,
|
ripemd160: ripemd160,
|
||||||
HmacSHA512: HmacSHA512
|
sha1: sha1,
|
||||||
|
sha256: sha256
|
||||||
}
|
}
|
||||||
|
|
29
src/ecdsa.js
29
src/ecdsa.js
|
@ -12,34 +12,7 @@ var ONE = new Buffer([1])
|
||||||
function deterministicGenerateK (curve, hash, d, checkSig) {
|
function deterministicGenerateK (curve, hash, d, checkSig) {
|
||||||
typeForce('Buffer', hash)
|
typeForce('Buffer', hash)
|
||||||
typeForce('BigInteger', d)
|
typeForce('BigInteger', d)
|
||||||
|
typeForce('Function', checkSig)
|
||||||
// FIXME: remove/uncomment for 2.0.0
|
|
||||||
// typeForce('Function', checkSig)
|
|
||||||
|
|
||||||
if (typeof checkSig !== 'function') {
|
|
||||||
console.warn('deterministicGenerateK requires a checkSig callback in 2.0.0, see #337 for more information')
|
|
||||||
|
|
||||||
checkSig = function (k) {
|
|
||||||
var G = curve.G
|
|
||||||
var n = curve.n
|
|
||||||
var e = BigInteger.fromBuffer(hash)
|
|
||||||
|
|
||||||
var Q = G.multiply(k)
|
|
||||||
|
|
||||||
if (curve.isInfinity(Q))
|
|
||||||
return false
|
|
||||||
|
|
||||||
var r = Q.affineX.mod(n)
|
|
||||||
if (r.signum() === 0)
|
|
||||||
return false
|
|
||||||
|
|
||||||
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n)
|
|
||||||
if (s.signum() === 0)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
assert.equal(hash.length, 32, 'Hash must be 256 bit')
|
assert.equal(hash.length, 32, 'Hash must be 256 bit')
|
||||||
|
|
|
@ -78,15 +78,7 @@ HDNode.fromSeedHex = function (hex, network) {
|
||||||
}
|
}
|
||||||
|
|
||||||
HDNode.fromBase58 = function (string, network) {
|
HDNode.fromBase58 = function (string, network) {
|
||||||
return HDNode.fromBuffer(base58check.decode(string), network, true)
|
var buffer = base58check.decode(string)
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
HDNode.fromBuffer = function (buffer, network, __ignoreDeprecation) {
|
|
||||||
if (!__ignoreDeprecation) {
|
|
||||||
console.warn('HDNode.fromBuffer() is deprecated for removal in 2.x.y, use fromBase58 instead')
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length')
|
assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length')
|
||||||
|
|
||||||
// 4 byte: version bytes
|
// 4 byte: version bytes
|
||||||
|
@ -145,11 +137,6 @@ HDNode.fromBuffer = function (buffer, network, __ignoreDeprecation) {
|
||||||
return hd
|
return hd
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
HDNode.fromHex = function (hex, network) {
|
|
||||||
return HDNode.fromBuffer(new Buffer(hex, 'hex'), network)
|
|
||||||
}
|
|
||||||
|
|
||||||
HDNode.prototype.getIdentifier = function () {
|
HDNode.prototype.getIdentifier = function () {
|
||||||
return bcrypto.hash160(this.pubKey.toBuffer())
|
return bcrypto.hash160(this.pubKey.toBuffer())
|
||||||
}
|
}
|
||||||
|
@ -171,26 +158,11 @@ HDNode.prototype.neutered = function () {
|
||||||
return neutered
|
return neutered
|
||||||
}
|
}
|
||||||
|
|
||||||
HDNode.prototype.toBase58 = function (isPrivate) {
|
HDNode.prototype.toBase58 = function (__isPrivate) {
|
||||||
return base58check.encode(this.toBuffer(isPrivate, true))
|
assert.strictEqual(__isPrivate, undefined, 'Unsupported argument in 2.0.0')
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
HDNode.prototype.toBuffer = function (isPrivate, __ignoreDeprecation) {
|
|
||||||
if (isPrivate === undefined) {
|
|
||||||
isPrivate = !!this.privKey
|
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
} else {
|
|
||||||
console.warn('isPrivate flag is deprecated, please use the .neutered() method instead')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!__ignoreDeprecation) {
|
|
||||||
console.warn('HDNode.toBuffer() is deprecated for removal in 2.x.y, use toBase58 instead')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
var version = isPrivate ? this.network.bip32.private : this.network.bip32.public
|
var version = this.privKey ? this.network.bip32.private : this.network.bip32.public
|
||||||
var buffer = new Buffer(HDNode.LENGTH)
|
var buffer = new Buffer(HDNode.LENGTH)
|
||||||
|
|
||||||
// 4 bytes: version bytes
|
// 4 bytes: version bytes
|
||||||
|
@ -210,25 +182,19 @@ HDNode.prototype.toBuffer = function (isPrivate, __ignoreDeprecation) {
|
||||||
// 32 bytes: the chain code
|
// 32 bytes: the chain code
|
||||||
this.chainCode.copy(buffer, 13)
|
this.chainCode.copy(buffer, 13)
|
||||||
|
|
||||||
// 33 bytes: the public key or private key data
|
// 33 bytes: the private key, or
|
||||||
if (isPrivate) {
|
if (this.privKey) {
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
assert(this.privKey, 'Missing private key')
|
|
||||||
|
|
||||||
// 0x00 + k for private keys
|
// 0x00 + k for private keys
|
||||||
buffer.writeUInt8(0, 45)
|
buffer.writeUInt8(0, 45)
|
||||||
this.privKey.d.toBuffer(32).copy(buffer, 46)
|
this.privKey.d.toBuffer(32).copy(buffer, 46)
|
||||||
|
|
||||||
|
// 33 bytes: the public key
|
||||||
} else {
|
} else {
|
||||||
// X9.62 encoding for public keys
|
// X9.62 encoding for public keys
|
||||||
this.pubKey.toBuffer().copy(buffer, 45)
|
this.pubKey.toBuffer().copy(buffer, 45)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer
|
return base58check.encode(buffer)
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
HDNode.prototype.toHex = function (isPrivate) {
|
|
||||||
return this.toBuffer(isPrivate).toString('hex')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
|
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Address: require('./address'),
|
Address: require('./address'),
|
||||||
base58check: require('./base58check'),
|
|
||||||
Block: require('./block'),
|
Block: require('./block'),
|
||||||
bufferutils: require('./bufferutils'),
|
bufferutils: require('./bufferutils'),
|
||||||
crypto: require('./crypto'),
|
crypto: require('./crypto'),
|
||||||
|
@ -8,13 +7,12 @@ module.exports = {
|
||||||
ECKey: require('./eckey'),
|
ECKey: require('./eckey'),
|
||||||
ECPubKey: require('./ecpubkey'),
|
ECPubKey: require('./ecpubkey'),
|
||||||
ECSignature: require('./ecsignature'),
|
ECSignature: require('./ecsignature'),
|
||||||
Message: require('./message'),
|
message: require('./message'),
|
||||||
opcodes: require('./opcodes'),
|
opcodes: require('./opcodes'),
|
||||||
HDNode: require('./hdnode'),
|
HDNode: require('./hdnode'),
|
||||||
Script: require('./script'),
|
Script: require('./script'),
|
||||||
scripts: require('./scripts'),
|
scripts: require('./scripts'),
|
||||||
Transaction: require('./transaction'),
|
Transaction: require('./transaction'),
|
||||||
TransactionBuilder: require('./transaction_builder'),
|
TransactionBuilder: require('./transaction_builder'),
|
||||||
networks: require('./networks'),
|
networks: require('./networks')
|
||||||
Wallet: require('./wallet')
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,9 +291,5 @@ module.exports = {
|
||||||
pubKeyHashInput: pubKeyHashInput,
|
pubKeyHashInput: pubKeyHashInput,
|
||||||
scriptHashInput: scriptHashInput,
|
scriptHashInput: scriptHashInput,
|
||||||
multisigInput: multisigInput,
|
multisigInput: multisigInput,
|
||||||
dataOutput: function (data) {
|
|
||||||
console.warn('dataOutput is deprecated, use nullDataOutput by 2.0.0')
|
|
||||||
return nullDataOutput(data)
|
|
||||||
},
|
|
||||||
nullDataOutput: nullDataOutput
|
nullDataOutput: nullDataOutput
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,7 @@ var bufferutils = require('./bufferutils')
|
||||||
var crypto = require('./crypto')
|
var crypto = require('./crypto')
|
||||||
var typeForce = require('typeforce')
|
var typeForce = require('typeforce')
|
||||||
var opcodes = require('./opcodes')
|
var opcodes = require('./opcodes')
|
||||||
var scripts = require('./scripts')
|
|
||||||
|
|
||||||
var Address = require('./address')
|
|
||||||
var ECSignature = require('./ecsignature')
|
|
||||||
var Script = require('./script')
|
var Script = require('./script')
|
||||||
|
|
||||||
function Transaction () {
|
function Transaction () {
|
||||||
|
@ -106,16 +103,6 @@ Transaction.isCoinbaseHash = function (buffer) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new txIn.
|
|
||||||
*
|
|
||||||
* Can be called with any of:
|
|
||||||
*
|
|
||||||
* - A transaction and an index
|
|
||||||
* - A transaction hash and an index
|
|
||||||
*
|
|
||||||
* Note that this method does not sign the created input.
|
|
||||||
*/
|
|
||||||
Transaction.prototype.addInput = function (hash, index, sequence, script) {
|
Transaction.prototype.addInput = function (hash, index, sequence, script) {
|
||||||
if (sequence === undefined || sequence === null) {
|
if (sequence === undefined || sequence === null) {
|
||||||
sequence = Transaction.DEFAULT_SEQUENCE
|
sequence = Transaction.DEFAULT_SEQUENCE
|
||||||
|
@ -123,13 +110,6 @@ Transaction.prototype.addInput = function (hash, index, sequence, script) {
|
||||||
|
|
||||||
script = script || Script.EMPTY
|
script = script || Script.EMPTY
|
||||||
|
|
||||||
if (typeof hash === 'string') {
|
|
||||||
// TxId hex is big-endian, we need little-endian
|
|
||||||
hash = bufferutils.reverse(new Buffer(hash, 'hex'))
|
|
||||||
} else if (hash instanceof Transaction) {
|
|
||||||
hash = hash.getHash()
|
|
||||||
}
|
|
||||||
|
|
||||||
typeForce('Buffer', hash)
|
typeForce('Buffer', hash)
|
||||||
typeForce('Number', index)
|
typeForce('Number', index)
|
||||||
typeForce('Number', sequence)
|
typeForce('Number', sequence)
|
||||||
|
@ -146,26 +126,7 @@ Transaction.prototype.addInput = function (hash, index, sequence, script) {
|
||||||
}) - 1)
|
}) - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new txOut.
|
|
||||||
*
|
|
||||||
* Can be called with:
|
|
||||||
*
|
|
||||||
* - A base58 address string and a value
|
|
||||||
* - An Address object and a value
|
|
||||||
* - A scriptPubKey Script and a value
|
|
||||||
*/
|
|
||||||
Transaction.prototype.addOutput = function (scriptPubKey, value) {
|
Transaction.prototype.addOutput = function (scriptPubKey, value) {
|
||||||
// Attempt to get a valid address if it's a base58 address string
|
|
||||||
if (typeof scriptPubKey === 'string') {
|
|
||||||
scriptPubKey = Address.fromBase58Check(scriptPubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to get a valid script if it's an Address object
|
|
||||||
if (scriptPubKey instanceof Address) {
|
|
||||||
scriptPubKey = scriptPubKey.toOutputScript()
|
|
||||||
}
|
|
||||||
|
|
||||||
typeForce('Script', scriptPubKey)
|
typeForce('Script', scriptPubKey)
|
||||||
typeForce('Number', value)
|
typeForce('Number', value)
|
||||||
|
|
||||||
|
@ -203,22 +164,12 @@ Transaction.prototype.clone = function () {
|
||||||
/**
|
/**
|
||||||
* Hash transaction for signing a specific input.
|
* Hash transaction for signing a specific input.
|
||||||
*
|
*
|
||||||
* Bitcoin uses a different hash for each signed transaction input. This
|
* Bitcoin uses a different hash for each signed transaction input.
|
||||||
* method copies the transaction, makes the necessary changes based on the
|
* This method copies the transaction, makes the necessary changes based on the
|
||||||
* hashType, serializes and finally hashes the result. This hash can then be
|
* hashType, and then hashes the result.
|
||||||
* used to sign the transaction input in question.
|
* This hash can then be used to sign the provided transaction input.
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) {
|
Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) {
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
if (arguments[0] instanceof Script) {
|
|
||||||
console.warn('hashForSignature(prevOutScript, inIndex, ...) has been deprecated. Use hashForSignature(inIndex, prevOutScript, ...)')
|
|
||||||
|
|
||||||
// swap the arguments (must be stored in tmp, arguments is special)
|
|
||||||
var tmp = arguments[0]
|
|
||||||
inIndex = arguments[1]
|
|
||||||
prevOutScript = tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
typeForce('Number', inIndex)
|
typeForce('Number', inIndex)
|
||||||
typeForce('Script', prevOutScript)
|
typeForce('Script', prevOutScript)
|
||||||
typeForce('Number', hashType)
|
typeForce('Number', hashType)
|
||||||
|
@ -333,37 +284,4 @@ Transaction.prototype.setInputScript = function (index, script) {
|
||||||
this.ins[index].script = script
|
this.ins[index].script = script
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
Transaction.prototype.sign = function (index, privKey, hashType) {
|
|
||||||
console.warn('Transaction.prototype.sign is deprecated. Use TransactionBuilder instead.')
|
|
||||||
|
|
||||||
var prevOutScript = privKey.pub.getAddress().toOutputScript()
|
|
||||||
var signature = this.signInput(index, prevOutScript, privKey, hashType)
|
|
||||||
|
|
||||||
var scriptSig = scripts.pubKeyHashInput(signature, privKey.pub)
|
|
||||||
this.setInputScript(index, scriptSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
Transaction.prototype.signInput = function (index, prevOutScript, privKey, hashType) {
|
|
||||||
console.warn('Transaction.prototype.signInput is deprecated. Use TransactionBuilder instead.')
|
|
||||||
|
|
||||||
hashType = hashType || Transaction.SIGHASH_ALL
|
|
||||||
|
|
||||||
var hash = this.hashForSignature(index, prevOutScript, hashType)
|
|
||||||
var signature = privKey.sign(hash)
|
|
||||||
|
|
||||||
return signature.toScriptSignature(hashType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
Transaction.prototype.validateInput = function (index, prevOutScript, pubKey, buffer) {
|
|
||||||
console.warn('Transaction.prototype.validateInput is deprecated. Use TransactionBuilder instead.')
|
|
||||||
|
|
||||||
var parsed = ECSignature.parseScriptSignature(buffer)
|
|
||||||
var hash = this.hashForSignature(index, prevOutScript, parsed.hashType)
|
|
||||||
|
|
||||||
return pubKey.verify(hash, parsed.signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Transaction
|
module.exports = Transaction
|
||||||
|
|
|
@ -2,6 +2,7 @@ var assert = require('assert')
|
||||||
var ops = require('./opcodes')
|
var ops = require('./opcodes')
|
||||||
var scripts = require('./scripts')
|
var scripts = require('./scripts')
|
||||||
|
|
||||||
|
var Address = require('./address')
|
||||||
var ECPubKey = require('./ecpubkey')
|
var ECPubKey = require('./ecpubkey')
|
||||||
var ECSignature = require('./ecsignature')
|
var ECSignature = require('./ecsignature')
|
||||||
var Script = require('./script')
|
var Script = require('./script')
|
||||||
|
@ -120,24 +121,17 @@ TransactionBuilder.fromTransaction = function (transaction) {
|
||||||
return txb
|
return txb
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionBuilder.prototype.addInput = function (prevTx, index, sequence, prevOutScript) {
|
TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
|
||||||
var prevOutHash
|
// is it a txId?
|
||||||
|
if (typeof txHash === 'string') {
|
||||||
|
// a txId is big-endian hex, we want a little-endian Buffer
|
||||||
|
txHash = new Buffer(txHash, 'hex')
|
||||||
|
Array.prototype.reverse.call(txHash)
|
||||||
|
|
||||||
// txId
|
// is it a Transaction?
|
||||||
if (typeof prevTx === 'string') {
|
} else if (txHash instanceof Transaction) {
|
||||||
prevOutHash = new Buffer(prevTx, 'hex')
|
prevOutScript = txHash.outs[vout].script
|
||||||
|
txHash = txHash.getHash()
|
||||||
// TxId hex is big-endian, we want little-endian hash
|
|
||||||
Array.prototype.reverse.call(prevOutHash)
|
|
||||||
|
|
||||||
// Transaction
|
|
||||||
} else if (prevTx instanceof Transaction) {
|
|
||||||
prevOutHash = prevTx.getHash()
|
|
||||||
prevOutScript = prevTx.outs[index].script
|
|
||||||
|
|
||||||
// txHash
|
|
||||||
} else {
|
|
||||||
prevOutHash = prevTx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var input = {}
|
var input = {}
|
||||||
|
@ -171,10 +165,10 @@ TransactionBuilder.prototype.addInput = function (prevTx, index, sequence, prevO
|
||||||
return input2.hashType & Transaction.SIGHASH_ANYONECANPAY
|
return input2.hashType & Transaction.SIGHASH_ANYONECANPAY
|
||||||
}), 'No, this would invalidate signatures')
|
}), 'No, this would invalidate signatures')
|
||||||
|
|
||||||
var prevOut = prevOutHash.toString('hex') + ':' + index
|
var prevOut = txHash.toString('hex') + ':' + vout
|
||||||
assert(!(prevOut in this.prevTxMap), 'Transaction is already an input')
|
assert(!(prevOut in this.prevTxMap), 'Transaction is already an input')
|
||||||
|
|
||||||
var vin = this.tx.addInput(prevOutHash, index, sequence)
|
var vin = this.tx.addInput(txHash, vout, sequence)
|
||||||
this.inputs[vin] = input
|
this.inputs[vin] = input
|
||||||
this.prevTxMap[prevOut] = vin
|
this.prevTxMap[prevOut] = vin
|
||||||
|
|
||||||
|
@ -188,6 +182,16 @@ TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
|
||||||
return (input.hashType & 0x1f) === Transaction.SIGHASH_SINGLE
|
return (input.hashType & 0x1f) === Transaction.SIGHASH_SINGLE
|
||||||
}), 'No, this would invalidate signatures')
|
}), 'No, this would invalidate signatures')
|
||||||
|
|
||||||
|
// Attempt to get a valid address if it's a base58 address string
|
||||||
|
if (typeof scriptPubKey === 'string') {
|
||||||
|
scriptPubKey = Address.fromBase58Check(scriptPubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to get a valid script if it's an Address object
|
||||||
|
if (scriptPubKey instanceof Address) {
|
||||||
|
scriptPubKey = scriptPubKey.toOutputScript()
|
||||||
|
}
|
||||||
|
|
||||||
return this.tx.addOutput(scriptPubKey, value)
|
return this.tx.addOutput(scriptPubKey, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
371
src/wallet.js
371
src/wallet.js
|
@ -1,371 +0,0 @@
|
||||||
var assert = require('assert')
|
|
||||||
var bufferutils = require('./bufferutils')
|
|
||||||
var crypto = require('crypto')
|
|
||||||
var typeForce = require('typeforce')
|
|
||||||
var networks = require('./networks')
|
|
||||||
|
|
||||||
var Address = require('./address')
|
|
||||||
var HDNode = require('./hdnode')
|
|
||||||
var TransactionBuilder = require('./transaction_builder')
|
|
||||||
var Script = require('./script')
|
|
||||||
|
|
||||||
function Wallet (seed, network) {
|
|
||||||
console.warn('Wallet is deprecated and will be removed in 2.0.0, see #296')
|
|
||||||
|
|
||||||
seed = seed || crypto.randomBytes(32)
|
|
||||||
network = network || networks.bitcoin
|
|
||||||
|
|
||||||
// Stored in a closure to make accidental serialization less likely
|
|
||||||
var masterKey = HDNode.fromSeedBuffer(seed, network)
|
|
||||||
|
|
||||||
// HD first-level child derivation method should be hardened
|
|
||||||
// See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254
|
|
||||||
var accountZero = masterKey.deriveHardened(0)
|
|
||||||
var externalAccount = accountZero.derive(0)
|
|
||||||
var internalAccount = accountZero.derive(1)
|
|
||||||
|
|
||||||
this.addresses = []
|
|
||||||
this.changeAddresses = []
|
|
||||||
this.network = network
|
|
||||||
this.unspents = []
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
this.unspentMap = {}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
var me = this
|
|
||||||
this.newMasterKey = function (seed) {
|
|
||||||
console.warn('newMasterKey is deprecated, please make a new Wallet instance instead')
|
|
||||||
|
|
||||||
seed = seed || crypto.randomBytes(32)
|
|
||||||
masterKey = HDNode.fromSeedBuffer(seed, network)
|
|
||||||
|
|
||||||
accountZero = masterKey.deriveHardened(0)
|
|
||||||
externalAccount = accountZero.derive(0)
|
|
||||||
internalAccount = accountZero.derive(1)
|
|
||||||
|
|
||||||
me.addresses = []
|
|
||||||
me.changeAddresses = []
|
|
||||||
|
|
||||||
me.unspents = []
|
|
||||||
me.unspentMap = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getMasterKey = function () {
|
|
||||||
return masterKey
|
|
||||||
}
|
|
||||||
this.getAccountZero = function () {
|
|
||||||
return accountZero
|
|
||||||
}
|
|
||||||
this.getExternalAccount = function () {
|
|
||||||
return externalAccount
|
|
||||||
}
|
|
||||||
this.getInternalAccount = function () {
|
|
||||||
return internalAccount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.createTransaction = function (to, value, options) {
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
if (typeof options !== 'object') {
|
|
||||||
if (options !== undefined) {
|
|
||||||
console.warn('Non options object parameters are deprecated, use options object instead')
|
|
||||||
|
|
||||||
options = {
|
|
||||||
fixedFee: arguments[2],
|
|
||||||
changeAddress: arguments[3]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options = options || {}
|
|
||||||
|
|
||||||
assert(value > this.network.dustThreshold, value + ' must be above dust threshold (' + this.network.dustThreshold + ' Satoshis)')
|
|
||||||
|
|
||||||
var changeAddress = options.changeAddress
|
|
||||||
var fixedFee = options.fixedFee
|
|
||||||
var minConf = options.minConf === undefined ? 0 : options.minConf // FIXME: change minConf:1 by default in 2.0.0
|
|
||||||
|
|
||||||
// filter by minConf, then pending and sort by descending value
|
|
||||||
var unspents = this.unspents.filter(function (unspent) {
|
|
||||||
return unspent.confirmations >= minConf
|
|
||||||
}).filter(function (unspent) {
|
|
||||||
return !unspent.pending
|
|
||||||
}).sort(function (o1, o2) {
|
|
||||||
return o2.value - o1.value
|
|
||||||
})
|
|
||||||
|
|
||||||
var accum = 0
|
|
||||||
var addresses = []
|
|
||||||
var subTotal = value
|
|
||||||
|
|
||||||
var txb = new TransactionBuilder()
|
|
||||||
txb.addOutput(to, value)
|
|
||||||
|
|
||||||
for (var i = 0; i < unspents.length; ++i) {
|
|
||||||
var unspent = unspents[i]
|
|
||||||
addresses.push(unspent.address)
|
|
||||||
|
|
||||||
txb.addInput(unspent.txHash, unspent.index)
|
|
||||||
|
|
||||||
var fee = fixedFee === undefined ? estimatePaddedFee(txb.buildIncomplete(), this.network) : fixedFee
|
|
||||||
|
|
||||||
accum += unspent.value
|
|
||||||
subTotal = value + fee
|
|
||||||
|
|
||||||
if (accum >= subTotal) {
|
|
||||||
var change = accum - subTotal
|
|
||||||
|
|
||||||
if (change > this.network.dustThreshold) {
|
|
||||||
txb.addOutput(changeAddress || this.getChangeAddress(), change)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal)
|
|
||||||
|
|
||||||
return this.signWith(txb, addresses).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
Wallet.prototype.processPendingTx = function (tx) {
|
|
||||||
this.__processTx(tx, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
Wallet.prototype.processConfirmedTx = function (tx) {
|
|
||||||
this.__processTx(tx, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
Wallet.prototype.__processTx = function (tx, isPending) {
|
|
||||||
console.warn('processTransaction is considered harmful, see issue #260 for more information')
|
|
||||||
|
|
||||||
var txId = tx.getId()
|
|
||||||
var txHash = tx.getHash()
|
|
||||||
|
|
||||||
tx.outs.forEach(function (txOut, i) {
|
|
||||||
var address
|
|
||||||
|
|
||||||
try {
|
|
||||||
address = Address.fromOutputScript(txOut.script, this.network).toString()
|
|
||||||
} catch (e) {
|
|
||||||
if (!(e.message.match(/has no matching Address/)))
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
var myAddresses = this.addresses.concat(this.changeAddresses)
|
|
||||||
if (myAddresses.indexOf(address) > -1) {
|
|
||||||
var lookup = txId + ':' + i
|
|
||||||
if (lookup in this.unspentMap) return
|
|
||||||
|
|
||||||
// its unique, add it
|
|
||||||
var unspent = {
|
|
||||||
address: address,
|
|
||||||
confirmations: 0, // no way to determine this without more information
|
|
||||||
index: i,
|
|
||||||
txHash: txHash,
|
|
||||||
txId: txId,
|
|
||||||
value: txOut.value,
|
|
||||||
pending: isPending
|
|
||||||
}
|
|
||||||
|
|
||||||
this.unspentMap[lookup] = unspent
|
|
||||||
this.unspents.push(unspent)
|
|
||||||
}
|
|
||||||
}, this)
|
|
||||||
|
|
||||||
tx.ins.forEach(function (txIn) {
|
|
||||||
// copy and convert to big-endian hex
|
|
||||||
var txInId = bufferutils.reverse(txIn.hash).toString('hex')
|
|
||||||
|
|
||||||
var lookup = txInId + ':' + txIn.index
|
|
||||||
if (!(lookup in this.unspentMap)) return
|
|
||||||
|
|
||||||
var unspent = this.unspentMap[lookup]
|
|
||||||
|
|
||||||
if (isPending) {
|
|
||||||
unspent.pending = true
|
|
||||||
unspent.spent = true
|
|
||||||
} else {
|
|
||||||
delete this.unspentMap[lookup]
|
|
||||||
|
|
||||||
this.unspents = this.unspents.filter(function (unspent2) {
|
|
||||||
return unspent !== unspent2
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.generateAddress = function () {
|
|
||||||
var k = this.addresses.length
|
|
||||||
var address = this.getExternalAccount().derive(k).getAddress()
|
|
||||||
|
|
||||||
this.addresses.push(address.toString())
|
|
||||||
|
|
||||||
return this.getReceiveAddress()
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.generateChangeAddress = function () {
|
|
||||||
var k = this.changeAddresses.length
|
|
||||||
var address = this.getInternalAccount().derive(k).getAddress()
|
|
||||||
|
|
||||||
this.changeAddresses.push(address.toString())
|
|
||||||
|
|
||||||
return this.getChangeAddress()
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getAddress = function () {
|
|
||||||
if (this.addresses.length === 0) {
|
|
||||||
this.generateAddress()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.addresses[this.addresses.length - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getBalance = function (minConf) {
|
|
||||||
minConf = minConf || 0
|
|
||||||
|
|
||||||
return this.unspents.filter(function (unspent) {
|
|
||||||
return unspent.confirmations >= minConf
|
|
||||||
|
|
||||||
// FIXME: remove spent filter in 2.0.0
|
|
||||||
}).filter(function (unspent) {
|
|
||||||
return !unspent.spent
|
|
||||||
}).reduce(function (accum, unspent) {
|
|
||||||
return accum + unspent.value
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getChangeAddress = function () {
|
|
||||||
if (this.changeAddresses.length === 0) {
|
|
||||||
this.generateChangeAddress()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.changeAddresses[this.changeAddresses.length - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getInternalPrivateKey = function (index) {
|
|
||||||
return this.getInternalAccount().derive(index).privKey
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getPrivateKey = function (index) {
|
|
||||||
return this.getExternalAccount().derive(index).privKey
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getPrivateKeyForAddress = function (address) {
|
|
||||||
var index
|
|
||||||
|
|
||||||
if ((index = this.addresses.indexOf(address)) > -1) {
|
|
||||||
return this.getPrivateKey(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((index = this.changeAddresses.indexOf(address)) > -1) {
|
|
||||||
return this.getInternalPrivateKey(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(false, 'Unknown address. Make sure the address is from the keychain and has been generated')
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getUnspentOutputs = function (minConf) {
|
|
||||||
minConf = minConf || 0
|
|
||||||
|
|
||||||
return this.unspents.filter(function (unspent) {
|
|
||||||
return unspent.confirmations >= minConf
|
|
||||||
|
|
||||||
// FIXME: remove spent filter in 2.0.0
|
|
||||||
}).filter(function (unspent) {
|
|
||||||
return !unspent.spent
|
|
||||||
}).map(function (unspent) {
|
|
||||||
return {
|
|
||||||
address: unspent.address,
|
|
||||||
confirmations: unspent.confirmations,
|
|
||||||
index: unspent.index,
|
|
||||||
txId: unspent.txId,
|
|
||||||
value: unspent.value,
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
hash: unspent.txId,
|
|
||||||
pending: unspent.pending
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.setUnspentOutputs = function (unspents) {
|
|
||||||
this.unspentMap = {}
|
|
||||||
this.unspents = unspents.map(function (unspent) {
|
|
||||||
// FIXME: remove unspent.hash in 2.0.0
|
|
||||||
var txId = unspent.txId || unspent.hash
|
|
||||||
var index = unspent.index
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
if (unspent.hash !== undefined) {
|
|
||||||
console.warn('unspent.hash is deprecated, use unspent.txId instead')
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
if (index === undefined) {
|
|
||||||
console.warn('unspent.outputIndex is deprecated, use unspent.index instead')
|
|
||||||
index = unspent.outputIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
typeForce('String', txId)
|
|
||||||
typeForce('Number', index)
|
|
||||||
typeForce('Number', unspent.value)
|
|
||||||
|
|
||||||
assert.equal(txId.length, 64, 'Expected valid txId, got ' + txId)
|
|
||||||
assert.doesNotThrow(function () {
|
|
||||||
Address.fromBase58Check(unspent.address)
|
|
||||||
}, 'Expected Base58 Address, got ' + unspent.address)
|
|
||||||
assert(isFinite(index), 'Expected finite index, got ' + index)
|
|
||||||
|
|
||||||
// FIXME: remove branch in 2.0.0
|
|
||||||
if (unspent.confirmations !== undefined) {
|
|
||||||
typeForce('Number', unspent.confirmations)
|
|
||||||
}
|
|
||||||
|
|
||||||
var txHash = bufferutils.reverse(new Buffer(txId, 'hex'))
|
|
||||||
|
|
||||||
unspent = {
|
|
||||||
address: unspent.address,
|
|
||||||
confirmations: unspent.confirmations || 0,
|
|
||||||
index: index,
|
|
||||||
txHash: txHash,
|
|
||||||
txId: txId,
|
|
||||||
value: unspent.value,
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
pending: unspent.pending || false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
this.unspentMap[txId + ':' + index] = unspent
|
|
||||||
|
|
||||||
return unspent
|
|
||||||
}, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.signWith = function (tx, addresses) {
|
|
||||||
addresses.forEach(function (address, i) {
|
|
||||||
var privKey = this.getPrivateKeyForAddress(address)
|
|
||||||
|
|
||||||
tx.sign(i, privKey)
|
|
||||||
}, this)
|
|
||||||
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
|
|
||||||
function estimatePaddedFee (tx, network) {
|
|
||||||
var tmpTx = tx.clone()
|
|
||||||
tmpTx.addOutput(Script.EMPTY, network.dustSoftThreshold || 0)
|
|
||||||
|
|
||||||
return network.estimateFee(tmpTx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: 1.0.0 shims, remove in 2.0.0
|
|
||||||
Wallet.prototype.getReceiveAddress = Wallet.prototype.getAddress
|
|
||||||
Wallet.prototype.createTx = Wallet.prototype.createTransaction
|
|
||||||
|
|
||||||
module.exports = Wallet
|
|
|
@ -1,30 +0,0 @@
|
||||||
/* global describe, it, beforeEach */
|
|
||||||
|
|
||||||
var assert = require('assert')
|
|
||||||
var base58check = require('../src/base58check')
|
|
||||||
var bs58check = require('bs58check')
|
|
||||||
var sinon = require('sinon')
|
|
||||||
|
|
||||||
describe('base58check', function () {
|
|
||||||
var param
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
param = {}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('wraps bs58check.decode', sinon.test(function () {
|
|
||||||
var expectation = this.mock(bs58check).expects('decode')
|
|
||||||
expectation.once().calledWith(param)
|
|
||||||
expectation.onCall(0).returns('foo')
|
|
||||||
|
|
||||||
assert.equal(base58check.decode(param), 'foo')
|
|
||||||
}))
|
|
||||||
|
|
||||||
it('wraps bs58check.encode', sinon.test(function () {
|
|
||||||
var expectation = this.mock(bs58check).expects('encode')
|
|
||||||
expectation.once().calledWith(param)
|
|
||||||
expectation.onCall(0).returns('foo')
|
|
||||||
|
|
||||||
assert.equal(base58check.encode(param), 'foo')
|
|
||||||
}))
|
|
||||||
})
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
var assert = require('assert')
|
var assert = require('assert')
|
||||||
var base58 = require('bs58')
|
var base58 = require('bs58')
|
||||||
|
var base58check = require('bs58check')
|
||||||
|
|
||||||
var Bitcoin = require('../')
|
var Bitcoin = require('../')
|
||||||
var Address = Bitcoin.Address
|
var Address = Bitcoin.Address
|
||||||
var base58check = Bitcoin.base58check
|
|
||||||
var networks = Bitcoin.networks
|
var networks = Bitcoin.networks
|
||||||
var ECKey = Bitcoin.ECKey
|
var ECKey = Bitcoin.ECKey
|
||||||
var ECSignature = Bitcoin.ECSignature
|
var ECSignature = Bitcoin.ECSignature
|
||||||
|
|
|
@ -77,6 +77,22 @@ describe('bufferutils', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('equal', function () {
|
||||||
|
fixtures.valid.forEach(function (f) {
|
||||||
|
describe('for ' + f.hexVI, function () {
|
||||||
|
fixtures.valid.forEach(function (f2) {
|
||||||
|
it('equates the string comparison: ' + f.hexVI + ' === ' + f2.hexVI, function () {
|
||||||
|
var a = new Buffer(f.hexVI, 'hex')
|
||||||
|
var b = new Buffer(f2.hexVI, 'hex')
|
||||||
|
var expected = f.hexVI === f2.hexVI
|
||||||
|
|
||||||
|
assert.equal(bufferutils.equal(a, b), expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('reverse', function () {
|
describe('reverse', function () {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(function (f) {
|
||||||
it('reverses ' + f.hex64 + ' correctly', function () {
|
it('reverses ' + f.hex64 + ' correctly', function () {
|
||||||
|
|
|
@ -3,84 +3,21 @@
|
||||||
var assert = require('assert')
|
var assert = require('assert')
|
||||||
var crypto = require('../src/crypto')
|
var crypto = require('../src/crypto')
|
||||||
|
|
||||||
var fixtures = require('./fixtures/crypto.json')
|
var fixtures = require('./fixtures/crypto')
|
||||||
|
|
||||||
describe('Crypto', function () {
|
describe('Crypto', function () {
|
||||||
describe('HASH160', function () {
|
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(function (algorithm) {
|
||||||
it('matches the test vectors', function () {
|
describe(algorithm, function () {
|
||||||
fixtures.before.hex.forEach(function (hex, i) {
|
fixtures.valid.forEach(function (f) {
|
||||||
var data = new Buffer(hex, 'hex')
|
var fn = crypto[algorithm]
|
||||||
var actual = crypto.hash160(data).toString('hex')
|
var expected = f[algorithm]
|
||||||
|
|
||||||
assert.equal(actual, fixtures.after.hash160[i])
|
it('returns ' + expected + ' for ' + f.hex, function () {
|
||||||
})
|
var data = new Buffer(f.hex, 'hex')
|
||||||
})
|
var actual = fn(data).toString('hex')
|
||||||
})
|
|
||||||
|
|
||||||
describe('HASH256', function () {
|
assert.equal(actual, expected)
|
||||||
it('matches the test vectors', function () {
|
})
|
||||||
fixtures.before.hex.forEach(function (hex, i) {
|
|
||||||
var data = new Buffer(hex, 'hex')
|
|
||||||
var actual = crypto.hash256(data).toString('hex')
|
|
||||||
|
|
||||||
assert.equal(actual, fixtures.after.hash256[i])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('RIPEMD160', function () {
|
|
||||||
it('matches the test vectors', function () {
|
|
||||||
fixtures.before.hex.forEach(function (hex, i) {
|
|
||||||
var data = new Buffer(hex, 'hex')
|
|
||||||
var actual = crypto.ripemd160(data).toString('hex')
|
|
||||||
|
|
||||||
assert.equal(actual, fixtures.after.ripemd160[i])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('SHA1', function () {
|
|
||||||
it('matches the test vectors', function () {
|
|
||||||
fixtures.before.hex.forEach(function (hex, i) {
|
|
||||||
var data = new Buffer(hex, 'hex')
|
|
||||||
var actual = crypto.sha1(data).toString('hex')
|
|
||||||
|
|
||||||
assert.equal(actual, fixtures.after.sha1[i])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('SHA256', function () {
|
|
||||||
it('matches the test vectors', function () {
|
|
||||||
fixtures.before.hex.forEach(function (hex, i) {
|
|
||||||
var data = new Buffer(hex, 'hex')
|
|
||||||
var actual = crypto.sha256(data).toString('hex')
|
|
||||||
|
|
||||||
assert.equal(actual, fixtures.after.sha256[i])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('HmacSHA256', function () {
|
|
||||||
it('matches the test vectors', function () {
|
|
||||||
fixtures.before.hex.forEach(function (hex, i) {
|
|
||||||
var data = new Buffer(hex, 'hex')
|
|
||||||
var secret = new Buffer(fixtures.before.secret)
|
|
||||||
var actual = crypto.HmacSHA256(data, secret).toString('hex')
|
|
||||||
|
|
||||||
assert.equal(actual, fixtures.after.hmacsha256[i])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('HmacSHA512', function () {
|
|
||||||
it('matches the test vectors', function () {
|
|
||||||
fixtures.before.hex.forEach(function (hex, i) {
|
|
||||||
var data = new Buffer(hex, 'hex')
|
|
||||||
var secret = new Buffer(fixtures.before.secret)
|
|
||||||
var actual = crypto.HmacSHA512(data, secret).toString('hex')
|
|
||||||
|
|
||||||
assert.equal(actual, fixtures.after.hmacsha512[i])
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -31,17 +31,6 @@ describe('ecdsa', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
fixtures.valid.ecdsa.forEach(function (f) {
|
|
||||||
it('(deprecated) for "' + f.message + '"', function () {
|
|
||||||
var d = BigInteger.fromHex(f.d)
|
|
||||||
var h1 = crypto.sha256(f.message)
|
|
||||||
|
|
||||||
var k = ecdsa.deterministicGenerateK(curve, h1, d) // default checkSig
|
|
||||||
assert.equal(k.toHex(), f.k)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('loops until an appropriate k value is found', sinon.test(function () {
|
it('loops until an appropriate k value is found', sinon.test(function () {
|
||||||
this.mock(BigInteger).expects('fromBuffer')
|
this.mock(BigInteger).expects('fromBuffer')
|
||||||
.exactly(3)
|
.exactly(3)
|
||||||
|
|
83
test/fixtures/crypto.json
vendored
83
test/fixtures/crypto.json
vendored
|
@ -1,55 +1,36 @@
|
||||||
{
|
{
|
||||||
"before": {
|
"valid": [
|
||||||
"secret": "vires is numeris",
|
{
|
||||||
"hex": [
|
"hex": "0000000000000001",
|
||||||
"0000000000000001",
|
"hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8",
|
||||||
"0101010101010101",
|
"hash256": "3ae5c198d17634e79059c2cd735491553d22c4e09d1d9fea3ecf214565df2284",
|
||||||
"FFFFFFFFFFFFFFFF",
|
"ripemd160": "8d1a05d1bc08870968eb8a81ad4393fd3aac6633",
|
||||||
"4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e65632061742066617563696275732073617069656e2c2076656c20666163696c6973697320617263752e20536564207574206d61737361206e6962682e205574206d6f6c6c69732070756c76696e6172206d617373612e20557420756c6c616d636f7270657220646f6c6f7220656e696d2c20696e206d6f6c657374696520656e696d20636f6e64696d656e74756d2061632e20416c697175616d206572617420766f6c75747061742e204e756c6c6120736f64616c657320617420647569206e656320"
|
"sha1": "cb473678976f425d6ec1339838f11011007ad27d",
|
||||||
]
|
"sha256": "cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50"
|
||||||
},
|
},
|
||||||
"after": {
|
{
|
||||||
"hash160": [
|
"hex": "0101010101010101",
|
||||||
"cdb00698f02afd929ffabea308340fa99ac2afa8",
|
"hash160": "abaf1119f83e384210fe8e222eac76e2f0da39dc",
|
||||||
"abaf1119f83e384210fe8e222eac76e2f0da39dc",
|
"hash256": "728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23",
|
||||||
"f86221f5a1fca059a865c0b7d374dfa9d5f3aeb4",
|
"ripemd160": "5825701b4b9767fd35063b286dca3582853e0630",
|
||||||
"9763e6b367c363bd6b88a7b361c98e6beee243a5"
|
"sha1": "c0357a32ed1f6a03be92dd094476f7f1a2e214ec",
|
||||||
],
|
"sha256": "04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061"
|
||||||
"hash256": [
|
},
|
||||||
"3ae5c198d17634e79059c2cd735491553d22c4e09d1d9fea3ecf214565df2284",
|
{
|
||||||
"728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23",
|
"hex": "ffffffffffffffff",
|
||||||
"752adad0a7b9ceca853768aebb6965eca126a62965f698a0c1bc43d83db632ad",
|
"hash160": "f86221f5a1fca059a865c0b7d374dfa9d5f3aeb4",
|
||||||
"033588797115feb3545052670cac2a46584ab3cb460de63756ee0275e66b5799"
|
"hash256": "752adad0a7b9ceca853768aebb6965eca126a62965f698a0c1bc43d83db632ad",
|
||||||
],
|
"ripemd160": "cb760221600ed34337ca3ab70016b5f58c838120",
|
||||||
"ripemd160": [
|
"sha1": "be673e8a56eaa9d8c1d35064866701c11ef8e089",
|
||||||
"8d1a05d1bc08870968eb8a81ad4393fd3aac6633",
|
"sha256": "12a3ae445661ce5dee78d0650d33362dec29c4f82af05e7e57fb595bbbacf0ca"
|
||||||
"5825701b4b9767fd35063b286dca3582853e0630",
|
},
|
||||||
"cb760221600ed34337ca3ab70016b5f58c838120",
|
{
|
||||||
"cad8593dcdef12ee334c97bab9787f07b3f3a1a5"
|
"hex": "4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e65632061742066617563696275732073617069656e2c2076656c20666163696c6973697320617263752e20536564207574206d61737361206e6962682e205574206d6f6c6c69732070756c76696e6172206d617373612e20557420756c6c616d636f7270657220646f6c6f7220656e696d2c20696e206d6f6c657374696520656e696d20636f6e64696d656e74756d2061632e20416c697175616d206572617420766f6c75747061742e204e756c6c6120736f64616c657320617420647569206e656320",
|
||||||
],
|
"hash160": "9763e6b367c363bd6b88a7b361c98e6beee243a5",
|
||||||
"sha1": [
|
"hash256": "033588797115feb3545052670cac2a46584ab3cb460de63756ee0275e66b5799",
|
||||||
"cb473678976f425d6ec1339838f11011007ad27d",
|
"ripemd160": "cad8593dcdef12ee334c97bab9787f07b3f3a1a5",
|
||||||
"c0357a32ed1f6a03be92dd094476f7f1a2e214ec",
|
"sha1": "10d96fb43aca84e342206887bbeed3065d4e4344",
|
||||||
"be673e8a56eaa9d8c1d35064866701c11ef8e089",
|
"sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da"
|
||||||
"10d96fb43aca84e342206887bbeed3065d4e4344"
|
}
|
||||||
],
|
|
||||||
"sha256": [
|
|
||||||
"cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50",
|
|
||||||
"04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061",
|
|
||||||
"12a3ae445661ce5dee78d0650d33362dec29c4f82af05e7e57fb595bbbacf0ca",
|
|
||||||
"a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da"
|
|
||||||
],
|
|
||||||
"hmacsha256": [
|
|
||||||
"73442dc8dd7f71a106a20fddd49d31856b1db12956c75070c8186b0b3eb71251",
|
|
||||||
"7204c72af7c73f5e84447a752dc8a2708f91b896f29de5fcf4b7f42f13a30c6e",
|
|
||||||
"a03c2ac6e9ca86678b5608a3d8682de46d17026f5fac4fd7147d2e5022061833",
|
|
||||||
"a780cd6e5c29cf11f756536ea5779992687c1b3b5e37f31b027a392d94e91fb8"
|
|
||||||
],
|
|
||||||
"hmacsha512": [
|
|
||||||
"4c0595aed1f5d066ea9f797727c060eb86cb55ff29d4d4fd2cd0ad3a012386763aea604c030619c79aa7fd8d03cda1b73a9ebd17906a3d2a350108d1a98b24ac",
|
|
||||||
"f80b90d63b804b3d2ab03b9bfb3ac94ee271352eb8bddfb6b4f5cf2a4fc9176acea35f517728e64943d1eb8af1e4674a114082c81bc8874d88b408b3b406d6a4",
|
|
||||||
"134cf60c30a5cd412c7a5cd6c3f878279e139b47c19550b7456fa137fbf90e580ae0a923a22052f42ec801ac658db32821e271161b563eac4926285ba6b8f410",
|
|
||||||
"7dee95aa3c462d3eb7ecb61536cb215e471d1fa73d8643a967905946e26c536588c5058abd5a049a22b987db95a7fb420f3bff12359dc53d03d7ce7df714e029"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
|
5
test/fixtures/mainnet_tx.json
vendored
5
test/fixtures/mainnet_tx.json
vendored
File diff suppressed because one or more lines are too long
|
@ -139,15 +139,6 @@ describe('HDNode', function () {
|
||||||
assert.equal(hd.toBase58(), f.master.base58Priv)
|
assert.equal(hd.toBase58(), f.master.base58Priv)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
it('fails when there is no private key', function () {
|
|
||||||
var hd = HDNode.fromBase58(fixtures.valid[0].master.base58)
|
|
||||||
|
|
||||||
assert.throws(function () {
|
|
||||||
hd.toBase58(true)
|
|
||||||
}, /Missing private key/)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromBase58', function () {
|
describe('fromBase58', function () {
|
||||||
|
@ -178,60 +169,6 @@ describe('HDNode', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromBuffer/fromHex', function () {
|
|
||||||
fixtures.valid.forEach(function (f) {
|
|
||||||
it('imports ' + f.master.hex + ' (public) correctly', function () {
|
|
||||||
var hd = HDNode.fromHex(f.master.hex)
|
|
||||||
|
|
||||||
assert.equal(hd.toBuffer().toString('hex'), f.master.hex)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f) {
|
|
||||||
it('imports ' + f.master.hexPriv + ' (private) correctly', function () {
|
|
||||||
var hd = HDNode.fromHex(f.master.hexPriv)
|
|
||||||
|
|
||||||
assert.equal(hd.toBuffer().toString('hex'), f.master.hexPriv)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
fixtures.invalid.fromBuffer.forEach(function (f) {
|
|
||||||
it('throws on ' + f.hex, function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
HDNode.fromHex(f.hex)
|
|
||||||
}, new RegExp(f.exception))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('toBuffer/toHex', function () {
|
|
||||||
fixtures.valid.forEach(function (f) {
|
|
||||||
it('exports ' + f.master.hex + ' (public) correctly', function () {
|
|
||||||
var hd = HDNode.fromSeedHex(f.master.seed).neutered()
|
|
||||||
|
|
||||||
assert.equal(hd.toHex(), f.master.hex)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f) {
|
|
||||||
it('exports ' + f.master.hexPriv + ' (private) correctly', function () {
|
|
||||||
var network = networks[f.network]
|
|
||||||
var hd = HDNode.fromSeedHex(f.master.seed, network)
|
|
||||||
|
|
||||||
assert.equal(hd.toHex(), f.master.hexPriv)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
it('fails when there is no private key', function () {
|
|
||||||
var hd = HDNode.fromHex(fixtures.valid[0].master.hex)
|
|
||||||
|
|
||||||
assert.throws(function () {
|
|
||||||
hd.toHex(true)
|
|
||||||
}, /Missing private key/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getIdentifier', function () {
|
describe('getIdentifier', function () {
|
||||||
var f = fixtures.valid[0]
|
var f = fixtures.valid[0]
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe('bitcoinjs-lib (advanced)', function () {
|
||||||
var key = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss')
|
var key = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss')
|
||||||
var message = 'This is an example of a signed message.'
|
var message = 'This is an example of a signed message.'
|
||||||
|
|
||||||
var signature = bitcoin.Message.sign(key, message)
|
var signature = bitcoin.message.sign(key, message)
|
||||||
assert.equal(signature.toString('base64'), 'G9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=')
|
assert.equal(signature.toString('base64'), 'G9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ describe('bitcoinjs-lib (advanced)', function () {
|
||||||
var signature = 'HJLQlDWLyb1Ef8bQKEISzFbDAKctIlaqOpGbrk3YVtRsjmC61lpE5ErkPRUFtDKtx98vHFGUWlFhsh3DiW6N0rE'
|
var signature = 'HJLQlDWLyb1Ef8bQKEISzFbDAKctIlaqOpGbrk3YVtRsjmC61lpE5ErkPRUFtDKtx98vHFGUWlFhsh3DiW6N0rE'
|
||||||
var message = 'This is an example of a signed message.'
|
var message = 'This is an example of a signed message.'
|
||||||
|
|
||||||
assert(bitcoin.Message.verify(address, signature, message))
|
assert(bitcoin.message.verify(address, signature, message))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create an OP_RETURN transaction', function (done) {
|
it('can create an OP_RETURN transaction', function (done) {
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
/* global describe, it */
|
/* global describe, it */
|
||||||
|
|
||||||
var assert = require('assert')
|
var assert = require('assert')
|
||||||
|
var message = require('../src/message')
|
||||||
var networks = require('../src/networks')
|
var networks = require('../src/networks')
|
||||||
|
|
||||||
var Address = require('../src/address')
|
var Address = require('../src/address')
|
||||||
var BigInteger = require('bigi')
|
var BigInteger = require('bigi')
|
||||||
var ECKey = require('../src/eckey')
|
var ECKey = require('../src/eckey')
|
||||||
var Message = require('../src/message')
|
|
||||||
|
|
||||||
var fixtures = require('./fixtures/message.json')
|
var fixtures = require('./fixtures/message.json')
|
||||||
|
|
||||||
describe('Message', function () {
|
describe('message', function () {
|
||||||
describe('magicHash', function () {
|
describe('magicHash', function () {
|
||||||
fixtures.valid.magicHash.forEach(function (f) {
|
fixtures.valid.magicHash.forEach(function (f) {
|
||||||
it('produces the correct magicHash for "' + f.message + '" (' + f.network + ')', function () {
|
it('produces the correct magicHash for "' + f.message + '" (' + f.network + ')', function () {
|
||||||
var network = networks[f.network]
|
var network = networks[f.network]
|
||||||
var actual = Message.magicHash(f.message, network)
|
var actual = message.magicHash(f.message, network)
|
||||||
|
|
||||||
assert.equal(actual.toString('hex'), f.magicHash)
|
assert.equal(actual.toString('hex'), f.magicHash)
|
||||||
})
|
})
|
||||||
|
@ -28,24 +28,24 @@ describe('Message', function () {
|
||||||
var network = networks[f.network]
|
var network = networks[f.network]
|
||||||
|
|
||||||
var address = Address.fromBase58Check(f.address)
|
var address = Address.fromBase58Check(f.address)
|
||||||
assert(Message.verify(address, f.signature, f.message, network))
|
assert(message.verify(address, f.signature, f.message, network))
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.valid.verify.forEach(function (f) {
|
fixtures.valid.verify.forEach(function (f) {
|
||||||
it('verifies a valid signature for "' + f.message + '" (' + f.network + ')', function () {
|
it('verifies a valid signature for "' + f.message + '" (' + f.network + ')', function () {
|
||||||
var network = networks[f.network]
|
var network = networks[f.network]
|
||||||
|
|
||||||
assert(Message.verify(f.address, f.signature, f.message, network))
|
assert(message.verify(f.address, f.signature, f.message, network))
|
||||||
|
|
||||||
if (f.compressed) {
|
if (f.compressed) {
|
||||||
assert(Message.verify(f.compressed.address, f.compressed.signature, f.message, network))
|
assert(message.verify(f.compressed.address, f.compressed.signature, f.message, network))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.verify.forEach(function (f) {
|
fixtures.invalid.verify.forEach(function (f) {
|
||||||
it(f.description, function () {
|
it(f.description, function () {
|
||||||
assert(!Message.verify(f.address, f.signature, f.message))
|
assert(!message.verify(f.address, f.signature, f.message))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -56,12 +56,12 @@ describe('Message', function () {
|
||||||
var network = networks[f.network]
|
var network = networks[f.network]
|
||||||
|
|
||||||
var privKey = new ECKey(new BigInteger(f.d), false)
|
var privKey = new ECKey(new BigInteger(f.d), false)
|
||||||
var signature = Message.sign(privKey, f.message, network)
|
var signature = message.sign(privKey, f.message, network)
|
||||||
assert.equal(signature.toString('base64'), f.signature)
|
assert.equal(signature.toString('base64'), f.signature)
|
||||||
|
|
||||||
if (f.compressed) {
|
if (f.compressed) {
|
||||||
var compressedPrivKey = new ECKey(new BigInteger(f.d))
|
var compressedPrivKey = new ECKey(new BigInteger(f.d))
|
||||||
var compressedSignature = Message.sign(compressedPrivKey, f.message)
|
var compressedSignature = message.sign(compressedPrivKey, f.message)
|
||||||
|
|
||||||
assert.equal(compressedSignature.toString('base64'), f.compressed.signature)
|
assert.equal(compressedSignature.toString('base64'), f.compressed.signature)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
/* global describe, it, beforeEach */
|
/* global describe, it, beforeEach */
|
||||||
|
|
||||||
var assert = require('assert')
|
var assert = require('assert')
|
||||||
var scripts = require('../src/scripts')
|
|
||||||
|
|
||||||
var Address = require('../src/address')
|
|
||||||
var ECKey = require('../src/eckey')
|
|
||||||
var Transaction = require('../src/transaction')
|
var Transaction = require('../src/transaction')
|
||||||
var Script = require('../src/script')
|
var Script = require('../src/script')
|
||||||
|
|
||||||
|
@ -65,21 +62,10 @@ describe('Transaction', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addInput', function () {
|
describe('addInput', function () {
|
||||||
// FIXME: not as pretty as could be
|
var prevTxHash
|
||||||
// Probably a bit representative of the API
|
|
||||||
var prevTxHash, prevTxId, prevTx
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
var f = fixtures.valid[0]
|
var f = fixtures.valid[0]
|
||||||
prevTx = Transaction.fromHex(f.hex)
|
prevTxHash = new Buffer(f.hash, 'hex')
|
||||||
prevTxHash = prevTx.getHash()
|
|
||||||
prevTxId = prevTx.getId()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('accepts a transaction id', function () {
|
|
||||||
var tx = new Transaction()
|
|
||||||
tx.addInput(prevTxId, 0)
|
|
||||||
|
|
||||||
assert.deepEqual(tx.ins[0].hash, prevTxHash)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts a transaction hash', function () {
|
it('accepts a transaction hash', function () {
|
||||||
|
@ -89,13 +75,6 @@ describe('Transaction', function () {
|
||||||
assert.deepEqual(tx.ins[0].hash, prevTxHash)
|
assert.deepEqual(tx.ins[0].hash, prevTxHash)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts a Transaction object', function () {
|
|
||||||
var tx = new Transaction()
|
|
||||||
tx.addInput(prevTx, 0)
|
|
||||||
|
|
||||||
assert.deepEqual(tx.ins[0].hash, prevTxHash)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an index', function () {
|
it('returns an index', function () {
|
||||||
var tx = new Transaction()
|
var tx = new Transaction()
|
||||||
assert.equal(tx.addInput(prevTxHash, 0), 0)
|
assert.equal(tx.addInput(prevTxHash, 0), 0)
|
||||||
|
@ -129,43 +108,19 @@ describe('Transaction', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addOutput', function () {
|
describe('addOutput', function () {
|
||||||
// FIXME: not as pretty as could be
|
fixtures.valid.forEach(function (f) {
|
||||||
// Probably a bit representative of the API
|
it('should add the outputs for ' + f.id + ' correctly', function () {
|
||||||
var destAddressB58, destAddress, destScript
|
|
||||||
beforeEach(function () {
|
|
||||||
destAddressB58 = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3'
|
|
||||||
destAddress = Address.fromBase58Check(destAddressB58)
|
|
||||||
destScript = destAddress.toOutputScript()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('accepts an address string', function () {
|
|
||||||
var tx = new Transaction()
|
var tx = new Transaction()
|
||||||
tx.addOutput(destAddressB58, 40000)
|
|
||||||
|
|
||||||
assert.deepEqual(tx.outs[0].script, destScript)
|
f.raw.outs.forEach(function (txOut, i) {
|
||||||
assert.equal(tx.outs[0].value, 40000)
|
var scriptPubKey = Script.fromASM(txOut.script)
|
||||||
|
var j = tx.addOutput(scriptPubKey, txOut.value)
|
||||||
|
|
||||||
|
assert.equal(i, j)
|
||||||
|
assert.equal(tx.outs[i].script, scriptPubKey)
|
||||||
|
assert.equal(tx.outs[i].value, txOut.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts an Address', function () {
|
|
||||||
var tx = new Transaction()
|
|
||||||
tx.addOutput(destAddress, 40000)
|
|
||||||
|
|
||||||
assert.deepEqual(tx.outs[0].script, destScript)
|
|
||||||
assert.equal(tx.outs[0].value, 40000)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts a scriptPubKey', function () {
|
|
||||||
var tx = new Transaction()
|
|
||||||
tx.addOutput(destScript, 40000)
|
|
||||||
|
|
||||||
assert.deepEqual(tx.outs[0].script, destScript)
|
|
||||||
assert.equal(tx.outs[0].value, 40000)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an index', function () {
|
|
||||||
var tx = new Transaction()
|
|
||||||
assert.equal(tx.addOutput(destScript, 40000), 0)
|
|
||||||
assert.equal(tx.addOutput(destScript, 40000), 1)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -192,9 +147,8 @@ describe('Transaction', function () {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(function (f) {
|
||||||
it('should return the id for ' + f.id, function () {
|
it('should return the id for ' + f.id, function () {
|
||||||
var tx = Transaction.fromHex(f.hex)
|
var tx = Transaction.fromHex(f.hex)
|
||||||
var actual = tx.getId()
|
|
||||||
|
|
||||||
assert.equal(actual, f.id)
|
assert.equal(tx.getId(), f.id)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -203,48 +157,12 @@ describe('Transaction', function () {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(function (f) {
|
||||||
it('should return the hash for ' + f.id, function () {
|
it('should return the hash for ' + f.id, function () {
|
||||||
var tx = Transaction.fromHex(f.hex)
|
var tx = Transaction.fromHex(f.hex)
|
||||||
var actual = tx.getHash().toString('hex')
|
|
||||||
|
|
||||||
assert.equal(actual, f.hash)
|
assert.deepEqual(tx.getHash().toString('hex'), f.hash)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// hashForSignature: [Function],
|
// hashForSignature: [Function],
|
||||||
|
|
||||||
// FIXME: remove in 2.x.y
|
|
||||||
describe('signInput/validateInput', function () {
|
|
||||||
it('works for multi-sig redeem script', function () {
|
|
||||||
var tx = new Transaction()
|
|
||||||
tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0)
|
|
||||||
tx.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1)
|
|
||||||
|
|
||||||
var privKeys = [
|
|
||||||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf',
|
|
||||||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAvUcVfH'
|
|
||||||
].map(function (wif) {
|
|
||||||
return ECKey.fromWIF(wif)
|
|
||||||
})
|
|
||||||
var pubKeys = privKeys.map(function (eck) {
|
|
||||||
return eck.pub
|
|
||||||
})
|
|
||||||
var redeemScript = scripts.multisigOutput(2, pubKeys)
|
|
||||||
|
|
||||||
var signatures = privKeys.map(function (privKey) {
|
|
||||||
return tx.signInput(0, redeemScript, privKey)
|
|
||||||
})
|
|
||||||
|
|
||||||
var redeemScriptSig = scripts.multisigInput(signatures)
|
|
||||||
var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript)
|
|
||||||
tx.setInputScript(0, scriptSig)
|
|
||||||
|
|
||||||
signatures.forEach(function (sig, i) {
|
|
||||||
assert(tx.validateInput(0, redeemScript, privKeys[i].pub, sig))
|
|
||||||
})
|
|
||||||
|
|
||||||
var expected = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d600000000fd1b0100483045022100e5be20d440b2bbbc886161f9095fa6d0bca749a4e41d30064f30eb97adc7a1f5022061af132890d8e4e90fedff5e9365aeeb77021afd8ef1d5c114d575512e9a130a0147304402205054e38e9d7b5c10481b6b4991fde5704cd94d49e344406e3c2ce4d18a43bf8e022051d7ba8479865b53a48bee0cce86e89a25633af5b2918aa276859489e232f51c014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0101000000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000'
|
|
||||||
assert.equal(tx.toHex(), expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
var assert = require('assert')
|
var assert = require('assert')
|
||||||
|
|
||||||
|
var Address = require('../src/address')
|
||||||
var BigInteger = require('bigi')
|
var BigInteger = require('bigi')
|
||||||
var ECKey = require('../src/eckey')
|
var ECKey = require('../src/eckey')
|
||||||
var Script = require('../src/script')
|
var Script = require('../src/script')
|
||||||
|
@ -62,8 +63,8 @@ describe('TransactionBuilder', function () {
|
||||||
txb = new TransactionBuilder()
|
txb = new TransactionBuilder()
|
||||||
|
|
||||||
prevTx = new Transaction()
|
prevTx = new Transaction()
|
||||||
prevTx.addOutput('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 0)
|
prevTx.addOutput(Address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH').toOutputScript(), 0)
|
||||||
prevTx.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 1)
|
prevTx.addOutput(Address.fromBase58Check('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP').toOutputScript(), 1)
|
||||||
prevTxHash = prevTx.getHash()
|
prevTxHash = prevTx.getHash()
|
||||||
|
|
||||||
privKey = new ECKey(BigInteger.ONE, false)
|
privKey = new ECKey(BigInteger.ONE, false)
|
||||||
|
@ -121,6 +122,33 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addOutput', function () {
|
describe('addOutput', function () {
|
||||||
|
it('accepts an address string and value', function () {
|
||||||
|
var vout = txb.addOutput(privAddress.toBase58Check(), 1000)
|
||||||
|
assert.equal(vout, 0)
|
||||||
|
|
||||||
|
var txout = txb.tx.outs[0]
|
||||||
|
assert.deepEqual(txout.script, privScript)
|
||||||
|
assert.equal(txout.value, 1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts an Address object and value', function () {
|
||||||
|
var vout = txb.addOutput(privAddress, 1000)
|
||||||
|
assert.equal(vout, 0)
|
||||||
|
|
||||||
|
var txout = txb.tx.outs[0]
|
||||||
|
assert.deepEqual(txout.script, privScript)
|
||||||
|
assert.equal(txout.value, 1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts a ScriptPubKey and value', function () {
|
||||||
|
var vout = txb.addOutput(privScript, 1000)
|
||||||
|
assert.equal(vout, 0)
|
||||||
|
|
||||||
|
var txout = txb.tx.outs[0]
|
||||||
|
assert.deepEqual(txout.script, privScript)
|
||||||
|
assert.equal(txout.value, 1000)
|
||||||
|
})
|
||||||
|
|
||||||
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function () {
|
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function () {
|
||||||
txb.addInput(prevTxHash, 0)
|
txb.addInput(prevTxHash, 0)
|
||||||
txb.addOutput(privScript, 2000)
|
txb.addOutput(privScript, 2000)
|
||||||
|
|
691
test/wallet.js
691
test/wallet.js
|
@ -1,691 +0,0 @@
|
||||||
/* global describe, it, beforeEach, afterEach */
|
|
||||||
|
|
||||||
var assert = require('assert')
|
|
||||||
var bufferutils = require('../src/bufferutils')
|
|
||||||
var crypto = require('../src/crypto')
|
|
||||||
var networks = require('../src/networks')
|
|
||||||
var sinon = require('sinon')
|
|
||||||
var scripts = require('../src/scripts')
|
|
||||||
|
|
||||||
var Address = require('../src/address')
|
|
||||||
var HDNode = require('../src/hdnode')
|
|
||||||
var Transaction = require('../src/transaction')
|
|
||||||
var TransactionBuilder = require('../src/transaction_builder')
|
|
||||||
var Wallet = require('../src/wallet')
|
|
||||||
|
|
||||||
var fixtureTxes = require('./fixtures/mainnet_tx')
|
|
||||||
var fixtureTx1Hex = fixtureTxes.prevTx
|
|
||||||
var fixtureTx2Hex = fixtureTxes.tx
|
|
||||||
|
|
||||||
function fakeTxHash (i) {
|
|
||||||
var hash = new Buffer(32)
|
|
||||||
hash.fill(i)
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
function fakeTxId (i) {
|
|
||||||
var hash = fakeTxHash(i)
|
|
||||||
Array.prototype.reverse.call(hash)
|
|
||||||
return hash.toString('hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Wallet', function () {
|
|
||||||
var seed
|
|
||||||
beforeEach(function () {
|
|
||||||
seed = crypto.sha256("don't use a string seed like this in real life")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('constructor', function () {
|
|
||||||
var wallet
|
|
||||||
beforeEach(function () {
|
|
||||||
wallet = new Wallet(seed)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('defaults to Bitcoin network', function () {
|
|
||||||
assert.equal(wallet.getMasterKey().network, networks.bitcoin)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("generates m/0' as the main account", function () {
|
|
||||||
var mainAccount = wallet.getAccountZero()
|
|
||||||
assert.equal(mainAccount.index, 0 + HDNode.HIGHEST_BIT)
|
|
||||||
assert.equal(mainAccount.depth, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("generates m/0'/0 as the external account", function () {
|
|
||||||
var account = wallet.getExternalAccount()
|
|
||||||
assert.equal(account.index, 0)
|
|
||||||
assert.equal(account.depth, 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("generates m/0'/1 as the internal account", function () {
|
|
||||||
var account = wallet.getInternalAccount()
|
|
||||||
assert.equal(account.index, 1)
|
|
||||||
assert.equal(account.depth, 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when seed is not specified', function () {
|
|
||||||
it('generates a seed', function () {
|
|
||||||
var wallet = new Wallet()
|
|
||||||
assert(wallet.getMasterKey())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('constructor options', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
wallet = new Wallet(seed, networks.testnet)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('uses the network if specified', function () {
|
|
||||||
assert.equal(wallet.getMasterKey().network, networks.testnet)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('newMasterKey', function () {
|
|
||||||
it('resets accounts', function () {
|
|
||||||
var wallet = new Wallet()
|
|
||||||
var oldAccountZero = wallet.getAccountZero()
|
|
||||||
var oldExternalAccount = wallet.getExternalAccount()
|
|
||||||
var oldInternalAccount = wallet.getInternalAccount()
|
|
||||||
|
|
||||||
wallet.newMasterKey(seed)
|
|
||||||
assertNotEqual(wallet.getAccountZero(), oldAccountZero)
|
|
||||||
assertNotEqual(wallet.getExternalAccount(), oldExternalAccount)
|
|
||||||
assertNotEqual(wallet.getInternalAccount(), oldInternalAccount)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('resets addresses', function () {
|
|
||||||
var wallet = new Wallet()
|
|
||||||
wallet.generateAddress()
|
|
||||||
wallet.generateChangeAddress()
|
|
||||||
var oldAddresses = wallet.addresses
|
|
||||||
var oldChangeAddresses = wallet.changeAddresses
|
|
||||||
assert.notDeepEqual(oldAddresses, [])
|
|
||||||
assert.notDeepEqual(oldChangeAddresses, [])
|
|
||||||
|
|
||||||
wallet.newMasterKey(seed)
|
|
||||||
assert.deepEqual(wallet.addresses, [])
|
|
||||||
assert.deepEqual(wallet.changeAddresses, [])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('generateAddress', function () {
|
|
||||||
it('generate receiving addresses', function () {
|
|
||||||
var wallet = new Wallet(seed, networks.testnet)
|
|
||||||
var expectedAddresses = [
|
|
||||||
'n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa',
|
|
||||||
'n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X'
|
|
||||||
]
|
|
||||||
|
|
||||||
assert.equal(wallet.generateAddress(), expectedAddresses[0])
|
|
||||||
assert.equal(wallet.generateAddress(), expectedAddresses[1])
|
|
||||||
assert.deepEqual(wallet.addresses, expectedAddresses)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('generateChangeAddress', function () {
|
|
||||||
it('generates change addresses', function () {
|
|
||||||
var wallet = new Wallet(seed, networks.testnet)
|
|
||||||
var expectedAddresses = ['mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn']
|
|
||||||
|
|
||||||
assert.equal(wallet.generateChangeAddress(), expectedAddresses[0])
|
|
||||||
assert.deepEqual(wallet.changeAddresses, expectedAddresses)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getPrivateKey', function () {
|
|
||||||
it('returns the private key at the given index of external account', function () {
|
|
||||||
var wallet = new Wallet(seed, networks.testnet)
|
|
||||||
|
|
||||||
assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).privKey)
|
|
||||||
assertEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).privKey)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getInternalPrivateKey', function () {
|
|
||||||
it('returns the private key at the given index of internal account', function () {
|
|
||||||
var wallet = new Wallet(seed, networks.testnet)
|
|
||||||
|
|
||||||
assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).privKey)
|
|
||||||
assertEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).privKey)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getPrivateKeyForAddress', function () {
|
|
||||||
it('returns the private key for the given address', function () {
|
|
||||||
var wallet = new Wallet(seed, networks.testnet)
|
|
||||||
wallet.generateChangeAddress()
|
|
||||||
wallet.generateAddress()
|
|
||||||
wallet.generateAddress()
|
|
||||||
|
|
||||||
assertEqual(
|
|
||||||
wallet.getPrivateKeyForAddress('n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X'),
|
|
||||||
wallet.getExternalAccount().derive(1).privKey
|
|
||||||
)
|
|
||||||
assertEqual(
|
|
||||||
wallet.getPrivateKeyForAddress('mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn'),
|
|
||||||
wallet.getInternalAccount().derive(0).privKey
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('raises an error when address is not found', function () {
|
|
||||||
var wallet = new Wallet(seed, networks.testnet)
|
|
||||||
|
|
||||||
assert.throws(function () {
|
|
||||||
wallet.getPrivateKeyForAddress('n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X')
|
|
||||||
}, /Unknown address. Make sure the address is from the keychain and has been generated/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Unspent Outputs', function () {
|
|
||||||
var utxo
|
|
||||||
var wallet
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
utxo = {
|
|
||||||
'address': '1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv',
|
|
||||||
'confirmations': 1,
|
|
||||||
'index': 0,
|
|
||||||
'txId': fakeTxId(6),
|
|
||||||
'value': 20000,
|
|
||||||
'pending': false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('on construction', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
wallet = new Wallet(seed, networks.bitcoin)
|
|
||||||
wallet.setUnspentOutputs([utxo])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches the expected behaviour', function () {
|
|
||||||
var output = wallet.unspents[0]
|
|
||||||
|
|
||||||
assert.equal(output.address, utxo.address)
|
|
||||||
assert.equal(output.value, utxo.value)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getBalance', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
var utxo1 = cloneObject(utxo)
|
|
||||||
utxo1.hash = fakeTxId(5)
|
|
||||||
|
|
||||||
wallet = new Wallet(seed, networks.bitcoin)
|
|
||||||
wallet.setUnspentOutputs([utxo, utxo1])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sums over utxo values', function () {
|
|
||||||
assert.equal(wallet.getBalance(), 40000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getUnspentOutputs', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
wallet = new Wallet(seed, networks.bitcoin)
|
|
||||||
wallet.setUnspentOutputs([utxo])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('parses wallet unspents to the expected format', function () {
|
|
||||||
var outputs = wallet.getUnspentOutputs()
|
|
||||||
var output = outputs[0]
|
|
||||||
|
|
||||||
assert.equal(utxo.address, output.address)
|
|
||||||
assert.equal(utxo.index, output.index)
|
|
||||||
assert.equal(utxo.value, output.value)
|
|
||||||
|
|
||||||
// FIXME: remove in 2.0.0
|
|
||||||
assert.equal(utxo.txId, output.hash)
|
|
||||||
assert.equal(utxo.pending, output.pending)
|
|
||||||
|
|
||||||
// new in 2.0.0
|
|
||||||
assert.equal(utxo.txId, output.txId)
|
|
||||||
assert.equal(utxo.confirmations, output.confirmations)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("ignores spent unspents (outputs with 'spent' property)", function () {
|
|
||||||
var unspent = wallet.unspents[0]
|
|
||||||
unspent.pending = true
|
|
||||||
unspent.spent = true
|
|
||||||
assert.deepEqual(wallet.getUnspentOutputs(), [])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('setUnspentOutputs', function () {
|
|
||||||
var utxo
|
|
||||||
var wallet
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
utxo = {
|
|
||||||
hash: fakeTxId(0),
|
|
||||||
index: 0,
|
|
||||||
address: '115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi',
|
|
||||||
value: 500000
|
|
||||||
}
|
|
||||||
|
|
||||||
wallet = new Wallet(seed, networks.bitcoin)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches the expected behaviour', function () {
|
|
||||||
wallet.setUnspentOutputs([utxo])
|
|
||||||
|
|
||||||
var output = wallet.unspents[0]
|
|
||||||
assert.equal(output.value, utxo.value)
|
|
||||||
assert.equal(output.address, utxo.address)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('required fields', function () {
|
|
||||||
['index', 'address', 'hash', 'value'].forEach(function (field) {
|
|
||||||
it('throws an error when ' + field + ' is missing', function () {
|
|
||||||
delete utxo[field]
|
|
||||||
|
|
||||||
assert.throws(function () {
|
|
||||||
wallet.setUnspentOutputs([utxo])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Process transaction', function () {
|
|
||||||
var wallet
|
|
||||||
beforeEach(function () {
|
|
||||||
wallet = new Wallet(seed)
|
|
||||||
})
|
|
||||||
|
|
||||||
var addresses
|
|
||||||
var tx
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
addresses = [
|
|
||||||
'115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi',
|
|
||||||
'1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd',
|
|
||||||
'1BBjuhF2jHxu7tPinyQGCuaNhEs6f5u59u'
|
|
||||||
]
|
|
||||||
|
|
||||||
tx = Transaction.fromHex(fixtureTx1Hex)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('processPendingTx', function () {
|
|
||||||
it('incoming: sets the pending flag on output', function () {
|
|
||||||
wallet.addresses = [addresses[0]]
|
|
||||||
wallet.processPendingTx(tx)
|
|
||||||
|
|
||||||
verifyOutputAdded(0, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when tx ins outpoint contains a known txhash:i', function () {
|
|
||||||
var spendTx
|
|
||||||
beforeEach(function () {
|
|
||||||
wallet.addresses = [addresses[0]]
|
|
||||||
wallet.processConfirmedTx(tx)
|
|
||||||
|
|
||||||
spendTx = Transaction.fromHex(fixtureTx2Hex)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("outgoing: sets the pending flag and 'spent' on output", function () {
|
|
||||||
var txIn = spendTx.ins[0]
|
|
||||||
var txInId = new Buffer(txIn.hash)
|
|
||||||
Array.prototype.reverse.call(txInId)
|
|
||||||
txInId = txInId.toString('hex')
|
|
||||||
|
|
||||||
var unspent = wallet.unspents[0]
|
|
||||||
assert(!unspent.pending)
|
|
||||||
|
|
||||||
wallet.processPendingTx(spendTx)
|
|
||||||
assert(unspent.pending)
|
|
||||||
assert(unspent.spent, true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('processConfirmedTx', function () {
|
|
||||||
it('does not throw on scripts with no corresponding Address', function () {
|
|
||||||
var pubKey = wallet.getPrivateKey(0).pub
|
|
||||||
var script = scripts.pubKeyOutput(pubKey)
|
|
||||||
var tx2 = new Transaction()
|
|
||||||
|
|
||||||
tx2.addInput(fakeTxHash(1), 0)
|
|
||||||
tx2.addOutput(script, 10000)
|
|
||||||
|
|
||||||
wallet.processConfirmedTx(tx2)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.unspentMap", function () {
|
|
||||||
it('works for receive address', function () {
|
|
||||||
var totalOuts = outputCount()
|
|
||||||
|
|
||||||
wallet.addresses = [addresses[0]]
|
|
||||||
wallet.processConfirmedTx(tx)
|
|
||||||
|
|
||||||
assert.equal(outputCount(), totalOuts + 1)
|
|
||||||
verifyOutputAdded(0, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('works for change address', function () {
|
|
||||||
var totalOuts = outputCount()
|
|
||||||
wallet.changeAddresses = [addresses[1]]
|
|
||||||
|
|
||||||
wallet.processConfirmedTx(tx)
|
|
||||||
|
|
||||||
assert.equal(outputCount(), totalOuts + 1)
|
|
||||||
verifyOutputAdded(1, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
function outputCount () {
|
|
||||||
return Object.keys(wallet.unspentMap).length
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when tx ins contains a known txhash:i', function () {
|
|
||||||
var spendTx
|
|
||||||
beforeEach(function () {
|
|
||||||
wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input
|
|
||||||
wallet.processConfirmedTx(tx)
|
|
||||||
|
|
||||||
spendTx = Transaction.fromHex(fixtureTx2Hex)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not add to wallet.unspentMap', function () {
|
|
||||||
wallet.processConfirmedTx(spendTx)
|
|
||||||
assert.deepEqual(wallet.unspentMap, {})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("deletes corresponding 'unspent'", function () {
|
|
||||||
var txIn = spendTx.ins[0]
|
|
||||||
var txInId = bufferutils.reverse(txIn.hash).toString('hex')
|
|
||||||
|
|
||||||
var expected = txInId + ':' + txIn.index
|
|
||||||
assert(expected in wallet.unspentMap)
|
|
||||||
|
|
||||||
wallet.processConfirmedTx(spendTx)
|
|
||||||
assert(!(expected in wallet.unspentMap))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does nothing when none of the involved addresses belong to the wallet', function () {
|
|
||||||
wallet.processConfirmedTx(tx)
|
|
||||||
assert.deepEqual(wallet.unspentMap, {})
|
|
||||||
})
|
|
||||||
|
|
||||||
function verifyOutputAdded (index, pending) {
|
|
||||||
var txOut = tx.outs[index]
|
|
||||||
|
|
||||||
var key = tx.getId() + ':' + index
|
|
||||||
var output = wallet.unspentMap[key]
|
|
||||||
assert.deepEqual(output.txHash, tx.getHash())
|
|
||||||
assert.equal(output.value, txOut.value)
|
|
||||||
assert.equal(output.pending, pending)
|
|
||||||
|
|
||||||
var txOutAddress = Address.fromOutputScript(txOut.script).toString()
|
|
||||||
assert.equal(output.address, txOutAddress)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('createTx', function () {
|
|
||||||
var wallet
|
|
||||||
var address1, address2
|
|
||||||
var to, value
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue'
|
|
||||||
value = 500000
|
|
||||||
|
|
||||||
address1 = 'n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa'
|
|
||||||
address2 = 'n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X'
|
|
||||||
|
|
||||||
// set up 3 utxos
|
|
||||||
var utxos = [
|
|
||||||
{
|
|
||||||
'txId': fakeTxId(1),
|
|
||||||
'index': 0,
|
|
||||||
'address': address1,
|
|
||||||
'value': 400000 // not enough for value
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'txId': fakeTxId(2),
|
|
||||||
'index': 1,
|
|
||||||
'address': address1,
|
|
||||||
'value': 500000 // enough for only value
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'txId': fakeTxId(3),
|
|
||||||
'index': 0,
|
|
||||||
'address': address2,
|
|
||||||
'value': 510000 // enough for value and fee
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
wallet = new Wallet(seed, networks.testnet)
|
|
||||||
wallet.setUnspentOutputs(utxos)
|
|
||||||
wallet.generateAddress()
|
|
||||||
wallet.generateAddress()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('transaction fee', function () {
|
|
||||||
it('allows fee to be specified', function () {
|
|
||||||
var fee = 30000
|
|
||||||
var tx = wallet.createTx(to, value, {
|
|
||||||
fixedFee: fee
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.equal(getFee(wallet, tx), fee)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('allows fee to be set to zero', function () {
|
|
||||||
value = 510000
|
|
||||||
var fee = 0
|
|
||||||
var tx = wallet.createTx(to, value, {
|
|
||||||
fixedFee: fee
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.equal(getFee(wallet, tx), fee)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not overestimate fees when network has dustSoftThreshold', function () {
|
|
||||||
var utxo = {
|
|
||||||
txId: fakeTxId(0),
|
|
||||||
index: 0,
|
|
||||||
address: 'LeyySKbQrRRwodKEj1W4a8y3YQupPLw5os',
|
|
||||||
value: 500000
|
|
||||||
}
|
|
||||||
|
|
||||||
var wallet = new Wallet(seed, networks.litecoin)
|
|
||||||
wallet.setUnspentOutputs([utxo])
|
|
||||||
wallet.generateAddress()
|
|
||||||
|
|
||||||
value = 200000
|
|
||||||
var tx = wallet.createTx(utxo.address, value)
|
|
||||||
|
|
||||||
assert.equal(getFee(wallet, tx), 100000)
|
|
||||||
})
|
|
||||||
|
|
||||||
function getFee (wallet, tx) {
|
|
||||||
var inputValue = tx.ins.reduce(function (accum, input) {
|
|
||||||
var txId = bufferutils.reverse(input.hash).toString('hex')
|
|
||||||
|
|
||||||
return accum + wallet.unspentMap[txId + ':' + input.index].value
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
return tx.outs.reduce(function (accum, output) {
|
|
||||||
return accum - output.value
|
|
||||||
}, inputValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('choosing utxo', function () {
|
|
||||||
it('takes fees into account', function () {
|
|
||||||
var tx = wallet.createTx(to, value)
|
|
||||||
|
|
||||||
assert.equal(tx.ins.length, 1)
|
|
||||||
assert.deepEqual(tx.ins[0].hash, fakeTxHash(3))
|
|
||||||
assert.equal(tx.ins[0].index, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('uses confirmed outputs', function () {
|
|
||||||
var tx2 = new Transaction()
|
|
||||||
tx2.addInput(fakeTxId(4), 0)
|
|
||||||
tx2.addOutput(address2, 530000)
|
|
||||||
|
|
||||||
wallet.processConfirmedTx(tx2)
|
|
||||||
var tx = wallet.createTx(to, value)
|
|
||||||
|
|
||||||
assert.equal(tx.ins.length, 1)
|
|
||||||
assert.deepEqual(tx.ins[0].hash, tx2.getHash())
|
|
||||||
assert.equal(tx.ins[0].index, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('ignores pending outputs', function () {
|
|
||||||
var tx2 = new Transaction()
|
|
||||||
tx2.addInput(fakeTxId(4), 0)
|
|
||||||
tx2.addOutput(address2, 530000)
|
|
||||||
|
|
||||||
wallet.processPendingTx(tx2)
|
|
||||||
var tx = wallet.createTx(to, value)
|
|
||||||
|
|
||||||
assert.equal(tx.ins.length, 1)
|
|
||||||
assert.deepEqual(tx.ins[0].hash, fakeTxHash(3))
|
|
||||||
assert.equal(tx.ins[0].index, 0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('changeAddress', function () {
|
|
||||||
it('should allow custom changeAddress', function () {
|
|
||||||
var changeAddress = 'mfrFjnKZUvTcvdAK2fUX5D8v1Epu5H8JCk'
|
|
||||||
var fromValue = 510000
|
|
||||||
var toValue = fromValue / 2
|
|
||||||
var fee = 1e3
|
|
||||||
|
|
||||||
var tx = wallet.createTx(to, toValue, {
|
|
||||||
fixedFee: fee,
|
|
||||||
changeAddress: changeAddress
|
|
||||||
})
|
|
||||||
assert.equal(tx.outs.length, 2)
|
|
||||||
|
|
||||||
var outAddress0 = Address.fromOutputScript(tx.outs[0].script, networks.testnet)
|
|
||||||
var outAddress1 = Address.fromOutputScript(tx.outs[1].script, networks.testnet)
|
|
||||||
|
|
||||||
assert.equal(outAddress0.toString(), to)
|
|
||||||
assert.equal(tx.outs[0].value, toValue)
|
|
||||||
|
|
||||||
assert.equal(outAddress1.toString(), changeAddress)
|
|
||||||
assert.equal(tx.outs[1].value, fromValue - (toValue + fee))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('transaction outputs', function () {
|
|
||||||
it('includes the specified address and amount', function () {
|
|
||||||
var tx = wallet.createTx(to, value)
|
|
||||||
|
|
||||||
assert.equal(tx.outs.length, 1)
|
|
||||||
var out = tx.outs[0]
|
|
||||||
var outAddress = Address.fromOutputScript(out.script, networks.testnet)
|
|
||||||
|
|
||||||
assert.equal(outAddress.toString(), to)
|
|
||||||
assert.equal(out.value, value)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('change', function () {
|
|
||||||
it('uses the last change address if there is any', function () {
|
|
||||||
var fee = 0
|
|
||||||
wallet.generateChangeAddress()
|
|
||||||
wallet.generateChangeAddress()
|
|
||||||
var tx = wallet.createTx(to, value, {
|
|
||||||
fixedFee: fee
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.equal(tx.outs.length, 2)
|
|
||||||
var out = tx.outs[1]
|
|
||||||
var outAddress = Address.fromOutputScript(out.script, networks.testnet)
|
|
||||||
|
|
||||||
assert.equal(outAddress.toString(), wallet.changeAddresses[1])
|
|
||||||
assert.equal(out.value, 10000)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('generates a change address if there is not any', function () {
|
|
||||||
var fee = 0
|
|
||||||
assert.equal(wallet.changeAddresses.length, 0)
|
|
||||||
|
|
||||||
var tx = wallet.createTx(to, value, {
|
|
||||||
fixedFee: fee
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.equal(wallet.changeAddresses.length, 1)
|
|
||||||
var out = tx.outs[1]
|
|
||||||
var outAddress = Address.fromOutputScript(out.script, networks.testnet)
|
|
||||||
|
|
||||||
assert.equal(outAddress.toString(), wallet.changeAddresses[0])
|
|
||||||
assert.equal(out.value, 10000)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('skips change if it is not above dust threshold', function () {
|
|
||||||
var tx1 = wallet.createTx(to, value - 546)
|
|
||||||
assert.equal(tx1.outs.length, 1)
|
|
||||||
|
|
||||||
var tx2 = wallet.createTx(to, value - 547)
|
|
||||||
assert.equal(tx2.outs.length, 2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('signing', function () {
|
|
||||||
afterEach(function () {
|
|
||||||
TransactionBuilder.prototype.sign.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('signs the inputs with respective keys', function () {
|
|
||||||
var fee = 30000
|
|
||||||
sinon.spy(TransactionBuilder.prototype, 'sign')
|
|
||||||
|
|
||||||
wallet.createTx(to, value, {
|
|
||||||
fixedFee: fee
|
|
||||||
})
|
|
||||||
|
|
||||||
var priv1 = wallet.getPrivateKeyForAddress(address1)
|
|
||||||
var priv2 = wallet.getPrivateKeyForAddress(address2)
|
|
||||||
|
|
||||||
// FIXME: boo, toString invokes reqiuired affine coordinate side effects
|
|
||||||
priv1.pub.Q.toString()
|
|
||||||
priv2.pub.Q.toString()
|
|
||||||
|
|
||||||
assert(TransactionBuilder.prototype.sign.calledWith(0, priv2))
|
|
||||||
assert(TransactionBuilder.prototype.sign.calledWith(1, priv1))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when value is below dust threshold', function () {
|
|
||||||
it('throws an error', function () {
|
|
||||||
var value = 546
|
|
||||||
|
|
||||||
assert.throws(function () {
|
|
||||||
wallet.createTx(to, value)
|
|
||||||
}, /546 must be above dust threshold \(546 Satoshis\)/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when there is not enough money', function () {
|
|
||||||
it('throws an error', function () {
|
|
||||||
var value = 1400001
|
|
||||||
|
|
||||||
assert.throws(function () {
|
|
||||||
wallet.createTx(to, value)
|
|
||||||
}, /Not enough funds \(incl. fee\): 1410000 < 1410001/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function assertEqual (obj1, obj2) {
|
|
||||||
assert.equal(obj1.toString(), obj2.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertNotEqual (obj1, obj2) {
|
|
||||||
assert.notEqual(obj1.toString(), obj2.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// quick and dirty: does not deal with functions on object
|
|
||||||
function cloneObject (obj) {
|
|
||||||
return JSON.parse(JSON.stringify(obj))
|
|
||||||
}
|
|
||||||
})
|
|
Loading…
Reference in a new issue