Browse Source

remove deprecated functionality

hk-custom-address
Daniel Cousens 10 years ago
parent
commit
8d3686d046
  1. 29
      src/ecdsa.js
  2. 52
      src/hdnode.js
  3. 3
      src/index.js
  4. 4
      src/scripts.js
  5. 45
      src/transaction.js
  6. 371
      src/wallet.js
  7. 11
      test/ecdsa.js
  8. 5
      test/fixtures/mainnet_tx.json
  9. 63
      test/hdnode.js
  10. 2
      test/message.js
  11. 37
      test/transaction.js
  12. 691
      test/wallet.js

29
src/ecdsa.js

@ -12,34 +12,7 @@ var ONE = new Buffer([1])
function deterministicGenerateK (curve, hash, d, checkSig) {
typeForce('Buffer', hash)
typeForce('BigInteger', d)
// 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
}
}
typeForce('Function', checkSig)
// sanity check
assert.equal(hash.length, 32, 'Hash must be 256 bit')

52
src/hdnode.js

@ -78,15 +78,7 @@ HDNode.fromSeedHex = function (hex, network) {
}
HDNode.fromBase58 = function (string, network) {
return HDNode.fromBuffer(base58check.decode(string), network, true)
}
// 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')
}
var buffer = base58check.decode(string)
assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length')
// 4 byte: version bytes
@ -145,11 +137,6 @@ HDNode.fromBuffer = function (buffer, network, __ignoreDeprecation) {
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 () {
return bcrypto.hash160(this.pubKey.toBuffer())
}
@ -171,26 +158,11 @@ HDNode.prototype.neutered = function () {
return neutered
}
HDNode.prototype.toBase58 = function (isPrivate) {
return base58check.encode(this.toBuffer(isPrivate, true))
}
// 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')
}
HDNode.prototype.toBase58 = function (__isPrivate) {
assert.strictEqual(__isPrivate, undefined, 'Unsupported argument in 2.0.0')
// 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)
// 4 bytes: version bytes
@ -210,25 +182,19 @@ HDNode.prototype.toBuffer = function (isPrivate, __ignoreDeprecation) {
// 32 bytes: the chain code
this.chainCode.copy(buffer, 13)
// 33 bytes: the public key or private key data
if (isPrivate) {
// FIXME: remove in 2.x.y
assert(this.privKey, 'Missing private key')
// 33 bytes: the private key, or
if (this.privKey) {
// 0x00 + k for private keys
buffer.writeUInt8(0, 45)
this.privKey.d.toBuffer(32).copy(buffer, 46)
// 33 bytes: the public key
} else {
// X9.62 encoding for public keys
this.pubKey.toBuffer().copy(buffer, 45)
}
return buffer
}
// FIXME: remove in 2.x.y
HDNode.prototype.toHex = function (isPrivate) {
return this.toBuffer(isPrivate).toString('hex')
return base58check.encode(buffer)
}
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions

3
src/index.js

@ -14,6 +14,5 @@ module.exports = {
scripts: require('./scripts'),
Transaction: require('./transaction'),
TransactionBuilder: require('./transaction_builder'),
networks: require('./networks'),
Wallet: require('./wallet')
networks: require('./networks')
}

4
src/scripts.js

@ -291,9 +291,5 @@ module.exports = {
pubKeyHashInput: pubKeyHashInput,
scriptHashInput: scriptHashInput,
multisigInput: multisigInput,
dataOutput: function (data) {
console.warn('dataOutput is deprecated, use nullDataOutput by 2.0.0')
return nullDataOutput(data)
},
nullDataOutput: nullDataOutput
}

45
src/transaction.js

@ -3,10 +3,8 @@ var bufferutils = require('./bufferutils')
var crypto = require('./crypto')
var typeForce = require('typeforce')
var opcodes = require('./opcodes')
var scripts = require('./scripts')
var Address = require('./address')
var ECSignature = require('./ecsignature')
var Script = require('./script')
function Transaction () {
@ -209,16 +207,6 @@ Transaction.prototype.clone = function () {
* used to sign the transaction input in question.
*/
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('Script', prevOutScript)
typeForce('Number', hashType)
@ -333,37 +321,4 @@ Transaction.prototype.setInputScript = function (index, 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

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

11
test/ecdsa.js

@ -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 () {
this.mock(BigInteger).expects('fromBuffer')
.exactly(3)

5
test/fixtures/mainnet_tx.json

File diff suppressed because one or more lines are too long

63
test/hdnode.js

@ -139,15 +139,6 @@ describe('HDNode', function () {
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 () {
@ -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 () {
var f = fixtures.valid[0]

2
test/message.js

@ -13,7 +13,7 @@ var fixtures = require('./fixtures/message.json')
describe('message', function () {
describe('magicHash', function () {
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 actual = message.magicHash(f.message, network)

37
test/transaction.js

@ -1,10 +1,8 @@
/* global describe, it, beforeEach */
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 Script = require('../src/script')
@ -212,39 +210,4 @@ describe('Transaction', function () {
// TODO:
// 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)
})
})
})

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…
Cancel
Save