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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
# test() bodies in this repo, so it cannot be trusted as a gate.
- run: pnpm test:types
- run: pnpm test
# Run a small fixed-seed sample of Stellar property tests on every PR
- name: Stellar property test sample
if: github.event_name == 'pull_request'
run: pnpm test:fuzz:sample

entrypoints:
name: Package entry point smoke tests
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
"format:check": "prettier --check .",
"prepare": "husky",
"test:fuzz": "FC_RUNS=100000 vitest run test/chains/stellar/properties.test.ts",
"test:fuzz:sample": "FC_RUNS=100 FC_SEED=12345 vitest run test/chains/stellar/properties.test.ts",
"test:fuzz:proof": "FC_PROOF_TEST=1 vitest run test/chains/stellar/properties.test.ts",
"test:leak": "node --expose-gc ./node_modules/vitest/vitest.mjs run test/leaks/scan-leak.test.ts --pool=threads",
"test:heap-leak": "node --expose-gc ./node_modules/vitest/vitest.mjs run test/leaks/heap-snapshot.test.ts --pool=threads",
"size": "size-limit",
Expand Down
74 changes: 62 additions & 12 deletions test/chains/stellar/properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,38 @@ import {
import { computeViewTag } from '../../../src/chains/stellar/stealth';

const FC_RUNS = process.env.FC_RUNS ? parseInt(process.env.FC_RUNS, 10) : 1000;
const FC_SEED = process.env.FC_SEED ? parseInt(process.env.FC_SEED, 10) : undefined;
const FC_PATH = process.env.FC_PATH;

// Configure fast-check with seed if provided
const configure = () => {
if (FC_SEED !== undefined) {
console.log(`\n🔬 Using fixed seed: ${FC_SEED}`);
console.log(
` Replay with: FC_SEED=${FC_SEED} FC_RUNS=${FC_RUNS} pnpm test test/chains/stellar/properties.test.ts\n`,
);
const config: Record<string, unknown> = { seed: FC_SEED };
if (FC_PATH !== undefined) {
config.path = FC_PATH;
}
return config;
}
return {};
};

// Custom reporter to print seed and path on failure
const reporter = (runDetails: fc.RunDetails<unknown>) => {
if (runDetails.failed) {
const seed = runDetails.seed;
const path = runDetails.counterexamplePath;
console.error(`\n❌ Property test failed`);
console.error(` Seed: ${seed}`);
console.error(` Path: ${path}`);
const replayCmd = `FC_SEED=${seed} FC_PATH=${path} FC_RUNS=${FC_RUNS} pnpm test test/chains/stellar/properties.test.ts`;
console.error(` Replay with: ${replayCmd}\n`);
throw new Error(fc.defaultReportMessage(runDetails));
}
};

// ─────────────────────────────────────────────────────────────
// Helpers
Expand Down Expand Up @@ -48,7 +80,7 @@ describe('scalar algebra sanity', () => {
const r2 = (b + a) % L;
expect(r1).toBe(r2);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});

Expand All @@ -57,7 +89,7 @@ describe('scalar algebra sanity', () => {
fc.property(scalarAnyArb, (a) => {
expect((a + 0n) % L).toBe(a % L);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});
});
Expand All @@ -73,7 +105,7 @@ describe('scalar encoding roundtrip', () => {
const normalized = a % L;
expect(bytesToScalar(scalarToBytes(normalized)) % L).toBe(normalized);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});

Expand All @@ -82,7 +114,7 @@ describe('scalar encoding roundtrip', () => {
fc.property(scalarAnyArb, (a) => {
expect(scalarToBytes(a)).toHaveLength(32);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});
});
Expand All @@ -99,7 +131,7 @@ describe('seedToScalar stability', () => {
const b = seedToScalar(seed);
expect(a).toBe(b);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});

Expand All @@ -116,7 +148,7 @@ describe('seedToScalar stability', () => {
// sanity: still a valid scalar space
expect(s < 2n ** 256n).toBe(true);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});
});
Expand All @@ -140,7 +172,7 @@ describe('stealth pubkey correctness', () => {

expect(lhs.equals(rhs)).toBe(true);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});

Expand All @@ -159,7 +191,7 @@ describe('stealth pubkey correctness', () => {

expect(derived).toEqual(expected);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});
});
Expand All @@ -175,7 +207,7 @@ describe('view tag distribution sanity', () => {
const tag = computeViewTag(secret);
expect(tag >= 0 && tag <= 255).toBe(true);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});
});
Expand All @@ -195,7 +227,7 @@ describe('signWithScalar correctness', () => {
expect(sig).toHaveLength(64);
expect(verifySignature(sig, msg, pub)).toBe(true);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});

Expand All @@ -210,7 +242,7 @@ describe('signWithScalar correctness', () => {

expect(verifySignature(sig, m2, pub)).toBe(false);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});

Expand All @@ -227,7 +259,25 @@ describe('signWithScalar correctness', () => {

expect(verifySignature(sig, msg, pub2)).toBe(false);
}),
{ numRuns: FC_RUNS },
{ numRuns: FC_RUNS, ...configure(), reporter },
);
});
});

// ─────────────────────────────────────────────────────────────
// 7. proof test: demonstrates replay output on failure
// ─────────────────────────────────────────────────────────────

if (process.env.FC_PROOF_TEST === '1') {
describe('replay output proof test', () => {
test('deliberately fails to prove replay output is emitted', () => {
fc.assert(
fc.property(scalarArb, (a: bigint) => {
// This will always fail
expect(a).toBe(0n);
}),
{ numRuns: 10, seed: 42, reporter },
);
});
});
}
2 changes: 1 addition & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default defineConfig({
'test/chains/evm/vectors.test.ts',
'test/chains/solana/vectors.test.ts',
],
testTimeout: 1200000, // 20 minutes for high-run nightly fuzz tests
testTimeout: 3600000, // 60 minutes for high-run nightly fuzz tests (100k runs takes ~6.5min + reporting overhead)
},
benchmark: {
include: ['test/chains/**/bench/**/*.bench.ts', 'test/bench/**/*.bench.ts'],
Expand Down
Loading