Skip to content

Commit eb6225b

Browse files
committed
Merge pull request bitcoinjs#145 from dcousens/b58coretests
Base58 Bitcoin core test compatibility
2 parents 1349ab2 + fb6c76a commit eb6225b

File tree

6 files changed

+732
-245
lines changed

6 files changed

+732
-245
lines changed

qa/coretests.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
var assert = require('assert')
2+
var base58 = require('..').base58
3+
var base58check = require('..').base58check
4+
var crypto = require('..').crypto
5+
var fs = require('fs')
6+
var request = require('request')
7+
var secureRandom = require('secure-random')
8+
9+
function b2h(b) { return new Buffer(b).toString('hex') }
10+
function h2b(h) { return new Buffer(h, 'hex') }
11+
function randomBuf(s) {
12+
return new Buffer(secureRandom(s))
13+
}
14+
15+
request('https://raw.githubusercontent.com/bitcoin/bitcoin/master/src/test/data/base58_encode_decode.json', function (error, response, body) {
16+
assert.ifError(error)
17+
assert.equal(response.statusCode, 200)
18+
19+
var data = JSON.parse(body)
20+
var valid = data.map(function(x) {
21+
return {
22+
hex: x[0],
23+
string: x[1]
24+
}
25+
})
26+
27+
// https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp#L73
28+
// FIXME: Doesn't work TODO
29+
// valid.push({
30+
// hex: '971a55',
31+
// string: ' \t\n\v\f\r skip \r\f\v\n\t '
32+
// })
33+
34+
var fixtureJSON = JSON.stringify({
35+
valid: valid,
36+
invalid: [
37+
{
38+
description: 'non-base58 string',
39+
string: 'invalid'
40+
},
41+
{
42+
description: 'non-base58 alphabet',
43+
string: 'c2F0b3NoaQo='
44+
},
45+
{
46+
description: 'leading whitespace',
47+
string: ' 1111111111'
48+
},
49+
{
50+
description: 'trailing whitespace',
51+
string: '1111111111 '
52+
},
53+
// https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp#L72
54+
{
55+
description: 'unexpected character after whitespace',
56+
string: ' \t\n\v\f\r skip \r\f\v\n\t a'
57+
}
58+
]
59+
}, null, ' ')
60+
61+
fs.writeFileSync('./test/fixtures/base58.js', 'module.exports = ' + fixtureJSON)
62+
})
63+
64+
request('https://raw.githubusercontent.com/bitcoin/bitcoin/master/src/test/data/base58_keys_valid.json', function (error, response, body) {
65+
request('https://raw.githubusercontent.com/bitcoin/bitcoin/master/src/test/data/base58_keys_invalid.json', function (error2, response2, body2) {
66+
assert.ifError(error)
67+
assert.ifError(error2)
68+
assert.equal(response.statusCode, 200)
69+
assert.equal(response2.statusCode, 200)
70+
71+
var validData = JSON.parse(body)
72+
var invalidData = JSON.parse(body2)
73+
74+
var valid = validData.map(function(x) {
75+
var string = x[0]
76+
var hex = x[1]
77+
var params = x[2]
78+
79+
if (params.isCompressed) {
80+
hex += '01'
81+
}
82+
assert.equal(b2h(base58check.decode(string).payload), hex)
83+
84+
return {
85+
string: string,
86+
decode: {
87+
version: base58check.decode(string).version,
88+
payload: hex,
89+
checksum: b2h(base58check.decode(string).checksum),
90+
}
91+
}
92+
})
93+
var invalid2 = invalidData.map(function(x) { return x[0] })
94+
95+
// Our own tests
96+
var hash = crypto.hash160(randomBuf(65))
97+
var checksum = base58check.decode(base58check.encode(hash)).checksum
98+
99+
var fixtureJSON = JSON.stringify({
100+
valid: valid,
101+
invalid: [
102+
{
103+
base58check: base58check.encode(hash.slice(0, 18), 0x05),
104+
description: 'hash too short'
105+
},
106+
{
107+
base58check: base58check.encode(Buffer.concat([hash, randomBuf(2)]), 0x05),
108+
description: 'hash too long'
109+
},
110+
{
111+
base58check: base58.encode(Buffer.concat([new Buffer([0x01]), hash, checksum])),
112+
description: 'bad version byte',
113+
},
114+
{
115+
base58check: base58.encode(Buffer.concat([new Buffer([0x05]), randomBuf(20), checksum])),
116+
description: 'bad payload',
117+
},
118+
{
119+
base58check: base58.encode(Buffer.concat([new Buffer([0x05]), hash, randomBuf(4)])),
120+
description: 'bad SHA256 checksum',
121+
}
122+
],
123+
invalid2: invalid2
124+
}, null, ' ')
125+
126+
fs.writeFileSync('./test/fixtures/base58check.js', 'module.exports = ' + fixtureJSON)
127+
})
128+
})

src/base58.js

Lines changed: 38 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,65 @@
22
// Originally written by Mike Hearn for BitcoinJ
33
// Copyright (c) 2011 Google Inc
44
// Ported to JavaScript by Stefan Thomas
5+
// Merged Buffer refactorings from base58-native by Stephen Pair
6+
// Copyright (c) 2013 BitPay Inc
57

68
var BigInteger = require('./jsbn/jsbn')
79

8-
// FIXME: ? This is a Base58Check alphabet
9-
var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
10-
var base = BigInteger.valueOf(58)
11-
12-
var alphabetMap = {}
13-
for (var i=0; i<alphabet.length; ++i) {
14-
var chr = alphabet[i]
15-
alphabetMap[chr] = BigInteger.valueOf(i)
10+
var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
11+
var ALPHABET_BUF = new Buffer(ALPHABET, 'ascii')
12+
var ALPHABET_MAP = {}
13+
for(var i = 0; i < ALPHABET.length; i++) {
14+
ALPHABET_MAP[ALPHABET[i]] = BigInteger.valueOf(i)
1615
}
16+
var BASE = BigInteger.valueOf(58)
1717

18-
// encode a byte array into a base58 encoded String
19-
// @return String
2018
function encode(buffer) {
2119
var bi = BigInteger.fromByteArrayUnsigned(buffer)
22-
var chars = []
20+
var result = new Buffer(buffer.length << 1)
2321

24-
while (bi.compareTo(base) >= 0) {
25-
var mod = bi.mod(base)
26-
bi = bi.subtract(mod).divide(base)
22+
var i = result.length - 1
23+
while (bi.compareTo(BigInteger.ZERO) > 0) {
24+
var remainder = bi.mod(BASE)
25+
bi = bi.divide(BASE)
2726

28-
chars.push(alphabet[mod.intValue()])
27+
result[i] = ALPHABET_BUF[remainder.intValue()]
28+
i--
2929
}
3030

31-
chars.push(alphabet[bi.intValue()])
32-
33-
// Convert leading zeros too.
34-
for (var i=0; i<buffer.length; i++) {
35-
if (buffer[i] !== 0x00) break
36-
37-
chars.push(alphabet[0])
31+
// deal with leading zeros
32+
var j = 0
33+
while (buffer[j] === 0) {
34+
result[i] = ALPHABET_BUF[0]
35+
j++
36+
i--
3837
}
3938

40-
return chars.reverse().join('')
39+
return result.slice(i + 1, result.length).toString('ascii')
4140
}
4241

43-
// decode a base58 encoded String into a byte array
44-
// @return Array
45-
function decode(str) {
46-
var num = BigInteger.valueOf(0)
47-
48-
var leading_zero = 0
49-
var seen_other = false
42+
function decode(string) {
43+
if (string.length === 0) return new Buffer(0)
5044

51-
for (var i=0; i<str.length; ++i) {
52-
var chr = str[i]
53-
var bi = alphabetMap[chr]
45+
var num = BigInteger.ZERO.clone()
5446

55-
// if we encounter an invalid character, decoding fails
56-
if (bi === undefined) {
57-
throw new Error('invalid base58 string: ' + str)
58-
}
59-
60-
num = num.multiply(base).add(bi)
61-
62-
if (chr === '1' && !seen_other) {
63-
++leading_zero
64-
} else {
65-
seen_other = true
66-
}
47+
for (var i = 0; i < string.length; i++) {
48+
num = num.multiply(BASE)
49+
num = num.add(ALPHABET_MAP[string.charAt(i)])
6750
}
6851

69-
var bytes = num.toByteArrayUnsigned()
70-
71-
// remove leading zeros
72-
while (leading_zero-- > 0) {
73-
bytes.unshift(0)
52+
// deal with leading zeros
53+
var i = 0
54+
while ((i < string.length) && (string[i] === ALPHABET[0])) {
55+
i++
7456
}
7557

76-
return new Buffer(bytes)
58+
// FIXME: If BigInteger supported buffers, this could be a copy
59+
var buffer = new Buffer(num.toByteArrayUnsigned())
60+
var padding = new Buffer(i)
61+
padding.fill(0)
62+
63+
return Buffer.concat([padding, buffer])
7764
}
7865

7966
module.exports = {

test/base58.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
var assert = require('assert')
22
var base58 = require('../').base58
3-
43
var fixtures = require('./fixtures/base58')
54

5+
function b2h(b) { return new Buffer(b).toString('hex') }
6+
function h2b(h) { return new Buffer(h, 'hex') }
7+
68
describe('base58', function() {
79
describe('decode', function() {
8-
it('decodes the test vectors', function() {
10+
it('can decode Bitcoin core test data', function() {
911
fixtures.valid.forEach(function(f) {
10-
var actual = base58.decode(f.encoded.string)
11-
var expected = f.encoded.hex
12+
var actual = base58.decode(f.string)
13+
var expected = f.hex
14+
15+
assert.strictEqual(b2h(actual), expected)
16+
})
17+
})
1218

13-
assert.equal(actual.toString('hex'), expected)
19+
fixtures.invalid.forEach(function(f) {
20+
it('throws on ' + f.description, function() {
21+
assert.throws(function() {
22+
base58.decode(f.string)
23+
})
1424
})
1525
})
1626
})
1727

1828
describe('encode', function() {
19-
it('encodes the test vectors', function() {
29+
it('can encode Bitcoin core test data', function() {
2030
fixtures.valid.forEach(function(f) {
21-
var actual = base58.encode(new Buffer(f.encoded.hex, 'hex'))
22-
var expected = f.encoded.string
31+
var actual = base58.encode(h2b(f.hex))
32+
var expected = f.string.trim()
2333

24-
assert.equal(actual, expected)
34+
assert.strictEqual(actual, expected)
2535
})
2636
})
2737
})

test/base58check.js

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,51 @@
11
var assert = require('assert')
22
var base58check = require('../').base58check
3+
var fixtures = require('./fixtures/base58check')
34

4-
var fixtures = require('./fixtures/base58')
5+
function b2h(b) { return new Buffer(b).toString('hex') }
6+
function h2b(h) { return new Buffer(h, 'hex') }
57

68
describe('base58check', function() {
79
describe('decode', function() {
8-
it('decodes the test vectors', function() {
10+
it('can decode Bitcoin core test data', function() {
911
fixtures.valid.forEach(function(f) {
10-
var actual = base58check.decode(f.encoded.string)
11-
var expected = f.decoded
12-
13-
assert.deepEqual({
14-
version: actual.version,
15-
payload: actual.payload.toString('hex'),
16-
checksum: actual.checksum.toString('hex')
17-
}, expected)
12+
var actual = base58check.decode(f.string)
13+
var expected = {
14+
version: f.decode.version,
15+
payload: h2b(f.decode.payload),
16+
checksum: h2b(f.decode.checksum)
17+
}
18+
19+
assert.deepEqual(actual, expected)
1820
})
1921
})
2022

21-
it('throws on invalid strings', function() {
22-
fixtures.invalid.forEach(function(f) {
23+
fixtures.invalid.forEach(function(f) {
24+
it('throws on ' + f.description, function() {
2325
assert.throws(function() {
24-
base58check.decode(f)
26+
base58check.decode(f.string)
27+
})
28+
})
29+
})
30+
31+
it('throws on [invalid] Bitcoin core test data', function() {
32+
fixtures.invalid2.forEach(function(f) {
33+
assert.throws(function() {
34+
base58check.decode(f.string)
2535
})
2636
})
2737
})
2838
})
2939

3040
describe('encode', function() {
31-
it('encodes the test vectors', function() {
41+
it('can encode Bitcoin core test data', function() {
3242
fixtures.valid.forEach(function(f) {
33-
var actual = base58check.encode(
34-
new Buffer(f.decoded.payload, 'hex'),
35-
f.decoded.version
36-
)
37-
var expected = f.encoded.string
43+
var actual = base58check.encode(h2b(f.decode.payload), f.decode.version)
44+
var expected = f.string
3845

39-
assert.equal(actual, expected)
46+
assert.strictEqual(actual, expected)
4047
})
4148
})
4249
})
4350
})
51+

0 commit comments

Comments
 (0)