From fee3429d36d9b5379d1386f953e1c44ebab0b565 Mon Sep 17 00:00:00 2001 From: earthflower Date: Tue, 27 Feb 2024 01:25:37 +0530 Subject: [PATCH 1/3] Update bitcoinEncoder and bitcoinDecoder as per BIP350 with taproot support * Update bitcoinEncoder and bitcoinDecoder as per BIP350 * Now btc addresses can support taproot support * Updated outdated test vectors to comply with BIP350 --- src/coin/btc.test.ts | 14 +++++++++++--- src/coin/btg.test.ts | 4 ++-- src/coin/mona.test.ts | 2 +- src/utils/bech32.ts | 28 ++++++++++++++++++++++++---- src/utils/bitcoin.ts | 8 +++++++- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/coin/btc.test.ts b/src/coin/btc.test.ts index 6625e999..4deec24b 100644 --- a/src/coin/btc.test.ts +++ b/src/coin/btc.test.ts @@ -16,18 +16,26 @@ describe.each([ hex: "0014751e76e8199196d454941c45d1b3a323f1433bd6", }, { - text: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", + text: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", hex: "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", }, - { text: "bc1sw50qa3jx3s", hex: "6002751e" }, + { text: "bc1sw50qgdz25j", hex: "6002751e" }, { - text: "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", + text: "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", hex: "5210751e76e8199196d454941c45d1b3a323", }, { text: "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", hex: "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", }, + { + text: "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", + hex: "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + }, + { + text: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", + hex: "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + } ])("btc address", ({ text, hex }) => { test(`encode: ${text}`, () => { expect(encodeBtcAddress(hexToBytes(hex))).toEqual(text); diff --git a/src/coin/btg.test.ts b/src/coin/btg.test.ts index b24be20a..0633fd4d 100644 --- a/src/coin/btg.test.ts +++ b/src/coin/btg.test.ts @@ -12,11 +12,11 @@ describe.each([ hex: "a9145ece0cadddc415b1980f001785947120acdb36fc87", }, { - text: "btg1zw508d6qejxtdg4y5r3zarvaryv2eet8g", + text: "btg1zw508d6qejxtdg4y5r3zarvaryvl9f8z2", hex: "5210751e76e8199196d454941c45d1b3a323", }, { - text: "btg1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kc36v4c", + text: "btg1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kdd2qs6", hex: "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", }, ])("btg address", ({ text, hex }) => { diff --git a/src/coin/mona.test.ts b/src/coin/mona.test.ts index d4bf83b2..23548c9a 100644 --- a/src/coin/mona.test.ts +++ b/src/coin/mona.test.ts @@ -12,7 +12,7 @@ describe.each([ hex: "a9146449f568c9cd2378138f2636e1567112a184a9e887", }, { - text: "mona1zw508d6qejxtdg4y5r3zarvaryvhm3vz7", + text: "mona1zw508d6qejxtdg4y5r3zarvaryvz8pq8u", hex: "5210751e76e8199196d454941c45d1b3a323", }, ])("mona address", ({ text, hex }) => { diff --git a/src/utils/bech32.ts b/src/utils/bech32.ts index e2d94b96..cbec8e52 100644 --- a/src/utils/bech32.ts +++ b/src/utils/bech32.ts @@ -44,10 +44,12 @@ export const createBech32SegwitEncoder = } else if (version !== 0x00) { throw Error("Unrecognised address format"); } - - const words = [version].concat( - bech32.toWords(source.slice(2, source[1] + 2)) - ); + let words: number[] = []; + if (version > 0 && version < 17) { + words = [version].concat(bech32m.toWords(source.slice(2, source[1] + 2))); + return bech32m.encode(hrp, words); + } + words = [version].concat(bech32.toWords(source.slice(2, source[1] + 2))); return bech32.encode(hrp, words); }; @@ -68,3 +70,21 @@ export const createBech32SegwitDecoder = new Uint8Array(script) ); }; + +export const createBech32mTaprootDecoder = + (hrp: string) => + (source: string): Uint8Array => { + const { prefix, words } = bech32m.decode(source); + if (prefix !== hrp) { + throw Error("Unexpected human-readable part in bech32 encoded address"); + } + const script = bech32m.fromWords(words.slice(1)); + let version = words[0]; + if (version > 0) { + version += 0x50; + } + return concatBytes( + new Uint8Array([version, script.length]), + new Uint8Array(script) + ); + }; diff --git a/src/utils/bitcoin.ts b/src/utils/bitcoin.ts index 40ddf708..a49d9648 100644 --- a/src/utils/bitcoin.ts +++ b/src/utils/bitcoin.ts @@ -6,6 +6,7 @@ import { import { createBech32SegwitDecoder, createBech32SegwitEncoder, + createBech32mTaprootDecoder, } from "./bech32.js"; export type BitcoinCoderParameters = { @@ -20,13 +21,18 @@ export const createBitcoinDecoder = ({ p2shVersions, }: BitcoinCoderParameters) => { const decodeBech32 = createBech32SegwitDecoder(hrp); + const decodeBech32m = createBech32mTaprootDecoder(hrp); const decodeBase58 = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); return (source: string): Uint8Array => { if (source.toLowerCase().startsWith(hrp + "1")) { - return decodeBech32(source); + try { + return decodeBech32(source); + } catch (error) { + return decodeBech32m(source); + } } return decodeBase58(source); }; From 65fd3ccb546d47fed78fdebda89b64f98daf04a1 Mon Sep 17 00:00:00 2001 From: tate Date: Tue, 27 Feb 2024 12:50:22 +1100 Subject: [PATCH 2/3] simplify taproot decode --- src/utils/bech32.ts | 32 +++++++++++--------------------- src/utils/bitcoin.ts | 8 +------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/utils/bech32.ts b/src/utils/bech32.ts index cbec8e52..9435f7cc 100644 --- a/src/utils/bech32.ts +++ b/src/utils/bech32.ts @@ -56,33 +56,23 @@ export const createBech32SegwitEncoder = export const createBech32SegwitDecoder = (hrp: string) => (source: string): Uint8Array => { - const { prefix, words } = bech32.decode(source); - if (prefix !== hrp) { - throw Error("Unexpected human-readable part in bech32 encoded address"); - } + const decodedObj = + bech32.decodeUnsafe(source) || bech32m.decodeUnsafe(source); + + if (!decodedObj) throw new Error("Unrecognised address format"); + const { prefix, words } = decodedObj; + + if (prefix !== hrp) + throw new Error( + "Unexpected human-readable part in bech32 encoded address" + ); + const script = bech32.fromWords(words.slice(1)); let version = words[0]; if (version > 0) { version += 0x50; } - return concatBytes( - new Uint8Array([version, script.length]), - new Uint8Array(script) - ); - }; -export const createBech32mTaprootDecoder = - (hrp: string) => - (source: string): Uint8Array => { - const { prefix, words } = bech32m.decode(source); - if (prefix !== hrp) { - throw Error("Unexpected human-readable part in bech32 encoded address"); - } - const script = bech32m.fromWords(words.slice(1)); - let version = words[0]; - if (version > 0) { - version += 0x50; - } return concatBytes( new Uint8Array([version, script.length]), new Uint8Array(script) diff --git a/src/utils/bitcoin.ts b/src/utils/bitcoin.ts index a49d9648..40ddf708 100644 --- a/src/utils/bitcoin.ts +++ b/src/utils/bitcoin.ts @@ -6,7 +6,6 @@ import { import { createBech32SegwitDecoder, createBech32SegwitEncoder, - createBech32mTaprootDecoder, } from "./bech32.js"; export type BitcoinCoderParameters = { @@ -21,18 +20,13 @@ export const createBitcoinDecoder = ({ p2shVersions, }: BitcoinCoderParameters) => { const decodeBech32 = createBech32SegwitDecoder(hrp); - const decodeBech32m = createBech32mTaprootDecoder(hrp); const decodeBase58 = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); return (source: string): Uint8Array => { if (source.toLowerCase().startsWith(hrp + "1")) { - try { - return decodeBech32(source); - } catch (error) { - return decodeBech32m(source); - } + return decodeBech32(source); } return decodeBase58(source); }; From b932bc7f61e2f1fa74b83072d2ed3e901e030748 Mon Sep 17 00:00:00 2001 From: tate Date: Tue, 27 Feb 2024 12:52:35 +1100 Subject: [PATCH 3/3] throw new --- src/coin/nuls.ts | 2 +- src/utils/base58.ts | 8 ++++---- src/utils/bech32.ts | 6 ++++-- src/utils/eosio.ts | 3 ++- src/utils/hex.ts | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/coin/nuls.ts b/src/coin/nuls.ts index ba3bbaf1..9271d67e 100644 --- a/src/coin/nuls.ts +++ b/src/coin/nuls.ts @@ -17,7 +17,7 @@ const decodePrefix = (source: string): string => { return source.slice(i + 1); } } - throw Error("Unrecognised address format"); + throw new Error("Unrecognised address format"); }; export const encodeNulsAddress = (source: Uint8Array): string => { diff --git a/src/utils/base58.ts b/src/utils/base58.ts index 0782a788..4457e4e9 100644 --- a/src/utils/base58.ts +++ b/src/utils/base58.ts @@ -22,7 +22,7 @@ export const createBase58VersionedEncoder = source[source.length - 2] !== 0x88 || source[source.length - 1] !== 0xac ) { - throw Error("Unrecognised address format"); + throw new Error("Unrecognised address format"); } return base58CheckEncode( concatBytes(p2pkhVersion, source.slice(3, 3 + source[2])) @@ -32,14 +32,14 @@ export const createBase58VersionedEncoder = // P2SH: OP_HASH160 OP_EQUAL if (source[0] === 0xa9) { if (source[source.length - 1] !== 0x87) { - throw Error("Unrecognised address format"); + throw new Error("Unrecognised address format"); } return base58CheckEncode( concatBytes(p2shVersion, source.slice(2, 2 + source[1])) ); } - throw Error("Unrecognised address format"); + throw new Error("Unrecognised address format"); }; export const createBase58VersionedDecoder = @@ -65,5 +65,5 @@ export const createBase58VersionedDecoder = addr.slice(p2shVersions[0].length), new Uint8Array([0x87]) ); - throw Error("Unrecognised address format"); + throw new Error("Unrecognised address format"); }; diff --git a/src/utils/bech32.ts b/src/utils/bech32.ts index 9435f7cc..dff51eb2 100644 --- a/src/utils/bech32.ts +++ b/src/utils/bech32.ts @@ -20,7 +20,9 @@ const createInternalBech32Decoder = (source: string): Uint8Array => { const { prefix, words } = bechLib.decode(source, limit); if (prefix !== hrp) { - throw Error("Unexpected human-readable part in bech32 encoded address"); + throw new Error( + "Unexpected human-readable part in bech32 encoded address" + ); } return new Uint8Array(bechLib.fromWords(words)); }; @@ -42,7 +44,7 @@ export const createBech32SegwitEncoder = if (version >= 0x51 && version <= 0x60) { version -= 0x50; } else if (version !== 0x00) { - throw Error("Unrecognised address format"); + throw new Error("Unrecognised address format"); } let words: number[] = []; if (version > 0 && version < 17) { diff --git a/src/utils/eosio.ts b/src/utils/eosio.ts index 5bc491f4..494e7028 100644 --- a/src/utils/eosio.ts +++ b/src/utils/eosio.ts @@ -17,7 +17,8 @@ export const createEosEncoder = export const createEosDecoder = (prefix: string) => (source: string): Uint8Array => { - if (!source.startsWith(prefix)) throw Error("Unrecognised address format"); + if (!source.startsWith(prefix)) + throw new Error("Unrecognised address format"); const prefixStripped = source.slice(prefix.length); const decoded = base58UncheckedDecode(prefixStripped); const checksummed = eosChecksum.decode(decoded); diff --git a/src/utils/hex.ts b/src/utils/hex.ts index 87f2e05c..8104face 100644 --- a/src/utils/hex.ts +++ b/src/utils/hex.ts @@ -55,7 +55,7 @@ export const createHexChecksummedEncoder = export const createHexChecksummedDecoder = (chainId?: number) => (source: string) => { if (!isValidChecksumAddress(source, chainId)) { - throw Error("Unrecognised address format"); + throw new Error("Unrecognised address format"); } return hexToBytes(source as Hex); };