diff --git a/bun.lockb b/bun.lockb index f107a5b2..fa0c4683 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 9da5c762..048876c2 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@types/fs-extra": "^11.0.2", - "bun-types": "latest", + "bun-types": "1.0.22", "fs-extra": "^11.1.1", "mitata": "^0.1.6", "typescript": "^5.1.6" @@ -80,7 +80,6 @@ "dependencies": { "@noble/curves": "^1.2.0", "@noble/hashes": "^1.3.2", - "@scure/base": "^1.1.3", - "bech32": "^2.0.0" + "@scure/base": "^1.1.5" } } diff --git a/src/coin/ae.ts b/src/coin/ae.ts index 2fbe0875..b0343c25 100644 --- a/src/coin/ae.ts +++ b/src/coin/ae.ts @@ -1,17 +1,17 @@ import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "ae"; const coinType = 457; export const encodeAeAddress = (source: Uint8Array): string => { - return `ak_${base58Encode(source.slice(2))}`; + return `ak_${base58CheckEncode(source.slice(2))}`; }; export const decodeAeAddress = (source: string): Uint8Array => { return concatBytes( new Uint8Array([0x30, 0x78] /* 0x string */), - base58Decode(source.slice(3)) + base58CheckDecode(source.slice(3)) ); }; diff --git a/src/coin/aib.ts b/src/coin/aib.ts index 55c7fb35..7152e123 100644 --- a/src/coin/aib.ts +++ b/src/coin/aib.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "aib"; @@ -10,11 +10,11 @@ const coinType = 55; const p2pkhVersions = [new Uint8Array([0x17])]; const p2shVersions = [new Uint8Array([0x05])]; -export const encodeAibAddress = createBase58WithCheckEncoder( +export const encodeAibAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeAibAddress = createBase58WithCheckDecoder( +export const decodeAibAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/algo.ts b/src/coin/algo.ts index bf61afb8..60affdba 100644 --- a/src/coin/algo.ts +++ b/src/coin/algo.ts @@ -1,11 +1,7 @@ import { sha512_256 } from "@noble/hashes/sha512"; import { utils } from "@scure/base"; import type { Coin } from "../types.js"; -import { - base32Decode, - base32Encode, - unpaddedBase32Options, -} from "../utils/base32.js"; +import { base32UnpaddedDecode, base32UnpaddedEncode } from "../utils/base32.js"; const name = "algo"; const coinType = 283; @@ -14,10 +10,10 @@ const algoChecksum = utils.checksum(4, (data) => sha512_256(data).slice(-4)); export const encodeAlgoAddress = (source: Uint8Array): string => { const checksum = algoChecksum.encode(source); - return base32Encode(checksum, unpaddedBase32Options); + return base32UnpaddedEncode(checksum); }; export const decodeAlgoAddress = (source: string): Uint8Array => { - const decoded = base32Decode(source, unpaddedBase32Options); + const decoded = base32UnpaddedDecode(source); if (decoded.length !== 36) throw new Error("Unrecognised address format"); diff --git a/src/coin/ar.ts b/src/coin/ar.ts index d1d02d81..510e7c24 100644 --- a/src/coin/ar.ts +++ b/src/coin/ar.ts @@ -1,27 +1,11 @@ +import { base64urlnopad } from "@scure/base"; import type { Coin } from "../types.js"; -import { base64Decode, base64Encode } from "../utils/base64.js"; const name = "ar"; const coinType = 472; -const encodeReplaceRegex = /\+|\/|\=/g; -const decodeReplaceRegex = /\-|\_/g; - -export const encodeArAddress = (source: Uint8Array): string => { - return base64Encode(source).replace(encodeReplaceRegex, (match) => { - if (match === "+") return "-"; - if (match === "/") return "_"; - return ""; - }); -}; -export const decodeArAddress = (source: string): Uint8Array => { - const restoredBase64 = - source.replace(decodeReplaceRegex, (match) => { - if (match === "-") return "+"; - return "/"; - }) + "=".repeat((4 - (source.length % 4)) % 4); - return base64Decode(restoredBase64); -}; +export const encodeArAddress = base64urlnopad.encode; +export const decodeArAddress = base64urlnopad.decode; export const ar = { name, diff --git a/src/coin/ark.ts b/src/coin/ark.ts index 6fc0adf0..b0bb6aec 100644 --- a/src/coin/ark.ts +++ b/src/coin/ark.ts @@ -1,12 +1,12 @@ import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "ark"; const coinType = 111; -export const encodeArkAddress = base58Encode; +export const encodeArkAddress = base58CheckEncode; export const decodeArkAddress = (source: string): Uint8Array => { - const decoded = base58Decode(source); + const decoded = base58CheckDecode(source); if (decoded[0] !== 23) throw new Error("Invalid address"); return decoded; }; diff --git a/src/coin/bch.ts b/src/coin/bch.ts index 4572c1c5..7af83a8e 100644 --- a/src/coin/bch.ts +++ b/src/coin/bch.ts @@ -1,6 +1,6 @@ import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { createBase58WithCheckDecoder } from "../utils/base58.js"; +import { createBase58VersionedDecoder } from "../utils/base58.js"; import { decodeBchAddressToTypeAndHash, encodeBchAddressWithVersion, @@ -12,7 +12,7 @@ const coinType = 145; const p2pkhVersions = [new Uint8Array([0x00])]; const p2shVersions = [new Uint8Array([0x05])]; -const bchBase58Decode = createBase58WithCheckDecoder( +const bchBase58Decode = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/bps.ts b/src/coin/bps.ts index 114afcfe..cc1e31b0 100644 --- a/src/coin/bps.ts +++ b/src/coin/bps.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "bps"; @@ -10,11 +10,11 @@ const coinType = 576; const p2pkhVersions = [new Uint8Array([0x00])]; const p2shVersions = [new Uint8Array([0x05])]; -export const encodeBpsAddress = createBase58WithCheckEncoder( +export const encodeBpsAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeBpsAddress = createBase58WithCheckDecoder( +export const decodeBpsAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/bsv.ts b/src/coin/bsv.ts index efa1052e..397f411a 100644 --- a/src/coin/bsv.ts +++ b/src/coin/bsv.ts @@ -1,14 +1,14 @@ import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "bsv"; const coinType = 236; export const encodeBsvAddress = (source: Uint8Array): string => - base58Encode(concatBytes(new Uint8Array([0x00]), source)); + base58CheckEncode(concatBytes(new Uint8Array([0x00]), source)); export const decodeBsvAddress = (source: string): Uint8Array => { - const decoded = base58Decode(source); + const decoded = base58CheckDecode(source); if (decoded.length !== 21) throw new Error("Unrecognised address format"); diff --git a/src/coin/cca.ts b/src/coin/cca.ts index 68a8dbf3..98a7cb1d 100644 --- a/src/coin/cca.ts +++ b/src/coin/cca.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "cca"; @@ -10,11 +10,11 @@ const coinType = 489; const p2pkhVersions = [new Uint8Array([0x0b])]; const p2shVersions = [new Uint8Array([0x05])]; -export const encodeCcaAddress = createBase58WithCheckEncoder( +export const encodeCcaAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeCcaAddress = createBase58WithCheckDecoder( +export const decodeCcaAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/dash.ts b/src/coin/dash.ts index ecc1a87f..c6e608d4 100644 --- a/src/coin/dash.ts +++ b/src/coin/dash.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "dash"; @@ -10,11 +10,11 @@ const coinType = 5; const p2pkhVersions = [new Uint8Array([0x4c])]; const p2shVersions = [new Uint8Array([0x10])]; -export const encodeDashAddress = createBase58WithCheckEncoder( +export const encodeDashAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeDashAddress = createBase58WithCheckDecoder( +export const decodeDashAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/dcr.ts b/src/coin/dcr.ts index db3ceca8..3aaf59cf 100644 --- a/src/coin/dcr.ts +++ b/src/coin/dcr.ts @@ -1,11 +1,14 @@ import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "dcr"; const coinType = 42; -export const encodeDcrAddress = base58EncodeNoCheck; -export const decodeDcrAddress = base58DecodeNoCheck; +export const encodeDcrAddress = base58UncheckedEncode; +export const decodeDcrAddress = base58UncheckedDecode; export const dcr = { name, diff --git a/src/coin/divi.ts b/src/coin/divi.ts index f8d4fc51..ad9903bc 100644 --- a/src/coin/divi.ts +++ b/src/coin/divi.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "divi"; @@ -10,11 +10,11 @@ const coinType = 301; const p2pkhVersions = [new Uint8Array([0x1e])]; const p2shVersions = [new Uint8Array([0xd])]; -export const encodeDiviAddress = createBase58WithCheckEncoder( +export const encodeDiviAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeDiviAddress = createBase58WithCheckDecoder( +export const decodeDiviAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/doge.ts b/src/coin/doge.ts index 115f24a2..e9b3cc46 100644 --- a/src/coin/doge.ts +++ b/src/coin/doge.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "doge"; @@ -10,11 +10,11 @@ const coinType = 3; const p2pkhVersions = [new Uint8Array([0x1e])]; const p2shVersions = [new Uint8Array([0x16])]; -export const encodeDogeAddress = createBase58WithCheckEncoder( +export const encodeDogeAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeDogeAddress = createBase58WithCheckDecoder( +export const decodeDogeAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/ela.ts b/src/coin/ela.ts index cb767cac..88b13c79 100644 --- a/src/coin/ela.ts +++ b/src/coin/ela.ts @@ -1,11 +1,14 @@ import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "ela"; const coinType = 2305; -export const encodeElaAddress = base58EncodeNoCheck; -export const decodeElaAddress = base58DecodeNoCheck; +export const encodeElaAddress = base58UncheckedEncode; +export const decodeElaAddress = base58UncheckedDecode; export const ela = { name, diff --git a/src/coin/fil.ts b/src/coin/fil.ts index 0439e84d..499653c2 100644 --- a/src/coin/fil.ts +++ b/src/coin/fil.ts @@ -2,11 +2,7 @@ import { equalBytes } from "@noble/curves/abstract/utils"; import { blake2b } from "@noble/hashes/blake2b"; import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { - base32Decode, - base32Encode, - unpaddedBase32Options, -} from "../utils/base32.js"; +import { base32UnpaddedDecode, base32UnpaddedEncode } from "../utils/base32.js"; import { decodeLeb128, encodeLeb128 } from "../utils/leb128.js"; const name = "fil"; @@ -41,7 +37,7 @@ export const encodeFilAddress = (source: Uint8Array): string => { } const checksum = blake2b(source, { dkLen: 4 }); const bytes = concatBytes(payload, checksum); - const decoded = base32Encode(bytes, unpaddedBase32Options).toLowerCase(); + const decoded = base32UnpaddedEncode(bytes).toLowerCase(); return `f${protocol}${decoded}`; }; export const decodeFilAddress = (source: string): Uint8Array => { @@ -56,10 +52,7 @@ export const decodeFilAddress = (source: string): Uint8Array => { return concatBytes(protocolByte, encodeLeb128(BigInt(encoded))); } - const payloadWithChecksum = base32Decode( - encoded.toUpperCase(), - unpaddedBase32Options - ); + const payloadWithChecksum = base32UnpaddedDecode(encoded.toUpperCase()); const payload = payloadWithChecksum.slice(0, -4); const checksum = payloadWithChecksum.slice(-4); const decoded = concatBytes(protocolByte, payload); diff --git a/src/coin/firo.ts b/src/coin/firo.ts index 71a8edc8..9613bec7 100644 --- a/src/coin/firo.ts +++ b/src/coin/firo.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "firo"; @@ -10,11 +10,11 @@ const coinType = 136; const p2pkhVersions = [new Uint8Array([0x52])]; const p2shVersions = [new Uint8Array([0x07])]; -export const encodeFiroAddress = createBase58WithCheckEncoder( +export const encodeFiroAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeFiroAddress = createBase58WithCheckDecoder( +export const decodeFiroAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/hnt.ts b/src/coin/hnt.ts index 2c444cb2..5adab41f 100644 --- a/src/coin/hnt.ts +++ b/src/coin/hnt.ts @@ -1,16 +1,16 @@ import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "hnt"; const coinType = 904; export const encodeHntAddress = (source: Uint8Array): string => { const sourceWithVersion = concatBytes(new Uint8Array([0x00]), source); - return base58Encode(sourceWithVersion); + return base58CheckEncode(sourceWithVersion); }; export const decodeHntAddress = (source: string): Uint8Array => { - const decoded = base58Decode(source); + const decoded = base58CheckDecode(source); const version = decoded[0]; if (version !== 0) throw new Error("Unrecognised address format"); diff --git a/src/coin/iost.ts b/src/coin/iost.ts index d6ec96ab..f8ae7810 100644 --- a/src/coin/iost.ts +++ b/src/coin/iost.ts @@ -1,11 +1,14 @@ import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "iost"; const coinType = 291; -export const encodeIostAddress = base58EncodeNoCheck; -export const decodeIostAddress = base58DecodeNoCheck; +export const encodeIostAddress = base58UncheckedEncode; +export const decodeIostAddress = base58UncheckedDecode; export const iost = { name, diff --git a/src/coin/kmd.ts b/src/coin/kmd.ts index e503bb8a..b65c62de 100644 --- a/src/coin/kmd.ts +++ b/src/coin/kmd.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "kmd"; @@ -10,11 +10,11 @@ const coinType = 141; const p2pkhVersions = [new Uint8Array([0x3c])]; const p2shVersions = [new Uint8Array([0x55])]; -export const encodeKmdAddress = createBase58WithCheckEncoder( +export const encodeKmdAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeKmdAddress = createBase58WithCheckDecoder( +export const decodeKmdAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/lrg.ts b/src/coin/lrg.ts index e0478cbc..f2c43c3b 100644 --- a/src/coin/lrg.ts +++ b/src/coin/lrg.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "lrg"; @@ -10,11 +10,11 @@ const coinType = 568; const p2pkhVersions = [new Uint8Array([0x1e])]; const p2shVersions = [new Uint8Array([0x0d])]; -export const encodeLrgAddress = createBase58WithCheckEncoder( +export const encodeLrgAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeLrgAddress = createBase58WithCheckDecoder( +export const decodeLrgAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/mrx.ts b/src/coin/mrx.ts index 02aaeff6..669fc469 100644 --- a/src/coin/mrx.ts +++ b/src/coin/mrx.ts @@ -1,11 +1,11 @@ import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "mrx"; const coinType = 326; -export const encodeMrxAddress = base58Encode; -export const decodeMrxAddress = base58Decode; +export const encodeMrxAddress = base58CheckEncode; +export const decodeMrxAddress = base58CheckDecode; export const mrx = { name, diff --git a/src/coin/nano.ts b/src/coin/nano.ts index e948beb5..7c2a0bb5 100644 --- a/src/coin/nano.ts +++ b/src/coin/nano.ts @@ -1,27 +1,67 @@ import { blake2b } from "@noble/hashes/blake2b"; +import { utils, type Coder } from "@scure/base"; import type { Coin } from "../types.js"; -import { - base32Decode, - base32Encode, - createBase32Options, -} from "../utils/base32.js"; const name = "nano"; const coinType = 165; -const nanoBase32Options = createBase32Options({ - alphabet: "13456789abcdefghijkmnopqrstuwxyz", -}); +const convertRadixNano = ( + data: ArrayLike, + from: number, + to: number +) => { + const leftover = (data.length * from) % to; + const offset = leftover === 0 ? 0 : to - leftover; + + let carry = 0; + let pos = 0; + const mask = 2 ** to - 1; + const res: number[] = []; + + for (let i = 0; i < data.length; i++) { + const n = data[i]; + if (n >= 2 ** from) + throw new Error(`convertRadixNano: invalid data word=${n} from=${from}`); + carry = (carry << from) | n; + if (pos + from > 32) + throw new Error( + `convertRadixNano: carry overflow pos=${pos} from=${from}` + ); + pos += from; + for (; pos >= to; pos -= to) { + res.push((carry >>> (pos + offset - to)) & mask); + } + } + carry = (carry << (to - (pos + offset))) & mask; + if (pos > 0) res.push(carry >>> 0); + return res; +}; + +const radixNano: Coder = { + encode: (source) => convertRadixNano(source, 8, 5), + decode: (source) => { + const leftover = (source.length * 5) % 8; + let result = convertRadixNano(source, 5, 8); + if (leftover !== 0) result = result.slice(1); + return Uint8Array.from(result); + }, +}; + +const base32Nano = utils.chain( + radixNano, + utils.alphabet("13456789abcdefghijkmnopqrstuwxyz"), + utils.join("") +); export const encodeNanoAddress = (source: Uint8Array): string => { - const encoded = base32Encode(source, nanoBase32Options); + const encoded = base32Nano.encode(source); const checksum = blake2b(source, { dkLen: 5 }).reverse(); - const checksumEncoded = base32Encode(checksum, nanoBase32Options); + const checksumEncoded = base32Nano.encode(checksum); return `nano_${encoded}${checksumEncoded}`; }; export const decodeNanoAddress = (source: string): Uint8Array => { - const decoded = base32Decode(source.slice(5), nanoBase32Options); - return decoded.slice(0, -5); + const decoded = base32Nano.decode(source.slice(5)); + return Uint8Array.from(decoded.slice(0, -5)); }; export const nano = { diff --git a/src/coin/nas.ts b/src/coin/nas.ts index edcfbf67..b4a862b0 100644 --- a/src/coin/nas.ts +++ b/src/coin/nas.ts @@ -1,7 +1,10 @@ import { sha3_256 } from "@noble/hashes/sha3"; import { utils } from "@scure/base"; import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "nas"; const coinType = 2718; @@ -10,10 +13,10 @@ const nasChecksum = utils.checksum(4, sha3_256); export const encodeNasAddress = (source: Uint8Array): string => { const checksummed = nasChecksum.encode(source); - return base58EncodeNoCheck(checksummed); + return base58UncheckedEncode(checksummed); }; export const decodeNasAddress = (source: string): Uint8Array => { - const decoded = base58DecodeNoCheck(source); + const decoded = base58UncheckedDecode(source); if ( decoded.length !== 26 || diff --git a/src/coin/neo.ts b/src/coin/neo.ts index d86cc4c5..3a5c701a 100644 --- a/src/coin/neo.ts +++ b/src/coin/neo.ts @@ -1,11 +1,11 @@ import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "neo"; const coinType = 239; -export const encodeNeoAddress = base58Encode; -export const decodeNeoAddress = base58Decode; +export const encodeNeoAddress = base58CheckEncode; +export const decodeNeoAddress = base58CheckDecode; export const neo = { name, diff --git a/src/coin/nim.ts b/src/coin/nim.ts index b55cb425..0aa746ff 100644 --- a/src/coin/nim.ts +++ b/src/coin/nim.ts @@ -1,17 +1,9 @@ +import { utils } from "@scure/base"; import type { Coin } from "../types.js"; -import { - base32Decode, - base32Encode, - createBase32Options, -} from "../utils/base32.js"; const name = "nim"; const coinType = 242; -const nimBase32Options = createBase32Options({ - alphabet: "0123456789ABCDEFGHJKLMNPQRSTUVXY", -}); - const CCODE = "NQ"; const ibanCheck = (data: string): number => { @@ -41,8 +33,15 @@ const nimChecksum = (source: string): string => { return ("00" + (98 - ibanCheck(source + CCODE + "00"))).slice(-2); }; +const base32Nim = utils.chain( + utils.radix2(5), + utils.alphabet("0123456789ABCDEFGHJKLMNPQRSTUVXY"), + utils.padding(5), + utils.join("") +); + export const encodeNimAddress = (source: Uint8Array): string => { - const base32Part = base32Encode(source, nimBase32Options); + const base32Part = base32Nim.encode(source); const checksummed = nimChecksum(base32Part); return `${CCODE}${checksummed}${base32Part}`.replace(/.{4}/g, "$& ").trim(); }; @@ -56,7 +55,7 @@ export const decodeNimAddress = (source: string): Uint8Array => { if (checksum !== nimChecksum(base32Part)) throw new Error("Unrecognised address format"); - return base32Decode(base32Part, nimBase32Options); + return base32Nim.decode(base32Part); }; export const nim = { diff --git a/src/coin/nmc.ts b/src/coin/nmc.ts index bc871ad7..f34f2a80 100644 --- a/src/coin/nmc.ts +++ b/src/coin/nmc.ts @@ -1,11 +1,11 @@ import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "nmc"; const coinType = 7; -export const encodeNmcAddress = base58Encode; -export const decodeNmcAddress = base58Decode; +export const encodeNmcAddress = base58CheckEncode; +export const decodeNmcAddress = base58CheckDecode; export const nmc = { name, diff --git a/src/coin/nuls.ts b/src/coin/nuls.ts index 63300fc3..669573d9 100644 --- a/src/coin/nuls.ts +++ b/src/coin/nuls.ts @@ -1,6 +1,9 @@ import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "nuls"; const coinType = 8964; @@ -38,11 +41,11 @@ export const encodeNulsAddress = (source: Uint8Array): string => { new Uint8Array([0xff & (chainId >> 0)]), new Uint8Array([0xff & (chainId >> 8)]) ); - prefix = base58EncodeNoCheck(chainIdBytes).toUpperCase(); + prefix = base58UncheckedEncode(chainIdBytes).toUpperCase(); } return ( - prefix + prefixReference[prefix.length - 1] + base58EncodeNoCheck(payload) + prefix + prefixReference[prefix.length - 1] + base58UncheckedEncode(payload) ); }; export const decodeNulsAddress = (source: string): Uint8Array => { @@ -51,7 +54,7 @@ export const decodeNulsAddress = (source: string): Uint8Array => { else if (source.startsWith("tNULS")) sourceWithoutPrefix = source.slice(6); else sourceWithoutPrefix = decodePrefix(source); - const payload = base58DecodeNoCheck(sourceWithoutPrefix); + const payload = base58UncheckedDecode(sourceWithoutPrefix); let xor = 0x00; for (let i = 0; i < payload.length - 1; i++) { diff --git a/src/coin/ont.ts b/src/coin/ont.ts index 92a7401a..ea771216 100644 --- a/src/coin/ont.ts +++ b/src/coin/ont.ts @@ -1,16 +1,16 @@ import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "ont"; const coinType = 1024; export const encodeOntAddress = (source: Uint8Array): string => { const sourceWithVersion = concatBytes(new Uint8Array([0x17]), source); - return base58Encode(sourceWithVersion); + return base58CheckEncode(sourceWithVersion); }; export const decodeOntAddress = (source: string): Uint8Array => { - const decoded = base58Decode(source); + const decoded = base58CheckDecode(source); const version = decoded[0]; if (version !== 0x17) throw new Error("Unrecognised address format"); diff --git a/src/coin/ppc.ts b/src/coin/ppc.ts index 63b825a3..ba5aa136 100644 --- a/src/coin/ppc.ts +++ b/src/coin/ppc.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "ppc"; @@ -10,11 +10,11 @@ const coinType = 6; const p2pkhVersions = [new Uint8Array([0x37])]; const p2shVersions = [new Uint8Array([0x75])]; -export const encodePpcAddress = createBase58WithCheckEncoder( +export const encodePpcAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodePpcAddress = createBase58WithCheckDecoder( +export const decodePpcAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/qtum.ts b/src/coin/qtum.ts index 90091b61..15a8f1a7 100644 --- a/src/coin/qtum.ts +++ b/src/coin/qtum.ts @@ -1,11 +1,11 @@ import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "qtum"; const coinType = 2301; -export const encodeQtumAddress = base58Encode; -export const decodeQtumAddress = base58Decode; +export const encodeQtumAddress = base58CheckEncode; +export const decodeQtumAddress = base58CheckDecode; export const qtum = { name, diff --git a/src/coin/rdd.ts b/src/coin/rdd.ts index 650ee497..b21321da 100644 --- a/src/coin/rdd.ts +++ b/src/coin/rdd.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "rdd"; @@ -10,11 +10,11 @@ const coinType = 4; const p2pkhVersions = [new Uint8Array([0x3d])]; const p2shVersions = [new Uint8Array([0x05])]; -export const encodeRddAddress = createBase58WithCheckEncoder( +export const encodeRddAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeRddAddress = createBase58WithCheckDecoder( +export const decodeRddAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/rvn.ts b/src/coin/rvn.ts index dcf6c83d..8da3d95d 100644 --- a/src/coin/rvn.ts +++ b/src/coin/rvn.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "rvn"; @@ -10,11 +10,11 @@ const coinType = 175; const p2pkhVersions = [new Uint8Array([0x3c])]; const p2shVersions = [new Uint8Array([0x7a])]; -export const encodeRvnAddress = createBase58WithCheckEncoder( +export const encodeRvnAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeRvnAddress = createBase58WithCheckDecoder( +export const decodeRvnAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/sero.ts b/src/coin/sero.ts index 53e994d7..5a377a65 100644 --- a/src/coin/sero.ts +++ b/src/coin/sero.ts @@ -1,12 +1,15 @@ import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "sero"; const coinType = 569; -export const encodeSeroAddress = base58EncodeNoCheck; +export const encodeSeroAddress = base58UncheckedEncode; export const decodeSeroAddress = (source: string): Uint8Array => { - const decoded = base58DecodeNoCheck(source); + const decoded = base58UncheckedDecode(source); if (decoded.length !== 96) throw new Error("Unrecognised address format"); return decoded; }; diff --git a/src/coin/sol.ts b/src/coin/sol.ts index 5a1cbd6b..c91b2203 100644 --- a/src/coin/sol.ts +++ b/src/coin/sol.ts @@ -1,11 +1,14 @@ import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "sol"; const coinType = 501; -export const encodeSolAddress = base58EncodeNoCheck; -export const decodeSolAddress = base58DecodeNoCheck; +export const encodeSolAddress = base58UncheckedEncode; +export const decodeSolAddress = base58UncheckedDecode; export const sol = { name, diff --git a/src/coin/srm.ts b/src/coin/srm.ts index 68f1b85d..97d03878 100644 --- a/src/coin/srm.ts +++ b/src/coin/srm.ts @@ -1,11 +1,14 @@ import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "srm"; const coinType = 573; -export const encodeSrmAddress = base58EncodeNoCheck; -export const decodeSrmAddress = base58DecodeNoCheck; +export const encodeSrmAddress = base58UncheckedEncode; +export const decodeSrmAddress = base58UncheckedDecode; export const srm = { name, diff --git a/src/coin/strat.ts b/src/coin/strat.ts index c149546c..697540e6 100644 --- a/src/coin/strat.ts +++ b/src/coin/strat.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "strat"; @@ -10,11 +10,11 @@ const coinType = 105; const p2pkhVersions = [new Uint8Array([0x3f])]; const p2shVersions = [new Uint8Array([0x7d])]; -export const encodeStratAddress = createBase58WithCheckEncoder( +export const encodeStratAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeStratAddress = createBase58WithCheckDecoder( +export const decodeStratAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/stx.ts b/src/coin/stx.ts index 947b7da8..99e0a7f1 100644 --- a/src/coin/stx.ts +++ b/src/coin/stx.ts @@ -1,12 +1,9 @@ import { equalBytes } from "@noble/curves/abstract/utils"; import { sha256 } from "@noble/hashes/sha256"; import { concatBytes } from "@noble/hashes/utils"; +import { utils, type Coder } from "@scure/base"; import type { Coin } from "../types.js"; -import { - base32Decode, - base32Encode, - crockfordBase32Options, -} from "../utils/base32.js"; +import { base32CrockfordNormalise } from "../utils/base32.js"; const name = "stx"; const coinType = 5757; @@ -17,6 +14,44 @@ const checkumLength = 4; const p2pkhVersion = new Uint8Array([22]); const p2shVersion = new Uint8Array([20]); +const radixStx: Coder = { + encode: (source) => convertRadixStx(source, 8, 5, true), + decode: (source) => Uint8Array.from(convertRadixStx(source, 5, 8, false)), +}; + +const convertRadixStx = ( + data: Uint8Array | number[], + from: number, + to: number, + padding: boolean +): number[] => { + let carry = 0; + let pos = 0; + const mask = 2 ** to - 1; + const res: number[] = []; + for (const n of data.reverse()) { + carry = (n << pos) | carry; + pos += from; + let i = 0; + for (; pos >= to; pos -= to) { + const v = ((carry >> (to * i)) & mask) >>> 0; + res.unshift(v); + i += 1; + } + carry = carry >> (to * i); + } + if (!padding && pos >= from) throw new Error("Excess padding"); + if (!padding && carry) throw new Error(`Non-zero padding: ${carry}`); + if (padding && pos > 0) res.unshift(carry >>> 0); + return res; +}; + +const base32Stx = utils.chain( + radixStx, + utils.alphabet("0123456789ABCDEFGHJKMNPQRSTVWXYZ"), + utils.join("") +); + const stxChecksum = (data: Uint8Array): Uint8Array => sha256(sha256(data)).slice(0, checkumLength); @@ -31,12 +66,12 @@ export const encodeStxAddress = (source: Uint8Array): string => { if (equalBytes(checksum, stxChecksum(concatBytes(p2pkhVersion, hash160)))) { version = "P"; - encoded = base32Encode(source, crockfordBase32Options); + encoded = base32Stx.encode(source); } else if ( equalBytes(checksum, stxChecksum(concatBytes(p2shVersion, hash160))) ) { version = "M"; - encoded = base32Encode(source, crockfordBase32Options); + encoded = base32Stx.encode(source); } else throw new Error("Unrecognised address format"); return `${prefix}${version}${encoded}`; @@ -45,7 +80,7 @@ export const decodeStxAddress = (source: string): Uint8Array => { if (source.length < 6) throw new Error("Unrecognised address format"); if (source[0] !== "S") throw new Error("Unrecognised address format"); - const normalised = source.toUpperCase(); + const normalised = base32CrockfordNormalise(source); const version = normalised[1]; let versionBytes: Uint8Array; @@ -53,7 +88,7 @@ export const decodeStxAddress = (source: string): Uint8Array => { else if (version === "M") versionBytes = p2shVersion; else throw new Error("Unrecognised address format"); - const payload = base32Decode(normalised.slice(2), crockfordBase32Options); + const payload = base32Stx.decode(normalised.slice(2)); const decoded = payload.slice(0, -checkumLength); const checksum = payload.slice(-checkumLength); const newChecksum = stxChecksum(concatBytes(versionBytes, decoded)); diff --git a/src/coin/trx.ts b/src/coin/trx.ts index 2285430e..5d9f19e1 100644 --- a/src/coin/trx.ts +++ b/src/coin/trx.ts @@ -1,11 +1,11 @@ import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "trx"; const coinType = 195; -export const encodeTrxAddress = base58Encode; -export const decodeTrxAddress = base58Decode; +export const encodeTrxAddress = base58CheckEncode; +export const decodeTrxAddress = base58CheckDecode; export const trx = { name, diff --git a/src/coin/via.ts b/src/coin/via.ts index 93e8b4bb..1de7df2d 100644 --- a/src/coin/via.ts +++ b/src/coin/via.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "via"; @@ -10,11 +10,11 @@ const coinType = 14; const p2pkhVersions = [new Uint8Array([0x47])]; const p2shVersions = [new Uint8Array([0x21])]; -export const encodeViaAddress = createBase58WithCheckEncoder( +export const encodeViaAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeViaAddress = createBase58WithCheckDecoder( +export const decodeViaAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/vlx.ts b/src/coin/vlx.ts index 164fac28..3fe2cfba 100644 --- a/src/coin/vlx.ts +++ b/src/coin/vlx.ts @@ -1,11 +1,14 @@ import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "vlx"; const coinType = 574; -export const encodeVlxAddress = base58EncodeNoCheck; -export const decodeVlxAddress = base58DecodeNoCheck; +export const encodeVlxAddress = base58UncheckedEncode; +export const decodeVlxAddress = base58UncheckedDecode; export const vlx = { name, diff --git a/src/coin/vsys.ts b/src/coin/vsys.ts index b28fc8c1..ba288717 100644 --- a/src/coin/vsys.ts +++ b/src/coin/vsys.ts @@ -2,7 +2,10 @@ import { equalBytes } from "@noble/curves/abstract/utils"; import { blake2b } from "@noble/hashes/blake2b"; import { keccak_256 } from "@noble/hashes/sha3"; import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "vsys"; const coinType = 360; @@ -21,13 +24,13 @@ const vsysChecksum = (source: Uint8Array): boolean => { export const encodeVsysAddress = (source: Uint8Array): string => { if (!vsysChecksum(source)) throw new Error("Unrecognised address format"); - return base58EncodeNoCheck(source); + return base58UncheckedEncode(source); }; export const decodeVsysAddress = (source: string): Uint8Array => { const encoded = source.startsWith("address:") ? source.slice(8) : source; if (encoded.length > 36) throw new Error("Unrecognised address format"); - const decoded = base58DecodeNoCheck(encoded); + const decoded = base58UncheckedDecode(encoded); if (!vsysChecksum(decoded)) throw new Error("Unrecognised address format"); return decoded; diff --git a/src/coin/waves.ts b/src/coin/waves.ts index aaf1709f..4c5206c1 100644 --- a/src/coin/waves.ts +++ b/src/coin/waves.ts @@ -2,7 +2,10 @@ import { blake2b } from "@noble/hashes/blake2b"; import { keccak_256 } from "@noble/hashes/sha3"; import { utils } from "@scure/base"; import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; +import { + base58UncheckedDecode, + base58UncheckedEncode, +} from "../utils/base58.js"; const name = "waves"; const coinType = 5741564; @@ -12,9 +15,9 @@ const checksumFn = (source: Uint8Array): Uint8Array => const checksumLength = 4; const wavesChecksum = utils.checksum(checksumLength, checksumFn); -export const encodeWavesAddress = base58EncodeNoCheck; +export const encodeWavesAddress = base58UncheckedEncode; export const decodeWavesAddress = (source: string): Uint8Array => { - const decoded = base58DecodeNoCheck(source); + const decoded = base58UncheckedDecode(source); if (decoded[0] !== 1) throw new Error("Invalid address format"); diff --git a/src/coin/wicc.ts b/src/coin/wicc.ts index d5f64723..ed030316 100644 --- a/src/coin/wicc.ts +++ b/src/coin/wicc.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "wicc"; @@ -10,11 +10,11 @@ const coinType = 99999; const p2pkhVersions = [new Uint8Array([0x49])]; const p2shVersions = [new Uint8Array([0x33])]; -export const encodeWiccAddress = createBase58WithCheckEncoder( +export const encodeWiccAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeWiccAddress = createBase58WithCheckDecoder( +export const decodeWiccAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/xmr.ts b/src/coin/xmr.ts index ba318f98..b204692c 100644 --- a/src/coin/xmr.ts +++ b/src/coin/xmr.ts @@ -1,39 +1,11 @@ +import { base58xmr } from "@scure/base"; import type { Coin } from "../types.js"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "../utils/base58.js"; const name = "xmr"; const coinType = 128; -// extra decoding/encoding for XMR pulled from https://github.com/paulmillr/scure-base/blob/12f6eacf5afd2af6a0eeb2db1222fb7cc262a204/index.ts#L379 - -const blockLengths = [0, 2, 3, 5, 6, 7, 9, 10, 11]; - -export const encodeXmrAddress = (source: Uint8Array): string => { - let encoded = ""; - for (let i = 0; i < source.length; i += 8) { - const block = source.subarray(i, i + 8); - encoded += base58EncodeNoCheck(block).padStart( - blockLengths[block.length], - "1" - ); - } - return encoded; -}; -export const decodeXmrAddress = (source: string): Uint8Array => { - let decoded: number[] = []; - for (let i = 0; i < source.length; i += 11) { - const slice = source.slice(i, i + 11); - const blockLength = blockLengths.indexOf(slice.length); - const block = base58DecodeNoCheck(slice); - for (let j = 0; j < block.length - blockLength; j++) { - if (block[j] !== 0) throw new Error("Invalid padding"); - } - decoded = decoded.concat( - Array.from(block.slice(block.length - blockLength)) - ); - } - return new Uint8Array(decoded); -}; +export const encodeXmrAddress = base58xmr.encode; +export const decodeXmrAddress = base58xmr.decode; export const xmr = { name, diff --git a/src/coin/xrp.ts b/src/coin/xrp.ts index 5dd5896a..a025598e 100644 --- a/src/coin/xrp.ts +++ b/src/coin/xrp.ts @@ -1,29 +1,17 @@ +import { sha256 } from "@noble/hashes/sha256"; +import { base58xrp, utils } from "@scure/base"; import type { Coin } from "../types.js"; -import { - base58Checksum, - base58DecodeNoCheckUnsafe, - base58EncodeNoCheck, - createBase58Options, -} from "../utils/base58.js"; const name = "xrp"; const coinType = 144; -const xrpBase58Options = createBase58Options( - "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz" +const base58XrpCheck = utils.chain( + utils.checksum(4, (data) => sha256(sha256(data))), + base58xrp ); -export const encodeXrpAddress = (source: Uint8Array): string => { - const checksummed = base58Checksum.encode(source); - return base58EncodeNoCheck(checksummed, xrpBase58Options); -}; -export const decodeXrpAddress = (source: string): Uint8Array => { - const decoded = base58DecodeNoCheckUnsafe(source, xrpBase58Options); - if (!decoded) throw new Error("Invalid address"); - - const payload = base58Checksum.decode(decoded); - return payload; -}; +export const encodeXrpAddress = base58XrpCheck.encode; +export const decodeXrpAddress = base58XrpCheck.decode; export const xrp = { name, diff --git a/src/coin/xtz.ts b/src/coin/xtz.ts index fae9a765..1d4871af 100644 --- a/src/coin/xtz.ts +++ b/src/coin/xtz.ts @@ -1,6 +1,6 @@ import { concatBytes } from "@noble/hashes/utils"; import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "xtz"; const coinType = 1729; @@ -22,10 +22,10 @@ export const encodeXtzAddress = (source: Uint8Array): string => { else if (source[1] === 0x02) prefix = new Uint8Array([0x06, 0xa1, 0xa4]); // prefix tz3 equal 06a1a4 else throw new Error("Unrecognised address format"); - return base58Encode(concatBytes(prefix, source.slice(2))); + return base58CheckEncode(concatBytes(prefix, source.slice(2))); } if (version === 1) { - return base58Encode( + return base58CheckEncode( concatBytes( new Uint8Array([0x02, 0x5a, 0x79]) /* prefix KT1 equal 025a79 */, source.slice(1, 21) @@ -35,7 +35,7 @@ export const encodeXtzAddress = (source: Uint8Array): string => { throw new Error("Unrecognised address format"); }; export const decodeXtzAddress = (source: string): Uint8Array => { - const decoded = base58Decode(source).slice(3); + const decoded = base58CheckDecode(source).slice(3); const prefix = source.slice(0, 3); if (prefix === "tz1") diff --git a/src/coin/xvg.ts b/src/coin/xvg.ts index a9abdae9..b57690c5 100644 --- a/src/coin/xvg.ts +++ b/src/coin/xvg.ts @@ -1,7 +1,7 @@ import type { Coin } from "../types.js"; import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "../utils/base58.js"; const name = "xvg"; @@ -10,11 +10,11 @@ const coinType = 77; const p2pkhVersions = [new Uint8Array([0x1e])]; const p2shVersions = [new Uint8Array([0x21])]; -export const encodeXvgAddress = createBase58WithCheckEncoder( +export const encodeXvgAddress = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); -export const decodeXvgAddress = createBase58WithCheckDecoder( +export const decodeXvgAddress = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); diff --git a/src/coin/zen.ts b/src/coin/zen.ts index 180aed95..4d283d59 100644 --- a/src/coin/zen.ts +++ b/src/coin/zen.ts @@ -1,6 +1,6 @@ import { equalBytes } from "@noble/curves/abstract/utils"; import type { Coin } from "../types.js"; -import { base58Decode, base58Encode } from "../utils/base58.js"; +import { base58CheckDecode, base58CheckEncode } from "../utils/base58.js"; const name = "zen"; const coinType = 121; @@ -18,10 +18,10 @@ export const encodeZenAddress = (source: Uint8Array): string => { if (!validPrefixes.some((x) => equalBytes(x, prefix))) throw new Error("Invalid prefix"); - return base58Encode(source); + return base58CheckEncode(source); }; export const decodeZenAddress = (source: string): Uint8Array => { - const decoded = base58Decode(source); + const decoded = base58CheckDecode(source); const prefix = decoded.slice(0, 2); if (!validPrefixes.some((x) => equalBytes(x, prefix))) diff --git a/src/utils/base32.ts b/src/utils/base32.ts index 6529b292..c7d35281 100644 --- a/src/utils/base32.ts +++ b/src/utils/base32.ts @@ -1,106 +1,15 @@ -export type Base32Options = { - alphabet: string; - base32Lookup: number[]; - padded: boolean; -}; +import { base32, utils } from "@scure/base"; -export const createBase32Options = ({ - alphabet, - padded = true, -}: { - alphabet: string; - padded?: boolean; -}): Base32Options => { - const base32Lookup: number[] = alphabet - .split("") - .reduce((acc, char, index) => { - acc[char.charCodeAt(0)] = index; - return acc; - }, [] as number[]); - return { alphabet, padded, base32Lookup }; -}; +export const base32Encode = base32.encode; +export const base32Decode = base32.decode; -const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +const base32Unpadded = utils.chain( + utils.radix2(5), + utils.alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), + utils.join("") +); +export const base32UnpaddedEncode = base32Unpadded.encode; +export const base32UnpaddedDecode = base32Unpadded.decode; -const DEFAULT_BASE32_OPTIONS = createBase32Options({ - alphabet: BASE32_ALPHABET, -}); - -export const crockfordBase32Options = createBase32Options({ - alphabet: "0123456789ABCDEFGHJKMNPQRSTVWXYZ", -}); - -export const unpaddedBase32Options = { - ...DEFAULT_BASE32_OPTIONS, - padded: false, -}; - -export const base32Encode = ( - input: Uint8Array, - { alphabet, padded }: Base32Options = DEFAULT_BASE32_OPTIONS -): string => { - const length = input.length; - const leftover = (length * 8) % 5; - const offset = padded ? (leftover === 0 ? 0 : 5 - leftover) : 0; - - let buffer = 0; - let bufferLength = 0; - let output = ""; - - for (let i = 0; i < length; i++) { - buffer = (buffer << 8) | input[i]; - bufferLength += 8; - - while (bufferLength >= 5) { - const index = (buffer >>> (bufferLength + offset - 5)) & 31; - output += alphabet[index]; - bufferLength -= 5; - } - } - - if (bufferLength > 0) { - output += alphabet[(buffer << (5 - (bufferLength + offset))) & 31]; - } - - return output; -}; - -export const base32Decode = ( - input: string, - { base32Lookup, padded }: Base32Options = DEFAULT_BASE32_OPTIONS -): Uint8Array => { - const length = input.length; - const leftover = padded ? (length * 5) % 8 : 0; - const offset = leftover === 0 ? 0 : 8 - leftover; - - let buffer = 0; - let bufferLength = 0; - let output = new Uint8Array(Math.ceil((length * 5) / 8)); - - let outputIndex = 0; - - for (let i = 0; i < length; i++) { - const charCode = input.charCodeAt(i); - const value = base32Lookup[charCode]; - - if (value === undefined) { - throw new Error("Invalid base32 character"); - } - - buffer = (buffer << 5) | value; - bufferLength += 5; - - while (bufferLength >= 8) { - output[outputIndex++] = (buffer >>> (bufferLength + offset - 8)) & 255; - bufferLength -= 8; - } - } - - if (bufferLength > 0 && padded) { - output[outputIndex++] = (buffer << (bufferLength + offset - 8)) & 255; - } - - if (leftover !== 0) output = output.slice(1); - - return output.subarray(0, outputIndex); -}; +export const base32CrockfordNormalise = (source: string) => + source.toUpperCase().replace(/O/g, "0").replace(/[IL]/g, "1"); diff --git a/src/utils/base58.ts b/src/utils/base58.ts index 6fa43617..0782a788 100644 --- a/src/utils/base58.ts +++ b/src/utils/base58.ts @@ -1,164 +1,18 @@ import { sha256 } from "@noble/hashes/sha256"; import { concatBytes } from "@noble/hashes/utils"; -import { utils } from "@scure/base"; +import { base58, createBase58check } from "@scure/base"; export type Base58CheckVersion = Uint8Array; -export type Base58Options = { - alphabet: string; - base58Lookup: number[]; - leader: string; -}; -const sha256x2 = (source: Uint8Array): Uint8Array => sha256(sha256(source)); +const base58Unchecked = base58; +export const base58UncheckedEncode = base58Unchecked.encode; +export const base58UncheckedDecode = base58Unchecked.decode; -const base = 58; -const factor = Math.log(base) / Math.log(256); -const iFactor = Math.log(256) / Math.log(base); +const base58Check = createBase58check(sha256); +export const base58CheckEncode = base58Check.encode; +export const base58CheckDecode = base58Check.decode; -export const createBase58Options = (alphabet: string): Base58Options => { - const base58Lookup: number[] = alphabet - .split("") - .reduce((acc, char, index) => { - acc[char.charCodeAt(0)] = index; - return acc; - }, [] as number[]); - const leader = alphabet[0]; - return { alphabet, base58Lookup, leader }; -}; - -const DEFAULT_BASE58_OPTIONS = createBase58Options( - "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" -); - -export const base58EncodeNoCheck = ( - source: Uint8Array, - { alphabet, leader }: Base58Options = DEFAULT_BASE58_OPTIONS -) => { - let pbegin = 0; - let pend = source.length; - let zeroes = 0; - while (pbegin !== pend && source[pbegin] === 0) { - pbegin++; - zeroes++; - } - - let size = ((pend - pbegin) * iFactor + 1) >>> 0; - let b58 = new Uint8Array(size); - - let length = 0; - while (pbegin !== pend) { - let carry = source[pbegin]; - let i = 0; - for ( - let it1 = size - 1; - (carry !== 0 || i < length) && it1 !== -1; - it1--, i++ - ) { - carry += (256 * b58[it1]) >>> 0; - b58[it1] = carry % base >>> 0; - carry = (carry / base) >>> 0; - } - if (carry !== 0) { - throw new Error("Non-zero carry"); - } - length = i; - pbegin++; - } - - let it2 = size - length; - while (it2 !== size && b58[it2] === 0) { - it2++; - } - - let str = leader.repeat(zeroes); - for (; it2 < size; ++it2) { - str += alphabet.charAt(b58[it2]); - } - return str; -}; - -export const base58DecodeNoCheckUnsafe = ( - source: string, - { base58Lookup, leader }: Base58Options = DEFAULT_BASE58_OPTIONS -): Uint8Array | undefined => { - if (typeof source !== "string") { - throw new TypeError("Expected String"); - } - if (source.length === 0) { - return new Uint8Array(0); - } - - let psz = 0; - let zeroes = 0; - let length = 0; - while (source[psz] === leader) { - zeroes++; - psz++; - } - - let size = ((source.length - psz) * factor + 1) >>> 0; - let b256 = new Uint8Array(size); - - while (source[psz]) { - let carry = base58Lookup[source.charCodeAt(psz)]; - if (carry === 255) { - return; - } - - let i = 0; - for ( - let it3 = size - 1; - (carry !== 0 || i < length) && it3 !== -1; - it3--, i++ - ) { - carry += (base * b256[it3]) >>> 0; - b256[it3] = carry % 256 >>> 0; - carry = (carry / 256) >>> 0; - } - if (carry !== 0) { - throw new Error("Non-zero carry"); - } - length = i; - psz++; - } - - let it4 = size - length; - while (it4 !== size && b256[it4] === 0) { - it4++; - } - - let vch = new Uint8Array(zeroes + (size - it4)); - vch.fill(0x00, 0, zeroes); - let j = zeroes; - while (it4 !== size) { - vch[j++] = b256[it4++]; - } - - return vch; -}; - -export const base58DecodeNoCheck = (source: string): Uint8Array => { - const value = base58DecodeNoCheckUnsafe(source); - if (value) { - return value; - } - throw new Error("Non-base58 character"); -}; - -export const base58Checksum = utils.checksum(4, sha256x2); - -export const base58Encode = (source: Uint8Array): string => { - const checksummed = base58Checksum.encode(source); - return base58EncodeNoCheck(checksummed); -}; - -export const base58Decode = (source: string): Uint8Array => { - const buffer = base58DecodeNoCheck(source); - const payload = base58Checksum.decode(buffer); - return payload; -}; - -export const createBase58WithCheckEncoder = +export const createBase58VersionedEncoder = (p2pkhVersion: Base58CheckVersion, p2shVersion: Base58CheckVersion) => (source: Uint8Array) => { // P2PKH: OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG @@ -170,7 +24,7 @@ export const createBase58WithCheckEncoder = ) { throw Error("Unrecognised address format"); } - return base58Encode( + return base58CheckEncode( concatBytes(p2pkhVersion, source.slice(3, 3 + source[2])) ); } @@ -180,7 +34,7 @@ export const createBase58WithCheckEncoder = if (source[source.length - 1] !== 0x87) { throw Error("Unrecognised address format"); } - return base58Encode( + return base58CheckEncode( concatBytes(p2shVersion, source.slice(2, 2 + source[1])) ); } @@ -188,10 +42,10 @@ export const createBase58WithCheckEncoder = throw Error("Unrecognised address format"); }; -export const createBase58WithCheckDecoder = +export const createBase58VersionedDecoder = (p2pkhVersions: Base58CheckVersion[], p2shVersions: Base58CheckVersion[]) => (source: string): Uint8Array => { - const addr = base58Decode(source); + const addr = base58CheckDecode(source); // Checks if the first addr bytes are exactly equal to provided version field const checkVersion = (version: Base58CheckVersion) => { diff --git a/src/utils/base64.bench.ts b/src/utils/base64.bench.ts deleted file mode 100644 index 8025ccb2..00000000 --- a/src/utils/base64.bench.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { bench, group, run } from "mitata"; - -import { base64 } from "@scure/base"; -import { base64Decode, base64Encode } from "./base64.js"; -import { stringToBytes } from "./bytes.js"; - -const data = "Hello World!"; -const bytes = stringToBytes(data); -const base64Encoded = base64Encode(bytes); - -group("encode", () => { - bench("@ensdomains/address-encoder", () => base64Encode(bytes)); - bench("@scure/base", () => base64.encode(bytes)); -}); - -group("decode", () => { - bench("@ensdomains/address-encoder", () => base64Decode(base64Encoded)); - bench("@scure/base", () => base64.decode(base64Encoded)); -}); - -await run({ - percentiles: false, -}); diff --git a/src/utils/base64.ts b/src/utils/base64.ts deleted file mode 100644 index 94e431d7..00000000 --- a/src/utils/base64.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const base64Encode = (input: Uint8Array): string => { - let binaryString = ""; - - for (let i = 0; i < input.length; i++) { - binaryString += String.fromCharCode(input[i]); - } - - return btoa(binaryString); -}; - -export const base64Decode = (input: string): Uint8Array => { - const binaryString = atob(input); - const output = new Uint8Array(binaryString.length); - - for (let i = 0; i < binaryString.length; i++) { - output[i] = binaryString.charCodeAt(i); - } - - return output; -}; diff --git a/src/utils/bech32.ts b/src/utils/bech32.ts index 8c3e786d..e2d94b96 100644 --- a/src/utils/bech32.ts +++ b/src/utils/bech32.ts @@ -1,5 +1,7 @@ import { concatBytes } from "@noble/hashes/utils"; -import { bech32, bech32m, type BechLib } from "bech32"; +import { bech32, bech32m } from "@scure/base"; + +type BechLib = typeof bech32 | typeof bech32m; type Bech32Parameters = { bechLib: BechLib; diff --git a/src/utils/bitcoin.ts b/src/utils/bitcoin.ts index 08038594..40ddf708 100644 --- a/src/utils/bitcoin.ts +++ b/src/utils/bitcoin.ts @@ -1,6 +1,6 @@ import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, type Base58CheckVersion, } from "./base58.js"; import { @@ -20,7 +20,7 @@ export const createBitcoinDecoder = ({ p2shVersions, }: BitcoinCoderParameters) => { const decodeBech32 = createBech32SegwitDecoder(hrp); - const decodeBase58 = createBase58WithCheckDecoder( + const decodeBase58 = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); @@ -38,7 +38,7 @@ export const createBitcoinEncoder = ({ p2shVersions, }: BitcoinCoderParameters) => { const encodeBech32 = createBech32SegwitEncoder(hrp); - const encodeBase58 = createBase58WithCheckEncoder( + const encodeBase58 = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] ); diff --git a/src/utils/byron.ts b/src/utils/byron.ts index 9a643eb9..3cba7eee 100644 --- a/src/utils/byron.ts +++ b/src/utils/byron.ts @@ -1,4 +1,4 @@ -import { base58DecodeNoCheck, base58EncodeNoCheck } from "./base58.js"; +import { base58UncheckedDecode, base58UncheckedEncode } from "./base58.js"; import { TaggedValue, cborDecode, cborEncode } from "./cbor.js"; import { crc32 } from "./crc32.js"; @@ -8,7 +8,7 @@ export const byronEncode = (source: Uint8Array): string => { const cborEncodedAddress = cborEncode([taggedValue, checksum]); - const address = base58EncodeNoCheck(new Uint8Array(cborEncodedAddress)); + const address = base58UncheckedEncode(new Uint8Array(cborEncodedAddress)); if (!address.startsWith("Ae2") && !address.startsWith("Ddz")) throw new Error("Unrecognised address format"); @@ -17,7 +17,7 @@ export const byronEncode = (source: Uint8Array): string => { }; export const byronDecode = (source: string): Uint8Array => { - const bytes = base58DecodeNoCheck(source); + const bytes = base58UncheckedDecode(source); const cborDecoded = cborDecode(bytes.buffer); diff --git a/src/utils/dot.ts b/src/utils/dot.ts index 07b21747..5bcd3992 100644 --- a/src/utils/dot.ts +++ b/src/utils/dot.ts @@ -1,7 +1,7 @@ import { equalBytes } from "@noble/curves/abstract/utils"; import { blake2b } from "@noble/hashes/blake2b"; import { concatBytes } from "@noble/hashes/utils"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "./base58.js"; +import { base58UncheckedDecode, base58UncheckedEncode } from "./base58.js"; const prefixStringBytes = new Uint8Array([ 0x53, 0x53, 0x35, 0x38, 0x50, 0x52, 0x45, @@ -16,13 +16,13 @@ export const createDotAddressEncoder = const typePrefix = new Uint8Array([type]); const sourceWithTypePrefix = concatBytes(typePrefix, source); const checksum = dotChecksum(sourceWithTypePrefix); - return base58EncodeNoCheck(concatBytes(sourceWithTypePrefix, checksum)); + return base58UncheckedEncode(concatBytes(sourceWithTypePrefix, checksum)); }; export const createDotAddressDecoder = (type: number) => (source: string): Uint8Array => { - const decoded = base58DecodeNoCheck(source); + const decoded = base58UncheckedDecode(source); if (decoded[0] !== type) throw new Error("Unrecognized address format"); const checksum = decoded.slice(33, 35); diff --git a/src/utils/eosio.ts b/src/utils/eosio.ts index 5a7318ce..5bc491f4 100644 --- a/src/utils/eosio.ts +++ b/src/utils/eosio.ts @@ -1,7 +1,7 @@ import { secp256k1 } from "@noble/curves/secp256k1"; import { ripemd160 } from "@noble/hashes/ripemd160"; import { utils } from "@scure/base"; -import { base58DecodeNoCheck, base58EncodeNoCheck } from "./base58.js"; +import { base58UncheckedDecode, base58UncheckedEncode } from "./base58.js"; const eosChecksum = utils.checksum(4, ripemd160); @@ -10,7 +10,7 @@ export const createEosEncoder = (source: Uint8Array): string => { const point = secp256k1.ProjectivePoint.fromHex(source); const checksummed = eosChecksum.encode(point.toRawBytes(true)); - const encoded = base58EncodeNoCheck(checksummed); + const encoded = base58UncheckedEncode(checksummed); return `${prefix}${encoded}`; }; @@ -19,7 +19,7 @@ export const createEosDecoder = (source: string): Uint8Array => { if (!source.startsWith(prefix)) throw Error("Unrecognised address format"); const prefixStripped = source.slice(prefix.length); - const decoded = base58DecodeNoCheck(prefixStripped); + const decoded = base58UncheckedDecode(prefixStripped); const checksummed = eosChecksum.decode(decoded); return checksummed; }; diff --git a/src/utils/index.ts b/src/utils/index.ts index a211b514..17bfbd48 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,25 +1,19 @@ export { + base32CrockfordNormalise, base32Decode, base32Encode, - createBase32Options, - crockfordBase32Options, - unpaddedBase32Options, - type Base32Options, + base32UnpaddedDecode, + base32UnpaddedEncode, } from "./base32.js"; export { - base58Checksum, - base58Decode, - base58DecodeNoCheck, - base58DecodeNoCheckUnsafe, - base58Encode, - base58EncodeNoCheck, - createBase58Options, - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + base58CheckDecode, + base58CheckEncode, + base58UncheckedDecode, + base58UncheckedEncode, + createBase58VersionedDecoder, + createBase58VersionedEncoder, type Base58CheckVersion, - type Base58Options, } from "./base58.js"; -export { base64Decode, base64Encode } from "./base64.js"; export { decodeBchAddressToTypeAndHash, encodeBchAddressWithVersion, diff --git a/src/utils/zcash.ts b/src/utils/zcash.ts index 0888549f..41e18877 100644 --- a/src/utils/zcash.ts +++ b/src/utils/zcash.ts @@ -1,6 +1,6 @@ import { - createBase58WithCheckDecoder, - createBase58WithCheckEncoder, + createBase58VersionedDecoder, + createBase58VersionedEncoder, } from "./base58.js"; import { createBech32Decoder, createBech32Encoder } from "./bech32.js"; import type { BitcoinCoderParameters } from "./bitcoin.js"; @@ -15,7 +15,7 @@ export const createZcashDecoder = ({ p2shVersions, }: BitcoinCoderParameters) => { const decodeBech32 = createBech32Decoder(hrp); - const decodeBase58 = createBase58WithCheckDecoder( + const decodeBase58 = createBase58VersionedDecoder( p2pkhVersions, p2shVersions ); @@ -33,7 +33,7 @@ export const createZcashEncoder = ({ p2shVersions, }: BitcoinCoderParameters) => { const encodeBech32 = createBech32Encoder(hrp); - const encodeBase58 = createBase58WithCheckEncoder( + const encodeBase58 = createBase58VersionedEncoder( p2pkhVersions[0], p2shVersions[0] );