Skip to content

Falcon512 - #1731

Merged
PhilippeR26 merged 3 commits into
starknet-io:developfrom
PhilippeR26:falcon512
Oct 16, 2026
Merged

PhilippeR26 merged 3 commits into
starknet-io:developfrom
PhilippeR26:falcon512

Conversation

@PhilippeR26

Copy link
Copy Markdown
Collaborator

Motivation and Resolution

Post-quantum accounts are possible on Starknet today, and starknet.js can now sign for them.

A standard Starknet account signs with ECDSA on the Stark curve. For NIST, ECDSA is "no longer secure" against a large-scale quantum computer (NISTIR 8105, table 1): such a computer could compute the private key of an account from its public key, which is visible on-chain. No such computer exists today, and NIST describes these machines as "years or decades away" (NIST PQC project). But a migration takes years, so NIST already plans it: a draft report (NIST IR 8547, November 2024) proposes to disallow ECDSA in its standards after 2035.

Falcon is one of the three post-quantum signature algorithms selected by NIST in 2022, "for applications that need smaller signatures" (NIST). It rests on lattice problems, which are believed to be hard even for a quantum computer. Thanks to native account abstraction, a Starknet account can check any signature scheme: the S2morrow account contract verifies Falcon-512 signatures on-chain, and its class (0x37c7626b50ba517835c9556b53bce57cb6633704bb702f8c2f13a44cf151d5) is already declared on Sepolia and Mainnet.

This PR adds a signer for this account, in a new entry point starknet/falcon, so the main bundle does not grow:

  • Pure TypeScript. The Falcon core is a copy of @noble/post-quantum 0.7.1 with one addition: signing and verifying a hash point computed outside noble, because the account hashes the transaction with Poseidon, not with SHAKE256. This addition is proposed upstream to noble. Once noble publishes it, starknet/falcon will use the npm package, with no change to its API (the steps are listed in the JSDoc of src/falcon/engine/engine.ts).
  • Security by design. The random seed of the sampler is drawn apart from the public salt (falcon-rs derives it from the salt). Every signature is checked again before it is returned. No public method signs an arbitrary hash. All inputs are strictly checked, and secret values are wiped after use (best effort).
  • Known limits, written down. This is Falcon as submitted to Round 3 of the NIST process, not FN-DSA (FIPS 206, still in development). noble is audited by its author only (version 0.6.1). Signing is not constant-time. Details in src/falcon/SECURITY.md and in the guide.

RPC version (if applicable)

Not applicable: the signer does not depend on the RPC version. Tested on devnet with RPC 0.10.2.

Usage related changes

  • New entry point starknet/falcon:

    • Falcon512Signer, a SignerInterface to use with Account;
    • the falcon512 namespace: generateKeyPair, getPublicKey, getPackedPublicKey, importFalconRsSecretKey, exportFalconRsSecretKey, verify, verifyMessage;
    • the type Falcon512KeyPair.
    import { Account } from 'starknet';
    import { Falcon512Signer, falcon512 } from 'starknet/falcon';
    
    const { secretKey } = falcon512.generateKeyPair();
    const account = new Account({ provider, address, signer: new Falcon512Signer(secretKey) });
    await account.execute(calls, { skipValidate: false });
  • Fees. Checking a Falcon signature on-chain costs much more L2 gas than an ECDSA one. Measured on devnet: a STRK transfer uses about 25 million L2 gas (about 13 times an ECDSA account), and the deployment about 39 million. By default, starknet.js estimates fees without running the validation of the account, and the node then rejects the transaction: always pass { skipValidate: false }.

  • Accounts created with falcon-rs keep working: Falcon512Signer.fromFalconRs(secretKey) imports their key. A warning explains why such keys should move to a new account for real funds.

  • New dependency @noble/ciphers (~2.4.0), imported only by the Falcon code; npm shares it with the copy that ox already installs.

  • Size: dist/index.* is unchanged, byte for byte. An app that imports starknet/falcon grows by about 31 KB gzip; the others do not change. The npm archive grows by 254 KB (compressed).

  • Docs: a new guide chapter, "Post-quantum signature (Falcon-512)", linked from the Signature guide, and API pages for starknet/falcon. The API docs no longer list protected members, such as signRaw of the signers, for all classes: these members are for subclasses, not for the users of an object.

Development related changes

  • src/falcon/, built by tsup as a third entry point (exports, typesVersions), following "Adding an entry point" (the model is starknet/ledger):
    • engine/engine.ts is the only file that imports the copy of noble;
    • engine/noble/ holds the copy, protected from ESLint (header) and Prettier (.prettierignore); PATCH.md records the diff and the SHA-256 of each file;
    • s2morrow/ holds the code specific to the account (Poseidon hash-to-point, packing in felts, key formats).
  • Tests:
    • 12 unit suites, 94 tests, in __tests__/utils/falcon/, with reference data from S2morrow and falcon-rs in __mocks__/falcon/, plus a test of the SHA-256 of the copy and a test of the import boundaries;
    • a devnet test, __tests__/falconAccount.test.ts: declare the account class, deploy an account, send an INVOKE and a DECLARE, and check a SNIP-12 message off-chain and on-chain, over HTTP and WebSocket.
  • Most of the diff (about 132,000 of the 140,000 added lines) is the compiled account class in __mocks__/cairo/falcon512Account/, used by the devnet test. Its README gives the source and provenance.
  • CI: the package check gets a starknet/falcon block. It signs and verifies a message with the built code, with require and import, and type-checks its use with Account, including that signRaw stays protected.
  • Docs build: a TypeDoc instance and a sidebar category for starknet/falcon, and excludeProtected: true in the shared TypeDoc options.
  • AGENTS.md lists the new module.

Checklist:

  • Performed a self-review of the code
  • Documented the changes in code (API docs will be generated automatically)
  • Updated the tests
  • All tests are passing

…or the S2morrow account

New entry point `starknet/falcon` (built to `dist/falcon/`). `Falcon512Signer` signs for the S2morrow account, whose contract verifies Falcon-512 signatures on a Poseidon hash point. The `falcon512` namespace adds key tools (`generateKeyPair`, `getPublicKey`, `getPackedPublicKey`, `importFalconRsSecretKey`, `exportFalconRsSecretKey`) and off-chain checks (`verify`, `verifyMessage`). Only V3 transactions are supported, and a signature is 61 felts. Security notes are in `src/falcon/SECURITY.md`.

The Falcon code is a copy of `@noble/post-quantum` 0.7.1 in `src/falcon/engine/noble/`, with one addition: `falcon512.internal.sign` and `verify` on a hash point computed outside noble, proposed upstream to noble. `PATCH.md` records the diff and the SHA-256 of each copied file, checked by a test. ESLint and Prettier skip the copy. `engine/engine.ts` is the only file that imports it; its JSDoc lists the steps to switch to the npm package once noble publishes this API.

`@noble/ciphers` ~2.4.0 becomes a dependency: only the copy imports it, and npm shares it with the copy that `ox` already installs. The main bundle (`dist/index.*`) does not change.

Tests: unit tests in `__tests__/utils/falcon/`, with reference data from S2morrow and falcon-rs in `__mocks__/falcon/`, and a devnet test that declares the account class, deploys an account, sends an INVOKE and a DECLARE, and checks a SNIP-12 message off-chain and on-chain. The package check of the CI signs and verifies a message with the built code, with require and import, and type-checks its use with `Account`.

Docs: a guide chapter on post-quantum signatures, linked from the Signature guide; a TypeDoc instance and a sidebar category for `starknet/falcon`; a line in AGENTS.md. The API docs no longer show protected members (`excludeProtected`), such as `signRaw` of the signers: they are for subclasses, not for the users of an object.
The examples of `falcon512.generateKeyPair`, `getPublicKey`, `getPackedPublicKey`, `exportFalconRsSecretKey`, and of `Falcon512Signer.getPubKey` and `getPackedPublicKey` built the key from a fixed seed (`new Uint8Array(48).fill(1)`). These examples are published in the API docs and in the type declarations, and a reader who copies one gets a key that anyone can rebuild.

They now call `generateKeyPair()` without a seed, and describe the result by what does not depend on the randomness: the sizes, and the header bytes of the formats (89 for the secret key, 9 for the public key). The examples of internal functions, which are not published, keep their fixed seed and exact values. Only comments change.
Comment thread __tests__/utils/falcon/signature.test.ts Fixed
CodeQL (js/implicit-operand-conversion) flagged `MSG + 1n` in `signature.test.ts`, saying the expression could be converted from undefined to a number. This is a false positive: `MSG` is a bigint constant of the module, so the sum is a bigint and nothing is converted. The test now uses a named constant, `OTHER_MSG` (the value of `MSG + 1`), which also says plainly that the hash is a different one.
@PhilippeR26
PhilippeR26 merged commit 06a4cbf into starknet-io:develop Oct 16, 2026
9 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 11.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants