UNPKG

@pharosnames/address-encoder

Version:

Encodes and decodes address formats for various cryptocurrencies with Pharos network support

108 lines (91 loc) 3.28 kB
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 { CheckedCoin } from "../types.js"; import { base32CrockfordNormalise } from "../utils/base32.js"; const name = "stx"; const coinType = 5757; const prefix = "S"; const length = 20; const checkumLength = 4; const p2pkhVersion = new Uint8Array([22]); const p2shVersion = new Uint8Array([20]); const radixStx: Coder<Uint8Array, number[]> = { 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); export const encodeStxAddress = (source: Uint8Array): string => { if (source.length !== length + checkumLength) throw new Error("Unrecognised address format"); const hash160 = source.slice(0, length); const checksum = source.slice(-checkumLength); let version: string; let encoded: string; if (equalBytes(checksum, stxChecksum(concatBytes(p2pkhVersion, hash160)))) { version = "P"; encoded = base32Stx.encode(source); } else if ( equalBytes(checksum, stxChecksum(concatBytes(p2shVersion, hash160))) ) { version = "M"; encoded = base32Stx.encode(source); } else throw new Error("Unrecognised address format"); return `${prefix}${version}${encoded}`; }; 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 = base32CrockfordNormalise(source); const version = normalised[1]; let versionBytes: Uint8Array; if (version === "P") versionBytes = p2pkhVersion; else if (version === "M") versionBytes = p2shVersion; else throw new Error("Unrecognised address format"); const payload = base32Stx.decode(normalised.slice(2)); const decoded = payload.slice(0, -checkumLength); const checksum = payload.slice(-checkumLength); const newChecksum = stxChecksum(concatBytes(versionBytes, decoded)); if (!equalBytes(checksum, newChecksum)) throw new Error("Unrecognised address format"); return payload; }; export const stx = { name, coinType, encode: encodeStxAddress, decode: decodeStxAddress, } as const satisfies CheckedCoin;