Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 66 additions & 10 deletions packages/core-ts/src/muxed/decode.ts
Original file line number Diff line number Diff line change
@@ -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;

Check failure on line 4 in packages/core-ts/src/muxed/decode.ts

View workflow job for this annotation

GitHub Actions / test

src/spec/validate.test.ts

ReferenceError: StellarSdk is not defined ❯ src/muxed/decode.ts:4:20 ❯ src/address/parse.ts:1:1

Check failure on line 4 in packages/core-ts/src/muxed/decode.ts

View workflow job for this annotation

GitHub Actions / test

src/spec/runner.test.ts

ReferenceError: StellarSdk is not defined ❯ src/muxed/decode.ts:4:20 ❯ src/address/parse.ts:1:1

Check failure on line 4 in packages/core-ts/src/muxed/decode.ts

View workflow job for this annotation

GitHub Actions / test

src/spec/detect.test.ts

ReferenceError: StellarSdk is not defined ❯ src/muxed/decode.ts:4:20 ❯ src/address/parse.ts:1:1

Check failure on line 4 in packages/core-ts/src/muxed/decode.ts

View workflow job for this annotation

GitHub Actions / test

src/address/parse.test.ts

ReferenceError: StellarSdk is not defined ❯ src/muxed/decode.ts:4:20 ❯ src/address/parse.ts:1:1

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,
};
}
2 changes: 1 addition & 1 deletion packages/core-ts/src/spec/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading