-
Generate an in-memory signer with
generateSigner()— keys never touch disk unless you export them, andsigner.destroy()zeroizes package-owned buffers when the lifecycle ends. -
Enroll the signer's full public key with a classical full-access
AddKey— enrollment always starts from an existing classical signer. -
Sign and submit through
sendTx({ signerId, signer, … })in@fastnear/api; the signer is bridged in via a structuralTransactionSigner, andqueryProtocolVersion()gates on v85.
Sign NEAR transactions with post-quantum ML-DSA-65 keys.
A sufficiently large quantum computer would break the elliptic-curve signatures
(Ed25519, secp256k1) that protect blockchain accounts today. NEAR's answer is
ML-DSA-65 — the NIST FIPS 204 lattice signature scheme also known as
Dilithium — added as an account access-key and transaction-signing type starting at
protocol version 85. FastNear ships it opt-in in the standalone
@fastnear/ml-dsa-65 package (Node.js 20.19+ or a modern browser), so apps that
only use classical keys never download the post-quantum implementation.
-
Full form
ml-dsa-65:<base58>— use it for AddKey, direct access-key lookup, signing, and DeleteKey. -
Compact handle
ml-dsa-65-hash:<base58>— what NEAR stores on-chain and what access-key list responses expose: the SHA3-256 digest of a domain tag (near:ml-dsa-65-pubkey-hash:v1) followed by the raw public key. -
Reconcile the two with
publicKeyToHandle()before comparing against list responses.
- Seed: 32 bytes. Public key: 1,952 bytes. Expanded secret key: 4,032 bytes. Signature: 3,309 bytes.
- NEAR charges 100 Ggas for each outer or delegated ML-DSA-65 signature verification, so transactions are substantially larger and costlier than classical equivalents.
- Scope: account access keys and transaction signatures only — validator and staking keys remain Ed25519.
Generate a signer, then send with it.
The two core flows from the catalog (ESM). The full set — generate, explicit-send,
enroll-delete, reconcile — lives in the mlDsa65 key of
recipes.json.
Retain only public recovery metadata, and always destroy the signer when its lifecycle ends.
import { generateSigner } from "@fastnear/ml-dsa-65";
const signer = generateSigner();
try {
// Public values are safe to retain for enrollment and cleanup.
const recovery = {
network: "testnet",
accountId: "device.testnet",
publicKey: signer.publicKey,
publicKeyHandle: signer.publicKeyHandle,
};
console.log(recovery);
// Never log or persist signer.exportSeed() or signer.exportSecretKey().
} finally {
signer.destroy();
}
The explicit-signer branch of sendTx, after the full public key has been
enrolled on the account with a classical full-access AddKey.
import {
actions,
queryProtocolVersion,
sendTx,
} from "@fastnear/api";
export async function sendOneYoctoWithMlDsa65({ accountId, signer }) {
const protocolVersion = await queryProtocolVersion({ network: "testnet" });
if (protocolVersion < 85) {
throw new Error(`testnet protocol ${protocolVersion} does not support ML-DSA-65`);
}
return sendTx({
signerId: accountId,
signer,
receiverId: accountId,
actions: [actions.transfer("1")],
waitUntil: "FINAL",
network: "testnet",
});
}
Treat key material and activation signals strictly.
These rules come from the machine-readable catalog — agents and humans should apply the same ones.
-
Check the selected RPC's active
protocol_versionand require 85 or later before adding or using an ML-DSA-65 key; do not use node software versions orlatest_protocol_versionas activation signals. - Never print or persist generated seeds or expanded secret keys. Keep a temporary recovery record public-only: network, account ID, full public key, and hash handle.
- After an AddKey attempt, do not trust a single absence read: submit a finalized classical DeleteKey nonce barrier, confirm absence at finality, and only then remove the record.
-
The reference backend (
@noble/post-quantum) describes itself as self-audited and does not claim constant-time side-channel protection — prefer a native, WASM, HSM, or hardwareTransactionSignerwhen the threat model requires one. -
destroy()provides best-effort zeroization of package-owned JavaScript buffers, not a hard memory-erasure guarantee. - Constrained QuickJS and MCU runtimes are not a v1 compatibility target.
AI agents: read the mlDsa65 key in
recipes.json
— it carries all four quickstarts plus the sizes, key forms, and safety rules on this page.
The scheme is defined in
NIST FIPS 204,
and the package lives on
npm.