Skip to content

Commit d1052e4

Browse files
committed
add from/toBech32
1 parent 348280e commit d1052e4

File tree

4 files changed

+179
-5
lines changed

4 files changed

+179
-5
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"src"
5252
],
5353
"dependencies": {
54+
"bech32": "0.0.3",
5455
"bigi": "^1.4.0",
5556
"bip66": "^1.1.0",
5657
"bitcoin-ops": "^1.3.0",

src/address.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var Buffer = require('safe-buffer').Buffer
2+
var bech32 = require('bech32')
23
var bs58check = require('bs58check')
34
var bscript = require('./script')
45
var networks = require('./networks')
@@ -16,6 +17,28 @@ function fromBase58Check (address) {
1617
return { hash: hash, version: version }
1718
}
1819

20+
function fromBech32 (address, expectedPrefix) {
21+
var result = bech32.decode(address)
22+
var prefix = result.prefix
23+
var words = result.words
24+
if (expectedPrefix !== undefined) {
25+
if (prefix !== expectedPrefix) throw new Error('Expected ' + expectedPrefix + ', got ' + prefix)
26+
}
27+
28+
var version = words[0]
29+
if (version > 16) throw new Error('Invalid version (' + version + ')')
30+
var program = bech32.fromWords(words.slice(1))
31+
32+
if (version === 0) {
33+
if (program.length !== 20 && program.length !== 32) throw new Error('Unknown program')
34+
} else {
35+
if (program.length < 2) throw new Error('Program too short')
36+
if (program.length > 40) throw new Error('Program too long')
37+
}
38+
39+
return { version, prefix, program: Buffer.from(program) }
40+
}
41+
1942
function toBase58Check (hash, version) {
2043
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments)
2144

@@ -26,6 +49,21 @@ function toBase58Check (hash, version) {
2649
return bs58check.encode(payload)
2750
}
2851

52+
function toBech32 (prefix, version, program) {
53+
if (version > 16) throw new Error('Invalid version (' + version + ')')
54+
if (version === 0) {
55+
if (program.length !== 20 && program.length !== 32) throw new Error('Unknown program')
56+
} else {
57+
if (program.length < 2) throw new Error('Program too short')
58+
if (program.length > 40) throw new Error('Program too long')
59+
}
60+
61+
var words = bech32.toWords(program)
62+
words.unshift(version)
63+
64+
return bech32.encode(prefix, words)
65+
}
66+
2967
function fromOutputScript (outputScript, network) {
3068
network = network || networks.bitcoin
3169

@@ -47,7 +85,9 @@ function toOutputScript (address, network) {
4785

4886
module.exports = {
4987
fromBase58Check: fromBase58Check,
88+
fromBech32: fromBech32,
5089
fromOutputScript: fromOutputScript,
5190
toBase58Check: toBase58Check,
91+
toBech32: toBech32,
5292
toOutputScript: toOutputScript
5393
}

test/address.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var fixtures = require('./fixtures/address.json')
88

99
describe('address', function () {
1010
describe('fromBase58Check', function () {
11-
fixtures.valid.forEach(function (f) {
11+
fixtures.standard.forEach(function (f) {
1212
it('decodes ' + f.base58check, function () {
1313
var decode = baddress.fromBase58Check(f.base58check)
1414

@@ -26,8 +26,30 @@ describe('address', function () {
2626
})
2727
})
2828

29+
describe('fromBech32', function () {
30+
fixtures.bech32.forEach((f) => {
31+
it('encodes ' + f.address, function () {
32+
var actual = baddress.fromBech32(f.address)
33+
34+
assert.strictEqual(actual.prefix, f.prefix)
35+
assert.strictEqual(actual.program.toString('hex'), f.program)
36+
assert.strictEqual(actual.version, f.version)
37+
})
38+
})
39+
40+
fixtures.invalid.bech32.forEach((f, i) => {
41+
if (f.address === undefined) return
42+
43+
it('decode fails for ' + f.address + '(' + f.exception + ')', function () {
44+
assert.throws(function () {
45+
baddress.fromBech32(f.address, f.prefix)
46+
}, new RegExp(f.exception))
47+
})
48+
})
49+
})
50+
2951
describe('fromOutputScript', function () {
30-
fixtures.valid.forEach(function (f) {
52+
fixtures.standard.forEach(function (f) {
3153
it('parses ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
3254
var script = bscript.fromASM(f.script)
3355
var address = baddress.fromOutputScript(script, networks[f.network])
@@ -48,7 +70,7 @@ describe('address', function () {
4870
})
4971

5072
describe('toBase58Check', function () {
51-
fixtures.valid.forEach(function (f) {
73+
fixtures.standard.forEach(function (f) {
5274
it('formats ' + f.hash + ' (' + f.network + ')', function () {
5375
var address = baddress.toBase58Check(Buffer.from(f.hash, 'hex'), f.version)
5476

@@ -57,8 +79,30 @@ describe('address', function () {
5779
})
5880
})
5981

82+
describe('toBech32', function () {
83+
fixtures.bech32.forEach((f, i) => {
84+
// unlike the reference impl., we don't support mixed/uppercase
85+
var string = f.address.toLowerCase()
86+
var program = Buffer.from(f.program, 'hex')
87+
88+
it('encode ' + string, function () {
89+
assert.deepEqual(baddress.toBech32(f.prefix, f.version, program), string)
90+
})
91+
})
92+
93+
fixtures.invalid.bech32.forEach((f, i) => {
94+
if (!f.prefix || f.version === undefined || f.program === undefined) return
95+
96+
it('encode fails (' + f.exception, function () {
97+
assert.throws(function () {
98+
baddress.toBech32(f.prefix, f.version, Buffer.from(f.program, 'hex'))
99+
}, new RegExp(f.exception))
100+
})
101+
})
102+
})
103+
60104
describe('toOutputScript', function () {
61-
fixtures.valid.forEach(function (f) {
105+
fixtures.standard.forEach(function (f) {
62106
var network = networks[f.network]
63107

64108
it('exports ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {

test/fixtures/address.json

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"valid": [
2+
"standard": [
33
{
44
"network": "bitcoin",
55
"version": 0,
@@ -43,7 +43,96 @@
4343
"script": "OP_HASH160 cd7b44d0b03f2d026d1e586d7ae18903b0d385f6 OP_EQUAL"
4444
}
4545
],
46+
"bech32": [
47+
{
48+
"address": "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
49+
"prefix": "bc",
50+
"program": "751e76e8199196d454941c45d1b3a323f1433bd6",
51+
"version": 0
52+
},
53+
{
54+
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
55+
"prefix": "tb",
56+
"program": "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
57+
"version": 0
58+
},
59+
{
60+
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
61+
"prefix": "bc",
62+
"program": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
63+
"version": 1
64+
},
65+
{
66+
"address": "BC1SW50QA3JX3S",
67+
"prefix": "bc",
68+
"program": "751e",
69+
"version": 16
70+
},
71+
{
72+
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
73+
"prefix": "bc",
74+
"program": "751e76e8199196d454941c45d1b3a323",
75+
"version": 2
76+
},
77+
{
78+
"address": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
79+
"prefix": "tb",
80+
"program": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
81+
"version": 0
82+
}
83+
],
4684
"invalid": {
85+
"bech32": [
86+
{
87+
"address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
88+
"exception": "Invalid checksum"
89+
},
90+
{
91+
"address": "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
92+
"prefix": "bc",
93+
"version": 17,
94+
"program": "751e76e8199196d454941c45d1b3a323f1433bd6",
95+
"exception": "Invalid version \\(17\\)"
96+
},
97+
{
98+
"address": "BC1SW50QA3JX3S",
99+
"prefix": "foo",
100+
"exception": "Expected foo, got bc"
101+
},
102+
{
103+
"address": "bc1rw5uspcuh",
104+
"prefix": "bc",
105+
"version": 1,
106+
"program": "75",
107+
"exception": "Program too short"
108+
},
109+
{
110+
"address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
111+
"prefix": "bc",
112+
"version": 1,
113+
"program": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675",
114+
"exception": "Program too long"
115+
},
116+
{
117+
"address": "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
118+
"prefix": "bc",
119+
"version": 0,
120+
"program": "1d1e76e8199196d454941c45d1b3a323",
121+
"exception": "Unknown program"
122+
},
123+
{
124+
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
125+
"exception": "Mixed-case string"
126+
},
127+
{
128+
"address": "tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
129+
"exception": "Excess padding"
130+
},
131+
{
132+
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
133+
"exception": "Non-zero padding"
134+
}
135+
],
47136
"fromBase58Check": [
48137
{
49138
"address": "7SeEnXWPaCCALbVrTnszCVGfRU8cGfx",

0 commit comments

Comments
 (0)