Golden vectors: prove the JS crypto agrees with the chain byte for byte #2

Open
opened 2026-09-10 10:11:42 +00:00 by grenade · 1 comment
Owner

Companion to #1. Without this, every downstream repo is testing that it agrees with itself.

The vectors already exist

qp-rusty-crystals-hdwallet ships golden HD-derivation vectors, regenerated by its own regenerate_variant_golden_vectors:

  • src/test_vectors_65.rs — ML-DSA-65 (the scheme new accounts use)
  • src/test_vectors_87.rs / src/test_vectors.rs — ML-DSA-87 (legacy accounts)
  • src/test_vectors_44.rs — unused by Quantus, ignore

Each entry is (seed_bytes, path, expected). Extract them to a JSON fixture rather than retyping — a transcription error here produces a test that passes against the wrong answer.

What to pin

  1. HD derivation — mnemonic + path → public key, from the vectors above.
  2. Account id — public key → poseidonHashBytes → SS58 at prefix 189. Take known pairs from quantus-apps:quantus_sdk tests, or generate them with quantus-cli and record where they came from.
  3. Signature determinism — the same (key, message, context) must produce the identical signature every time. The chain signs with hedge: None, and anything non-deterministic here means we have accidentally introduced randomness the reference implementation does not have.
  4. Context separation — a signature made under "QUANTUS_EXTRINSIC" must fail verification under the empty context, and vice versa. This is the cheapest possible guard against the spec-148 boundary being got wrong somewhere downstream.

Parity CI

A test that only checks our JS against our JSON will keep passing while both drift. Add a CI job that builds the Rust crates and asserts the same vectors through both paths, so that bumping a qp-* dependency version fails loudly if it changes behaviour.

Downstream

Publish the fixture in a form quantus/common can consume directly — its keyring tests need exactly these vectors and should not be maintaining a second copy.

Companion to #1. Without this, every downstream repo is testing that it agrees with itself. ## The vectors already exist `qp-rusty-crystals-hdwallet` ships golden HD-derivation vectors, regenerated by its own `regenerate_variant_golden_vectors`: - `src/test_vectors_65.rs` — ML-DSA-65 (the scheme new accounts use) - `src/test_vectors_87.rs` / `src/test_vectors.rs` — ML-DSA-87 (legacy accounts) - `src/test_vectors_44.rs` — unused by Quantus, ignore Each entry is `(seed_bytes, path, expected)`. Extract them to a JSON fixture rather than retyping — a transcription error here produces a test that passes against the wrong answer. ## What to pin 1. **HD derivation** — mnemonic + path → public key, from the vectors above. 2. **Account id** — public key → `poseidonHashBytes` → SS58 at prefix 189. Take known pairs from `quantus-apps:quantus_sdk` tests, or generate them with `quantus-cli` and record where they came from. 3. **Signature determinism** — the same (key, message, context) must produce the identical signature every time. The chain signs with `hedge: None`, and anything non-deterministic here means we have accidentally introduced randomness the reference implementation does not have. 4. **Context separation** — a signature made under `"QUANTUS_EXTRINSIC"` must fail verification under the empty context, and vice versa. This is the cheapest possible guard against the spec-148 boundary being got wrong somewhere downstream. ## Parity CI A test that only checks our JS against our JSON will keep passing while both drift. Add a CI job that builds the Rust crates and asserts the same vectors through both paths, so that bumping a `qp-*` dependency version fails loudly if it changes behaviour. ## Downstream Publish the fixture in a form quantus/common can consume directly — its keyring tests need exactly these vectors and should not be maintaining a second copy.
Author
Owner

Checked against main (dc295dc3). Staying open.

Done:

  • Account ids with their sources recorded: three dev accounts from quantus-cli, and the node's TEST_WORMHOLE_ADDRESS (src/rs/tests.rs).
  • Signing determinism: signing_is_deterministic (ML-DSA-65 only).
  • Context separation, one direction: a signature under QUANTUS_EXTRINSIC fails under the empty context.
  • Nullifiers: checked against qp-wormhole-circuit in a known-answer test.

Remaining:

  1. Generate a JSON fixture from the hdwallet crate's own test_vectors_65.rs and _87.rs, extracted rather than retyped. The only HD pins today are two CLI-derived hashes.
  2. Assert that fixture in both tests.rs and test/consumer.mjs.
  3. Test the reverse direction: a signature made under the empty context fails under QUANTUS_EXTRINSIC.
  4. Add a Gitea CI job that runs both the Rust and JS suites. Today yarn test runs Rust only, and the .github workflows are upstream's.
  5. Ship the fixture where quantus/common can import it, which also closes out quantus/common#4's golden-vector criterion.
Checked against `main` (`dc295dc3`). Staying open. **Done:** - **Account ids with their sources recorded:** three dev accounts from `quantus-cli`, and the node's `TEST_WORMHOLE_ADDRESS` (`src/rs/tests.rs`). - **Signing determinism:** `signing_is_deterministic` (ML-DSA-65 only). - **Context separation, one direction:** a signature under `QUANTUS_EXTRINSIC` fails under the empty context. - **Nullifiers:** checked against `qp-wormhole-circuit` in a known-answer test. **Remaining:** 1. Generate a JSON fixture from the hdwallet crate's own `test_vectors_65.rs` and `_87.rs`, extracted rather than retyped. The only HD pins today are two CLI-derived hashes. 2. Assert that fixture in both `tests.rs` and `test/consumer.mjs`. 3. Test the reverse direction: a signature made under the empty context fails under `QUANTUS_EXTRINSIC`. 4. Add a Gitea CI job that runs both the Rust and JS suites. Today `yarn test` runs Rust only, and the `.github` workflows are upstream's. 5. Ship the fixture where quantus/common can import it, which also closes out quantus/common#4's golden-vector criterion.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: quantus/wasm#2