Skip to content

Add SLH-DSA-SHA2-128s (FIPS 205) to aptos-crypto#18293

Merged
alinush merged 15 commits intomainfrom
alin/slh-dsa-sha2-128s
Dec 10, 2025
Merged

Add SLH-DSA-SHA2-128s (FIPS 205) to aptos-crypto#18293
alinush merged 15 commits intomainfrom
alin/slh-dsa-sha2-128s

Conversation

@alinush
Copy link
Contributor

@alinush alinush commented Dec 9, 2025

Description

This PR introduces aptos-crypto types for the SLH-DSA signature scheme1 using the SHA2-128s parameterization.

This lays a path forward towards post-quantum security for Aptos users.

Follow-up PRs

Benchmarks

Important

These are single-threaded benchmarks run on my Macbook Pro M1 Max.
On x86_64, the #'s may look different.

The scheme is sufficiently-fast to be used on Aptos:

slh_dsa/sha2-128s/sig_deserialize  time:   [1.2508 µs 1.3175 µs 1.3891 µs]
                        thrpt:  [719.88 Kelem/s 759.04 Kelem/s 799.51 Kelem/s]

slh_dsa/sha2-128s/pk_deserialize    time:   [147.90 ns 192.91 ns 244.32 ns]
                        thrpt:  [4.0930 Melem/s 5.1838 Melem/s 6.7612 Melem/s]

slh_dsa/sha2-128s/sign_32_bytes   time:   [170.02 ms 170.04 ms 170.07 ms]
                        thrpt:  [5.8799  elem/s 5.8809  elem/s 5.8818  elem/s]

slh_dsa/sha2-128s/verify_32_bytes time:   [181.56 µs 184.43 µs 187.56 µs]
                        thrpt:  [5.3318 Kelem/s 5.4222 Kelem/s 5.5080 Kelem/s]

Signature verification times are only 3-4x slower than Ed25519:

ed25519/sig_verify_struct
                        time:   [51.662 µs 51.779 µs 51.934 µs]
                        thrpt:  [19.255 Kelem/s 19.313 Kelem/s 19.357 Kelem/s]

Signature sizes are 122.75x larger (7,856 bytes, instead of 64 bytes.)

Nonetheless: This loss in performance is very acceptable given that SLH-DSA is the most conservative post-quantum signature scheme in terms of cryptographic assumptions.

We can always add cheaper schemes that make extra cryptographic assumptions: e.g., ML-DSA in FIPS-2042 is the next natural choice; it only assumes MLWE.

How Has This Been Tested?

  • signing & verification work (unit test & basic viability doctest in mod.rs)
  • SK (de)serialization roundtrip
  • SKs of wrong length don't deserialize
  • PK (de)serialization roundtrip
  • signature (de)serialization roundtrip
  • key generation returns different keys
  • SK cloning works

Key Areas to Review

This is just a wrapper around the slh-dsa crate in RustCrypto/signatures.

Not a lot of meaningful stuff to review, except:

  1. RNG logic for key generation
  2. Is there a better choice than RustCrypto/signatures for SLH-DSA?

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Performance improvement
  • Refactoring
  • Dependency update
  • Documentation update
  • Tests

Which Components or Systems Does This Change Impact?

  • Validator Node
  • Full Node (API, Indexer, etc.)
  • Move/Aptos Virtual Machine
  • Aptos Framework
  • Aptos CLI/SDK
  • Developer Infrastructure
  • Move Compiler
  • Other (specify): for now, none. It's just a library.

Checklist

  • I have read and followed the CONTRIBUTING doc
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I identified and added all stakeholders and component owners affected by this change as reviewers
  • I tested both happy and unhappy path of the functionality
  • I have made corresponding changes to the documentation

Note

Adds SLH-DSA SHA2-128s to aptos-crypto, providing key/signature types with trait impls, serialization, tests, and benchmarks, plus dependency updates.

  • Crypto:
    • New module crates/aptos-crypto/src/slh_dsa_sha2_128s: adds PrivateKey, PublicKey, Signature, constants, and impls for SigningKey, VerifyingKey, Signature, Uniform, ValidCryptoMaterial.
    • Serialization & tests: to/from bytes for keys/signatures; unit tests for round-trip, length checks, RNG generation, and deterministic signing.
    • Benchmarks: new bench benches/slh_dsa_sha2_128s.rs measuring sign/verify and (de)serialization; wired via Cargo bench target.
    • Integration: exports module in lib.rs and registers types in traits::private::Sealed.
  • Build/Dependencies:
    • Adds slh-dsa = "0.2.0-rc.1"; updates related crypto prereleases (digest, hmac, sha2, sha3, keccak, pkcs8, spki, signature, etc.) and bumps zeroize.
  • Misc:
    • Fixes BLS12-381 unvalidated public key JSON deserialization cast in bls12381_validatable.rs.

Written by Cursor Bugbot for commit 919f6ed. This will update automatically on new commits. Configure here.

Footnotes

  1. https://csrc.nist.gov/pubs/fips/205/final

  2. https://csrc.nist.gov/pubs/fips/204/final

@alinush alinush requested a review from JoshLind December 9, 2025 01:12
@alinush alinush marked this pull request as ready for review December 9, 2025 01:27
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@alinush alinush marked this pull request as draft December 9, 2025 04:52
@alinush
Copy link
Contributor Author

alinush commented Dec 9, 2025

Note to self: Change SK (de)serialization to treat our SLH-DSA PrivateKey that wraps a SigningKey in RustCrypto/signatures as 48-bytes: the SK seed, PRF seed and the PK seed.

Importantly, re-derive the PK's pk_root manually (and slowly) during deserialization. (Add bench to see how slow this is.)

This will ensure that we can recover an SLH-DSA PrivateKey from a mnemonic by reconstructing the SK seed, PRF seed, PK seed and then deriving the PK root from them.

@aptos-labs aptos-labs deleted a comment from cursor bot Dec 9, 2025
@alinush alinush changed the title Add support for SLH-DSA-SHA2-128s as per FIPS 205 Add SLH-DSA-SHA2-128s (FIPS 205) to aptos-crypto Dec 9, 2025
@alinush alinush marked this pull request as ready for review December 9, 2025 18:27
Copy link
Contributor

@JoshLind JoshLind left a comment

Choose a reason for hiding this comment

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

Looks reasonable to me (a non-cryptographer 😆)

prettydiff = "0.6.2"
primitive-types = { version = "0.12.2" }
signature = "2.1.0"
slh-dsa = "0.2.0-rc.1"
Copy link
Contributor

Choose a reason for hiding this comment

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

Always interesting to see -rc in a rust crate version 😄

@alinush alinush enabled auto-merge (rebase) December 9, 2025 23:14
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

✅ Forge suite compat success on c893f1d0c7d695a43604b336865f3f9f0c62ac9d ==> 919f6ed53b749b9c4cc46a7a61291141445e8e29

Compatibility test results for c893f1d0c7d695a43604b336865f3f9f0c62ac9d ==> 919f6ed53b749b9c4cc46a7a61291141445e8e29 (PR)
1. Check liveness of validators at old version: c893f1d0c7d695a43604b336865f3f9f0c62ac9d
compatibility::simple-validator-upgrade::liveness-check : committed: 13532.50 txn/s, latency: 2577.28 ms, (p50: 2700 ms, p70: 3000, p90: 3000 ms, p99: 3300 ms), latency samples: 442500
2. Upgrading first Validator to new version: 919f6ed53b749b9c4cc46a7a61291141445e8e29
compatibility::simple-validator-upgrade::single-validator-upgrade : committed: 6058.52 txn/s, latency: 5569.87 ms, (p50: 6200 ms, p70: 6300, p90: 6300 ms, p99: 6400 ms), latency samples: 209480
3. Upgrading rest of first batch to new version: 919f6ed53b749b9c4cc46a7a61291141445e8e29
compatibility::simple-validator-upgrade::half-validator-upgrade : committed: 6154.04 txn/s, latency: 5516.72 ms, (p50: 6100 ms, p70: 6200, p90: 6300 ms, p99: 6400 ms), latency samples: 213660
4. upgrading second batch to new version: 919f6ed53b749b9c4cc46a7a61291141445e8e29
compatibility::simple-validator-upgrade::rest-validator-upgrade : committed: 7287.32 txn/s, latency: 4283.49 ms, (p50: 3400 ms, p70: 3600, p90: 11000 ms, p99: 12800 ms), latency samples: 239080
5. check swarm health
Compatibility test for c893f1d0c7d695a43604b336865f3f9f0c62ac9d ==> 919f6ed53b749b9c4cc46a7a61291141445e8e29 passed
Test Ok

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

✅ Forge suite framework_upgrade success on c893f1d0c7d695a43604b336865f3f9f0c62ac9d ==> 919f6ed53b749b9c4cc46a7a61291141445e8e29

Compatibility test results for c893f1d0c7d695a43604b336865f3f9f0c62ac9d ==> 919f6ed53b749b9c4cc46a7a61291141445e8e29 (PR)
Upgrade the nodes to version: 919f6ed53b749b9c4cc46a7a61291141445e8e29
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 2207.33 txn/s, submitted: 2214.21 txn/s, failed submission: 6.88 txn/s, expired: 6.88 txn/s, latency: 1332.49 ms, (p50: 1200 ms, p70: 1500, p90: 1800 ms, p99: 2700 ms), latency samples: 198782
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 2089.39 txn/s, submitted: 2094.70 txn/s, failed submission: 5.31 txn/s, expired: 5.31 txn/s, latency: 1422.60 ms, (p50: 1500 ms, p70: 1500, p90: 1800 ms, p99: 2700 ms), latency samples: 188820
5. check swarm health
Compatibility test for c893f1d0c7d695a43604b336865f3f9f0c62ac9d ==> 919f6ed53b749b9c4cc46a7a61291141445e8e29 passed
Upgrade the remaining nodes to version: 919f6ed53b749b9c4cc46a7a61291141445e8e29
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 1960.24 txn/s, submitted: 1968.77 txn/s, failed submission: 8.53 txn/s, expired: 8.53 txn/s, latency: 1870.90 ms, (p50: 1200 ms, p70: 1500, p90: 1800 ms, p99: 11500 ms), latency samples: 165462
Test Ok

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

✅ Forge suite realistic_env_max_load success on 919f6ed53b749b9c4cc46a7a61291141445e8e29

two traffics test: inner traffic : committed: 13643.01 txn/s, submitted: 13643.17 txn/s, expired: 0.16 txn/s, latency: 2758.57 ms, (p50: 2700 ms, p70: 2900, p90: 3000 ms, p99: 3500 ms), latency samples: 5081880
two traffics test : committed: 100.02 txn/s, latency: 725.94 ms, (p50: 700 ms, p70: 800, p90: 800 ms, p99: 1400 ms), latency samples: 1580
Latency breakdown for phase 0: ["MempoolToBlockCreation: max: 2.292, avg: 2.203", "ConsensusProposalToOrdered: max: 0.167, avg: 0.165", "ConsensusOrderedToCommit: max: 0.040, avg: 0.038", "ConsensusProposalToCommit: max: 0.208, avg: 0.203"]
Max non-epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.47s no progress at version 5833346 (avg 0.07s) [limit 15].
Max epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.31s no progress at version 2400347 (avg 0.31s) [limit 16].
Test Ok

@alinush alinush merged commit f6ecc2e into main Dec 10, 2025
64 of 65 checks passed
@alinush alinush deleted the alin/slh-dsa-sha2-128s branch December 10, 2025 03:28

/// The length in bytes of the SLH-DSA SHA2-128s PrivateKey: the SK seed, PRF seed and PK seed.
/// The PK seed is actually public, but must be stored in the SK, since it is picked randomly
/// and independently of all other SK material.
Copy link
Collaborator

Choose a reason for hiding this comment

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

... and one wants the SK to be sufficient to regenerate the PK?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes!


/// Benchmarks the time to deserialize an SLH-DSA signature from a sequence of bytes.
fn sig_deserialize<M: Measurement>(g: &mut BenchmarkGroup<M>) {
let mut csprng = thread_rng();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe consider using a deterministic RNG for benchmarks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah... I chose to test more cases over reproducibility here 🤷

It'd be nice to do both somehow: e.g., by automatically printing the seed during each test case so that it shows if it fails

}
}

/// return an all-zero signature (for test only)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like "all-ones"

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

Labels

None yet