diff --git a/packages/core-ts/src/muxed/decode.ts b/packages/core-ts/src/muxed/decode.ts index aa92844..442e048 100644 --- a/packages/core-ts/src/muxed/decode.ts +++ b/packages/core-ts/src/muxed/decode.ts @@ -1,16 +1,72 @@ import { MuxedAccount } from "@stellar/stellar-sdk"; import { MuxedResult } from "./types"; -/** - * Decodes a Stellar muxed address (M-address). - * - * @param {string} mAddress - The muxed address to decode. - * @returns {baseG: string, id: bigint} - */ -export function decodeMuxed(mAddress: string): MuxedResult { - const muxed = MuxedAccount.fromAddress(mAddress, "0"); +const { StrKey } = StellarSdk; + +const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +function decodeBase32(input: string): Uint8Array { + const s = input.toUpperCase().replace(/=+$/, ""); + const byteCount = Math.floor((s.length * 5) / 8); + const result = new Uint8Array(byteCount); + let buffer = 0; + let bitsLeft = 0; + let byteIndex = 0; + for (const ch of s) { + const value = BASE32_CHARS.indexOf(ch); + if (value === -1) throw new Error(`Invalid base32 character: ${ch}`); + buffer = (buffer << 5) | value; + bitsLeft += 5; + if (bitsLeft >= 8) { + if (byteIndex < byteCount) { + result[byteIndex++] = (buffer >> (bitsLeft - 8)) & 0xff; + } + bitsLeft -= 8; + } + } + return result; +} + +function crc16(bytes: Uint8Array): number { + let crc = 0; + for (const byte of bytes) { + crc ^= byte << 8; + for (let i = 0; i < 8; i++) { + crc = crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1; + } + } + return crc & 0xffff; +} + +function decodeStrKey(address: string): Uint8Array { + const up = address.toUpperCase(); + const decoded = decodeBase32(up); + if (decoded.length < 3) throw new Error("invalid encoded string"); + + const data = decoded.slice(0, decoded.length - 2); + const checksum = + decoded[decoded.length - 2] | (decoded[decoded.length - 1] << 8); + const computed = crc16(data); + if (computed !== checksum) throw new Error("invalid checksum"); + + return data; +} + +export function decodeMuxed(mAddress: string): { baseG: string; id: bigint } { + const data = decodeStrKey(mAddress); + // Layout: [Version(1)] [Pubkey(32)] [ID(8)] + if (data.length !== 41) throw new Error("invalid payload length"); + + const pubkey = data.slice(1, 33); + const idBytes = data.slice(33, 41); + + let id = 0n; + for (const byte of idBytes) { + id = (id << 8n) + BigInt(byte); + } + return { - baseG: muxed.baseAccount().accountId(), - id: BigInt(muxed.id()), + baseG: StrKey.encodeEd25519PublicKey(Buffer.from(pubkey)), + id: id, }; } diff --git a/packages/core-ts/src/spec/runner.test.ts b/packages/core-ts/src/spec/runner.test.ts index 1155af4..5b496a3 100644 --- a/packages/core-ts/src/spec/runner.test.ts +++ b/packages/core-ts/src/spec/runner.test.ts @@ -23,7 +23,7 @@ describe("Normative Vector Tests", () => { } else { const result = decodeMuxed(c.input.mAddress); expect(result.baseG).toBe(c.expected.base_g); - expect(result.id.toString()).toBe(c.expected.id); + expect(result.id).toBe(BigInt(c.expected.id)); } break; }