Skip to content

feat: post-quantum (ML-DSA-65) key generation#605

Merged
r-near merged 5 commits into
mainfrom
pq/post-quantum-keys
Jul 2, 2026
Merged

feat: post-quantum (ML-DSA-65) key generation#605
r-near merged 5 commits into
mainfrom
pq/post-quantum-keys

Conversation

@r-near

@r-near r-near commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Adds full CLI support for the post-quantum ML-DSA-65 (FIPS 204) signature scheme that NEAR protocol 2.13 introduces as a third access-key type, alongside the classic Ed25519.

1. A new offline near generate-keypair command — create a key pair for either scheme and print it or save it to a JSON file (no account, no network):

near generate-keypair --signature-scheme ml-dsa-65 print-to-terminal
near generate-keypair --signature-scheme ml-dsa-65 save-to-file ./pq-key.json

2. A --signature-scheme option on the on-chain autogenerate flowsaccount add-key, account create-account fund-myself, and account create-account sponsor-by-faucet-service can now autogenerate and submit an ML-DSA-65 access key:

near account add-key alice.testnet grant-full-access \
  autogenerate-new-keypair --signature-scheme ml-dsa-65 save-to-keychain network-config testnet ...

The flag defaults to ed25519 when omitted (and only prompts in an interactive terminal), so every existing non-interactive invocation behaves exactly as before. ML-DSA-65 keys are also emitted in the standard ml-dsa-65:<base58> string form, so an externally generated key can be added via the existing account add-key … use-manually-provided-public-key flow too.

Why

NEAR protocol 2.13 introduces post-quantum ML-DSA-65 access keys (see near/nearcore#15731). This lands the CLI side: generating PQ key pairs. Related ecosystem work: near/docs#3061, near/near-sdk-rs#1557.

2.13 API adaptations

Bumping to 2.13 required adapting to a few near-primitives / near-crypto changes (unrelated to keygen, but needed to compile):

  • New Action::DelegateV2 / ActionView::DelegateV2 (v2 meta-transactions) — handled in the transaction display paths.
  • New ActionErrorKind::DelegateActionInvalidNonceIndex and InvalidAccessKeyError::DelegateActionRequiresNonGasKey / DelegateActionRequiresGasKey (gas keys) — mapped to CLI error messages.
  • QueryRequest::ViewState gained after_key / limit fields.
  • The access-key list now returns PublicKeyHandle; the delete-key picker uses full_pubkey() and skips ML-DSA-65 entries (which are stored on-chain only as a hash, so the full key isn't recoverable for a DeleteKey action).
  • KeyType no longer derives PartialEq — comparisons switched to pattern matching.

Scope / limitations

  • ML-DSA-65 keys are generated randomly (no seed-phrase / HD recovery — there is no NEAR standard for that yet) and have no implicit-account-id form (deferred to a later protocol milestone), so create-account named-account flows support them but the ed25519-only implicit-account paths are unchanged.
  • On-chain use of PQ keys is enabled by the PostQuantumSignatures protocol feature, stabilized at protocol version 85 (the 2.13 stable version) — so the AddKey transactions these flows build become valid on a network once it upgrades to protocol 85 with the 2.13 rollout (it is not nightly-gated).

Testing

  • New unit tests generate an ML-DSA-65 key pair, parse it back through near-crypto, and verify a signature round-trips.
  • Full suite green locally: cargo test --all-features, cargo clippy --all-features --all-targets -- -D warnings, cargo fmt --check, and the --no-default-features (+ledger) checks.

Add a top-level `near generate-keypair` command that creates a key pair
offline for either Ed25519 (classic) or the post-quantum ML-DSA-65 scheme
(FIPS 204), printing it to the terminal or saving it to a JSON file.

Post-quantum access keys require nearcore 2.13 (nearcore#15731), which is not
yet published to crates.io, so the near-* crates are temporarily git-pinned to
the nearcore 2.13-release branch and the matching jsonrpc/socialdb draft
branches so they resolve to a single version. This also adapts near-cli-rs to
the 2.13 API changes: the new DelegateV2 action and its gas-key/delegate
access-key error variants, the ViewState after_key/limit query fields, the
PublicKeyHandle returned by the access-key list, and the removal of
KeyType: PartialEq.
r-near added 2 commits June 21, 2026 17:20
near-crypto's post-quantum support pulls in aws-lc-sys, which needs NASM to
assemble its x86_64 code; the x86_64-pc-windows-msvc release build has no NASM
on the runner. Set AWS_LC_SYS_PREBUILT_NASM=1 to use the crate's pregenerated
NASM objects instead. (release.yml is allow-dirty=["ci"], so the manual edit
is fine.)
Add a --signature-scheme (ed25519 | ml-dsa-65) option to the autogenerate-new-keypair
paths of `account add-key`, `account create-account fund-myself`, and
`account create-account sponsor-by-faucet-service`, so a post-quantum access key can be
generated and put on-chain (valid once a network upgrades to protocol 85 / 2.13).

Factor the shared SignatureScheme enum and a GeneratedKeyPair helper into common.rs and
reuse them from the standalone `generate-keypair` command. The flag defaults to ed25519
when omitted (prompting only in an interactive terminal), so existing non-interactive
invocations are unchanged. KeyPairProperties and the seed-phrase flows are untouched.
@r-near r-near marked this pull request as ready for review July 2, 2026 01:03
@r-near r-near requested a review from a team as a code owner July 2, 2026 01:03
Copilot AI review requested due to automatic review settings July 2, 2026 01:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CLI support for NEAR protocol 2.13’s post-quantum ML-DSA-65 key type by introducing an offline keypair generator and extending existing autogeneration flows to optionally emit ML-DSA-65 keys, alongside the existing Ed25519 behavior. Also includes required nearcore 2.13 API adaptations (new delegate action variants, error kinds, and ViewState query fields) and updates release packaging to accommodate new crypto dependencies.

Changes:

  • Introduce a shared SignatureScheme + GeneratedKeyPair abstraction to generate/store/display Ed25519 vs ML-DSA-65 keys and reuse it across autogeneration flows.
  • Add a new offline near generate-keypair top-level command with output modes (print vs JSON file) plus unit tests for ML-DSA-65 key parsing/signing.
  • Update multiple code paths to compile against nearcore 2.13 APIs (DelegateV2, new errors, ViewState fields, access-key list PublicKeyHandle) and adjust release workflow for aws-lc/Windows build needs.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/transaction_signature_options/sign_with_mpc/mod.rs Reject ML-DSA-65 for MPC signing and document/guard MPC domain-id mapping.
src/common.rs Add SignatureScheme, interactive resolver, and GeneratedKeyPair (Ed25519 + ML-DSA-65) plus 2.13 API adaptations.
src/commands/transaction/reconstruct_transaction/mod.rs Block reconstructing DelegateV2 transactions with a clear error.
src/commands/mod.rs Register new top-level generate-keypair command module/variant.
src/commands/generate_keypair/mod.rs Implement offline keypair generation command, output modes, and unit tests.
src/commands/contract/view_storage/output_format/mod.rs Populate new ViewState fields (after_key, limit) for 2.13 compatibility.
src/commands/account/delete_key/public_keys_to_delete.rs Adapt to PublicKeyHandle and skip ML-DSA-65 entries that can’t provide full pubkeys for DeleteKey.
src/commands/account/create_account/sponsor_by_faucet_service/add_key/autogenerate_new_keypair/mod.rs Add --signature-scheme to autogenerate flow and use GeneratedKeyPair.
src/commands/account/create_account/fund_myself_create_account/add_key/autogenerate_new_keypair/mod.rs Add --signature-scheme to autogenerate flow and use GeneratedKeyPair.
src/commands/account/add_key/autogenerate_new_keypair/mod.rs Add --signature-scheme to autogenerate flow and switch to GeneratedKeyPair.
src/commands/account/add_key/autogenerate_new_keypair/save_keypair_to_legacy_keychain/mod.rs Save ML-DSA-65-compatible credentials JSON via GeneratedKeyPair.
src/commands/account/add_key/autogenerate_new_keypair/save_keypair_to_keychain/mod.rs Save ML-DSA-65-compatible credentials JSON via GeneratedKeyPair.
src/commands/account/add_key/autogenerate_new_keypair/print_keypair_to_terminal/mod.rs Print keypair info via GeneratedKeyPair (supports ML-DSA-65).
Cargo.toml Bump nearcore-related deps to 0.37.0-rc.2 + git-pin draft client libs.
Cargo.lock Lockfile updates reflecting new dependency graph (aws-lc, etc.).
.github/workflows/release.yml Set AWS_LC_SYS_PREBUILT_NASM to avoid NASM requirement on Windows MSVC builds.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/transaction_signature_options/sign_with_mpc/mod.rs
Comment thread Cargo.toml

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e170e55345

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@r-near r-near merged commit 8fbe1c1 into main Jul 2, 2026
14 checks passed
@r-near r-near deleted the pq/post-quantum-keys branch July 2, 2026 01:39
@github-project-automation github-project-automation Bot moved this from NEW❗ to Shipped 🚀 in DevTools Jul 2, 2026
@r-near r-near changed the title feat: post-quantum (ML-DSA-65) key generation (draft) feat: post-quantum (ML-DSA-65) key generation Jul 2, 2026
r-near added a commit that referenced this pull request Jul 2, 2026
## Cause

nearcore 2.13 (protocol v85) introduces gas keys: an access-key
permission that
carries its own NEAR balance and `N` parallel nonces. near-cli has no
way to
create one, fund it, or inspect its nonces.

## Fix

Stacked on the nearcore-2.13 branch (#605):

- **Create** — `account add-key` gains two permission variants,
`grant-gas-key-full-access` and `grant-gas-key-function-call`, that
build an
  `AddKey` action with `AccessKeyPermission::GasKeyFullAccess` /
`GasKeyFunctionCall`. Gas keys are created **empty** (the protocol
rejects an
`AddKey` with a non-zero gas-key balance), so there is no balance
argument;
fund the key afterwards with `fund-gas-key`. The function-call variant
takes a
contract id and method names but **no allowance** (the protocol rejects
an
  allowance on a gas-key function-call key). The parallel-nonce count is
collected as `u64` (interactive-clap has no `ToCli for u16`) and
narrowed to
  `NonceIndex` with a `1..=MAX_NONCES_FOR_GAS_KEY` bound check.
- **Fund** — `account fund-gas-key` transfers NEAR from the account into
a gas
  key's balance.
- **Withdraw** — `account withdraw-from-gas-key` moves NEAR from a gas
key's
  balance back to the account.
- **View** — `account view-gas-key-nonces <account> <public-key>` issues
the
`view_gas_key_nonces` query (via the generic
`QueryRequest::ViewGasKeyNonces`
  path) and prints the per-index nonces from `GasKeyNoncesView`.

Signing a transaction with a specific `GasKeyNonce` (DelegateV2 /
parallel-nonce
signing) remains a follow-up increment.
r-near added a commit that referenced this pull request Jul 2, 2026
## 🤖 New release

* `near-cli-rs`: 0.27.0 -> 0.28.0 (⚠ API breaking changes)

### ⚠ `near-cli-rs` breaking changes

```text
--- failure constructible_struct_adds_field: externally-constructible struct adds field ---

Description:
A pub struct constructible with a struct literal has a new pub field. Existing struct literals must be updated to include the new field.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field CliSignPrivateKey.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_private_key/mod.rs:5
  field InteractiveClapContextScopeForGenerateKeypair.signature_scheme in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/create_account/sponsor_by_faucet_service/add_key/autogenerate_new_keypair/mod.rs:3
  field CliSignLedger.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_ledger/mod.rs:18
  field CliSignLegacyKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_legacy_keychain/mod.rs:12
  field CliSignKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_keychain/mod.rs:9
  field SignLedgerContext.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_ledger/mod.rs:51
  field InteractiveClapContextScopeForSignSeedPhrase.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_seed_phrase/mod.rs:7
  field CliSignAccessKeyFile.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_access_key_file/mod.rs:5
  field CliGenerateKeypair.signature_scheme in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/create_account/sponsor_by_faucet_service/add_key/autogenerate_new_keypair/mod.rs:3
  field InteractiveClapContextScopeForSignPrivateKey.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_private_key/mod.rs:5
  field InteractiveClapContextScopeForSignLedger.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_ledger/mod.rs:18
  field InteractiveClapContextScopeForSignLegacyKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_legacy_keychain/mod.rs:12
  field InteractiveClapContextScopeForSignKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_keychain/mod.rs:9
  field InteractiveClapContextScopeForSignAccessKeyFile.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_access_key_file/mod.rs:5
  field CliSignSeedPhrase.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_seed_phrase/mod.rs:7

--- failure enum_no_repr_variant_discriminant_changed: enum variant had its discriminant change value ---

Description:
The enum's variant had its discriminant value change. This breaks downstream code that used its value via a numeric cast like `as isize`.
        ref: https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/enum_no_repr_variant_discriminant_changed.ron

Failed in:
  variant TopLevelCommandDiscriminants::Extensions 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:60
  variant TopLevelCommandDiscriminants::Extensions 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:60
  variant AccountActionsDiscriminants::GetPublicKey 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:69
  variant AccountActionsDiscriminants::AddKey 8 -> 9 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:74
  variant AccountActionsDiscriminants::DeleteKeys 9 -> 12 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:89
  variant AccountActionsDiscriminants::ManageStorageDeposit 10 -> 13 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:94
  variant AccountActionsDiscriminants::GetPublicKey 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:69
  variant AccountActionsDiscriminants::AddKey 8 -> 9 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:74
  variant AccountActionsDiscriminants::DeleteKeys 9 -> 12 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:89
  variant AccountActionsDiscriminants::ManageStorageDeposit 10 -> 13 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:94

--- failure enum_variant_added: enum variant added on exhaustive enum ---

Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/enum_variant_added.ron

Failed in:
  variant AccountActionsDiscriminants:ViewGasKeyNonces in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:64
  variant AccountActionsDiscriminants:FundGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:79
  variant AccountActionsDiscriminants:WithdrawFromGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:84
  variant AccountActionsDiscriminants:ViewGasKeyNonces in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:64
  variant AccountActionsDiscriminants:FundGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:79
  variant AccountActionsDiscriminants:WithdrawFromGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:84
  variant CliTopLevelCommand:GenerateKeypair in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:16
  variant CliAccountActions:ViewGasKeyNonces in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:27
  variant CliAccountActions:FundGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:27
  variant CliAccountActions:WithdrawFromGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:27
  variant TopLevelCommandDiscriminants:GenerateKeypair in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:56
  variant TopLevelCommandDiscriminants:GenerateKeypair in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:56

--- failure function_missing: pub fn removed or renamed ---

Description:
A publicly-visible function cannot be imported by its prior path. A `pub use` may have been removed, or the function itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/function_missing.ron

Failed in:
  function near_cli_rs::transaction_signature_options::send::sleep_after_error, previously in file /tmp/.tmppWZUdR/near-cli-rs/src/transaction_signature_options/send/mod.rs:193

--- failure trait_method_added: pub trait method added ---

Description:
A non-sealed public trait added a new method without a default implementation, which breaks downstream implementations of the trait
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-item-no-default
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/trait_method_added.ron

Failed in:
  trait method near_cli_rs::common::RpcQueryResponseExt::gas_key_nonces_view in file /tmp/.tmpat9wFR/near-cli-rs/src/common.rs:3499
```

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

##
[0.28.0](v0.27.0...v0.28.0)
- 2026-07-02

### Added

- sign transactions with a gas key (nonce_index)
([#608](#608))
- gas-key support (nearcore 2.13)
([#607](#607))
- post-quantum (ML-DSA-65) key generation (draft)
([#605](#605))
- Validate beneficiary account before deleting account
([#596](#596))

### Fixed

- Fixed output information for transaction status "Started"
([#594](#594))

### Other

- Fixed NPM Trusted Publishing
([#603](#603))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

---------

Co-authored-by: r-near <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Shipped 🚀

Development

Successfully merging this pull request may close these issues.

3 participants