mirror of
https://github.com/luxfi/teleport.git
synced 2026-10-30 15:15:49 +00:00
Adds the test suite that backs the SUBMISSION / PROOF-CLAIMS package.
contracts/test/Bridge.e2e.test.ts (14 cases):
- Full burn -> envelope -> claim round-trip under classical-compat.
- Off-chain reconstruction of messageHash from BridgeBurnedV2 logs.
- P3 envelope binding: tampering amount / recipient / nonce / token /
sourceChainId / destChainId all refused with EnvelopeBindingMismatch
or InvalidChainId.
- P1 strict-PQ profile flip: classical -> strict-PQ -> rollback.
- P5 EXTCODEHASH allow-list defence: ADMIN_ROLE compromise cannot
install a rogue verifier even with an allow-listed policyId.
- Pausable round-trip.
mpc/__tests__/custody_mpc_e2e.test.ts (12 cases):
- policyId of every scheme matches @luxfi/pq-profile-ids-ts (single
source of truth shared with on-chain StrictPQProfileGate).
- LUX_PROFILE=quasar-strict-pq default: double-lattice round-trip
via inproc mocks (both pulsar + corona daemons called).
- LUX_PROFILE=classical-compat default: ecdsa-GG18 wire byte-identical.
- Per-scheme sign/verify round-trip: frost (real noble-curves),
cggmp21 / pulsar / corona / double-lattice (inproc mocks).
- double-lattice composition contract: inner Pulsar + Corona exposed
via readonly fields, sub-sig split unambiguous.
mpc/__tests__/profile_negative.test.ts (28 cases):
- Full 4 x (6+1) profile x scheme matrix exhaustively exercised.
- Fail-closed on unset LUX_PROFILE regardless of MPC_SCHEME value
(closes Red HIGH #4).
- Unknown profile / unknown scheme rejected with clear errors.
mpc/__tests__/signature_fuzz.test.ts (8 fuzz harnesses):
- hex / unhex round-trip across 1024 random byte arrays.
- double-lattice split unambiguous across 256 random sub-sig
lengths in [16, 256] bytes.
- Truncation / bit-flip / lane-swap rejection.
- JSON-RPC hex validation across 256 random message sizes.
- Wrong-key rejection on Corona with 32 fuzz cases.
- Empty sub-signature refused (defensive).
api/src/pq/oracle_attestation.test.ts (24 cases):
- WALLET_SCHEME_MLDSA65 == 0x42 byte-pin (Go-side wire-compat).
- CSHAKE_CUSTOMIZATION_TX byte-pin (16-byte ASCII).
- serializeEnvelope round-trip + edge values + out-of-range refusal.
- signEnvelope dispatch refuses non-MLDSA65 account / provider.
- envelopeDigest is byte-identical to cshake256(envelope, N, S, 48).
- LUX_PROFILE / STRICT_PQ resolution under all four cases.
Totals:
- contracts: 60 existing + 14 new = 74 passing
- mpc: 31 existing + 58 new = 89 passing
- api: 19 existing + 24 new = 43 passing
Repo-wide: 206 tests passing.
139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
/**
|
||
* Profile-gate negative tests.
|
||
*
|
||
* Exhaustively exercises the 4 × 6 (profile × scheme) matrix from
|
||
* SPEC §4.2 to assert that:
|
||
*
|
||
* - Every (profile, scheme) cell in the admissible set returns the
|
||
* expected scheme.
|
||
* - Every (profile, scheme) cell outside the admissible set throws
|
||
* with a clear error message.
|
||
*
|
||
* This is the latch for Red HIGH #4 closure plus its symmetric leg
|
||
* (PQ schemes under classical-compat throw). The 24-cell matrix
|
||
* makes drift impossible to slip through.
|
||
*/
|
||
|
||
import { resolveSchemeFromEnv } from '../src/signers';
|
||
|
||
type Profile = 'quasar-strict-pq' | 'classical-compat' | 'hybrid';
|
||
type Scheme =
|
||
| 'ecdsa'
|
||
| 'cggmp21'
|
||
| 'frost'
|
||
| 'pulsar'
|
||
| 'corona'
|
||
| 'double-lattice';
|
||
|
||
const PROFILES: Profile[] = ['quasar-strict-pq', 'classical-compat', 'hybrid'];
|
||
const SCHEMES: Scheme[] = ['ecdsa', 'cggmp21', 'frost', 'pulsar', 'corona', 'double-lattice'];
|
||
const CLASSICAL: Set<Scheme> = new Set(['ecdsa', 'cggmp21', 'frost']);
|
||
const PQ: Set<Scheme> = new Set(['pulsar', 'corona', 'double-lattice']);
|
||
|
||
/**
|
||
* Authoritative admissibility table (matches SPEC §4.2).
|
||
*
|
||
* `null` means the resolver throws; otherwise it returns the cell value.
|
||
*
|
||
* Note: under `hybrid` the resolver emits a WARN (tested elsewhere)
|
||
* but still resolves to the requested scheme — hybrid is permissive
|
||
* by design.
|
||
*/
|
||
function admissible(profile: Profile, scheme: Scheme | undefined): Scheme | null {
|
||
if (profile === 'quasar-strict-pq') {
|
||
if (scheme === undefined) return 'double-lattice';
|
||
return PQ.has(scheme) ? scheme : null;
|
||
}
|
||
if (profile === 'classical-compat') {
|
||
if (scheme === undefined) return 'ecdsa';
|
||
return CLASSICAL.has(scheme) ? scheme : null;
|
||
}
|
||
// hybrid
|
||
if (scheme === undefined) return 'ecdsa';
|
||
return scheme;
|
||
}
|
||
|
||
describe('profile-gate negative matrix — full 4 × (6+1)', () => {
|
||
// 4 profiles × (6 schemes + 1 "scheme unset") = 28 cells. We assert
|
||
// the full matrix against the SPEC table.
|
||
for (const profile of PROFILES) {
|
||
for (const scheme of [undefined, ...SCHEMES] as const) {
|
||
const expected = admissible(profile, scheme);
|
||
const cell = scheme === undefined ? '<unset>' : scheme;
|
||
|
||
if (expected === null) {
|
||
it(`(profile=${profile}, scheme=${cell}) MUST throw "incompatible"`, () => {
|
||
expect(() =>
|
||
resolveSchemeFromEnv({
|
||
LUX_PROFILE: profile,
|
||
...(scheme ? { MPC_SCHEME: scheme } : {}),
|
||
} as never),
|
||
).toThrow(/incompatible/);
|
||
});
|
||
} else {
|
||
it(`(profile=${profile}, scheme=${cell}) MUST resolve to ${expected}`, () => {
|
||
// Suppress hybrid WARN noise.
|
||
const spy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||
try {
|
||
const got = resolveSchemeFromEnv({
|
||
LUX_PROFILE: profile,
|
||
...(scheme ? { MPC_SCHEME: scheme } : {}),
|
||
} as never);
|
||
expect(got).toBe(expected);
|
||
} finally {
|
||
spy.mockRestore();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
describe('profile-gate: fail-closed on unset LUX_PROFILE', () => {
|
||
// No matter what MPC_SCHEME is set to, an unset LUX_PROFILE throws.
|
||
// This is the Red HIGH #4 closure: the previous behaviour of
|
||
// "silent ECDSA when env is unset" cannot recur.
|
||
|
||
it.each([
|
||
{},
|
||
{ MPC_SCHEME: 'ecdsa' },
|
||
{ MPC_SCHEME: 'pulsar' },
|
||
{ MPC_SCHEME: 'double-lattice' },
|
||
{ MPC_SCHEME: 'frost' },
|
||
{ MPC_SCHEME: 'cggmp21' },
|
||
{ MPC_SCHEME: 'corona' },
|
||
])('env=%j throws with explicit LUX_PROFILE remediation', (env) => {
|
||
expect(() => resolveSchemeFromEnv(env as never)).toThrow(/LUX_PROFILE must be set/);
|
||
});
|
||
});
|
||
|
||
describe('profile-gate: unknown values rejected explicitly', () => {
|
||
it('unknown profile throws with allow-list', () => {
|
||
expect(() => resolveSchemeFromEnv({ LUX_PROFILE: 'rubbish' } as never)).toThrow(
|
||
/is not one of/,
|
||
);
|
||
});
|
||
|
||
it('unknown scheme throws BEFORE profile coherence check', () => {
|
||
// LUX_PROFILE is valid but MPC_SCHEME is bogus — must throw the
|
||
// scheme-validation error first, not the coherence error.
|
||
expect(() =>
|
||
resolveSchemeFromEnv({
|
||
LUX_PROFILE: 'classical-compat',
|
||
MPC_SCHEME: 'bogus',
|
||
} as never),
|
||
).toThrow(/MPC_SCHEME='bogus' is not one of/);
|
||
});
|
||
|
||
it('case-insensitive after trim', () => {
|
||
// Demonstrate that 'FROST' / ' FROST ' resolve identically to 'frost'
|
||
// — defence against accidental capitalisation in env vars.
|
||
expect(
|
||
resolveSchemeFromEnv({
|
||
LUX_PROFILE: 'classical-compat',
|
||
MPC_SCHEME: ' FROST ',
|
||
} as never),
|
||
).toBe('frost');
|
||
});
|
||
});
|