diff --git a/src/address.js b/src/address.js index a952d3d..354dda8 100644 --- a/src/address.js +++ b/src/address.js @@ -1,4 +1,5 @@ var base58 = require('./base58') +var base58check = require('./base58check') var convert = require('./convert') var error = require('./util').error var mainnet = require('./network').mainnet.addressVersion @@ -13,8 +14,19 @@ function Address(bytes, version) { this.version = bytes.version } else if (typeof bytes === 'string') { - this.hash = stringToHash(bytes) - this.version = version || this.hash.version || mainnet + 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 || mainnet + } + else { + error('invalid or unrecognized input') + } } else { this.hash = bytes @@ -22,22 +34,12 @@ function Address(bytes, version) { } } -function stringToHash(str) { - if (str.length <= 35) { - return base58.checkDecode(str) - } - if (str.length <= 40) { - return convert.hexToBytes(str) - } - error('invalid or unrecognized input') -} - /** * 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 base58.checkEncode(this.hash.slice(0), this.version) + return base58check.encode(this.hash.slice(0), this.version) } /** @@ -53,7 +55,7 @@ Address.getVersion = function (address) { */ Address.validate = function (address) { try { - base58.checkDecode(address) + base58check.decode(address) return true } catch (e) { return false diff --git a/src/base58.js b/src/base58.js index 0b38586..3715c3b 100644 --- a/src/base58.js +++ b/src/base58.js @@ -1,66 +1,65 @@ -// https://en.bitcoin.it/wiki/Base58Check_encoding +// Base58 encoding/decoding +// Originally written by Mike Hearn for BitcoinJ +// Copyright (c) 2011 Google Inc +// Ported to JavaScript by Stefan Thomas var BigInteger = require('./jsbn/jsbn') -var Crypto = require('crypto-js') -var convert = require('./convert') -var SHA256 = Crypto.SHA256 +// FIXME: ? This is a Base58Check alphabet var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" var base = BigInteger.valueOf(58) -var positions = {} +var alphabetMap = {} for (var i=0; i= 0) { var mod = bi.mod(base) - chars.push(alphabet[mod.intValue()]) bi = bi.subtract(mod).divide(base) + + chars.push(alphabet[mod.intValue()]) } chars.push(alphabet[bi.intValue()]) // Convert leading zeros too. - for (var i=0; i b) } -HDWallet.getChecksum = base58.getChecksum - HDWallet.fromSeedHex = function(hex, network) { return new HDWallet(convert.hexToBytes(hex), network) } @@ -45,20 +52,17 @@ HDWallet.fromSeedString = function(string, network) { return new HDWallet(convert.stringToBytes(string), network) } -HDWallet.fromBase58 = function(input) { - var buffer = base58.decode(input) +HDWallet.fromBase58 = function(string) { + var buffer = base58.decode(string) - if (buffer.length == HDWallet.LENGTH + 4) { - var expectedChecksum = buffer.slice(HDWallet.LENGTH, HDWallet.LENGTH + 4) - buffer = buffer.slice(0, HDWallet.LENGTH) - var actualChecksum = HDWallet.getChecksum(buffer) + var payload = buffer.slice(0, -4) + var checksum = buffer.slice(-4) + var newChecksum = sha256(sha256(payload)).slice(0, 4) - if (!arrayEqual(expectedChecksum, actualChecksum)) { - throw new Error('Checksum mismatch') - } - } + assert.deepEqual(newChecksum, checksum) + assert.equal(payload.length, HDWallet.LENGTH) - return HDWallet.fromBytes(buffer) + return HDWallet.fromBytes(payload) } HDWallet.fromHex = function(input) { @@ -71,6 +75,11 @@ HDWallet.fromBytes = function(input) { throw new Error(format('Invalid input length, %s. Expected %s.', input.length, HDWallet.LENGTH)) } + // FIXME: transitionary fix + if (Buffer.isBuffer(input)) { + input = Array.prototype.map.bind(input, function(x) { return x })() + } + var hd = new HDWallet() // 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private @@ -182,10 +191,13 @@ HDWallet.prototype.toHex = function(priv) { } HDWallet.prototype.toBase58 = function(priv) { - var buffer = this.toBytes(priv) - , checksum = HDWallet.getChecksum(buffer) - buffer = buffer.concat(checksum) - return base58.encode(buffer) + var buffer = new Buffer(this.toBytes(priv)) + var checksum = sha256(sha256(buffer)).slice(0, 4) + + return base58.encode(Buffer.concat([ + buffer, + checksum + ])) } HDWallet.prototype.derive = function(i) { @@ -252,3 +264,4 @@ function HmacFromBytesToBytes(hasher, message, key) { return convert.wordArrayToBytes(hmac.finalize()) } +module.exports = HDWallet diff --git a/src/index.js b/src/index.js index c45c919..52fd4f4 100644 --- a/src/index.js +++ b/src/index.js @@ -21,5 +21,6 @@ module.exports = { ecdsa: require('./ecdsa'), HDWallet: require('./hdwallet.js'), base58: require('./base58'), + base58check: require('./base58check'), convert: require('./convert') } diff --git a/src/jsbn/jsbn.js b/src/jsbn/jsbn.js index 143488e..61b4d0b 100644 --- a/src/jsbn/jsbn.js +++ b/src/jsbn/jsbn.js @@ -1200,6 +1200,11 @@ BigInteger.valueOf = nbv; * endian notation and ignore leading zeros. */ BigInteger.fromByteArrayUnsigned = function(ba) { + // FIXME: BigInteger doesn't yet support Buffers + if (Buffer.isBuffer(ba)) { + ba = Array.prototype.map.bind(ba, function(x) { return x })() + } + if (!ba.length) { return new BigInteger.valueOf(0); } else if (ba[0] & 0x80) { diff --git a/src/script.js b/src/script.js index 726bd16..49d78fa 100644 --- a/src/script.js +++ b/src/script.js @@ -279,6 +279,11 @@ Script.prototype.writeOp = function(opcode) { * Add a data chunk to the script. */ Script.prototype.writeBytes = function(data) { + // FIXME: Script module doesn't support buffers yet + if (Buffer.isBuffer(data)) { + data = Array.prototype.map.bind(data, function(x) { return x })() + } + if (data.length < Opcode.map.OP_PUSHDATA1) { this.buffer.push(data.length) } else if (data.length <= 0xff) { diff --git a/test/address.js b/test/address.js index ff6cea6..98dd03f 100644 --- a/test/address.js +++ b/test/address.js @@ -1,7 +1,8 @@ var assert = require('assert') -var Address = require('../src/address.js') -var network = require('../src/network.js') -var base58 = require('../src/base58.js') +var Address = require('../src/address') +var network = require('../src/network') +var base58 = require('../src/base58') +var base58check = require('../src/base58check') var mainnet = network.mainnet.addressVersion var testnet = network.testnet.addressVersion @@ -10,10 +11,10 @@ describe('Address', function() { var testnetP2shAddress, mainnetP2shAddress beforeEach(function(){ - testnetAddress = 'mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef' mainnetAddress = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' - testnetP2shAddress = '2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7' + testnetAddress = 'mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef' mainnetP2shAddress = '3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt' + testnetP2shAddress = '2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7' }) describe('parsing', function() { @@ -36,14 +37,14 @@ describe('Address', function() { }) it('works for byte input', function() { - var hash = base58.checkDecode('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa') - var addr = new Address(hash) - assert.equal(addr.hash, hash) + var hash = base58check.decode(mainnetAddress) + var addr = new Address(hash.payload) + assert.equal(addr.hash, hash.payload) assert.equal(network.mainnet.addressVersion, hash.version) - var hash = base58.checkDecode('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef') - var addr = new Address(hash) - assert.equal(addr.hash, hash) + var hash = base58check.decode(testnetAddress) + var addr = new Address(hash.payload) + assert.equal(addr.hash, hash.payload) assert.equal(network.testnet.addressVersion, hash.version) }) @@ -56,8 +57,8 @@ describe('Address', function() { describe('getVersion', function() { it('returns the proper address version', function() { - assert.equal(Address.getVersion('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'), network.mainnet.addressVersion) - assert.equal(Address.getVersion('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef'), network.testnet.addressVersion) + assert.equal(Address.getVersion(mainnetAddress), network.mainnet.addressVersion) + assert.equal(Address.getVersion(testnetAddress), network.testnet.addressVersion) }) }) diff --git a/test/base58.js b/test/base58.js index 574db6d..fad2635 100644 --- a/test/base58.js +++ b/test/base58.js @@ -1,48 +1,50 @@ var assert = require('assert') var base58 = require('../').base58 -var convert = require('../').convert describe('base58', function() { + var evec, dvec + + beforeEach(function() { + // base58 encoded strings + evec = [ + '5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAbuatmU', // 0x00 WIF + '5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf', // 0x01 WIF + '5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreQyNNN1W', // 0x7f WIF + '1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm', // uncompressed 0x01 address + '1FB8cZijTpRQp3HX8AEkNuQJBqApqfTcX7' // uncompressed 0x7f address + ] + + // decoded equivalent of above + dvec = [ + '8000000000000000000000000000000000000000000000000000000000000000000565fba7', + '800000000000000000000000000000000000000000000000000000000000000001a85aa87e', + '80000000000000000000000000000000000000000000000000000000000000007f64046be9', + '0091b24bf9f5288532960ac687abb035127b1d28a50074ffe0', + '009b7c46977b68474e12066a370b169ec6b9b026444d210d6e' + ].map(function(h) { + return new Buffer(h, 'hex') + }) + }) + describe('decode', function() { - it('validates known examples', function() { - var enc = '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ' - var hex = '800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d507a5b8d' - assert.deepEqual(base58.decode(enc), convert.hexToBytes(hex)) + it('decodes the test vectors', function() { + evec.forEach(function(x, i) { + var actual = base58.decode(x) + var expected = dvec[i] + + assert.deepEqual(expected, actual) + }) }) }) describe('encode', function() { - it('handles known examples', function() { - var enc = '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ' - var hex = '800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d507a5b8d' - assert.equal(base58.encode(convert.hexToBytes(hex)), enc) - }) - }) + it('encodes the test vectors', function() { + dvec.forEach(function(x, i) { + var actual = base58.encode(x) + var expected = evec[i] - describe('checkEncode', function() { - it('handles known examples', function() { - var input = [ - 171, 210, 178, 125, 2, 16, 86, 184, 248, 88, 235, - 163, 244, 160, 83, 156, 184, 186, 45, 167, 169, 164, - 67, 125, 163, 89, 106, 243, 207, 193, 149, 206 - ] - var vbyte = 239 - - assert.equal(base58.checkEncode(input, vbyte), - '92tb9mjz6q9eKZjYvLsgk87kPrMoh7BGRumSzPeUGhmigtsfrbP') - }) - }) - - describe('checkDecode', function() { - it('handles known examples', function() { - var input = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' - var expected = [ - 98, 233, 7, 177, 92, 191, 39, 213, 66, 83, - 153, 235, 246, 240, 251, 80, 235, 184, 143, 24 - ] - expected.version = 0 - - assert.deepEqual(base58.checkDecode(input), expected) + assert.deepEqual(expected, actual) + }) }) }) }) diff --git a/test/base58check.js b/test/base58check.js new file mode 100644 index 0000000..fdf7833 --- /dev/null +++ b/test/base58check.js @@ -0,0 +1,76 @@ +var assert = require('assert') +var base58check = require('../').base58check + +describe('base58check', function() { + var evec, dvec + + beforeEach(function() { + function fromHex(h) { return new Buffer(h, 'hex') } + + // base58check encoded strings + evec = [ + '5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAbuatmU', // 0x00 WIF + '5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf', // 0x01 WIF + '5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreQyNNN1W', // 0x7f WIF + '1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm', // uncompressed 0x01 address + '1FB8cZijTpRQp3HX8AEkNuQJBqApqfTcX7' // uncompressed 0x7f address + ] + + // decoded equivalent of above + dvec = [ + { + version: 0x80, + payload: '0000000000000000000000000000000000000000000000000000000000000000', + checksum: '0565fba7' + }, + { + version: 0x80, + payload: '0000000000000000000000000000000000000000000000000000000000000001', + checksum: 'a85aa87e', + }, + { + version: 0x80, + payload: '000000000000000000000000000000000000000000000000000000000000007f', + checksum: '64046be9', + }, + { + version: 0x00, + payload: '91b24bf9f5288532960ac687abb035127b1d28a5', + checksum: '0074ffe0', + }, + { + version: 0x00, + payload: '9b7c46977b68474e12066a370b169ec6b9b02644', + checksum: '4d210d6e' + } + ].map(function(x) { + return { + version: x.version, + payload: fromHex(x.payload), + checksum: fromHex(x.checksum) + } + }) + }) + + describe('decode', function() { + it('decodes the test vectors', function() { + evec.forEach(function(x, i) { + var actual = base58check.decode(x) + var expected = dvec[i] + + assert.deepEqual(expected, actual) + }) + }) + }) + + describe('encode', function() { + it('encodes the test vectors', function() { + dvec.forEach(function(x, i) { + var actual = base58check.encode(x.payload, x.version) + var expected = evec[i] + + assert.deepEqual(expected, actual) + }) + }) + }) +})