# @aere/pq-sign Hybrid signatures for documents, payloads, receipts and API calls: a classical scheme (**secp256k1** or **Ed25519**) and a NIST post-quantum scheme (**ML-DSA-44 / 65 / 87**, FIPS 204) sign the same digest, and a signature is valid only when **both** halves verify. Keys are generated and used only on your side; nothing is sent anywhere. The post-quantum half can be re-verified independently by the Aere Network chain precompile, so a third party does not have to trust this library, or you, to check it. ``` npm install @aere/pq-sign ``` Pure JavaScript (the audited noble libraries; no native code): Node 18+, and any bundler for the browser. ## Why hybrid A signature made with two independent schemes stays unforgeable while **either** scheme holds. If large quantum computers break secp256k1 and Ed25519, the ML-DSA half still binds the signer; if a flaw is found in the young lattice scheme, the classical half still does. This is the migration shape recommended by NIST, the German BSI and the French ANSSI for the transition years: not "replace", but "add, and require both". ## Use ```js import { generateKeyPair, sign, verify, serialize, parse, envelopeHash } from '@aere/pq-sign'; const keys = generateKeyPair({ alg: 'secp256k1+ml-dsa-65' }); // or 'ed25519+ml-dsa-65', '...+ml-dsa-44', '...+ml-dsa-87' const envelope = sign('release notes of myapp 1.4.2', keys); // string or Uint8Array const r = verify('release notes of myapp 1.4.2', envelope); // r = { valid: true, classical: true, pq: true, messageHashMatches: true, toSignMatches: true, reason: null } const text = serialize(envelope); // JSON, all byte fields 0x-hex; parse(text) gives it back ``` Keep `keys.classical.secretKey` and `keys.pq.secretKey` where you keep secrets. Publish `keys.classical.publicKey` and `keys.pq.publicKey`; they travel inside every envelope anyway. `generateKeyPair({ seed })` makes the ML-DSA key deterministic (32-byte seed) for test vectors; classical keys are always fresh unless `classicalSecretKey` is given. ## What exactly is signed ``` messageHash = sha256(message) toSign = sha256("aere-hybrid-signature-v1" || alg || messageHash) ``` Both schemes sign the 32 bytes of `toSign`: secp256k1 as a digest (RFC 6979, compact 64-byte signature, compressed 33-byte key), Ed25519 as a message (64-byte signature, 32-byte key), ML-DSA in the pure FIPS 204 interface with an empty context. The domain string and the algorithm label are under the hash, so an envelope cannot be replayed as a different algorithm pair or for another message. The envelope: ```json { "v": 1, "kind": "aere-hybrid-signature", "alg": "secp256k1+ml-dsa-65", "hash": "sha256", "messageHash": "0x…", "toSign": "0x…", "classicalPublicKey": "0x…", "classicalSignature": "0x…", "pqPublicKey": "0x…", "pqSignature": "0x…" } ``` Sizes: ML-DSA-44 key 1,312 B, signature 2,420 B; ML-DSA-65 1,952 / 3,309 B; ML-DSA-87 2,592 / 4,627 B. ## Independent verification on chain The Aere Network chain (2800) runs the NIST post-quantum verifiers as native precompiles. With an [Aere Cloud](https://aere.network/cloud.html) key, the ML-DSA half of any envelope can be judged by the chain itself: ```js import { AereCloud } from '@aere/cloud'; import { verifyOnChain } from '@aere/pq-sign'; const r = await verifyOnChain(envelope, new AereCloud({ apiKey })); // r = { pqValidOnChain: true, precompile: '0x…0ae3', block: 19018096, chainId: 2800 } ``` That answer does not depend on this library's code. One FIPS 204 detail, measured on chain 2800 on 2026-09-17: the ML-DSA precompile implements the internal verify (Algorithm 8), and a standard external signature is made over `M' = 0x00 || len(ctx) || ctx || M`; `verifyOnChain` asks the gateway for the external interface (`interface: "external"`), which builds `M'` for an empty context. Callers of the precompile directly should pass `precompileMessage(envelope)` as the message. And if you want a first-seen time for the signature that cannot be backdated and is covered by the validators' post-quantum certificate, notarize `envelopeHash(envelope)` with `POST /v1/notarize` and read `GET /v1/proof/{hash}`: first appearance, covering anchor, finality, verification steps. ## Tests `npm test` runs every claim with its negative pair (a flipped bit in either half, another message, another key, a foreign envelope). With `AERE_CHEIE` (or `AERE_CHEIE_FISIER`) in the environment it also asks the chain precompile to confirm a fresh post-quantum signature and to refuse a corrupted one. ## License MIT. Aere Network, 2026.