From f53b821cc909176ad576d63f6cb3f2b5b38cfdb6 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 3 Apr 2014 19:45:05 +1100 Subject: [PATCH 1/2] Isolates Base58Check encoding from Base58 code/tests --- src/base58.js | 94 +++++++++++++++------------------------------ src/base58check.js | 53 +++++++++++++++++++++++++ src/index.js | 1 + test/base58.js | 66 ++++++++++++++++--------------- test/base58check.js | 76 ++++++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 95 deletions(-) create mode 100644 src/base58check.js create mode 100644 test/base58check.js 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 Date: Fri, 4 Apr 2014 05:10:12 +1100 Subject: [PATCH 2/2] Changes existing code to use new base58 API --- src/address.js | 30 +++++++++++++++-------------- src/convert.js | 5 +++++ src/eckey.js | 14 +++++++------- src/hdwallet.js | 49 ++++++++++++++++++++++++++++++------------------ src/jsbn/jsbn.js | 5 +++++ src/script.js | 5 +++++ test/address.js | 27 +++++++++++++------------- 7 files changed, 83 insertions(+), 52 deletions(-) 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/convert.js b/src/convert.js index c917d61..e39710f 100644 --- a/src/convert.js +++ b/src/convert.js @@ -8,6 +8,11 @@ function lpad(str, padString, length) { } function bytesToHex(bytes) { + // FIXME: transitionary fix + if (Buffer.isBuffer(bytes)) { + return bytes.toString('hex') + } + return bytes.map(function(x) { return lpad(x.toString(16), '0', 2) }).join('') diff --git a/src/eckey.js b/src/eckey.js index 8467c60..190e6de 100644 --- a/src/eckey.js +++ b/src/eckey.js @@ -1,7 +1,7 @@ var Address = require('./address') var assert = require('assert') var convert = require('./convert') -var base58 = require('./base58') +var base58check = require('./base58check') var BigInteger = require('./jsbn/jsbn') var ecdsa = require('./ecdsa') var ECPointFp = require('./jsbn/ec').ECPointFp @@ -32,10 +32,10 @@ ECKey.prototype.import = function (input, compressed) { : Array.isArray(input) ? fromBin(input.slice(0, 32)) : typeof input != "string" ? null : input.length == 44 ? fromBin(convert.base64ToBytes(input)) - : input.length == 51 && input[0] == '5' ? fromBin(base58.checkDecode(input)) - : input.length == 51 && input[0] == '9' ? fromBin(base58.checkDecode(input)) - : input.length == 52 && has('LK', input[0]) ? fromBin(base58.checkDecode(input).slice(0, 32)) - : input.length == 52 && input[0] == 'c' ? fromBin(base58.checkDecode(input).slice(0, 32)) + : input.length == 51 && input[0] == '5' ? fromBin(base58check.decode(input).payload) + : input.length == 51 && input[0] == '9' ? fromBin(base58check.decode(input).payload) + : input.length == 52 && has('LK', input[0]) ? fromBin(base58check.decode(input).payload.slice(0, 32)) + : input.length == 52 && input[0] == 'c' ? fromBin(base58check.decode(input).payload.slice(0, 32)) : has([64,65],input.length) ? fromBin(convert.hexToBytes(input.slice(0, 64))) : null @@ -76,7 +76,7 @@ ECKey.version_bytes = { ECKey.prototype.toWif = function(version) { version = version || Network.mainnet.addressVersion - return base58.checkEncode(this.toBytes(), ECKey.version_bytes[version]) + return base58check.encode(this.toBytes(), ECKey.version_bytes[version]) } ECKey.prototype.toHex = function() { @@ -167,7 +167,7 @@ ECPubKey.prototype.toBin = function(compressed) { ECPubKey.prototype.toWif = function(version) { version = version || Network.mainnet.addressVersion - return base58.checkEncode(this.toBytes(), version) + return base58check.encode(this.toBytes(), version) } ECPubKey.prototype.toString = ECPubKey.prototype.toHex diff --git a/src/hdwallet.js b/src/hdwallet.js index 6f18dda..a6998b5 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -11,7 +11,16 @@ var ECPubKey = require('./eckey.js').ECPubKey var Address = require('./address.js') var Network = require('./network') -var HDWallet = module.exports = function(seed, network) { +var crypto = require('crypto') + +function sha256(buf) { + var hash = crypto.createHash('sha256') + hash.update(buf) + + return hash.digest() +} + +function HDWallet(seed, network) { if (seed === undefined) return; var seedWords = convert.bytesToWordArray(seed) @@ -35,8 +44,6 @@ function arrayEqual(a, b) { return !(a < b || a > 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/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) }) })