Skip to content

Commit d008921

Browse files
authored
Merge pull request #6 from cryptidtech/ml/kem
Add ML-Kem
2 parents 2d28dab + d05268b commit d008921

File tree

13 files changed

+1047
-145
lines changed

13 files changed

+1047
-145
lines changed

Cargo.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,21 @@
22
resolver = "2"
33
default-members = ["crates/bs"]
44
members = [
5-
"crates/*",
65
"cli",
6+
"crates/bs",
7+
"crates/bsp2p",
8+
"crates/content-addressable",
9+
"crates/multibase",
10+
"crates/multicid",
11+
"crates/multicodec",
12+
"crates/multihash",
13+
"crates/multikey",
14+
"crates/multisig",
15+
"crates/multitrait",
16+
"crates/multiutil",
17+
"crates/provenance-log",
18+
"crates/rng",
19+
"crates/wacc",
720
]
821

922
[workspace.package]
@@ -48,6 +61,7 @@ serde = { version = "1.0.219", default-features = false, features = ["alloc", "d
4861
serde_cbor = { version = "0.11.2", features = ["tags"]}
4962
serde_json = { version = "1.0.104"}
5063
serde_test = { version = "1.0.104"}
64+
sha3 = "0.10.8"
5165
test-log = { version = "0.2.17", features = ["trace", "color"] }
5266
thiserror = "2.0.12"
5367
tokio = { version = "1.44.2", features = ["fs", "io-util", "macros", "rt", "test-util"] }

crates/multicodec/src/table_gen.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ build_codec_enum! {
174174
0x120b => (Mlkem512Pub, "mlkem-512-pub"),
175175
0x120c => (Mlkem768Pub, "mlkem-768-pub"),
176176
0x120d => (Mlkem1024Pub, "mlkem-1024-pub"),
177+
0x120e => (Fndsa512Pub, "fndsa-512-pub"),
178+
0x120f => (Fndsa1024Pub, "fndsa-1024-pub"),
177179
0x1239 => (Multisig, "multisig"),
178180
0x123a => (Multikey, "multikey"),
179181
0x123b => (Nonce, "nonce"),
@@ -193,6 +195,11 @@ build_codec_enum! {
193195
0x130e => (Bls12381G1PrivShare, "bls12_381-g1-priv-share"),
194196
0x130f => (Bls12381G2PrivShare, "bls12_381-g2-priv-share"),
195197
0x1310 => (Sm2Priv, "sm2-priv"),
198+
0x1311 => (Fndsa512Priv, "fndsa-512-priv"),
199+
0x1312 => (Fndsa1024Priv, "fndsa-1024-priv"),
200+
0x1313 => (Mlkem512Priv, "mlkem-512-priv"),
201+
0x1314 => (Mlkem768Priv, "mlkem-768-priv"),
202+
0x1315 => (Mlkem1024Priv, "mlkem-1024-priv"),
196203
0x1a14 => (LamportSha3512Pub, "lamport-sha3-512-pub"),
197204
0x1a15 => (LamportSha3384Pub, "lamport-sha3-384-pub"),
198205
0x1a16 => (LamportSha3256Pub, "lamport-sha3-256-pub"),
@@ -553,6 +560,8 @@ build_codec_enum! {
553560
0xd0ea => (Bls12381G1Sig, "bls12_381-g1-sig"),
554561
0xd0eb => (Bls12381G2Sig, "bls12_381-g2-sig"),
555562
0xd0ed => (Eddsa, "eddsa"),
563+
0xd0ee => (Fndsa512Sig, "fndsa-512-sig"),
564+
0xd0ef => (Fndsa1024Sig, "fndsa-1024-sig"),
556565
0xd191 => (Eip191, "eip-191"),
557566
0xeb51 => (JwkJcsPub, "jwk_jcs-pub"),
558567
0xf101 => (FilCommitmentUnsealed, "fil-commitment-unsealed"),
@@ -590,4 +599,7 @@ build_codec_enum! {
590599
0xd0130a => (Es521Msig, "es521-msig"),
591600
0xd0130b => (Rs256Msig, "rs256-msig"),
592601
0xd02000 => (Scion, "scion"),
602+
0xe00000 => (Mlkem512Cap, "mlkem-512-cap"),
603+
0xe00001 => (Mlkem768Cap, "mlkem-768-cap"),
604+
0xe00002 => (Mlkem1024Cap, "mlkem-1024-cap"),
593605
}

crates/multicodec/table.csv

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ provenance-log-script, serialization, 0x120a, draft, Veri
174174
mlkem-512-pub, key, 0x120b, draft, ML-KEM 512 public key; as specified by FIPS 203
175175
mlkem-768-pub, key, 0x120c, draft, ML-KEM 768 public key; as specified by FIPS 203
176176
mlkem-1024-pub, key, 0x120d, draft, ML-KEM 1024 public key; as specified by FIPS 203
177+
fndsa-512-pub, key, 0x120e, draft, FALCON-512 DSA public key; as specified by FIPS 206
178+
fndsa-1024-pub, key, 0x120f, draft, FALCON-1024 DSA public key; as specified by FIPS 206
177179
multisig, multiformat, 0x1239, draft, Digital signature multiformat
178180
multikey, multiformat, 0x123a, draft, Encryption key multiformat
179181
nonce, nonce, 0x123b, draft, Nonce random value
@@ -193,6 +195,11 @@ bls12_381-g2-pub-share, key, 0x130d, draft, BLS1
193195
bls12_381-g1-priv-share, key, 0x130e, draft, BLS12-381 G1 private key share
194196
bls12_381-g2-priv-share, key, 0x130f, draft, BLS12-381 G2 private key share
195197
sm2-priv, key, 0x1310, draft, SM2 private key
198+
fndsa-512-priv, key, 0x1311, draft, FALCON-512 DSA private key; as specified by FIPS 206
199+
fndsa-1024-priv, key, 0x1312, draft, FALCON-1024 DSA private key; as specified by FIPS 206
200+
mlkem-512-priv, key, 0x1313, draft, ML-KEM 512 private key; as specified by FIPS 203
201+
mlkem-768-priv, key, 0x1314, draft, ML-KEM 768 private key; as specified by FIPS 203
202+
mlkem-1024-priv, key, 0x1315, draft, ML-KEM 1024 private key; as specified by FIPS 203
196203
lamport-sha3-512-pub, key, 0x1a14, draft, Lamport public key based on SHA3-512
197204
lamport-sha3-384-pub, key, 0x1a15, draft, Lamport public key based on SHA3-384
198205
lamport-sha3-256-pub, key, 0x1a16, draft, Lamport public key based on SHA3-256
@@ -553,6 +560,8 @@ es256k, varsig, 0xd0e7, draft, ES25
553560
bls12_381-g1-sig, varsig, 0xd0ea, draft, G1 signature for BLS12-381
554561
bls12_381-g2-sig, varsig, 0xd0eb, draft, G2 signature for BLS12-381
555562
eddsa, varsig, 0xd0ed, draft, Edwards-Curve Digital Signature Algorithm
563+
fndsa-512-sig, varsig, 0xd0ee, draft, FNDSA-512 Signature Algorithm
564+
fndsa-1024-sig, varsig, 0xd0ef, draft, FNDSA-1024 Signature Algorithm
556565
eip-191, varsig, 0xd191, draft, EIP-191 Ethereum Signed Data Standard
557566
jwk_jcs-pub, key, 0xeb51, draft, JSON object containing only the required members of a JWK (RFC 7518 and RFC 7517) representing the public key. Serialisation based on JCS (RFC 8785)
558567
fil-commitment-unsealed, filecoin, 0xf101, permanent, Filecoin piece or sector data commitment merkle node/root (CommP & CommD)
@@ -590,3 +599,6 @@ es384-msig, multisig, 0xd01309, draft, ECDS
590599
es521-msig, multisig, 0xd0130a, draft, ECDSA P-521 Signature as Multisig
591600
rs256-msig, multisig, 0xd0130b, draft, RS256 Signature as Multisig
592601
scion, multiaddr, 0xd02000, draft, SCION Internet architecture
602+
mlkem-512-cap, capsule, 0xe00000, draft, MLKEM-512 capsule
603+
mlkem-768-cap, capsule, 0xe00001, draft, MLKEM-768 capsule
604+
mlkem-1024-cap, capsule, 0xe00002, draft, MLKEM-1024 capsule

crates/multikey/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,24 @@ wasm = ["getrandom/wasm_js"] # needed for CI testing on wasm32-unknown-unknown
1414
[dependencies]
1515
bcrypt-pbkdf = "0.10"
1616
blsful = "2.5"
17-
chacha20 = "0.9"
17+
chacha20poly1305 = "0.10.1"
1818
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
1919
elliptic-curve.workspace = true
2020
hex.workspace = true
2121
k256 = "0.13"
22+
ml-kem = { version = "0.2.1", features = ["deterministic"]}
2223
multibase.workspace = true
2324
multicodec.workspace = true
2425
multihash.workspace = true
2526
multisig.workspace = true
2627
multitrait.workspace = true
2728
multiutil.workspace = true
28-
poly1305 = "0.8"
2929
rand.workspace = true
3030
rand_core_6.workspace = true
3131
rng.workspace = true
3232
sec1 = "0.7"
3333
serde = { workspace = true, optional = true }
34+
sha3.workspace = true
3435
ssh-encoding = { version = "0.2" }
3536
test-log.workspace = true
3637
thiserror.workspace = true

crates/multikey/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ cryptography it supports ChaCha-256 keys.
1818
This implementation supports encrypting/decrypting keys using ChaCha20-Poly1305
1919
AEAD with keys derived with Bcrypt KDF from a preimage.
2020

21-
When using BLS12-381 keys, this implementations supports threshold key
21+
When using BLS12-381 keys, this implementation supports threshold key
2222
splitting and combining as well as threshold signing and verifying.
2323

2424
This crate also supports converting to/from SSH format keys using the

crates/multikey/src/cipher.rs

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
// SPDX-License-Idnetifier: Apache-2.0
2-
use crate::{mk::Attributes, AttrId, Error, Multikey};
2+
use crate::{
3+
error::CipherError,
4+
mk::Attributes,
5+
views::{chacha20, mlkem},
6+
AttrId, Error, Multikey,
7+
};
38
use multicodec::Codec;
9+
use multiutil::Varuint;
10+
use tracing::info;
411
use zeroize::Zeroizing;
512

13+
/// the list of cipher codecs
14+
pub const CIPHER_CODECS: [Codec; 4] = [
15+
Codec::Chacha20Poly1305,
16+
Codec::Mlkem512Priv,
17+
Codec::Mlkem768Priv,
18+
Codec::Mlkem1024Priv,
19+
];
20+
621
/// Builder for creating a Multikey intended for encryption/decryption of other
722
/// Multikeys
823
#[derive(Clone, Debug, Default)]
@@ -31,6 +46,7 @@ impl Builder {
3146
}
3247
// try to look up the key_length in the multikey attributes
3348
if let Some(v) = mk.attributes.get(&AttrId::CipherKeyLen) {
49+
info!("setting key length: {}", v.len());
3450
self.key_length = Some(v.clone());
3551
}
3652
// try to look up the nonce in the multikey attributes
@@ -41,23 +57,53 @@ impl Builder {
4157
}
4258

4359
/// add in the nonce for the cipher
44-
pub fn with_nonce(mut self, nonce: &impl AsRef<[u8]>) -> Self {
45-
let n: Vec<u8> = nonce.as_ref().into();
46-
self.nonce = Some(n.into());
47-
self
60+
pub fn with_nonce(mut self, nonce: &impl AsRef<[u8]>) -> Result<Self, Error> {
61+
let n: Zeroizing<Vec<u8>> = Zeroizing::new(nonce.as_ref().to_vec());
62+
match self.codec {
63+
Codec::Chacha20Poly1305 => {
64+
if n.len() != chacha20::nonce_length(self.codec)? {
65+
info!(
66+
"nonce length: {}, expected: {}",
67+
n.len(),
68+
chacha20::nonce_length(self.codec)?
69+
);
70+
return Err(CipherError::InvalidNonceLen.into());
71+
}
72+
}
73+
Codec::Mlkem512Priv | Codec::Mlkem768Priv | Codec::Mlkem1024Priv => {
74+
if n.len() != mlkem::nonce_length(self.codec)? {
75+
info!(
76+
"nonce length: {}, expected: {}",
77+
n.len(),
78+
chacha20::nonce_length(self.codec)?
79+
);
80+
return Err(CipherError::InvalidNonceLen.into());
81+
}
82+
}
83+
_ => return Err(CipherError::UnsupportedCodec(self.codec).into()),
84+
}
85+
86+
self.nonce = Some(n);
87+
Ok(self)
4888
}
4989

5090
/// add a random nonce for cipher
5191
pub fn with_random_nonce(
5292
mut self,
53-
len: usize,
5493
rng: &mut impl rand_core_6::CryptoRngCore,
55-
) -> Self {
94+
) -> Result<Self, Error> {
95+
let len = match self.codec {
96+
Codec::Chacha20Poly1305 => chacha20::nonce_length(self.codec)?,
97+
Codec::Mlkem512Priv | Codec::Mlkem768Priv | Codec::Mlkem1024Priv => {
98+
mlkem::nonce_length(self.codec)?
99+
}
100+
_ => return Err(CipherError::UnsupportedCodec(self.codec).into()),
101+
};
56102
// heap allocate a buffer to receive the random nonce
57103
let mut buf: Zeroizing<Vec<u8>> = vec![0; len].into();
58104
rng.fill_bytes(buf.as_mut_slice());
59105
self.nonce = Some(buf);
60-
self
106+
Ok(self)
61107
}
62108

63109
/// build a key using key bytes
@@ -69,6 +115,16 @@ impl Builder {
69115
let mut attributes = Attributes::new();
70116
if let Some(key_length) = self.key_length {
71117
attributes.insert(AttrId::CipherKeyLen, key_length);
118+
} else {
119+
let len = match codec {
120+
Codec::Chacha20Poly1305 => chacha20::key_length(codec)?,
121+
Codec::Mlkem512Priv | Codec::Mlkem768Priv | Codec::Mlkem1024Priv => {
122+
mlkem::key_length(codec)?
123+
}
124+
_ => return Err(CipherError::UnsupportedCodec(self.codec).into()),
125+
};
126+
let key_length: Vec<u8> = Varuint(len).into();
127+
attributes.insert(AttrId::CipherKeyLen, key_length.into());
72128
}
73129
if let Some(nonce) = self.nonce {
74130
attributes.insert(AttrId::CipherNonce, nonce);
@@ -85,14 +141,30 @@ impl Builder {
85141
mod tests {
86142
use rng::StdRng;
87143
use test_log::test;
88-
use tracing::{span, Level};
144+
use tracing::{info, span, Level};
89145

90146
use super::*;
91147
use crate::{kdf, mk, Views};
92148

149+
#[test]
150+
fn test_keygen() {
151+
let _s = span!(Level::INFO, "test_keygen").entered();
152+
for codec in CIPHER_CODECS {
153+
let mut rng = StdRng::from_os_rng();
154+
let ciphermk = Builder::new(codec)
155+
.with_random_nonce(&mut rng)
156+
.unwrap()
157+
.try_build()
158+
.unwrap();
159+
160+
let nonce = ciphermk.cipher_attr_view().unwrap().nonce_bytes().unwrap();
161+
info!("codec: {codec}, nonce: {}", hex::encode(nonce));
162+
}
163+
}
164+
93165
#[test]
94166
fn test_chacha20() {
95-
let _ = span!(Level::INFO, "test_chacha20").entered();
167+
let _s = span!(Level::INFO, "test_chacha20").entered();
96168
let salt = hex::decode("8bb78be51ac7cc98f44e38947ff8a128764ec039b89687a790dfa8444ba97682")
97169
.unwrap();
98170
// create a kdf multikey
@@ -103,10 +175,12 @@ mod tests {
103175
.unwrap();
104176

105177
// ChaCha needs 12 bytes of nonce iaw RFC8439
106-
let nonce = hex::decode("00b61a43d4d1e8d700b61a43").unwrap();
178+
let nonce = hex::decode("c6691d95f44e18f4cff311e3781eb2fc744de398585a94a3").unwrap();
179+
107180
// create a cipher multikey
108181
let ciphermk = Builder::new(Codec::Chacha20Poly1305)
109182
.with_nonce(&nonce)
183+
.unwrap()
110184
.try_build()
111185
.unwrap();
112186

crates/multikey/src/kdf.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,10 @@ mod tests {
104104
.try_build()
105105
.unwrap();
106106

107-
let nonce = hex::decode("714e5abf0f7beae8").unwrap();
107+
let nonce = hex::decode("c6691d95f44e18f4cff311e3781eb2fc744de398585a94a3").unwrap();
108108
let ciphermk = cipher::Builder::new(Codec::Chacha20Poly1305)
109109
.with_nonce(&nonce)
110+
.unwrap()
110111
.try_build()
111112
.unwrap();
112113

0 commit comments

Comments
 (0)