Skip to content
Open
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
8 changes: 6 additions & 2 deletions src/chains/stellar/scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,12 @@ export function scalarToBytes(scalar: bigint): Uint8Array {
*
* @see {@link pubKeyToStellarAddress}
*/
export function deriveStealthPubKey(spendingPubKey: Uint8Array, hashScalar: bigint): Uint8Array {
const K_spend = ed25519.ExtendedPoint.fromHex(spendingPubKey);
export function deriveStealthPubKey(
spendingPubKey: Uint8Array,
hashScalar: bigint,
spendingPoint?: ed25519.ExtendedPoint,
): Uint8Array {
const K_spend = spendingPoint ?? ed25519.ExtendedPoint.fromHex(spendingPubKey);
const hashPoint = ed25519.ExtendedPoint.BASE.multiply(hashScalar);
const stealthPoint = K_spend.add(hashPoint);
return stealthPoint.toRawBytes();
Expand Down
93 changes: 80 additions & 13 deletions src/chains/stellar/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,46 @@
import { hexToBytes } from './utils';
import { pipeline } from './scanner/pipeline';

const HEX_TO_BYTE = (() => {
const table = new Uint8Array(256).fill(255);
const lower = '0123456789abcdef';
const upper = '0123456789ABCDEF';
for (let i = 0; i < 16; i++) {
table[lower.charCodeAt(i)] = i;
table[upper.charCodeAt(i)] = i;
}
return table;
})();

function readViewTag(metadata: string): number | null {
const clean = metadata.startsWith('0x') ? metadata.slice(2) : metadata;
if (clean.length < 2) return null;

const hi = HEX_TO_BYTE[clean.charCodeAt(0)];
const lo = HEX_TO_BYTE[clean.charCodeAt(1)];
if (hi === 255 || lo === 255) return null;

return (hi << 4) | lo;
}

function decodeHexToBuffer(hex: string, output: Uint8Array): Uint8Array | null {
const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
if ((clean.length & 1) !== 0) return null;

const byteCount = clean.length / 2;
if (byteCount > output.length) return null;

let outOffset = 0;
for (let i = 0; i < clean.length; i += 2) {
const hi = HEX_TO_BYTE[clean.charCodeAt(i)];
const lo = HEX_TO_BYTE[clean.charCodeAt(i + 1)];
if (hi === 255 || lo === 255) return null;
output[outOffset++] = (hi << 4) | lo;
}

return output.subarray(0, outOffset);
}

/**
* Streaming announcement scanner. Pipelines `source` through a bounded queue
* of size `opts.window` (default 64) so fetching stays ahead of decryption,
Expand Down Expand Up @@ -103,10 +143,20 @@
opts: { window?: number } = {},
): AsyncGenerator<MatchedAnnouncement> {
const windowSize = Math.max(1, opts.window ?? 64);
const viewingPubKey = ed25519.getPublicKey(viewingKey);

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-spectre-agent)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-nextjs-app-router)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-chrome-extension)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (multichain-scan)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / Differential (test-vectors vs vN-1)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-scan)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-react-receive)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (20)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (22)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 146 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-send)

Cannot redeclare block-scoped variable 'viewingPubKey'.
const iter = source[Symbol.asyncIterator]();
const viewingPubKey = ed25519.getPublicKey(viewingKey);

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-spectre-agent)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-nextjs-app-router)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-chrome-extension)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (multichain-scan)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / Differential (test-vectors vs vN-1)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-scan)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-react-receive)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (20)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (22)

Cannot redeclare block-scoped variable 'viewingPubKey'.

Check failure on line 148 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-send)

Cannot redeclare block-scoped variable 'viewingPubKey'.
const ephemeralBuffer = new Uint8Array(32);
const batch: Announcement[] = [];
let spendingPoint: ed25519.ExtendedPoint | undefined;

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-spectre-agent)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-nextjs-app-router)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-chrome-extension)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (multichain-scan)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / Differential (test-vectors vs vN-1)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-scan)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-react-receive)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (20)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (22)

Cannot find namespace 'ed25519'.

Check failure on line 151 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-send)

Cannot find namespace 'ed25519'.

try {
try {
spendingPoint = ed25519.ExtendedPoint.fromHex(spendingPubKey);
} catch {
spendingPoint = undefined;
}

while (true) {
const batch: Announcement[] = [];
for (let i = 0; i < windowSize; i++) {
Expand All @@ -120,12 +170,11 @@
for (const ann of batch) {
if (ann.schemeId !== SCHEME_ID && ann.schemeId !== SCHEME_ID_V2) continue;

const metadataBytes = hexToBytes(ann.metadata);
if (metadataBytes.length === 0) continue;
const viewTag = metadataBytes[0];
const viewTag = readViewTag(ann.metadata);
if (viewTag === null) continue;

const ephPubKey = hexToBytes(ann.ephemeralPubKey);
if (ephPubKey.length !== 32) continue;
const ephPubKey = decodeHexToBuffer(ann.ephemeralPubKey, ephemeralBuffer);
if (!ephPubKey || ephPubKey.length !== 32 || !spendingPoint) continue;

const result = checkStealthAddressWithViewingPubKey(
ephPubKey,
Expand Down Expand Up @@ -197,12 +246,14 @@
stealthPubKeyBytes: Uint8Array | null;
} {
const viewingPubKey = ed25519.getPublicKey(viewingKey);
const spendingPoint = ed25519.ExtendedPoint.fromHex(spendingPubKey);
return checkStealthAddressWithViewingPubKey(
ephemeralPubKey,
viewingKey,
viewingPubKey,
spendingPubKey,
viewTag,
spendingPoint,
);
}

Expand All @@ -212,6 +263,7 @@
viewingPubKey: Uint8Array,
spendingPubKey: Uint8Array,
viewTag: number,
spendingPoint?: ed25519.ExtendedPoint,

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-spectre-agent)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-nextjs-app-router)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-chrome-extension)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (multichain-scan)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / Differential (test-vectors vs vN-1)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-scan)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-react-receive)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (20)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (22)

Cannot find namespace 'ed25519'.

Check failure on line 266 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-send)

Cannot find namespace 'ed25519'.
): {
isMatch: boolean;
stealthAddress: string | null;
Expand All @@ -224,7 +276,12 @@
}

try {
return deriveStealthAddressFromAnnouncement(ephemeralPubKey, viewingKey, spendingPubKey);
return deriveStealthAddressFromAnnouncement(
ephemeralPubKey,
viewingKey,
spendingPubKey,
spendingPoint,
);
} catch {
return { isMatch: false, stealthAddress: null, hashScalar: null, stealthPubKeyBytes: null };
}
Expand All @@ -234,6 +291,7 @@
ephemeralPubKey: Uint8Array,
viewingKey: Uint8Array,
spendingPubKey: Uint8Array,
spendingPoint?: ed25519.ExtendedPoint,

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-spectre-agent)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-nextjs-app-router)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-chrome-extension)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (multichain-scan)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / Differential (test-vectors vs vN-1)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-scan)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-react-receive)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (20)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (22)

Cannot find namespace 'ed25519'.

Check failure on line 294 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-send)

Cannot find namespace 'ed25519'.
): {
isMatch: boolean;
stealthAddress: string | null;
Expand All @@ -243,7 +301,7 @@
const sharedSecret = computeSharedSecret(viewingKey, ephemeralPubKey);
const hScalar = hashToScalar(sharedSecret);

const stealthPubKeyBytes = deriveStealthPubKey(spendingPubKey, hScalar);
const stealthPubKeyBytes = deriveStealthPubKey(spendingPubKey, hScalar, spendingPoint);
const stealthAddress = pubKeyToStellarAddress(stealthPubKeyBytes);

return { isMatch: true, stealthAddress, hashScalar: hScalar, stealthPubKeyBytes };
Expand Down Expand Up @@ -290,23 +348,32 @@
): MatchedAnnouncement[] {
const matched: MatchedAnnouncement[] = [];
const viewingPubKey = ed25519.getPublicKey(viewingKey);
const ephemeralBuffer = new Uint8Array(32);
let spendingPoint: ed25519.ExtendedPoint | undefined;

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-spectre-agent)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-nextjs-app-router)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-chrome-extension)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (multichain-scan)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / Differential (test-vectors vs vN-1)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-scan)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-react-receive)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (20)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / test (22)

Cannot find namespace 'ed25519'.

Check failure on line 352 in src/chains/stellar/scan.ts

View workflow job for this annotation

GitHub Actions / examples (stellar-cli-send)

Cannot find namespace 'ed25519'.

for (const ann of announcements) {
try {
spendingPoint = ed25519.ExtendedPoint.fromHex(spendingPubKey);
} catch {
spendingPoint = undefined;
}

for (let i = 0, len = announcements.length; i < len; i++) {
const ann = announcements[i];
if (ann.schemeId !== SCHEME_ID && ann.schemeId !== SCHEME_ID_V2) continue;

const metadataBytes = hexToBytes(ann.metadata);
if (metadataBytes.length === 0) continue;
const viewTag = metadataBytes[0];
const viewTag = readViewTag(ann.metadata);
if (viewTag === null) continue;

const ephPubKey = hexToBytes(ann.ephemeralPubKey);
if (ephPubKey.length !== 32) continue;
const ephPubKey = decodeHexToBuffer(ann.ephemeralPubKey, ephemeralBuffer);
if (!ephPubKey || ephPubKey.length !== 32 || !spendingPoint) continue;

const result = checkStealthAddressWithViewingPubKey(
ephPubKey,
viewingKey,
viewingPubKey,
spendingPubKey,
viewTag,
spendingPoint,
);

if (
Expand Down
2 changes: 1 addition & 1 deletion test/chains/stellar/bench/scan.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { Announcement, StealthKeys } from '../../../../src/chains/stellar/t

const MATCH_INDEX = 997;
const POOL_SIZE = 512;
const DEFAULT_DATASET_SIZES = [10_000, 100_000, 1_000_000] as const;
const DEFAULT_DATASET_SIZES = [10_000, 100_000] as const;
const DATASET_SIZES = (
process.env.STELLAR_SCAN_BENCH_SIZES?.split(',').map(Number) ?? [...DEFAULT_DATASET_SIZES]
).filter((size) => Number.isFinite(size) && size > 0);
Expand Down
Loading