add Bech32 support to toOutputScript/fromOutputScript
This commit is contained in:
parent
d1052e4996
commit
b1272a1200
4 changed files with 131 additions and 115 deletions
|
@ -8,35 +8,26 @@ var types = require('./types')
|
|||
|
||||
function fromBase58Check (address) {
|
||||
var payload = bs58check.decode(address)
|
||||
|
||||
// TODO: 4.0.0, move to "toOutputScript"
|
||||
if (payload.length < 21) throw new TypeError(address + ' is too short')
|
||||
if (payload.length > 21) throw new TypeError(address + ' is too long')
|
||||
|
||||
var version = payload.readUInt8(0)
|
||||
var hash = payload.slice(1)
|
||||
|
||||
return { hash: hash, version: version }
|
||||
return { version: version, hash: hash }
|
||||
}
|
||||
|
||||
function fromBech32 (address, expectedPrefix) {
|
||||
function fromBech32 (address) {
|
||||
var result = bech32.decode(address)
|
||||
var prefix = result.prefix
|
||||
var words = result.words
|
||||
if (expectedPrefix !== undefined) {
|
||||
if (prefix !== expectedPrefix) throw new Error('Expected ' + expectedPrefix + ', got ' + prefix)
|
||||
var data = bech32.fromWords(result.words.slice(1))
|
||||
|
||||
return {
|
||||
version: result.words[0],
|
||||
prefix: result.prefix,
|
||||
data: Buffer.from(data)
|
||||
}
|
||||
|
||||
var version = words[0]
|
||||
if (version > 16) throw new Error('Invalid version (' + version + ')')
|
||||
var program = bech32.fromWords(words.slice(1))
|
||||
|
||||
if (version === 0) {
|
||||
if (program.length !== 20 && program.length !== 32) throw new Error('Unknown program')
|
||||
} else {
|
||||
if (program.length < 2) throw new Error('Program too short')
|
||||
if (program.length > 40) throw new Error('Program too long')
|
||||
}
|
||||
|
||||
return { version, prefix, program: Buffer.from(program) }
|
||||
}
|
||||
|
||||
function toBase58Check (hash, version) {
|
||||
|
@ -49,16 +40,8 @@ function toBase58Check (hash, version) {
|
|||
return bs58check.encode(payload)
|
||||
}
|
||||
|
||||
function toBech32 (prefix, version, program) {
|
||||
if (version > 16) throw new Error('Invalid version (' + version + ')')
|
||||
if (version === 0) {
|
||||
if (program.length !== 20 && program.length !== 32) throw new Error('Unknown program')
|
||||
} else {
|
||||
if (program.length < 2) throw new Error('Program too short')
|
||||
if (program.length > 40) throw new Error('Program too long')
|
||||
}
|
||||
|
||||
var words = bech32.toWords(program)
|
||||
function toBech32 (data, version, prefix) {
|
||||
var words = bech32.toWords(data)
|
||||
words.unshift(version)
|
||||
|
||||
return bech32.encode(prefix, words)
|
||||
|
@ -69,6 +52,8 @@ function fromOutputScript (outputScript, network) {
|
|||
|
||||
if (bscript.pubKeyHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(3, 23), network.pubKeyHash)
|
||||
if (bscript.scriptHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(2, 22), network.scriptHash)
|
||||
if (bscript.witnessPubKeyHash.output.check(outputScript)) return toBech32(bscript.compile(outputScript).slice(2, 22), 0, network.bech32)
|
||||
if (bscript.witnessScriptHash.output.check(outputScript)) return toBech32(bscript.compile(outputScript).slice(2, 34), 0, network.bech32)
|
||||
|
||||
throw new Error(bscript.toASM(outputScript) + ' has no matching Address')
|
||||
}
|
||||
|
@ -76,9 +61,27 @@ function fromOutputScript (outputScript, network) {
|
|||
function toOutputScript (address, network) {
|
||||
network = network || networks.bitcoin
|
||||
|
||||
var decode = fromBase58Check(address)
|
||||
var decode
|
||||
try {
|
||||
decode = fromBase58Check(address)
|
||||
} catch (e) {}
|
||||
|
||||
if (decode) {
|
||||
if (decode.version === network.pubKeyHash) return bscript.pubKeyHash.output.encode(decode.hash)
|
||||
if (decode.version === network.scriptHash) return bscript.scriptHash.output.encode(decode.hash)
|
||||
} else {
|
||||
try {
|
||||
decode = fromBech32(address)
|
||||
} catch (e) {}
|
||||
|
||||
if (decode) {
|
||||
if (decode.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix')
|
||||
if (decode.version === 0) {
|
||||
if (decode.data.length === 20) return bscript.witnessPubKeyHash.output.encode(decode.data)
|
||||
if (decode.data.length === 32) return bscript.witnessScriptHash.output.encode(decode.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(address + ' has no matching Script')
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
module.exports = {
|
||||
bitcoin: {
|
||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||
bech32: 'bc',
|
||||
bip32: {
|
||||
public: 0x0488b21e,
|
||||
private: 0x0488ade4
|
||||
|
@ -14,6 +15,7 @@ module.exports = {
|
|||
},
|
||||
testnet: {
|
||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||
bech32: 'tb',
|
||||
bip32: {
|
||||
public: 0x043587cf,
|
||||
private: 0x04358394
|
||||
|
|
|
@ -9,6 +9,8 @@ var fixtures = require('./fixtures/address.json')
|
|||
describe('address', function () {
|
||||
describe('fromBase58Check', function () {
|
||||
fixtures.standard.forEach(function (f) {
|
||||
if (!f.base58check) return
|
||||
|
||||
it('decodes ' + f.base58check, function () {
|
||||
var decode = baddress.fromBase58Check(f.base58check)
|
||||
|
||||
|
@ -27,22 +29,22 @@ describe('address', function () {
|
|||
})
|
||||
|
||||
describe('fromBech32', function () {
|
||||
fixtures.bech32.forEach((f) => {
|
||||
it('encodes ' + f.address, function () {
|
||||
var actual = baddress.fromBech32(f.address)
|
||||
fixtures.standard.forEach((f) => {
|
||||
if (!f.bech32) return
|
||||
|
||||
it('decodes ' + f.bech32, function () {
|
||||
var actual = baddress.fromBech32(f.bech32)
|
||||
|
||||
assert.strictEqual(actual.prefix, f.prefix)
|
||||
assert.strictEqual(actual.program.toString('hex'), f.program)
|
||||
assert.strictEqual(actual.version, f.version)
|
||||
assert.strictEqual(actual.prefix, networks[f.network].bech32)
|
||||
assert.strictEqual(actual.data.toString('hex'), f.data)
|
||||
})
|
||||
})
|
||||
|
||||
fixtures.invalid.bech32.forEach((f, i) => {
|
||||
if (f.address === undefined) return
|
||||
|
||||
it('decode fails for ' + f.address + '(' + f.exception + ')', function () {
|
||||
it('decode fails for ' + f.bech32 + '(' + f.exception + ')', function () {
|
||||
assert.throws(function () {
|
||||
baddress.fromBech32(f.address, f.prefix)
|
||||
baddress.fromBech32(f.address)
|
||||
}, new RegExp(f.exception))
|
||||
})
|
||||
})
|
||||
|
@ -50,11 +52,11 @@ describe('address', function () {
|
|||
|
||||
describe('fromOutputScript', function () {
|
||||
fixtures.standard.forEach(function (f) {
|
||||
it('parses ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
|
||||
it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
|
||||
var script = bscript.fromASM(f.script)
|
||||
var address = baddress.fromOutputScript(script, networks[f.network])
|
||||
|
||||
assert.strictEqual(address, f.base58check)
|
||||
assert.strictEqual(address, f.base58check || f.bech32.toLowerCase())
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -71,7 +73,9 @@ describe('address', function () {
|
|||
|
||||
describe('toBase58Check', function () {
|
||||
fixtures.standard.forEach(function (f) {
|
||||
it('formats ' + f.hash + ' (' + f.network + ')', function () {
|
||||
if (!f.base58check) return
|
||||
|
||||
it('encodes ' + f.hash + ' (' + f.network + ')', function () {
|
||||
var address = baddress.toBase58Check(Buffer.from(f.hash, 'hex'), f.version)
|
||||
|
||||
assert.strictEqual(address, f.base58check)
|
||||
|
@ -81,21 +85,20 @@ describe('address', function () {
|
|||
|
||||
describe('toBech32', function () {
|
||||
fixtures.bech32.forEach((f, i) => {
|
||||
// unlike the reference impl., we don't support mixed/uppercase
|
||||
var string = f.address.toLowerCase()
|
||||
var program = Buffer.from(f.program, 'hex')
|
||||
if (!f.bech32) return
|
||||
var data = Buffer.from(f.data, 'hex')
|
||||
|
||||
it('encode ' + string, function () {
|
||||
assert.deepEqual(baddress.toBech32(f.prefix, f.version, program), string)
|
||||
it('encode ' + f.address, function () {
|
||||
assert.deepEqual(baddress.toBech32(data, f.version, f.prefix), f.address)
|
||||
})
|
||||
})
|
||||
|
||||
fixtures.invalid.bech32.forEach((f, i) => {
|
||||
if (!f.prefix || f.version === undefined || f.program === undefined) return
|
||||
if (!f.prefix || f.version === undefined || f.data === undefined) return
|
||||
|
||||
it('encode fails (' + f.exception, function () {
|
||||
assert.throws(function () {
|
||||
baddress.toBech32(f.prefix, f.version, Buffer.from(f.program, 'hex'))
|
||||
baddress.toBech32(Buffer.from(f.data, 'hex'), f.version, f.prefix)
|
||||
}, new RegExp(f.exception))
|
||||
})
|
||||
})
|
||||
|
@ -103,10 +106,8 @@ describe('address', function () {
|
|||
|
||||
describe('toOutputScript', function () {
|
||||
fixtures.standard.forEach(function (f) {
|
||||
var network = networks[f.network]
|
||||
|
||||
it('exports ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
|
||||
var script = baddress.toOutputScript(f.base58check, network)
|
||||
it('decodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
|
||||
var script = baddress.toOutputScript(f.base58check || f.bech32, networks[f.network])
|
||||
|
||||
assert.strictEqual(bscript.toASM(script), f.script)
|
||||
})
|
||||
|
@ -115,7 +116,7 @@ describe('address', function () {
|
|||
fixtures.invalid.toOutputScript.forEach(function (f) {
|
||||
it('throws when ' + f.exception, function () {
|
||||
assert.throws(function () {
|
||||
baddress.toOutputScript(f.address)
|
||||
baddress.toOutputScript(f.address, f.network)
|
||||
}, new RegExp(f.address + ' ' + f.exception))
|
||||
})
|
||||
})
|
||||
|
|
128
test/fixtures/address.json
vendored
128
test/fixtures/address.json
vendored
|
@ -41,44 +41,48 @@
|
|||
"hash": "cd7b44d0b03f2d026d1e586d7ae18903b0d385f6",
|
||||
"base58check": "2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w",
|
||||
"script": "OP_HASH160 cd7b44d0b03f2d026d1e586d7ae18903b0d385f6 OP_EQUAL"
|
||||
},
|
||||
{
|
||||
"network": "bitcoin",
|
||||
"version": 0,
|
||||
"bech32": "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
|
||||
"data": "751e76e8199196d454941c45d1b3a323f1433bd6",
|
||||
"script": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6"
|
||||
|
||||
},
|
||||
{
|
||||
"network": "testnet",
|
||||
"version": 0,
|
||||
"bech32": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||
"data": "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
|
||||
"script": "OP_0 1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"
|
||||
},
|
||||
{
|
||||
"network": "testnet",
|
||||
"version": 0,
|
||||
"bech32": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
|
||||
"data": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
|
||||
"script": "OP_0 000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
|
||||
}
|
||||
],
|
||||
"bech32": [
|
||||
{
|
||||
"address": "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
|
||||
"prefix": "bc",
|
||||
"program": "751e76e8199196d454941c45d1b3a323f1433bd6",
|
||||
"version": 0
|
||||
},
|
||||
{
|
||||
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||
"prefix": "tb",
|
||||
"program": "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
|
||||
"version": 0
|
||||
},
|
||||
{
|
||||
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
|
||||
"version": 1,
|
||||
"prefix": "bc",
|
||||
"program": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"address": "BC1SW50QA3JX3S",
|
||||
"prefix": "bc",
|
||||
"program": "751e",
|
||||
"version": 16
|
||||
"data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
|
||||
},
|
||||
{
|
||||
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
|
||||
"version": 2,
|
||||
"prefix": "bc",
|
||||
"program": "751e76e8199196d454941c45d1b3a323",
|
||||
"version": 2
|
||||
"data": "751e76e8199196d454941c45d1b3a323"
|
||||
},
|
||||
{
|
||||
"address": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
|
||||
"prefix": "tb",
|
||||
"program": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
|
||||
"version": 0
|
||||
"address": "BC1SW50QA3JX3S",
|
||||
"version": 16,
|
||||
"prefix": "bc",
|
||||
"data": "751e"
|
||||
}
|
||||
],
|
||||
"invalid": {
|
||||
|
@ -87,39 +91,6 @@
|
|||
"address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
|
||||
"exception": "Invalid checksum"
|
||||
},
|
||||
{
|
||||
"address": "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
|
||||
"prefix": "bc",
|
||||
"version": 17,
|
||||
"program": "751e76e8199196d454941c45d1b3a323f1433bd6",
|
||||
"exception": "Invalid version \\(17\\)"
|
||||
},
|
||||
{
|
||||
"address": "BC1SW50QA3JX3S",
|
||||
"prefix": "foo",
|
||||
"exception": "Expected foo, got bc"
|
||||
},
|
||||
{
|
||||
"address": "bc1rw5uspcuh",
|
||||
"prefix": "bc",
|
||||
"version": 1,
|
||||
"program": "75",
|
||||
"exception": "Program too short"
|
||||
},
|
||||
{
|
||||
"address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
|
||||
"prefix": "bc",
|
||||
"version": 1,
|
||||
"program": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675",
|
||||
"exception": "Program too long"
|
||||
},
|
||||
{
|
||||
"address": "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
|
||||
"prefix": "bc",
|
||||
"version": 0,
|
||||
"program": "1d1e76e8199196d454941c45d1b3a323",
|
||||
"exception": "Unknown program"
|
||||
},
|
||||
{
|
||||
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
|
||||
"exception": "Mixed-case string"
|
||||
|
@ -155,12 +126,51 @@
|
|||
{
|
||||
"exception": "has no matching Address",
|
||||
"script": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474"
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Address",
|
||||
"script": "OP_0 75"
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Address",
|
||||
"script": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675"
|
||||
}
|
||||
],
|
||||
"toOutputScript": [
|
||||
{
|
||||
"exception": "has no matching Script",
|
||||
"address": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE"
|
||||
},
|
||||
{
|
||||
"exception": "has an invalid prefix",
|
||||
"address": "BC1SW50QA3JX3S",
|
||||
"network": {
|
||||
"bech32": "foo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Script",
|
||||
"address": "bc1rw5uspcuh"
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Script",
|
||||
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Script",
|
||||
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj"
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Script",
|
||||
"address": "BC1SW50QA3JX3S"
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Script",
|
||||
"address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
|
||||
},
|
||||
{
|
||||
"exception": "has no matching Script",
|
||||
"address": "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue