-
Notifications
You must be signed in to change notification settings - Fork 293
RFC-0004: ML-DSA Key Support for libp2p Peer Identities #710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d259de6
f794fcd
035eaac
bcf1dba
a917466
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| - Start Date: 2026-04-11 | ||
| - Related PRs: [js-libp2p/pull/3432](https://github.com/libp2p/js-libp2p/pull/3432) | ||
| - Related Spec: [peer-ids/peer-ids.md](../peer-ids/peer-ids.md) | ||
|
|
||
| # RFC 0004: ML-DSA Key Support for libp2p Peer Identities | ||
|
|
||
| ## Abstract | ||
|
|
||
| This RFC proposes adding support for post-quantum ML-DSA identity keys to | ||
| libp2p. | ||
|
|
||
| It defines a new `KeyType` value for ML-DSA in libp2p key protobufs, a wire | ||
| format for serializing ML-DSA public/private keys, and how peer IDs are derived | ||
| from ML-DSA public keys. | ||
|
|
||
| ## Motivation | ||
|
|
||
| libp2p peer identities are currently based on classical signature schemes (RSA, | ||
| Ed25519, secp256k1, ECDSA). | ||
|
|
||
| To prepare libp2p identity and signature systems for post-quantum migration, we | ||
| need an interoperable encoding and verification story for ML-DSA keys. | ||
|
|
||
| Without a shared specification: | ||
|
|
||
| - Implementations may choose incompatible key encodings. | ||
| - Cross-implementation interoperability is not guaranteed. | ||
| - Future migration to hybrid or fully post-quantum deployments becomes harder. | ||
|
|
||
| ## Design | ||
|
|
||
| ### 1. `KeyType` extension | ||
|
|
||
| The key protobuf enum in the peer-id spec is extended with: | ||
|
|
||
| ```protobuf | ||
| enum KeyType { | ||
| RSA = 0; | ||
| Ed25519 = 1; | ||
| Secp256k1 = 2; | ||
| ECDSA = 3; | ||
| MLDSA = 4; | ||
| } | ||
| ``` | ||
|
|
||
| ### 2. ML-DSA parameter sets | ||
|
|
||
| This RFC supports the three standardized ML-DSA parameter sets: | ||
|
|
||
| - `ML-DSA-44` | ||
| - `ML-DSA-65` | ||
| - `ML-DSA-87` | ||
|
|
||
| Parameter names and sizes are defined by [FIPS 204](#references). | ||
|
|
||
| ### 3. Key serialization in protobuf `Data` | ||
|
|
||
| For `PublicKey` and `PrivateKey` messages where `Type = MLDSA`, the `Data` | ||
| field is: | ||
|
|
||
| ``` | ||
| <variant-prefix><raw-key-bytes> | ||
| ``` | ||
|
|
||
| Where `variant-prefix` is one byte: | ||
|
|
||
| - `0x01` = `ML-DSA-44` | ||
| - `0x02` = `ML-DSA-65` | ||
| - `0x03` = `ML-DSA-87` | ||
|
Comment on lines
+65
to
+69
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For public keys: length alone identifies the variant (1312 / 1952 / 2592 bytes are unique), so the prefix is redundant. iiuc the JS PoC implementation already falls back to length-based detection? For private keys: necessity depends on the format choice:
I think if you go with expanded private key format for this RFC, then this prefix is redundant, all we need is length of the expanded priv key alone to tell which version it is |
||
|
|
||
| Raw key and signature lengths are fixed per ML-DSA variant: | ||
|
|
||
| | Variant | Public key (bytes) | Expanded private key (bytes) | Signature (bytes) | | ||
| | --- | ---: | ---: | ---: | | ||
| | `ML-DSA-44` | 1312 | 2560 | 2420 | | ||
| | `ML-DSA-65` | 1952 | 4032 | 3309 | | ||
| | `ML-DSA-87` | 2592 | 4896 | 4627 | | ||
|
|
||
| Implementations MUST reject malformed key payloads, including unknown | ||
| `variant-prefix`, missing prefix, or length mismatch against the table above. | ||
|
|
||
| ### 4. Signature semantics | ||
|
|
||
| ML-DSA signatures are generated and verified using the standard ML-DSA | ||
| algorithm for the corresponding parameter set. | ||
|
|
||
| - Implementations MUST sign the exact message bytes. | ||
| - Implementations MUST NOT apply an additional pre-hash at the libp2p key API | ||
| boundary. | ||
| - Implementations MUST verify signatures over the exact same message bytes and | ||
| ML-DSA parameter set. | ||
|
|
||
| ### 5. Peer ID derivation | ||
|
|
||
| Peer ID derivation remains unchanged: | ||
|
|
||
| 1. Protobuf-encode `PublicKey` with `Type = MLDSA` and `Data` as specified | ||
| above. | ||
| 2. Compute peer ID multihash according to the existing rule: | ||
| - identity multihash if encoded key is `<= 42` bytes | ||
| - SHA-256 multihash otherwise | ||
|
|
||
| Because all ML-DSA public key encodings are much larger than 42 bytes, ML-DSA | ||
| peer IDs always use SHA-256 multihash. | ||
|
|
||
| ## Backward Compatibility | ||
|
|
||
| - Existing peers and key types are unaffected. | ||
| - Implementations that do not support `KeyType = MLDSA` will reject those keys | ||
| as unsupported. | ||
| - Text and CID peer ID formats are unchanged. | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| - This RFC only defines identity key representation and signature verification | ||
| semantics. | ||
| - It does not by itself provide hybrid authentication or downgrade resistance | ||
| between classical and post-quantum identities. | ||
| - Deployments should continue evaluating algorithm maturity, implementation | ||
| quality, and runtime support in their target environments. | ||
|
|
||
| ## Implementation Status (Non-Normative) | ||
|
|
||
| Current language support for ML-DSA is still evolving: | ||
|
|
||
| - Node.js runtime support is experimental. | ||
| - Browser WebCrypto support is unavailable. | ||
| - Go has an internal implementation for ML-DSA but no public API as of Go 1.26. | ||
|
dozyio marked this conversation as resolved.
|
||
| - Worth noting: `crypto/mlkem` went public in Go 1.24, `crypto/mldsa` is in | ||
| proposal phase [golang/go#77626](https://github.com/golang/go/issues/77626). | ||
| For now, `github.com/cloudflare/circl/sign/mldsa` is available. | ||
|
|
||
| This RFC specifies interoperability behavior independent of implementation | ||
| maturity. | ||
| Production deployments SHOULD evaluate runtime support, performance, and audit | ||
| status before enabling ML-DSA identities. | ||
|
|
||
| ## Open Questions | ||
|
|
||
| The following questions should be resolved before promoting this to a candidate | ||
| recommendation: | ||
|
|
||
| 1. Default variant selection for new key generation (`ML-DSA-44`, `-65`, or | ||
| `-87`). | ||
| 2. Whether any libp2p subsystem should require dual-signature or hybrid | ||
| identity strategies. | ||
| 3. Whether to define mandatory test vectors in the peer-id spec for ML-DSA | ||
| encodings. | ||
| 4. Canonical private key format: should it be FIPS 204 expanded form | ||
| [multiformats/multicodec#399](https://github.com/multiformats/multicodec/pull/399), | ||
| W3C DI Quantum-Safe Cryptosuite)? | ||
|
|
||
| ## References | ||
|
|
||
| - [FIPS 204] NIST, *Module-Lattice-Based Digital Signature Standard* (ML-DSA), | ||
| https://csrc.nist.gov/pubs/fips/204/final | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MLDSA = 4covers all three parameter sets; the variant is only inData.Multicodec assigns separate code points per variant (multiformats/multicodec#392).
With this design, the security level is not visible from the protobuf envelope without parsing the key data.
The choice is defensible, but this is an opportunity to remove indirection for new key types and just use the same number as in multicodec table:
Assuming we use expanded key format, we could do:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't the type be inferred from the length of the data field? This would be similar to the RSA case, no?
Edit: I see that this is being discussed in another comment.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If my understanding of the protobuf wire format is correct I would limit the enum values to a maximum of
7(as long as there's space) because going beyond that would then require two bytes on the wire due to varint encoding.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you mean 7 bits, right? or values less than
0x80/128https://protobuf.dev/programming-guides/encoding/#varints.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh of course, my brain slipped. For some reason I thought the number seven requires seven bits (so that the maximum enum value is 7). The argument still holds because the proposed multicodec values are larger than 127 and thus would still require multiple bytes to encode
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One could argue that it doesn't matter because MLDSA keys are large anyway. One thing to consider could be how they will fit in MTUs (just to rule out that these values don't push the encoded data into an additional packet). At the same time they are not sent in isolation anyway. So nevermind...