Merge pull request #140 from dcousens/addrstrict
Migrates Address to stricter API
This commit is contained in:
commit
c08f6a3f00
9 changed files with 161 additions and 190 deletions
|
@ -1,64 +1,27 @@
|
|||
var base58 = require('./base58')
|
||||
var assert = require('assert')
|
||||
var base58check = require('./base58check')
|
||||
var convert = require('./convert')
|
||||
var bitcoin = require('./network').bitcoin.pubKeyHash
|
||||
|
||||
function Address(bytes, version) {
|
||||
if (!(this instanceof Address)) {
|
||||
return new Address(bytes, version)
|
||||
}
|
||||
function Address(hash, version) {
|
||||
assert(Buffer.isBuffer(hash), 'First argument must be a Buffer')
|
||||
assert.strictEqual(hash.length, 20, 'Invalid hash length')
|
||||
assert.strictEqual(version & 0xFF, version, 'Invalid version byte')
|
||||
|
||||
if (bytes instanceof Address) {
|
||||
this.hash = bytes.hash
|
||||
this.version = bytes.version
|
||||
}
|
||||
else if (typeof bytes === 'string') {
|
||||
if (bytes.length <= 35) {
|
||||
var decode = base58check.decode(bytes)
|
||||
|
||||
this.hash = decode.payload
|
||||
this.version = decode.version
|
||||
}
|
||||
else if (bytes.length <= 40) {
|
||||
this.hash = convert.hexToBytes(bytes)
|
||||
this.version = version || bitcoin
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid or unrecognized input')
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.hash = bytes
|
||||
this.version = version || bitcoin
|
||||
}
|
||||
this.hash = hash
|
||||
this.version = version
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this object as a standard Bitcoin address.
|
||||
* Returns the address as a base58-encoded string in the standardized format.
|
||||
*/
|
||||
Address.prototype.toString = function () {
|
||||
return base58check.encode(this.hash.slice(0), this.version)
|
||||
}
|
||||
// Import functions
|
||||
Address.fromBase58Check = function(string) {
|
||||
var decode = base58check.decode(string)
|
||||
|
||||
/**
|
||||
* Returns the version of an address, e.g. if the address belongs to the main
|
||||
* net or the test net.
|
||||
*/
|
||||
Address.getVersion = function (address) {
|
||||
return base58.decode(address)[0]
|
||||
return new Address(decode.payload, decode.version)
|
||||
}
|
||||
Address.prototype.fromString = Address.prototype.fromBase58Check
|
||||
|
||||
/**
|
||||
* Returns true if a bitcoin address is a valid address, otherwise false.
|
||||
*/
|
||||
Address.validate = function (address) {
|
||||
try {
|
||||
base58check.decode(address)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
// Export functions
|
||||
Address.prototype.toBase58Check = function () {
|
||||
return base58check.encode(this.hash, this.version)
|
||||
}
|
||||
Address.prototype.toString = Address.prototype.toBase58Check
|
||||
|
||||
module.exports = Address
|
||||
|
|
|
@ -121,6 +121,8 @@ ECPubKey.prototype.verify = function(hash, sig) {
|
|||
}
|
||||
|
||||
ECPubKey.prototype.getAddress = function(version) {
|
||||
version = version || network.bitcoin.pubKeyHash
|
||||
|
||||
return new Address(crypto.hash160(this.toBuffer()), version)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,13 +46,17 @@ function sign(key, message) {
|
|||
return sig
|
||||
}
|
||||
|
||||
// FIXME: stricter API?
|
||||
function verify(address, sig, message) {
|
||||
if (typeof address === 'string') {
|
||||
address = Address.fromBase58Check(address)
|
||||
}
|
||||
|
||||
sig = ecdsa.parseSigCompact(sig)
|
||||
|
||||
var pubKey = new ECPubKey(ecdsa.recoverPubKey(sig.r, sig.s, magicHash(message), sig.i))
|
||||
pubKey.compressed = !!(sig.i & 4)
|
||||
|
||||
address = new Address(address)
|
||||
return pubKey.getAddress(address.version).toString() === address.toString()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
var assert = require('assert')
|
||||
var Address = require('./address')
|
||||
var crypto = require('./crypto')
|
||||
var convert = require('./convert')
|
||||
var network = require('./network')
|
||||
var Network = require('./network')
|
||||
var Opcode = require('./opcode')
|
||||
|
||||
function Script(data) {
|
||||
|
@ -211,22 +212,22 @@ Script.prototype.toScriptHash = function() {
|
|||
return crypto.hash160(this.buffer)
|
||||
}
|
||||
|
||||
//TODO: support testnet
|
||||
Script.prototype.getToAddress = function() {
|
||||
Script.prototype.getToAddress = function(network) {
|
||||
network = network || Network.bitcoin
|
||||
|
||||
if(isPubkeyhash.call(this)) {
|
||||
return new Address(this.chunks[2])
|
||||
return new Address(new Buffer(this.chunks[2]), network.pubKeyHash)
|
||||
}
|
||||
|
||||
if(isScripthash.call(this)) {
|
||||
return new Address(this.chunks[1], 5)
|
||||
}
|
||||
assert(isScripthash.call(this))
|
||||
|
||||
return new Address(this.chunks[1], 5)
|
||||
return new Address(new Buffer(this.chunks[1]), network.scriptHash)
|
||||
}
|
||||
|
||||
//TODO: support testnet
|
||||
Script.prototype.getFromAddress = function(){
|
||||
return new Address(this.simpleInHash())
|
||||
Script.prototype.getFromAddress = function(version) {
|
||||
version = version || Network.bitcoin.pubKeyHash
|
||||
|
||||
return new Address(this.simpleInHash(), version)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -364,25 +365,30 @@ Script.prototype.writeBytes = function(data) {
|
|||
/**
|
||||
* Create an output for an address
|
||||
*/
|
||||
Script.createOutputScript = function(address) {
|
||||
Script.createOutputScript = function(address, network) {
|
||||
assert(address instanceof Address)
|
||||
network = network || Network.bitcoin
|
||||
|
||||
var script = new Script()
|
||||
|
||||
address = new Address(address)
|
||||
if (address.version == network.bitcoin.scriptHash ||
|
||||
address.version == network.testnet.scriptHash) {
|
||||
// Standard pay-to-script-hash
|
||||
// Standard pay-to-script-hash
|
||||
if (address.version === network.scriptHash) {
|
||||
script.writeOp(Opcode.map.OP_HASH160)
|
||||
script.writeBytes(address.hash)
|
||||
script.writeOp(Opcode.map.OP_EQUAL)
|
||||
|
||||
return script
|
||||
}
|
||||
else {
|
||||
// Standard pay-to-pubkey-hash
|
||||
script.writeOp(Opcode.map.OP_DUP)
|
||||
script.writeOp(Opcode.map.OP_HASH160)
|
||||
script.writeBytes(address.hash)
|
||||
script.writeOp(Opcode.map.OP_EQUALVERIFY)
|
||||
script.writeOp(Opcode.map.OP_CHECKSIG)
|
||||
}
|
||||
|
||||
assert.strictEqual(address.version, network.pubKeyHash, 'Unknown address type')
|
||||
|
||||
// Standard pay-to-pubkey-hash
|
||||
script.writeOp(Opcode.map.OP_DUP)
|
||||
script.writeOp(Opcode.map.OP_HASH160)
|
||||
script.writeBytes(address.hash)
|
||||
script.writeOp(Opcode.map.OP_EQUALVERIFY)
|
||||
script.writeOp(Opcode.map.OP_CHECKSIG)
|
||||
|
||||
return script
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ var convert = require('./convert')
|
|||
var crypto = require('./crypto')
|
||||
var ECKey = require('./eckey').ECKey
|
||||
var ecdsa = require('./ecdsa')
|
||||
var Network = require('./network')
|
||||
|
||||
var Transaction = function (doc) {
|
||||
if (!(this instanceof Transaction)) { return new Transaction(doc) }
|
||||
|
@ -84,23 +85,33 @@ Transaction.prototype.addInput = function (tx, outIndex) {
|
|||
* i) An existing TransactionOut object
|
||||
* ii) An address object or an address and a value
|
||||
* iii) An address:value string
|
||||
* iv) Either ii), iii) with an optional network argument
|
||||
*
|
||||
* FIXME: This is a bit convoluted
|
||||
*/
|
||||
Transaction.prototype.addOutput = function (address, value) {
|
||||
Transaction.prototype.addOutput = function (address, value, network) {
|
||||
if (arguments[0] instanceof TransactionOut) {
|
||||
this.outs.push(arguments[0])
|
||||
return
|
||||
}
|
||||
|
||||
if (arguments[0].indexOf(':') >= 0) {
|
||||
network = value
|
||||
|
||||
var args = arguments[0].split(':')
|
||||
address = args[0]
|
||||
value = parseInt(args[1])
|
||||
}
|
||||
|
||||
network = network || Network.bitcoin
|
||||
|
||||
if (typeof address === 'string') {
|
||||
address = Address.fromBase58Check(address)
|
||||
}
|
||||
|
||||
this.outs.push(new TransactionOut({
|
||||
value: value,
|
||||
script: Script.createOutputScript(address)
|
||||
script: Script.createOutputScript(address, network)
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -297,18 +308,19 @@ Transaction.deserialize = function(buffer) {
|
|||
|
||||
/**
|
||||
* Signs a standard output at some index with the given key
|
||||
* FIXME: network support is ugly
|
||||
*/
|
||||
Transaction.prototype.sign = function(index, key, type) {
|
||||
Transaction.prototype.sign = function(index, key, type, network) {
|
||||
assert(key instanceof ECKey)
|
||||
type = type || SIGHASH_ALL
|
||||
network = network || Network.bitcoin
|
||||
|
||||
var pub = key.pub.toBuffer()
|
||||
var hash160 = crypto.hash160(pub)
|
||||
var script = Script.createOutputScript(new Address(hash160))
|
||||
var address = key.pub.getAddress(network.pubKeyHash)
|
||||
var script = Script.createOutputScript(address, network)
|
||||
var hash = this.hashTransactionForSignature(script, index, type)
|
||||
var sig = key.sign(hash).concat([type])
|
||||
|
||||
this.ins[index].script = Script.createInputScript(sig, pub)
|
||||
this.ins[index].script = Script.createInputScript(sig, key.pub)
|
||||
}
|
||||
|
||||
// Takes outputs of the form [{ output: 'txhash:index', address: 'address' },...]
|
||||
|
@ -413,6 +425,7 @@ TransactionIn.prototype.clone = function () {
|
|||
})
|
||||
}
|
||||
|
||||
// FIXME: Support for alternate networks
|
||||
var TransactionOut = function (data) {
|
||||
this.script =
|
||||
data.script instanceof Script ? data.script.clone()
|
||||
|
|
130
test/address.js
130
test/address.js
|
@ -1,111 +1,49 @@
|
|||
var assert = require('assert')
|
||||
var Address = require('../src/address')
|
||||
var network = require('../src/network')
|
||||
var base58 = require('../src/base58')
|
||||
var base58check = require('../src/base58check')
|
||||
var bitcoin = network.bitcoin.pubKeyHash
|
||||
var testnet = network.testnet.pubKeyHash
|
||||
var Address = require('..').Address
|
||||
var fixtures = require('./fixtures/address')
|
||||
|
||||
describe('Address', function() {
|
||||
var testnetAddress, bitcoinAddress
|
||||
var testnetP2shAddress, bitcoinP2shAddress
|
||||
var bothVectors = fixtures.pubKeyHash.concat(fixtures.scriptHash)
|
||||
|
||||
beforeEach(function(){
|
||||
bitcoinAddress = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'
|
||||
testnetAddress = 'mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef'
|
||||
bitcoinP2shAddress = '3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt'
|
||||
testnetP2shAddress = '2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7'
|
||||
})
|
||||
describe('Constructor', function() {
|
||||
it('does not mutate the input', function() {
|
||||
bothVectors.forEach(function(f) {
|
||||
var hash = new Buffer(f.hex, 'hex')
|
||||
var addr = new Address(hash, f.version)
|
||||
|
||||
describe('parsing', function() {
|
||||
it('works with Address object', function() {
|
||||
var addr = new Address(new Address('mwrB4fgT1KSBCqELaWv7o7tsExuQzW3NY3', network.testnet.pubKeyHash))
|
||||
|
||||
assert.equal(addr.toString(), 'mwrB4fgT1KSBCqELaWv7o7tsExuQzW3NY3')
|
||||
assert.equal(addr.version, network.testnet.pubKeyHash)
|
||||
})
|
||||
|
||||
it('works with hex', function() {
|
||||
var addr = new Address('13483382d3c3d43fc9d7b52e652b6bbb70e8b667')
|
||||
assert.equal(addr.toString(), '12kxLGqrnnchwN9bHHNV2fWDtJGwxKTcJS')
|
||||
})
|
||||
|
||||
it('throws error for invalid or unrecognized input', function() {
|
||||
assert.throws(function() {
|
||||
new Address('beepboopbeepboopbeepboopbeepboopbeepboopbeep')
|
||||
}, Error)
|
||||
})
|
||||
|
||||
it('works for byte input', function() {
|
||||
var hash = base58check.decode(bitcoinAddress)
|
||||
var addr = new Address(hash.payload)
|
||||
assert.equal(addr.hash, hash.payload)
|
||||
assert.equal(network.bitcoin.pubKeyHash, hash.version)
|
||||
|
||||
var hash = base58check.decode(testnetAddress)
|
||||
var addr = new Address(hash.payload)
|
||||
assert.equal(addr.hash, hash.payload)
|
||||
assert.equal(network.testnet.pubKeyHash, hash.version)
|
||||
})
|
||||
|
||||
it('fails for bad input', function() {
|
||||
assert.throws(function() {
|
||||
new Address('foo')
|
||||
}, Error)
|
||||
assert.equal(addr.version, f.version)
|
||||
assert.equal(addr.hash.toString('hex'), f.hex)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getVersion', function() {
|
||||
it('returns the proper address version', function() {
|
||||
assert.equal(Address.getVersion(bitcoinAddress), network.bitcoin.pubKeyHash)
|
||||
assert.equal(Address.getVersion(testnetAddress), network.testnet.pubKeyHash)
|
||||
describe('fromBase58Check', function() {
|
||||
it('throws on invalid base58check', function() {
|
||||
fixtures.malformed.forEach(function(f) {
|
||||
assert.throws(function() {
|
||||
Address.fromBase58Check(f.base58check)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
bothVectors.forEach(function(f) {
|
||||
it('imports ' + f.description + ' correctly', function() {
|
||||
var addr = Address.fromBase58Check(f.base58check)
|
||||
|
||||
assert.equal(addr.version, f.version)
|
||||
assert.equal(addr.hash.toString('hex'), f.hex)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('toString', function() {
|
||||
it('defaults to base58', function() {
|
||||
var addr = '18fN1QTGWmHWCA9r2dyDH6FbMEyc7XHmQQ'
|
||||
assert.equal((new Address(addr)).toString(), addr)
|
||||
})
|
||||
})
|
||||
describe('toBase58Check', function() {
|
||||
bothVectors.forEach(function(f) {
|
||||
it('exports ' + f.description + ' correctly', function() {
|
||||
var addr = Address.fromBase58Check(f.base58check)
|
||||
var result = addr.toBase58Check()
|
||||
|
||||
describe('Constructor', function(){
|
||||
it('resolves version correctly', function(){
|
||||
assert.equal((new Address(testnetAddress)).version, testnet)
|
||||
assert.equal((new Address(bitcoinAddress)).version, bitcoin)
|
||||
assert.equal((new Address(testnetP2shAddress)).version, network.testnet.scriptHash)
|
||||
assert.equal((new Address(bitcoinP2shAddress)).version, network.bitcoin.scriptHash)
|
||||
})
|
||||
})
|
||||
|
||||
describe('validate', function() {
|
||||
it('validates known good addresses', function() {
|
||||
function validate(addr, expectedVersion) {
|
||||
assert.ok(Address.validate(addr))
|
||||
}
|
||||
|
||||
validate(testnetAddress)
|
||||
validate(bitcoinAddress)
|
||||
validate('12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP')
|
||||
validate('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y')
|
||||
validate('1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs')
|
||||
validate('1SQHtwR5oJRKLfiWQ2APsAd9miUc4k2ez')
|
||||
validate('116CGDLddrZhMrTwhCVJXtXQpxygTT1kHd')
|
||||
|
||||
// p2sh addresses
|
||||
validate(testnetP2shAddress)
|
||||
validate(bitcoinP2shAddress)
|
||||
})
|
||||
|
||||
it('does not validate illegal examples', function() {
|
||||
function invalid(addr) {
|
||||
assert.ok(!Address.validate(addr))
|
||||
}
|
||||
|
||||
invalid(''); //empty should be invalid
|
||||
invalid('%%@'); // invalid base58 string
|
||||
invalid('1A1zP1eP5QGefi2DzPTf2L5SLmv7DivfNz'); // bad address (doesn't checksum)
|
||||
invalid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhe'); // bad address (doesn't checksum)
|
||||
assert.equal(result, f.base58check)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
42
test/fixtures/address.js
vendored
Normal file
42
test/fixtures/address.js
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
module.exports = {
|
||||
pubKeyHash: [
|
||||
{
|
||||
description: 'pubKeyHash (bitcoin)',
|
||||
version: 0,
|
||||
hex: '751e76e8199196d454941c45d1b3a323f1433bd6',
|
||||
base58check: '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH'
|
||||
},
|
||||
{
|
||||
description: 'pubKeyHash (testnet)',
|
||||
version: 111,
|
||||
hex: '751e76e8199196d454941c45d1b3a323f1433bd6',
|
||||
base58check: 'mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r'
|
||||
}
|
||||
],
|
||||
scriptHash: [
|
||||
{
|
||||
description: 'scriptHash (bitcoin)',
|
||||
version: 5,
|
||||
hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6',
|
||||
base58check: '3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr'
|
||||
},
|
||||
{
|
||||
description: 'scriptHash (testnet)',
|
||||
version: 196,
|
||||
hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6',
|
||||
base58check: '2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w'
|
||||
}
|
||||
],
|
||||
malformed: [
|
||||
'45k2PvUfZw',
|
||||
'8cVHMKGRJGMEVz',
|
||||
'AMPCMAGBmj9EE9oGED',
|
||||
'oJPsqvHTSFFWMcmNS3aDidZexw',
|
||||
'bpiuHmqwCdiHx4ASNLGvZeBw9taY',
|
||||
'2ansc1MsREU2HetNdPGs2eHXTY16ircdyaH',
|
||||
'iTKsHH39ooQPFxzX6RFtjPESpQ1',
|
||||
'4TU74v3jnoTZGV5UuJGcr7XRg7hU',
|
||||
'2a3wk37F1YmfqVtBam4gEn63oNuj',
|
||||
'3rtH2aquyk4q1KGaXuiMGxaGfVPH'
|
||||
]
|
||||
}
|
|
@ -115,11 +115,10 @@ describe('Script', function() {
|
|||
it('should create valid multi-sig address', function() {
|
||||
script = Script.createMultiSigOutputScript(numSigs, compressedPubKeys.map(hexToBytes))
|
||||
multisig = crypto.hash160(script.buffer)
|
||||
var multiSigAddress = Address(multisig, network).toString()
|
||||
var multisigAddress = new Address(multisig, network)
|
||||
|
||||
assert.ok(Address.validate(multiSigAddress))
|
||||
assert.equal(Address.getVersion(multiSigAddress), Network.bitcoin.scriptHash)
|
||||
assert.equal(multiSigAddress,'32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v')
|
||||
assert.equal(multisigAddress.version, Network.bitcoin.scriptHash)
|
||||
assert.equal(multisigAddress.toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v')
|
||||
})
|
||||
|
||||
it('should create valid redeemScript', function() {
|
||||
|
@ -138,7 +137,7 @@ describe('Script', function() {
|
|||
assert.equal(sigs[0], '02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f')
|
||||
assert.equal(sigs[1], '02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f')
|
||||
assert.equal(sigs[2], '036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19')
|
||||
assert.equal(Address(crypto.hash160(redeemScript), network).toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v')
|
||||
assert.equal(new Address(crypto.hash160(redeemScript), network).toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
var assert = require('assert')
|
||||
var convert = require('../src/convert')
|
||||
|
||||
var Address = require('../src/address')
|
||||
var ECKey = require('../src/eckey').ECKey
|
||||
var T = require('../src/transaction')
|
||||
var Transaction = T.Transaction
|
||||
var TransactionOut = T.TransactionOut
|
||||
var convert = require('../src/convert')
|
||||
var ECKey = require('../src/eckey').ECKey
|
||||
var Script = require('../src/script')
|
||||
var assert = require('assert')
|
||||
|
||||
var fixtureTxes = require('./fixtures/mainnet_tx')
|
||||
var fixtureTx1Hex = fixtureTxes.prevTx
|
||||
|
@ -220,9 +222,11 @@ describe('Transaction', function() {
|
|||
describe('TransactionOut', function() {
|
||||
describe('scriptPubKey', function() {
|
||||
it('returns hex string', function() {
|
||||
var address = Address.fromBase58Check("1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv")
|
||||
|
||||
var txOut = new TransactionOut({
|
||||
value: 50000,
|
||||
script: Script.createOutputScript("1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv")
|
||||
script: Script.createOutputScript(address)
|
||||
})
|
||||
|
||||
assert.equal(txOut.scriptPubKey(), "76a91468edf28474ee22f68dfe7e56e76c017c1701b84f88ac")
|
||||
|
|
Loading…
Reference in a new issue