diff --git a/BUNDLE_SIZE.md b/BUNDLE_SIZE.md new file mode 100644 index 0000000..c08cfe8 --- /dev/null +++ b/BUNDLE_SIZE.md @@ -0,0 +1,64 @@ +# Bundle Size Baseline — Stellar Entry + +> Last measured: 2026-06-23 +> Bundler: tsup (esbuild) via `size-limit` + +## Current Size + +| Format | Size (gzip) | Budget | +|--------|-------------|--------| +| ESM (`import *`) | TBD | 20 KB | +| CJS (`require`) | TBD | 20 KB | + +> TBD — run `pnpm build && pnpm size` after installation to populate +> actual measurements, then update this table. + +## Dependency Graph + +Generate a visual treemap of the Stellar entry's dependency graph: + +```bash +ANALYZE=true pnpm build +# produces stats/ folder with metafile data +npx esbuild-visualizer --metadata stats/metafile-stellar.json --open +``` + +> `esbuild-visualizer` is an optional dev tool — install it globally or +> via `npx` when you need to inspect the graph. + +## Measurement Commands + +### esbuild (tsup) — via size-limit (CI gate) + +```bash +pnpm build +pnpm size +``` + +### Vite-style bundling — standalone esbuild + +```bash +pnpm measure:vite +``` + +Output written to `stats/vite-measurement.json`. + +## Budget Policy + +The Stellar entry budget is **20 KB gzipped** for each format (ESM, CJS). + +- If a PR increases the Stellar bundle beyond the budget, CI will fail. +- Reviewers should verify no non-Stellar code was introduced into + `src/chains/stellar/` by checking imports. +- To adjust the budget, update the `size-limit` array in `package.json`. + +## Known Optimizations + +1. **Lazy `@stellar/stellar-sdk` import** — `pubKeyToStellarAddress()` uses a + dynamic `import()` instead of a top-level static import, ensuring the + optional peer dependency is never loaded until the function is actually + called. See `src/chains/stellar/scalar.ts`. + +2. **No cross-chain leaks** — `src/chains/stellar/` imports zero code from + `evm/`, `solana/`, `ckb/`, or `agent/` directories. All imports are + local (`./`) or external npm packages (`@noble/curves`, `@noble/hashes`). diff --git a/package.json b/package.json index 968797f..9d35d9e 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,19 @@ "prepare": "husky", "test:fuzz": "FC_RUNS=100000 vitest run test/chains/stellar/properties.test.ts" }, + "size-limit": [ + { + "name": "Stellar ESM (import *)", + "path": "dist/chains/stellar/index.js", + "import": "*", + "limit": "20 KB" + }, + { + "name": "Stellar CJS (require)", + "path": "dist/chains/stellar/index.cjs", + "limit": "20 KB" + } + ], "dependencies": { "@noble/curves": "^1.8.0", "@noble/hashes": "^1.7.0", @@ -77,6 +90,7 @@ "devDependencies": { "@commitlint/cli": "^19.6.0", "@commitlint/config-conventional": "^19.6.0", + "@size-limit/esbuild": "^11.0.0", "@solana/web3.js": "^1.98.4", "@stellar/stellar-sdk": "^13.1.0", "fake-indexeddb": "^6.2.5", diff --git a/scripts/measure-vite.mjs b/scripts/measure-vite.mjs new file mode 100644 index 0000000..825c0f9 --- /dev/null +++ b/scripts/measure-vite.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node +import { build } from 'esbuild'; +import { readFileSync, writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname, '..'); + +async function measure() { + const result = await build({ + entryPoints: [join(root, 'src/chains/stellar/index.ts')], + bundle: true, + format: 'esm', + outfile: '/dev/null', + metafile: true, + platform: 'browser', + external: ['@stellar/stellar-sdk', '@solana/web3.js'], + }); + + const metafile = result.metafile; + const output = Object.values(metafile.outputs)[0]; + const totalBytes = output.bytes; + const totalGzip = estimateGzip(output.bytes); + + const inputs = Object.entries(metafile.inputs) + .filter(([path]) => !path.includes('node_modules')) + .map(([path, info]) => ({ + path, + bytes: info.bytes, + importedBy: info.importedBy.length, + imports: info.imports.length, + })) + .sort((a, b) => b.bytes - a.bytes); + + const external = Object.entries(metafile.inputs) + .filter(([path]) => path.includes('node_modules')) + .map(([path, info]) => ({ + path, + bytes: info.bytes, + })) + .sort((a, b) => b.bytes - a.bytes); + + const report = { + bundler: 'esbuild (standalone — Vite-analogous)', + totalBytes, + totalGzip, + sourceInputs: inputs, + externalDeps: external, + }; + + writeFileSync(join(root, 'stats/vite-measurement.json'), JSON.stringify(report, null, 2)); + + console.log('\n=== Stellar Entry Bundle Size (Vite-style bundling) ===\n'); + console.log(`Total bundle size: ${(totalBytes / 1024).toFixed(2)} KB`); + console.log(`Estimated gzip: ${(totalGzip / 1024).toFixed(2)} KB`); + console.log(`\nSource files included (top 10 by size):`); + inputs.slice(0, 10).forEach((f) => { + console.log(` ${(f.bytes / 1024).toFixed(2)} KB ${f.path.replace(root + '/', '')}`); + }); + console.log(`\nExternal dependencies:`); + external.forEach((f) => { + const pkg = f.path.match(/node_modules\/([^/]+)/)?.[1] || f.path; + console.log(` ${(f.bytes / 1024).toFixed(2)} KB ${pkg}`); + }); +} + +function estimateGzip(bytes) { + return Math.round(bytes * 0.35); +} + +measure().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/chains/stellar/scan.ts b/src/chains/stellar/scan.ts index 195c24d..ad12059 100644 --- a/src/chains/stellar/scan.ts +++ b/src/chains/stellar/scan.ts @@ -109,12 +109,12 @@ export async function* scanAnnouncementsStream( * * @see {@link scanAnnouncements} */ -export function checkStealthAddress( +export async function checkStealthAddress( ephemeralPubKey: Uint8Array, viewingKey: Uint8Array, spendingPubKey: Uint8Array, viewTag: number, -): { +): Promise<{ isMatch: boolean; stealthAddress: string | null; hashScalar: bigint | null; @@ -168,7 +168,7 @@ function deriveStealthAddressFromAnnouncement( const hScalar = hashToScalar(sharedSecret); const stealthPubKeyBytes = deriveStealthPubKey(spendingPubKey, hScalar); - const stealthAddress = pubKeyToStellarAddress(stealthPubKeyBytes); + const stealthAddress = await pubKeyToStellarAddress(stealthPubKeyBytes); return { isMatch: true, stealthAddress, hashScalar: hScalar, stealthPubKeyBytes }; } @@ -206,12 +206,12 @@ function deriveStealthAddressFromAnnouncement( * * @see {@link deriveStealthPrivateScalar} */ -export function scanAnnouncements( +export async function scanAnnouncements( announcements: Announcement[], viewingKey: Uint8Array, spendingPubKey: Uint8Array, spendingScalar: bigint, -): MatchedAnnouncement[] { +): Promise { const matched: MatchedAnnouncement[] = []; const viewingPubKey = ed25519.getPublicKey(viewingKey); diff --git a/src/chains/stellar/stealth.ts b/src/chains/stellar/stealth.ts index 7736c03..03c472c 100644 --- a/src/chains/stellar/stealth.ts +++ b/src/chains/stellar/stealth.ts @@ -41,11 +41,11 @@ const LEGACY_VIEW_TAG_PREFIX = new TextEncoder().encode('wraith:tag:'); * * @see {@link scanAnnouncements} to detect announcements for generated addresses. */ -export function generateStealthAddress( +export async function generateStealthAddress( spendingPubKey: Uint8Array, viewingPubKey: Uint8Array, ephemeralSeed?: Uint8Array, -): GeneratedStealthAddress { +): Promise { const ephSeed = ephemeralSeed ?? ed25519.utils.randomPrivateKey(); const ephPubKey = ed25519.getPublicKey(ephSeed); @@ -57,7 +57,7 @@ export function generateStealthAddress( const stealthPubKeyBytes = deriveStealthPubKey(spendingPubKey, hScalar); - const stealthAddress = pubKeyToStellarAddress(stealthPubKeyBytes); + const stealthAddress = await pubKeyToStellarAddress(stealthPubKeyBytes); return { stealthAddress, diff --git a/test/chains/stellar/bench/scan.bench.ts b/test/chains/stellar/bench/scan.bench.ts new file mode 100644 index 0000000..177401b --- /dev/null +++ b/test/chains/stellar/bench/scan.bench.ts @@ -0,0 +1,162 @@ +import { bench, describe, expect, test } from 'vitest'; +import { deriveStealthKeys } from '../../../../src/chains/stellar/keys'; +import { + computeAnnouncementViewTag, + computeSharedSecret, + computeViewTag, + generateStealthAddress, +} from '../../../../src/chains/stellar/stealth'; +import { + scanAnnouncements, + scanAnnouncementsLegacySharedSecretTag, +} from '../../../../src/chains/stellar/scan'; +import { SCHEME_ID } from '../../../../src/chains/stellar/constants'; +import { bytesToHex } from '../../../../src/chains/stellar/utils'; +import type { Announcement, StealthKeys } from '../../../../src/chains/stellar/types'; + +const MATCH_INDEX = 997; +const POOL_SIZE = 512; +const DEFAULT_DATASET_SIZES = [10_000, 100_000, 1_000_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); +const BENCH_OPTIONS = { time: 1, iterations: 1, warmupTime: 0, warmupIterations: 0 }; + +const keys = deriveStealthKeys(new Uint8Array(64).fill(0xaa)); +const foreignKeys = deriveStealthKeys(new Uint8Array(64).fill(0xbb)); + +function seedFor(index: number): Uint8Array { + const seed = new Uint8Array(32); + let state = (index + 1) * 0x9e3779b1; + for (let i = 0; i < seed.length; i++) { + state ^= state << 13; + state ^= state >>> 17; + state ^= state << 5; + seed[i] = state & 0xff; + } + return seed; +} + +async function makeAnnouncementFor( + recipient: StealthKeys, + ephemeralSeed: Uint8Array, + tagScheme: 'legacy-shared-secret' | 'public-announcement', +): Promise { + const stealth = await generateStealthAddress( + recipient.spendingPubKey, + recipient.viewingPubKey, + ephemeralSeed, + ); + const sharedSecret = computeSharedSecret(ephemeralSeed, recipient.viewingPubKey); + const viewTag = + tagScheme === 'legacy-shared-secret' + ? computeViewTag(sharedSecret) + : computeAnnouncementViewTag(stealth.ephemeralPubKey, recipient.viewingPubKey); + + return { + schemeId: SCHEME_ID, + stealthAddress: stealth.stealthAddress, + caller: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF', + ephemeralPubKey: bytesToHex(stealth.ephemeralPubKey), + metadata: viewTag.toString(16).padStart(2, '0'), + }; +} + +let pools: { legacy: Announcement[]; optimized: Announcement[] } | undefined; +let matchingAnnouncements: { legacy: Announcement; optimized: Announcement } | undefined; + +async function initFixtures() { + if (pools && matchingAnnouncements) return; + pools = { + legacy: await Promise.all( + Array.from({ length: POOL_SIZE }, (_, i) => + makeAnnouncementFor(foreignKeys, seedFor(i), 'legacy-shared-secret'), + ), + ), + optimized: await Promise.all( + Array.from({ length: POOL_SIZE }, (_, i) => + makeAnnouncementFor(foreignKeys, seedFor(i), 'public-announcement'), + ), + ), + }; + matchingAnnouncements = { + legacy: await makeAnnouncementFor(keys, seedFor(POOL_SIZE + 1), 'legacy-shared-secret'), + optimized: await makeAnnouncementFor(keys, seedFor(POOL_SIZE + 1), 'public-announcement'), + }; +} + +function makeDataset(size: number, tagScheme: 'legacy' | 'optimized') { + const foreignPool = pools![tagScheme]; + const matchingAnnouncement = matchingAnnouncements![tagScheme]; + + return Array.from({ length: size }, (_, i) => + i === MATCH_INDEX ? matchingAnnouncement : foreignPool[i % foreignPool.length], + ); +} + +async function getDatasets(): Promise< + Map +> { + await initFixtures(); + return new Map( + DATASET_SIZES.map((size) => [ + size, + { + legacy: makeDataset(size, 'legacy'), + optimized: makeDataset(size, 'optimized'), + }, + ]), + ); +} + +describe('Stellar scan benchmark fixtures', () => { + test('optimized scanner preserves correctness on the 10k synthetic dataset', async () => { + const datasets = await getDatasets(); + const dataset = datasets.get(10_000)?.optimized; + expect(dataset).toBeDefined(); + + const matched = await scanAnnouncements( + dataset!, + keys.viewingKey, + keys.spendingPubKey, + keys.spendingScalar, + ); + + expect(matched).toHaveLength(1); + expect(matched[0].stealthAddress).toBe(matchingAnnouncements!.optimized.stealthAddress); + }); +}); + +describe('Stellar scan announcement view-tag batching', () => { + for (const size of DATASET_SIZES) { + bench( + `before: shared-secret view tag (${size.toLocaleString()} announcements)`, + async () => { + const datasets = await getDatasets(); + const dataset = datasets.get(size)!; + await scanAnnouncementsLegacySharedSecretTag( + dataset.legacy, + keys.viewingKey, + keys.spendingPubKey, + keys.spendingScalar, + ); + }, + BENCH_OPTIONS, + ); + + bench( + `after: public view-tag prefilter (${size.toLocaleString()} announcements)`, + async () => { + const datasets = await getDatasets(); + const dataset = datasets.get(size)!; + await scanAnnouncements( + dataset.optimized, + keys.viewingKey, + keys.spendingPubKey, + keys.spendingScalar, + ); + }, + BENCH_OPTIONS, + ); + } +}); diff --git a/test/chains/stellar/e2e.test.ts b/test/chains/stellar/e2e.test.ts index d939818..6742fd2 100644 --- a/test/chains/stellar/e2e.test.ts +++ b/test/chains/stellar/e2e.test.ts @@ -19,7 +19,7 @@ const testSig = new Uint8Array(64).fill(0xaa); const fixedSeed = new Uint8Array(32).fill(0xcc); describe('e2e: full stealth payment flow on Stellar', () => { - test('derive → generate → scan → spend → verify', () => { + test('derive → generate → scan → spend → verify', async () => { const keys = deriveStealthKeys(testSig); const meta = encodeStealthMetaAddress(keys.spendingPubKey, keys.viewingPubKey); @@ -27,7 +27,7 @@ describe('e2e: full stealth payment flow on Stellar', () => { const decoded = decodeStealthMetaAddress(meta); - const stealth = generateStealthAddress( + const stealth = await generateStealthAddress( decoded.spendingPubKey, decoded.viewingPubKey, fixedSeed, @@ -42,7 +42,7 @@ describe('e2e: full stealth payment flow on Stellar', () => { metadata: stealth.viewTag.toString(16).padStart(2, '0'), }; - const matched = scanAnnouncements( + const matched = await scanAnnouncements( [announcement], keys.viewingKey, keys.spendingPubKey, diff --git a/test/chains/stellar/scan.test.ts b/test/chains/stellar/scan.test.ts index 35d7e96..1f6afa5 100644 --- a/test/chains/stellar/scan.test.ts +++ b/test/chains/stellar/scan.test.ts @@ -31,11 +31,14 @@ async function collectStream( } describe('checkStealthAddress', () => { - test('matches own announcement', () => { + test('matches own announcement', async () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ); - const result = checkStealthAddress( + const result = await checkStealthAddress( stealth.ephemeralPubKey, keys.viewingKey, keys.spendingPubKey, @@ -46,12 +49,15 @@ describe('checkStealthAddress', () => { expect(result.stealthAddress).toBe(stealth.stealthAddress); }); - test('rejects wrong view tag', () => { + test('rejects wrong view tag', async () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ); const wrongTag = (stealth.viewTag + 1) % 256; - const result = checkStealthAddress( + const result = await checkStealthAddress( stealth.ephemeralPubKey, keys.viewingKey, keys.spendingPubKey, @@ -62,14 +68,17 @@ describe('checkStealthAddress', () => { expect(result.stealthAddress).toBeNull(); }); - test('rejects wrong viewing key', () => { + test('rejects wrong viewing key', async () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ); const otherSig = new Uint8Array(64).fill(0xbb); const otherKeys = deriveStealthKeys(otherSig); - const result = checkStealthAddress( + const result = await checkStealthAddress( stealth.ephemeralPubKey, otherKeys.viewingKey, keys.spendingPubKey, @@ -83,9 +92,12 @@ describe('checkStealthAddress', () => { }); describe('scanAnnouncements', () => { - test('finds matching payments', () => { + test('finds matching payments', async () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ); const announcements: Announcement[] = [ { @@ -97,7 +109,7 @@ describe('scanAnnouncements', () => { }, ]; - const matched = scanAnnouncements( + const matched = await scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, @@ -135,7 +147,10 @@ describe('scanAnnouncements', () => { test('skips wrong scheme ID', () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ); const announcements: Announcement[] = [ { @@ -147,7 +162,7 @@ describe('scanAnnouncements', () => { }, ]; - const matched = scanAnnouncements( + const matched = await scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, @@ -229,11 +244,101 @@ describe('scanAnnouncements', () => { test('filters mix of own and foreign announcements', () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey); + const invalidEphemeralPubKey = new Uint8Array(32); + const matchingPublicTag = computeAnnouncementViewTag( + invalidEphemeralPubKey, + keys.viewingPubKey, + ); + + const announcements: Announcement[] = [ + { + schemeId: SCHEME_ID, + stealthAddress: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF', + caller: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF', + ephemeralPubKey: bytesToHex(invalidEphemeralPubKey), + metadata: matchingPublicTag.toString(16).padStart(2, '0'), + }, + ]; + + const matched = await scanAnnouncements( + announcements, + keys.viewingKey, + keys.spendingPubKey, + keys.spendingScalar, + ); + + expect(matched).toHaveLength(0); + }); + + test('keeps legacy shared-secret view tags on the legacy scanner path', async () => { + const keys = deriveStealthKeys(testSig); + let ephemeralSeed = new Uint8Array(32).fill(0x11); + let stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ephemeralSeed, + ); + let sharedSecret = computeSharedSecret(ephemeralSeed, keys.viewingPubKey); + let legacyTag = computeViewTag(sharedSecret); + + // Use a deterministic seed whose legacy shared-secret tag differs from the + // optimized public-announcement tag so the migration boundary is explicit. + for (let i = 0; legacyTag === stealth.viewTag && i < 255; i++) { + ephemeralSeed = new Uint8Array(32).fill(0x12 + i); + stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ephemeralSeed, + ); + sharedSecret = computeSharedSecret(ephemeralSeed, keys.viewingPubKey); + legacyTag = computeViewTag(sharedSecret); + } + + expect(legacyTag).not.toBe(stealth.viewTag); + + const announcements: Announcement[] = [ + { + schemeId: SCHEME_ID, + stealthAddress: stealth.stealthAddress, + caller: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF', + ephemeralPubKey: bytesToHex(stealth.ephemeralPubKey), + metadata: legacyTag.toString(16).padStart(2, '0'), + }, + ]; + + expect( + await scanAnnouncements( + announcements, + keys.viewingKey, + keys.spendingPubKey, + keys.spendingScalar, + ), + ).toHaveLength(0); + + const legacyMatched = await scanAnnouncementsLegacySharedSecretTag( + announcements, + keys.viewingKey, + keys.spendingPubKey, + keys.spendingScalar, + ); + + expect(legacyMatched).toHaveLength(1); + expect(legacyMatched[0].stealthAddress).toBe(stealth.stealthAddress); + }); + + test('filters mix of own and foreign announcements', async () => { + const keys = deriveStealthKeys(testSig); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + ); const otherSig = new Uint8Array(64).fill(0xbb); const otherKeys = deriveStealthKeys(otherSig); - const otherStealth = generateStealthAddress(otherKeys.spendingPubKey, otherKeys.viewingPubKey); + const otherStealth = await generateStealthAddress( + otherKeys.spendingPubKey, + otherKeys.viewingPubKey, + ); const announcements: Announcement[] = [ { @@ -252,7 +357,7 @@ describe('scanAnnouncements', () => { }, ]; - const matched = scanAnnouncements( + const matched = await scanAnnouncements( announcements, keys.viewingKey, keys.spendingPubKey, diff --git a/test/chains/stellar/spend.test.ts b/test/chains/stellar/spend.test.ts index da232ef..01efe1e 100644 --- a/test/chains/stellar/spend.test.ts +++ b/test/chains/stellar/spend.test.ts @@ -12,9 +12,13 @@ const testSig = new Uint8Array(64).fill(0xaa); const fixedSeed = new Uint8Array(32).fill(0xcc); describe('deriveStealthPrivateScalar', () => { - test('returns a valid bigint scalar', () => { + test('returns a valid bigint scalar', async () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, fixedSeed); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + fixedSeed, + ); const scalar = deriveStealthPrivateScalar( keys.spendingScalar, @@ -26,9 +30,13 @@ describe('deriveStealthPrivateScalar', () => { expect(scalar > 0n).toBe(true); }); - test('derived scalar produces the stealth public key', () => { + test('derived scalar produces the stealth public key', async () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, fixedSeed); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + fixedSeed, + ); const scalar = deriveStealthPrivateScalar( keys.spendingScalar, @@ -37,14 +45,18 @@ describe('deriveStealthPrivateScalar', () => { ); const derivedPub = ed25519.ExtendedPoint.BASE.multiply(scalar).toRawBytes(); - const derivedAddress = pubKeyToStellarAddress(derivedPub); + const derivedAddress = await pubKeyToStellarAddress(derivedPub); expect(derivedAddress).toBe(stealth.stealthAddress); }); - test('deterministic', () => { + test('deterministic', async () => { const keys = deriveStealthKeys(testSig); - const stealth = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, fixedSeed); + const stealth = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + fixedSeed, + ); const s1 = deriveStealthPrivateScalar( keys.spendingScalar, diff --git a/test/chains/stellar/stealth.test.ts b/test/chains/stellar/stealth.test.ts index c4a053d..408a44a 100644 --- a/test/chains/stellar/stealth.test.ts +++ b/test/chains/stellar/stealth.test.ts @@ -6,9 +6,13 @@ const testSig = new Uint8Array(64).fill(0xaa); const fixedSeed = new Uint8Array(32).fill(0xcc); describe('generateStealthAddress', () => { - test('generates valid stealth address', () => { + test('generates valid stealth address', async () => { const keys = deriveStealthKeys(testSig); - const result = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, fixedSeed); + const result = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + fixedSeed, + ); expect(result.stealthAddress).toMatch(/^G[A-Z2-7]{55}$/); expect(result.ephemeralPubKey).toBeInstanceOf(Uint8Array); @@ -17,32 +21,56 @@ describe('generateStealthAddress', () => { expect(result.viewTag).toBeLessThanOrEqual(255); }); - test('deterministic with fixed ephemeral seed', () => { + test('deterministic with fixed ephemeral seed', async () => { const keys = deriveStealthKeys(testSig); - const r1 = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, fixedSeed); - const r2 = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, fixedSeed); + const r1 = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + fixedSeed, + ); + const r2 = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + fixedSeed, + ); expect(r1.stealthAddress).toBe(r2.stealthAddress); expect(r1.ephemeralPubKey).toEqual(r2.ephemeralPubKey); expect(r1.viewTag).toBe(r2.viewTag); }); - test('different ephemeral seeds produce different addresses', () => { + test('different ephemeral seeds produce different addresses', async () => { const keys = deriveStealthKeys(testSig); - const r1 = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, fixedSeed); + const r1 = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + fixedSeed, + ); const altSeed = new Uint8Array(32).fill(0xdd); - const r2 = generateStealthAddress(keys.spendingPubKey, keys.viewingPubKey, altSeed); + const r2 = await generateStealthAddress( + keys.spendingPubKey, + keys.viewingPubKey, + altSeed, + ); expect(r1.stealthAddress).not.toBe(r2.stealthAddress); }); - test('different recipients produce different addresses', () => { + test('different recipients produce different addresses', async () => { const keys1 = deriveStealthKeys(testSig); const sig2 = new Uint8Array(64).fill(0xbb); const keys2 = deriveStealthKeys(sig2); - const r1 = generateStealthAddress(keys1.spendingPubKey, keys1.viewingPubKey, fixedSeed); - const r2 = generateStealthAddress(keys2.spendingPubKey, keys2.viewingPubKey, fixedSeed); + const r1 = await generateStealthAddress( + keys1.spendingPubKey, + keys1.viewingPubKey, + fixedSeed, + ); + const r2 = await generateStealthAddress( + keys2.spendingPubKey, + keys2.viewingPubKey, + fixedSeed, + ); expect(r1.stealthAddress).not.toBe(r2.stealthAddress); }); diff --git a/tsup.config.ts b/tsup.config.ts index b32ca9f..9004f02 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -15,4 +15,5 @@ export default defineConfig({ splitting: true, clean: true, treeshake: true, + metafile: !!process.env.ANALYZE, });