From 72ad5ebe92d0ed7435f893eabbd336b9985e4c70 Mon Sep 17 00:00:00 2001 From: Lewechi Date: Tue, 31 Mar 2026 18:46:39 +0100 Subject: [PATCH] fix: align MuxedAccount decoding and C-address routing compliance --- .../node_modules/.vite/vitest/results.json | 2 +- .../stellar-address-kit/src/muxed/decode.ts | 83 ++----------------- .../src/routing/extract.ts | 10 ++- .../node_modules/.vite/vitest/results.json | 2 +- packages/core-ts/src/muxed/decode.ts | 83 ++----------------- packages/core-ts/src/routing/extract.ts | 10 ++- 6 files changed, 30 insertions(+), 160 deletions(-) diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json b/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json index aaf2a027..7e79d74f 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/node_modules/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"1.6.1","results":[[":src/test/detect.test.ts",{"duration":24,"failed":false}],[":src/test/validate.test.ts",{"duration":26,"failed":false}],[":src/muxed/encode.test.ts",{"duration":18,"failed":false}],[":src/test/extract.test.ts",{"duration":51,"failed":false}],[":src/address/parse.test.ts",{"duration":45,"failed":false}],[":src/test/integration.test.ts",{"duration":71,"failed":false}],[":src/spec/runner.test.ts",{"duration":55,"failed":false}],[":src/spec/validate.test.ts",{"duration":8,"failed":false}],[":src/test/bigint-edge-cases.test.ts",{"duration":12,"failed":false}],[":src/spec/detect.test.ts",{"duration":13,"failed":false}]]} \ No newline at end of file +{"version":"1.6.1","results":[[":src/test/detect.test.ts",{"duration":33,"failed":false}],[":src/test/validate.test.ts",{"duration":29,"failed":false}],[":src/muxed/encode.test.ts",{"duration":16,"failed":false}],[":src/test/extract.test.ts",{"duration":62,"failed":false}],[":src/test/integration.test.ts",{"duration":47,"failed":false}],[":src/address/parse.test.ts",{"duration":42,"failed":false}],[":src/spec/runner.test.ts",{"duration":39,"failed":false}],[":src/spec/validate.test.ts",{"duration":7,"failed":false}],[":src/test/bigint-edge-cases.test.ts",{"duration":17,"failed":false}],[":src/spec/detect.test.ts",{"duration":10,"failed":false}]]} \ No newline at end of file diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts index c13a3896..9ce76920 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/muxed/decode.ts @@ -1,87 +1,20 @@ -import { StrKey } from "@stellar/stellar-sdk"; - -const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - -/** - * Decodes a Base32-encoded string into binary data. - * @internal - */ -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; -} - -/** - * Computes a 16-bit CRC (CCITT-FALSE). - * @internal - */ -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; -} - -/** - * Validates and decodes a Stellar StrKey string. - * @internal - */ -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; -} +import { MuxedAccount } from "@stellar/stellar-sdk"; /** * Decodes a muxed Stellar address (SEP-23) into its base account and ID. - * The payload is expected to be [Version(1)] [Pubkey(32)] [ID(8)]. + * Uses the official Stellar SDK MuxedAccount parser to ensure specification compliance. * * @param mAddress - The muxed address string starting with 'M'. * @returns Metadata containing the base G address and the 64-bit BigInt ID. + * @throws {Error} If the address is not a valid muxed address. */ export function decodeMuxed(mAddress: string): { baseG: string; id: bigint } { - const data = decodeStrKey(mAddress); - 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); - } + // MuxedAccount.fromAddress requires a sequence number. Since this is an + // offline decoding of the address parts, we provide a placeholder. + const muxed = MuxedAccount.fromAddress(mAddress, "0"); return { - baseG: StrKey.encodeEd25519PublicKey(Buffer.from(pubkey)), - id: id, + baseG: muxed.baseAccount().accountId(), + id: BigInt(muxed.id()), }; } diff --git a/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts b/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts index eb3d3c21..72dcbb98 100644 --- a/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts +++ b/node_modules/.pnpm/node_modules/stellar-address-kit/src/routing/extract.ts @@ -77,10 +77,12 @@ export function extractRouting(input: RoutingInput): RoutingResult { const warnings: Warning[] = [...parsed.warnings]; warnings.push({ - code: "CONTRACT_SENDER_DETECTED", - severity: "warn", - message: - "Contract address detected. Contract addresses cannot be used as transaction senders.", + code: "INVALID_DESTINATION", + severity: "error", + message: "C address is not a valid destination", + context: { + destinationKind: "C", + }, }); return { diff --git a/packages/core-ts/node_modules/.vite/vitest/results.json b/packages/core-ts/node_modules/.vite/vitest/results.json index aaf2a027..7e79d74f 100644 --- a/packages/core-ts/node_modules/.vite/vitest/results.json +++ b/packages/core-ts/node_modules/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"1.6.1","results":[[":src/test/detect.test.ts",{"duration":24,"failed":false}],[":src/test/validate.test.ts",{"duration":26,"failed":false}],[":src/muxed/encode.test.ts",{"duration":18,"failed":false}],[":src/test/extract.test.ts",{"duration":51,"failed":false}],[":src/address/parse.test.ts",{"duration":45,"failed":false}],[":src/test/integration.test.ts",{"duration":71,"failed":false}],[":src/spec/runner.test.ts",{"duration":55,"failed":false}],[":src/spec/validate.test.ts",{"duration":8,"failed":false}],[":src/test/bigint-edge-cases.test.ts",{"duration":12,"failed":false}],[":src/spec/detect.test.ts",{"duration":13,"failed":false}]]} \ No newline at end of file +{"version":"1.6.1","results":[[":src/test/detect.test.ts",{"duration":33,"failed":false}],[":src/test/validate.test.ts",{"duration":29,"failed":false}],[":src/muxed/encode.test.ts",{"duration":16,"failed":false}],[":src/test/extract.test.ts",{"duration":62,"failed":false}],[":src/test/integration.test.ts",{"duration":47,"failed":false}],[":src/address/parse.test.ts",{"duration":42,"failed":false}],[":src/spec/runner.test.ts",{"duration":39,"failed":false}],[":src/spec/validate.test.ts",{"duration":7,"failed":false}],[":src/test/bigint-edge-cases.test.ts",{"duration":17,"failed":false}],[":src/spec/detect.test.ts",{"duration":10,"failed":false}]]} \ No newline at end of file diff --git a/packages/core-ts/src/muxed/decode.ts b/packages/core-ts/src/muxed/decode.ts index c13a3896..9ce76920 100644 --- a/packages/core-ts/src/muxed/decode.ts +++ b/packages/core-ts/src/muxed/decode.ts @@ -1,87 +1,20 @@ -import { StrKey } from "@stellar/stellar-sdk"; - -const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - -/** - * Decodes a Base32-encoded string into binary data. - * @internal - */ -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; -} - -/** - * Computes a 16-bit CRC (CCITT-FALSE). - * @internal - */ -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; -} - -/** - * Validates and decodes a Stellar StrKey string. - * @internal - */ -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; -} +import { MuxedAccount } from "@stellar/stellar-sdk"; /** * Decodes a muxed Stellar address (SEP-23) into its base account and ID. - * The payload is expected to be [Version(1)] [Pubkey(32)] [ID(8)]. + * Uses the official Stellar SDK MuxedAccount parser to ensure specification compliance. * * @param mAddress - The muxed address string starting with 'M'. * @returns Metadata containing the base G address and the 64-bit BigInt ID. + * @throws {Error} If the address is not a valid muxed address. */ export function decodeMuxed(mAddress: string): { baseG: string; id: bigint } { - const data = decodeStrKey(mAddress); - 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); - } + // MuxedAccount.fromAddress requires a sequence number. Since this is an + // offline decoding of the address parts, we provide a placeholder. + const muxed = MuxedAccount.fromAddress(mAddress, "0"); return { - baseG: StrKey.encodeEd25519PublicKey(Buffer.from(pubkey)), - id: id, + baseG: muxed.baseAccount().accountId(), + id: BigInt(muxed.id()), }; } diff --git a/packages/core-ts/src/routing/extract.ts b/packages/core-ts/src/routing/extract.ts index eb3d3c21..72dcbb98 100644 --- a/packages/core-ts/src/routing/extract.ts +++ b/packages/core-ts/src/routing/extract.ts @@ -77,10 +77,12 @@ export function extractRouting(input: RoutingInput): RoutingResult { const warnings: Warning[] = [...parsed.warnings]; warnings.push({ - code: "CONTRACT_SENDER_DETECTED", - severity: "warn", - message: - "Contract address detected. Contract addresses cannot be used as transaction senders.", + code: "INVALID_DESTINATION", + severity: "error", + message: "C address is not a valid destination", + context: { + destinationKind: "C", + }, }); return {