RFC-0004: ML-DSA Key Support for libp2p Peer Identities#710
RFC-0004: ML-DSA Key Support for libp2p Peer Identities#710dozyio wants to merge 5 commits intolibp2p:masterfrom
Conversation
|
Hi @dozyio , great to see this as a formal RFC. I have been working on the handshake side of the PQC picture (XXhfs + X-Wing KEM, ChainSafe/js-libp2p-noise#665)
The ML-DSA-65 identity cost (~6,600 B across both sides: 1,952 B public key + 3,309 B signature + overhead, per side) is 2.7x larger than the KEM overhead (~2,352 B). Worth factoring in when discussing whether to require hybrid signatures or allow a phased migration. One argument for deferring hybrid signatures: the threat timelines are different. For the KEM, XXhfs hybrid makes sense today because of store-now-decrypt-later... someone recording traffic now can decrypt it later. For identity signatures, the threat only materialises once quantum computers actually exist, so a clean migration from Ed25519 to ML-DSA could be reasonable rather than carrying both indefinitely. That said, downgrade resistance is a real concern your security considerations section mentions |
| Where `variant-prefix` is one byte: | ||
|
|
||
| - `0x01` = `ML-DSA-44` | ||
| - `0x02` = `ML-DSA-65` | ||
| - `0x03` = `ML-DSA-87` |
There was a problem hiding this comment.
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:
- Seed (32 bytes for all variants): prefix is required to identify the variant.
- Expanded (unique sizes per variant): prefix is redundant, consistent with the multicodec approach.
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
| - Input to signing is the exact message bytes. | ||
| - No additional pre-hash step is applied by libp2p at the key API boundary. | ||
| - Verification is performed over the same message bytes with the same parameter | ||
| set. |
There was a problem hiding this comment.
FIPS 204 §5.2 defines ML-DSA.Sign(sk, M, ctx) where ctx is a caller-supplied context string (0–255 bytes). The spec says nothing about what value libp2p uses for ctx. Two compliant implementations using different values produce incompatible signatures.
Existing libp2p signing contexts use message-level string prefixes prepended
before calling Sign(), not algorithm-level context parameters:
- Noise:
"noise-libp2p-static-key:" || static_pub_key(handshake.go) - TLS:
"libp2p-tls-handshake:" || cert_pub_key(crypto.go) - Record envelope:
varint(len(domain)) || domain || varint(len(type)) || type || varint(len(payload)) || payload(envelope.go)
So.. iiuc to stay consistent with this pattern, the RFC should explicitly state what ctx is? ctx = "" ? ctx = "libp2p-identity"?
| - Input to signing is the exact message bytes. | |
| - No additional pre-hash step is applied by libp2p at the key API boundary. | |
| - Verification is performed over the same message bytes with the same parameter | |
| set. | |
| - Input to signing is the exact message bytes. | |
| - Implementations MUST pass an empty context string (`ctx = "??TODO DECIDE??"`) to `ML-DSA.Sign`. Domain separation is the responsibility of the caller via message prefixes, as with all other libp2p key types. | |
| - No additional pre-hash step is applied by libp2p at the key API boundary. | |
| - Verification is performed over the same message bytes with the same parameter | |
| set. |
Note I'm not a cryptographer, but it may be safer to do belt-and-supenders and also require ctx = "libp2p-identity".
There was a problem hiding this comment.
The context string should be a unique based on what the signature is for. Similar to the context in Ed25519ctx. Applications should be be able to pass this in.
| Ed25519 = 1; | ||
| Secp256k1 = 2; | ||
| ECDSA = 3; | ||
| MLDSA = 4; |
There was a problem hiding this comment.
MLDSA = 4 covers all three parameter sets; the variant is only in Data.
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:
| MLDSA = 4; | |
| MLDSA44 = 0x1317; | |
| MLDSA65 = 0x1318; | |
| MLDSA87 = 0x1319; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
I think you mean 7 bits, right? or values less than 0x80/128 https://protobuf.dev/programming-guides/encoding/#varints.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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...
|
A different tact would be to add a PKIX key type, and the data is the PKIX key encoding. Then we would get ML-DSA for free with https://www.rfc-editor.org/rfc/rfc9881.html#name-ml-dsa-public-keys-in-pkix |
To be concrete: #711 |
add expanded to private key size Co-authored-by: Marcin Rataj <lidel@lidel.org>
Co-authored-by: Marcin Rataj <lidel@lidel.org>
Co-authored-by: Marcin Rataj <lidel@lidel.org>
RFC to kick of some discussion around a post quantum strategy for libp2p