Skip to content

Commit b1272a1

Browse files
committed
add Bech32 support to toOutputScript/fromOutputScript
1 parent d1052e4 commit b1272a1

File tree

4 files changed

+129
-113
lines changed

4 files changed

+129
-113
lines changed

src/address.js

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,26 @@ var types = require('./types')
88

99
function fromBase58Check (address) {
1010
var payload = bs58check.decode(address)
11+
12+
// TODO: 4.0.0, move to "toOutputScript"
1113
if (payload.length < 21) throw new TypeError(address + ' is too short')
1214
if (payload.length > 21) throw new TypeError(address + ' is too long')
1315

1416
var version = payload.readUInt8(0)
1517
var hash = payload.slice(1)
1618

17-
return { hash: hash, version: version }
19+
return { version: version, hash: hash }
1820
}
1921

20-
function fromBech32 (address, expectedPrefix) {
22+
function fromBech32 (address) {
2123
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))
24+
var data = bech32.fromWords(result.words.slice(1))
3125

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')
26+
return {
27+
version: result.words[0],
28+
prefix: result.prefix,
29+
data: Buffer.from(data)
3730
}
38-
39-
return { version, prefix, program: Buffer.from(program) }
4031
}
4132

4233
function toBase58Check (hash, version) {
@@ -49,16 +40,8 @@ function toBase58Check (hash, version) {
4940
return bs58check.encode(payload)
5041
}
5142

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)
43+
function toBech32 (data, version, prefix) {
44+
var words = bech32.toWords(data)
6245
words.unshift(version)
6346

6447
return bech32.encode(prefix, words)
@@ -69,16 +52,36 @@ function fromOutputScript (outputScript, network) {
6952

7053
if (bscript.pubKeyHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(3, 23), network.pubKeyHash)
7154
if (bscript.scriptHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(2, 22), network.scriptHash)
55+
if (bscript.witnessPubKeyHash.output.check(outputScript)) return toBech32(bscript.compile(outputScript).slice(2, 22), 0, network.bech32)
56+
if (bscript.witnessScriptHash.output.check(outputScript)) return toBech32(bscript.compile(outputScript).slice(2, 34), 0, network.bech32)
7257

7358
throw new Error(bscript.toASM(outputScript) + ' has no matching Address')
7459
}
7560

7661
function toOutputScript (address, network) {
7762
network = network || networks.bitcoin
7863

79-
var decode = fromBase58Check(address)
80-
if (decode.version === network.pubKeyHash) return bscript.pubKeyHash.output.encode(decode.hash)
81-
if (decode.version === network.scriptHash) return bscript.scriptHash.output.encode(decode.hash)
64+
var decode
65+
try {
66+
decode = fromBase58Check(address)
67+
} catch (e) {}
68+
69+
if (decode) {
70+
if (decode.version === network.pubKeyHash) return bscript.pubKeyHash.output.encode(decode.hash)
71+
if (decode.version === network.scriptHash) return bscript.scriptHash.output.encode(decode.hash)
72+
} else {
73+
try {
74+
decode = fromBech32(address)
75+
} catch (e) {}
76+
77+
if (decode) {
78+
if (decode.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix')
79+
if (decode.version === 0) {
80+
if (decode.data.length === 20) return bscript.witnessPubKeyHash.output.encode(decode.data)
81+
if (decode.data.length === 32) return bscript.witnessScriptHash.output.encode(decode.data)
82+
}
83+
}
84+
}
8285

8386
throw new Error(address + ' has no matching Script')
8487
}

src/networks.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module.exports = {
55
bitcoin: {
66
messagePrefix: '\x18Bitcoin Signed Message:\n',
7+
bech32: 'bc',
78
bip32: {
89
public: 0x0488b21e,
910
private: 0x0488ade4
@@ -14,6 +15,7 @@ module.exports = {
1415
},
1516
testnet: {
1617
messagePrefix: '\x18Bitcoin Signed Message:\n',
18+
bech32: 'tb',
1719
bip32: {
1820
public: 0x043587cf,
1921
private: 0x04358394

test/address.js

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ var fixtures = require('./fixtures/address.json')
99
describe('address', function () {
1010
describe('fromBase58Check', function () {
1111
fixtures.standard.forEach(function (f) {
12+
if (!f.base58check) return
13+
1214
it('decodes ' + f.base58check, function () {
1315
var decode = baddress.fromBase58Check(f.base58check)
1416

@@ -27,34 +29,34 @@ describe('address', function () {
2729
})
2830

2931
describe('fromBech32', function () {
30-
fixtures.bech32.forEach((f) => {
31-
it('encodes ' + f.address, function () {
32-
var actual = baddress.fromBech32(f.address)
32+
fixtures.standard.forEach((f) => {
33+
if (!f.bech32) return
34+
35+
it('decodes ' + f.bech32, function () {
36+
var actual = baddress.fromBech32(f.bech32)
3337

34-
assert.strictEqual(actual.prefix, f.prefix)
35-
assert.strictEqual(actual.program.toString('hex'), f.program)
3638
assert.strictEqual(actual.version, f.version)
39+
assert.strictEqual(actual.prefix, networks[f.network].bech32)
40+
assert.strictEqual(actual.data.toString('hex'), f.data)
3741
})
3842
})
3943

4044
fixtures.invalid.bech32.forEach((f, i) => {
41-
if (f.address === undefined) return
42-
43-
it('decode fails for ' + f.address + '(' + f.exception + ')', function () {
45+
it('decode fails for ' + f.bech32 + '(' + f.exception + ')', function () {
4446
assert.throws(function () {
45-
baddress.fromBech32(f.address, f.prefix)
47+
baddress.fromBech32(f.address)
4648
}, new RegExp(f.exception))
4749
})
4850
})
4951
})
5052

5153
describe('fromOutputScript', function () {
5254
fixtures.standard.forEach(function (f) {
53-
it('parses ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
55+
it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
5456
var script = bscript.fromASM(f.script)
5557
var address = baddress.fromOutputScript(script, networks[f.network])
5658

57-
assert.strictEqual(address, f.base58check)
59+
assert.strictEqual(address, f.base58check || f.bech32.toLowerCase())
5860
})
5961
})
6062

@@ -71,7 +73,9 @@ describe('address', function () {
7173

7274
describe('toBase58Check', function () {
7375
fixtures.standard.forEach(function (f) {
74-
it('formats ' + f.hash + ' (' + f.network + ')', function () {
76+
if (!f.base58check) return
77+
78+
it('encodes ' + f.hash + ' (' + f.network + ')', function () {
7579
var address = baddress.toBase58Check(Buffer.from(f.hash, 'hex'), f.version)
7680

7781
assert.strictEqual(address, f.base58check)
@@ -81,32 +85,29 @@ describe('address', function () {
8185

8286
describe('toBech32', function () {
8387
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')
88+
if (!f.bech32) return
89+
var data = Buffer.from(f.data, 'hex')
8790

88-
it('encode ' + string, function () {
89-
assert.deepEqual(baddress.toBech32(f.prefix, f.version, program), string)
91+
it('encode ' + f.address, function () {
92+
assert.deepEqual(baddress.toBech32(data, f.version, f.prefix), f.address)
9093
})
9194
})
9295

9396
fixtures.invalid.bech32.forEach((f, i) => {
94-
if (!f.prefix || f.version === undefined || f.program === undefined) return
97+
if (!f.prefix || f.version === undefined || f.data === undefined) return
9598

9699
it('encode fails (' + f.exception, function () {
97100
assert.throws(function () {
98-
baddress.toBech32(f.prefix, f.version, Buffer.from(f.program, 'hex'))
101+
baddress.toBech32(Buffer.from(f.data, 'hex'), f.version, f.prefix)
99102
}, new RegExp(f.exception))
100103
})
101104
})
102105
})
103106

104107
describe('toOutputScript', function () {
105108
fixtures.standard.forEach(function (f) {
106-
var network = networks[f.network]
107-
108-
it('exports ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
109-
var script = baddress.toOutputScript(f.base58check, network)
109+
it('decodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
110+
var script = baddress.toOutputScript(f.base58check || f.bech32, networks[f.network])
110111

111112
assert.strictEqual(bscript.toASM(script), f.script)
112113
})
@@ -115,7 +116,7 @@ describe('address', function () {
115116
fixtures.invalid.toOutputScript.forEach(function (f) {
116117
it('throws when ' + f.exception, function () {
117118
assert.throws(function () {
118-
baddress.toOutputScript(f.address)
119+
baddress.toOutputScript(f.address, f.network)
119120
}, new RegExp(f.address + ' ' + f.exception))
120121
})
121122
})

0 commit comments

Comments
 (0)