diff --git a/Cargo.lock b/Cargo.lock
index 67abec7..4e39c40 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,30 +2,6 @@
# It is not intended for manual editing.
version = 4
-[[package]]
-name = "addr2line"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
-[[package]]
-name = "aead"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
-dependencies = [
- "generic-array",
-]
-
[[package]]
name = "aead"
version = "0.5.2"
@@ -36,18 +12,6 @@ dependencies = [
"generic-array",
]
-[[package]]
-name = "aes"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
-dependencies = [
- "cfg-if",
- "cipher 0.3.0",
- "cpufeatures",
- "opaque-debug",
-]
-
[[package]]
name = "aes"
version = "0.8.4"
@@ -55,35 +19,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
- "cipher 0.4.4",
+ "cipher",
"cpufeatures",
]
-[[package]]
-name = "aes-gcm"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f"
-dependencies = [
- "aead 0.4.3",
- "aes 0.7.5",
- "cipher 0.3.0",
- "ctr 0.7.0",
- "ghash 0.4.4",
- "subtle",
-]
-
[[package]]
name = "aes-gcm"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
- "aead 0.5.2",
- "aes 0.8.4",
- "cipher 0.4.4",
- "ctr 0.9.2",
- "ghash 0.5.1",
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
"subtle",
]
@@ -364,21 +314,6 @@ dependencies = [
"tokio",
]
-[[package]]
-name = "backtrace"
-version = "0.3.76"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "windows-link",
-]
-
[[package]]
name = "base16ct"
version = "0.2.0"
@@ -447,15 +382,6 @@ dependencies = [
"cpufeatures",
]
-[[package]]
-name = "block-buffer"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
-dependencies = [
- "generic-array",
-]
-
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -608,18 +534,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
-[[package]]
-name = "chacha20"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6"
-dependencies = [
- "cfg-if",
- "cipher 0.3.0",
- "cpufeatures",
- "zeroize",
-]
-
[[package]]
name = "chacha20"
version = "0.9.1"
@@ -627,33 +541,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
- "cipher 0.4.4",
+ "cipher",
"cpufeatures",
]
-[[package]]
-name = "chacha20poly1305"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5"
-dependencies = [
- "aead 0.4.3",
- "chacha20 0.8.2",
- "cipher 0.3.0",
- "poly1305 0.7.2",
- "zeroize",
-]
-
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
- "aead 0.5.2",
- "chacha20 0.9.1",
- "cipher 0.4.4",
- "poly1305 0.8.0",
+ "aead",
+ "chacha20",
+ "cipher",
+ "poly1305",
"zeroize",
]
@@ -696,15 +597,6 @@ dependencies = [
"half",
]
-[[package]]
-name = "cipher"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
-dependencies = [
- "generic-array",
-]
-
[[package]]
name = "cipher"
version = "0.4.4"
@@ -875,6 +767,17 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "core-models"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4"
+dependencies = [
+ "hax-lib",
+ "pastey",
+ "rand 0.9.2",
+]
+
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -1023,35 +926,13 @@ dependencies = [
"hybrid-array 0.4.7",
]
-[[package]]
-name = "ctr"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481"
-dependencies = [
- "cipher 0.3.0",
-]
-
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
- "cipher 0.4.4",
-]
-
-[[package]]
-name = "curve25519-dalek"
-version = "3.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
-dependencies = [
- "byteorder",
- "digest 0.9.0",
- "rand_core 0.5.1",
- "subtle",
- "zeroize",
+ "cipher",
]
[[package]]
@@ -1101,19 +982,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "curve25519-dalek-ng"
-version = "4.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8"
-dependencies = [
- "byteorder",
- "digest 0.9.0",
- "rand_core 0.6.4",
- "subtle-ng",
- "zeroize",
-]
-
[[package]]
name = "darling"
version = "0.20.11"
@@ -1324,15 +1192,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
-[[package]]
-name = "digest"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
-dependencies = [
- "generic-array",
-]
-
[[package]]
name = "digest"
version = "0.10.7"
@@ -1426,15 +1285,6 @@ dependencies = [
"spki 0.7.3",
]
-[[package]]
-name = "ed25519"
-version = "1.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7"
-dependencies = [
- "signature 1.6.4",
-]
-
[[package]]
name = "ed25519"
version = "2.2.3"
@@ -1457,20 +1307,6 @@ dependencies = [
"signature 3.0.0-rc.10",
]
-[[package]]
-name = "ed25519-dalek"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
-dependencies = [
- "curve25519-dalek 3.2.0",
- "ed25519 1.5.3",
- "rand 0.7.3",
- "serde",
- "sha2 0.9.9",
- "zeroize",
-]
-
[[package]]
name = "ed25519-dalek"
version = "2.2.0"
@@ -1854,17 +1690,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "getrandom"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
-]
-
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -1874,7 +1699,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
"wasm-bindgen",
]
@@ -1907,16 +1732,6 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "ghash"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
-dependencies = [
- "opaque-debug",
- "polyval 0.5.3",
-]
-
[[package]]
name = "ghash"
version = "0.5.1"
@@ -1924,15 +1739,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
dependencies = [
"opaque-debug",
- "polyval 0.6.2",
+ "polyval",
]
-[[package]]
-name = "gimli"
-version = "0.32.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
-
[[package]]
name = "gloo-timers"
version = "0.3.0"
@@ -2091,6 +1900,43 @@ dependencies = [
"hashbrown 0.14.5",
]
+[[package]]
+name = "hax-lib"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc"
+dependencies = [
+ "hax-lib-macros",
+ "num-bigint",
+ "num-traits",
+]
+
+[[package]]
+name = "hax-lib-macros"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f"
+dependencies = [
+ "hax-lib-macros-types",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "hax-lib-macros-types"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "uuid",
+]
+
[[package]]
name = "heapless"
version = "0.7.17"
@@ -2205,48 +2051,65 @@ dependencies = [
[[package]]
name = "hpke-rs"
-version = "0.1.2"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40d78d066f8d487fa69d5c3f92f98c11e2540796d213016d107fe86eabf9f26b"
+checksum = "7fcd4b22e7fc3318a1674085f943a35794023ecfe8b24a1691d1d1e016f869c8"
dependencies = [
"hpke-rs-crypto",
+ "hpke-rs-libcrux",
+ "hpke-rs-rust-crypto",
+ "libcrux-sha3",
"log",
+ "rand_core 0.9.5",
"serde",
- "serde_json",
- "tls_codec 0.4.2",
+ "tls_codec",
"zeroize",
]
[[package]]
name = "hpke-rs-crypto"
-version = "0.1.3"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79df748353d9cee46d565f591d0039973a6554f8ef026b2647ab1ef2b64b91df"
+checksum = "2dd92b7d7f0deaae59c152e01c01f5280ea92dfac82090e5c025879b32df9193"
dependencies = [
- "getrandom 0.2.17",
- "rand 0.8.5",
- "serde",
- "serde_json",
- "tls_codec 0.4.2",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "hpke-rs-libcrux"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd99129e6e5ab959fca63fe83aebbd1b5ff1107eeb549dca597b6d9484e51684"
+dependencies = [
+ "hpke-rs-crypto",
+ "libcrux-aead",
+ "libcrux-ecdh",
+ "libcrux-hkdf",
+ "libcrux-kem",
+ "libcrux-traits",
+ "rand 0.9.2",
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.5",
]
[[package]]
name = "hpke-rs-rust-crypto"
-version = "0.1.3"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d6fcfe6949aedbacad5aedb2f8ef9f054a142510e8f4f7355a4ccb3f5bd01f"
+checksum = "019f9a15c71981dffb32882487c372d3e6e48557c1c1ac84f235cbded330a2ef"
dependencies = [
- "aes-gcm 0.10.3",
- "chacha20poly1305 0.10.1",
- "getrandom 0.2.17",
+ "aes-gcm",
+ "chacha20poly1305",
"hkdf",
"hpke-rs-crypto",
+ "k256",
"p256",
"p384",
"rand 0.8.5",
"rand_chacha 0.3.1",
+ "rand_core 0.6.4",
"sha2 0.10.9",
- "x25519-dalek-ng",
+ "x25519-dalek",
]
[[package]]
@@ -2922,6 +2785,16 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "k256"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
+dependencies = [
+ "cfg-if",
+ "elliptic-curve",
+]
+
[[package]]
name = "keccak"
version = "0.1.6"
@@ -2959,6 +2832,222 @@ version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
+[[package]]
+name = "libcrux-aead"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31ca5c9cb6a0f4dcf2bab1b85aa302537f40b801fc5efe10b5b76fbd677e8161"
+dependencies = [
+ "libcrux-aesgcm",
+ "libcrux-chacha20poly1305",
+ "libcrux-secrets",
+ "libcrux-traits",
+]
+
+[[package]]
+name = "libcrux-aesgcm"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95d897badc420310155f90ed1ea48872809c3446c94ebb116e8a810b66651623"
+dependencies = [
+ "libcrux-intrinsics",
+ "libcrux-platform",
+ "libcrux-secrets",
+ "libcrux-traits",
+]
+
+[[package]]
+name = "libcrux-chacha20poly1305"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6070c5d3991e208511daaf0efae2c747b14a8c136718a3a0a474a82cc0c45522"
+dependencies = [
+ "libcrux-hacl-rs",
+ "libcrux-macros",
+ "libcrux-poly1305",
+ "libcrux-secrets",
+ "libcrux-traits",
+]
+
+[[package]]
+name = "libcrux-curve25519"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552571ff92bcdf2992b61b600c74d2eaba2c42a14d478c1e9e29391c39db8761"
+dependencies = [
+ "libcrux-hacl-rs",
+ "libcrux-macros",
+ "libcrux-secrets",
+ "libcrux-traits",
+]
+
+[[package]]
+name = "libcrux-ecdh"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1fceb737840ec67255068f6d90e9782ae17fad2337aeb7d7203d76560966216"
+dependencies = [
+ "libcrux-curve25519",
+ "libcrux-p256",
+ "rand 0.9.2",
+]
+
+[[package]]
+name = "libcrux-hacl-rs"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2637dc87d158e1f1b550fd9b226443e84153fded4de69028d897b534d16d22e6"
+dependencies = [
+ "libcrux-macros",
+]
+
+[[package]]
+name = "libcrux-hkdf"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "295d04515de24bb0f81e5c46d79949517b66ba6a4aaf24328764c6f999e01e36"
+dependencies = [
+ "libcrux-hacl-rs",
+ "libcrux-hmac",
+ "libcrux-secrets",
+]
+
+[[package]]
+name = "libcrux-hmac"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d081af93c27d7cebc9a8cc4b3720cba5411186297f9adeddf853d994bba4e7b"
+dependencies = [
+ "libcrux-hacl-rs",
+ "libcrux-macros",
+ "libcrux-sha2",
+]
+
+[[package]]
+name = "libcrux-intrinsics"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0aa4779454e853d1de200cd12f19a8185aac47d99a5ec404cea3295c943d48f1"
+dependencies = [
+ "core-models",
+ "hax-lib",
+]
+
+[[package]]
+name = "libcrux-kem"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34adb7fdaddd04136e7b4b7368e680f0bca8f1392dfafbb7cb809148c6eb48c7"
+dependencies = [
+ "libcrux-curve25519",
+ "libcrux-ecdh",
+ "libcrux-ml-kem",
+ "libcrux-p256",
+ "libcrux-sha3",
+ "libcrux-traits",
+ "rand 0.9.2",
+]
+
+[[package]]
+name = "libcrux-macros"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "libcrux-ml-kem"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a930ff130a63e9d89648d0e22203ca034995191cbfa606b9f3c151ba67306963"
+dependencies = [
+ "hax-lib",
+ "libcrux-intrinsics",
+ "libcrux-platform",
+ "libcrux-secrets",
+ "libcrux-sha3",
+ "libcrux-traits",
+ "rand 0.9.2",
+ "tls_codec",
+]
+
+[[package]]
+name = "libcrux-p256"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94a3d3d7567b86434b34a98faf19ce5a4dd20f964e0d9a2d13f02792b4ad0109"
+dependencies = [
+ "libcrux-hacl-rs",
+ "libcrux-macros",
+ "libcrux-secrets",
+ "libcrux-sha2",
+ "libcrux-traits",
+]
+
+[[package]]
+name = "libcrux-platform"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libcrux-poly1305"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccfb6399682b2dee13b728c779ab5dcc51afbe982b63508ca524806994336134"
+dependencies = [
+ "libcrux-hacl-rs",
+ "libcrux-macros",
+]
+
+[[package]]
+name = "libcrux-secrets"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b"
+dependencies = [
+ "hax-lib",
+]
+
+[[package]]
+name = "libcrux-sha2"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a9b200262e529493e459609895f3a02434eadb58897352236ebde491b5d6d87"
+dependencies = [
+ "libcrux-hacl-rs",
+ "libcrux-macros",
+ "libcrux-traits",
+]
+
+[[package]]
+name = "libcrux-sha3"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3dabce2795479bd7294f853f7966a678cadf7a26d3d29f61cf15f5123e7ba4f"
+dependencies = [
+ "hax-lib",
+ "libcrux-intrinsics",
+ "libcrux-platform",
+ "libcrux-traits",
+]
+
+[[package]]
+name = "libcrux-traits"
+version = "0.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695ff2fb97627e4d57315a2fdfbfe50df1c80c6ef7d91ba34216169bd6f41c00"
+dependencies = [
+ "libcrux-secrets",
+ "rand 0.9.2",
+]
+
[[package]]
name = "libloading"
version = "0.8.9"
@@ -3159,15 +3248,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-[[package]]
-name = "miniz_oxide"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
-dependencies = [
- "adler2",
-]
-
[[package]]
name = "mio"
version = "1.1.1"
@@ -3176,7 +3256,7 @@ checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"log",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
"windows-sys 0.61.2",
]
@@ -3569,15 +3649,6 @@ dependencies = [
"objc2-security",
]
-[[package]]
-name = "object"
-version = "0.37.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "oid-registry"
version = "0.7.1"
@@ -3641,63 +3712,66 @@ dependencies = [
[[package]]
name = "openmls"
-version = "0.5.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95d4a03899704c17e7b280676a3301216faccf9b19eb860968f84a492aef944b"
+checksum = "dcb512bfe6a55777518853ea535c6241f069cb0e8984678c117151d2a1e7e903"
dependencies = [
- "backtrace",
"log",
"openmls_traits",
"rayon",
"serde",
- "thiserror 1.0.69",
- "tls_codec 0.3.0",
+ "serde_bytes",
+ "thiserror 2.0.18",
+ "tls_codec",
+ "zeroize",
]
[[package]]
-name = "openmls_memory_keystore"
-version = "0.2.0"
+name = "openmls_memory_storage"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1532fed34a1d3cf29962c1c07624f628501537eafac47913a08caea4bf08319e"
+checksum = "1a52c927ddb9940acb96d51aebd54b8b9c601c7119e6609622fb3f2cbe16abe3"
dependencies = [
+ "log",
"openmls_traits",
+ "serde",
"serde_json",
- "thiserror 1.0.69",
+ "thiserror 2.0.18",
]
[[package]]
name = "openmls_rust_crypto"
-version = "0.2.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b34960cce81b5a8b8a178f330e28895f092b49a28923d36c03fa56dd3d6f7173"
+checksum = "b8bc087eeb4cf230327b156014df77464f62ee5fcd682c3292be11fbba8e7811"
dependencies = [
- "aes-gcm 0.9.2",
- "chacha20poly1305 0.9.1",
- "ed25519-dalek 1.0.1",
+ "aes-gcm",
+ "chacha20poly1305",
+ "ed25519-dalek 2.2.0",
"hkdf",
"hmac",
"hpke-rs",
"hpke-rs-crypto",
"hpke-rs-rust-crypto",
- "openmls_memory_keystore",
+ "openmls_memory_storage",
"openmls_traits",
"p256",
- "rand 0.7.3",
"rand 0.8.5",
"rand_chacha 0.3.1",
+ "serde",
"sha2 0.10.9",
- "thiserror 1.0.69",
- "tls_codec 0.3.0",
+ "thiserror 2.0.18",
+ "tls_codec",
]
[[package]]
name = "openmls_traits"
-version = "0.2.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a839131d13dfa50e6eef6f11f7718195c3194ac9efd81ebf2cb72e554c4e12f2"
+checksum = "4f88ccdd53448dfdbfa5b8da8ba4e527c418fdb966418172bace2e3b41eedd56"
dependencies = [
"serde",
- "tls_codec 0.3.0",
+ "tls_codec",
]
[[package]]
@@ -3724,10 +3798,8 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
dependencies = [
- "ecdsa",
"elliptic-curve",
"primeorder",
- "sha2 0.10.9",
]
[[package]]
@@ -3786,6 +3858,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+[[package]]
+name = "pastey"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec"
+
[[package]]
name = "pem"
version = "3.0.6"
@@ -3986,17 +4064,6 @@ dependencies = [
"windows-sys 0.48.0",
]
-[[package]]
-name = "poly1305"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede"
-dependencies = [
- "cpufeatures",
- "opaque-debug",
- "universal-hash 0.4.0",
-]
-
[[package]]
name = "poly1305"
version = "0.8.0"
@@ -4005,19 +4072,7 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug",
- "universal-hash 0.5.1",
-]
-
-[[package]]
-name = "polyval"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "opaque-debug",
- "universal-hash 0.4.0",
+ "universal-hash",
]
[[package]]
@@ -4029,7 +4084,7 @@ dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug",
- "universal-hash 0.5.1",
+ "universal-hash",
]
[[package]]
@@ -4181,6 +4236,28 @@ dependencies = [
"toml_edit 0.23.10+spec-1.0.0",
]
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -4261,7 +4338,7 @@ dependencies = [
"libc",
"once_cell",
"raw-cpuid",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
"web-sys",
"winapi",
]
@@ -4285,7 +4362,7 @@ dependencies = [
"bincode",
"capnp",
"capnp-rpc",
- "chacha20poly1305 0.10.1",
+ "chacha20poly1305",
"ciborium",
"clap",
"crossterm",
@@ -4318,6 +4395,7 @@ dependencies = [
"thiserror 1.0.69",
"tokio",
"tokio-util",
+ "toml",
"tracing",
"tracing-subscriber",
"zeroize",
@@ -4330,7 +4408,7 @@ dependencies = [
"argon2",
"bincode",
"capnp",
- "chacha20poly1305 0.10.1",
+ "chacha20poly1305",
"ciborium",
"criterion",
"ed25519-dalek 2.2.0",
@@ -4340,6 +4418,7 @@ dependencies = [
"ml-kem",
"opaque-ke",
"openmls",
+ "openmls_memory_storage",
"openmls_rust_crypto",
"openmls_traits",
"prost",
@@ -4349,7 +4428,7 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"thiserror 1.0.69",
- "tls_codec 0.3.0",
+ "tls_codec",
"tokio",
"x25519-dalek",
"zeroize",
@@ -4370,7 +4449,7 @@ name = "quicprochat-p2p"
version = "0.1.0"
dependencies = [
"anyhow",
- "chacha20poly1305 0.10.1",
+ "chacha20poly1305",
"hex",
"iroh",
"quicprochat-core",
@@ -4430,7 +4509,7 @@ dependencies = [
"argon2",
"bincode",
"bytes",
- "chacha20poly1305 0.10.1",
+ "chacha20poly1305",
"futures",
"hex",
"opaque-ke",
@@ -4587,19 +4666,6 @@ dependencies = [
"nibble_vec",
]
-[[package]]
-name = "rand"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
-dependencies = [
- "getrandom 0.1.16",
- "libc",
- "rand_chacha 0.2.2",
- "rand_core 0.5.1",
- "rand_hc",
-]
-
[[package]]
name = "rand"
version = "0.8.5"
@@ -4621,16 +4687,6 @@ dependencies = [
"rand_core 0.9.5",
]
-[[package]]
-name = "rand_chacha"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.5.1",
-]
-
[[package]]
name = "rand_chacha"
version = "0.3.1"
@@ -4651,15 +4707,6 @@ dependencies = [
"rand_core 0.9.5",
]
-[[package]]
-name = "rand_core"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-dependencies = [
- "getrandom 0.1.16",
-]
-
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -4678,15 +4725,6 @@ dependencies = [
"getrandom 0.3.4",
]
-[[package]]
-name = "rand_hc"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
-dependencies = [
- "rand_core 0.5.1",
-]
-
[[package]]
name = "ratatui"
version = "0.29.0"
@@ -4918,12 +4956,6 @@ dependencies = [
"smallvec",
]
-[[package]]
-name = "rustc-demangle"
-version = "0.1.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
-
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -5295,19 +5327,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
-[[package]]
-name = "sha2"
-version = "0.9.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
-dependencies = [
- "block-buffer 0.9.0",
- "cfg-if",
- "cpufeatures",
- "digest 0.9.0",
- "opaque-debug",
-]
-
[[package]]
name = "sha2"
version = "0.10.9"
@@ -5386,12 +5405,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "signature"
-version = "1.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
-
[[package]]
name = "signature"
version = "2.2.0"
@@ -5586,12 +5599,6 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
-[[package]]
-name = "subtle-ng"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142"
-
[[package]]
name = "syn"
version = "2.0.117"
@@ -5766,17 +5773,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-[[package]]
-name = "tls_codec"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aee1e621cbf57f36f5b51ebf366b57ba153be7fed133182a9513e443ecdf506e"
-dependencies = [
- "serde",
- "tls_codec_derive 0.3.0",
- "zeroize",
-]
-
[[package]]
name = "tls_codec"
version = "0.4.2"
@@ -5784,21 +5780,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
dependencies = [
"serde",
- "tls_codec_derive 0.4.2",
+ "tls_codec_derive",
"zeroize",
]
-[[package]]
-name = "tls_codec_derive"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3226440488120aabe7e7cc80292634a68e541c407d97b66eceaae787454dae25"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "tls_codec_derive"
version = "0.4.2"
@@ -6158,16 +6143,6 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
-[[package]]
-name = "universal-hash"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
-dependencies = [
- "generic-array",
- "subtle",
-]
-
[[package]]
name = "universal-hash"
version = "0.5.1"
@@ -6345,12 +6320,6 @@ dependencies = [
"try-lock",
]
-[[package]]
-name = "wasi"
-version = "0.9.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
-
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -7122,18 +7091,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "x25519-dalek-ng"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf7074de8999662970c3c4c8f7f30925028dd8f4ca31ad4c055efa9cdf2ec326"
-dependencies = [
- "curve25519-dalek-ng",
- "rand 0.8.5",
- "rand_core 0.6.4",
- "zeroize",
-]
-
[[package]]
name = "x509-parser"
version = "0.16.0"
diff --git a/Cargo.toml b/Cargo.toml
index 1b3a93b..2df47c8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,12 +26,13 @@ categories = ["cryptography", "network-programming"]
[workspace.dependencies]
# ── Crypto ────────────────────────────────────────────────────────────────────
-openmls = { version = "0.5", default-features = false, features = ["crypto-subtle"] }
-openmls_rust_crypto = { version = "0.2" }
-openmls_traits = { version = "0.2" }
-# tls_codec must match the version used by openmls 0.5 (which uses 0.3) to avoid
+openmls = { version = "0.8" }
+openmls_rust_crypto = { version = "0.5" }
+openmls_traits = { version = "0.5" }
+openmls_memory_storage = { version = "0.5" }
+# tls_codec must match the version used by openmls 0.8 (which uses 0.4) to avoid
# duplicate Serialize trait versions in the dependency graph.
-tls_codec = { version = "0.3", features = ["derive"] }
+tls_codec = { version = "0.4", features = ["derive"] }
# ml-kem 0.2 is the current stable release (FIPS 203, ML-KEM-768).
ml-kem = { version = "0.2" }
x25519-dalek = { version = "2", features = ["static_secrets"] }
@@ -87,7 +88,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = { version = "1" }
thiserror = { version = "1" }
-# ── CLI ───────────────────────────────────────────────────────────────────────
+# ── Config / CLI ──────────────────────────────────────────────────────────────
+toml = { version = "0.8" }
clap = { version = "4", features = ["derive", "env"] }
rustyline = { version = "14" }
diff --git a/README.md b/README.md
index 43392b1..d58bd85 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
+ Why quicprochat? ·
Roadmap ·
SDK Docs ·
Operations ·
diff --git a/crates/quicprochat-client/Cargo.toml b/crates/quicprochat-client/Cargo.toml
index 0104cc3..4c3d1cd 100644
--- a/crates/quicprochat-client/Cargo.toml
+++ b/crates/quicprochat-client/Cargo.toml
@@ -50,8 +50,9 @@ rustls = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
-# CLI
+# CLI + config
clap = { workspace = true }
+toml = { workspace = true }
# Local message/conversation storage
rusqlite = { workspace = true }
diff --git a/crates/quicprochat-client/src/client/repl.rs b/crates/quicprochat-client/src/client/repl.rs
index 5e737ad..c724290 100644
--- a/crates/quicprochat-client/src/client/repl.rs
+++ b/crates/quicprochat-client/src/client/repl.rs
@@ -1449,10 +1449,8 @@ pub(crate) async fn cmd_dm(
},
display_name: format!("@{username}"),
mls_group_blob: member
- .group_ref()
- .map(bincode::serialize)
- .transpose()
- .context("serialize group")?,
+ .serialize_mls_state()
+ .context("serialize MLS state")?,
keystore_blob: None,
member_keys,
unread_count: 0,
@@ -1493,10 +1491,8 @@ pub(crate) fn cmd_create_group(session: &mut SessionState, name: &str) -> anyhow
kind: ConversationKind::Group { name: name.to_string() },
display_name: format!("#{name}"),
mls_group_blob: member
- .group_ref()
- .map(bincode::serialize)
- .transpose()
- .context("serialize group")?,
+ .serialize_mls_state()
+ .context("serialize MLS state")?,
keystore_blob: None,
member_keys,
unread_count: 0,
@@ -1780,9 +1776,7 @@ pub(crate) async fn cmd_join(
kind: ConversationKind::Group { name: display.clone() },
display_name: format!("#{display}"),
mls_group_blob: new_member
- .group_ref()
- .map(bincode::serialize)
- .transpose()
+ .serialize_mls_state()
.context("serialize joined group")?,
keystore_blob: None,
member_keys,
@@ -3186,8 +3180,9 @@ async fn try_auto_join(
};
let mls_blob = member
- .group_ref()
- .and_then(|g| bincode::serialize(g).ok());
+ .serialize_mls_state()
+ .ok()
+ .flatten();
let conv = Conversation {
id: conv_id.clone(),
diff --git a/crates/quicprochat-client/src/client/session.rs b/crates/quicprochat-client/src/client/session.rs
index 7535160..dc6f3d9 100644
--- a/crates/quicprochat-client/src/client/session.rs
+++ b/crates/quicprochat-client/src/client/session.rs
@@ -16,7 +16,7 @@ use quicprochat_core::{DiskKeyStore, GroupMember, HybridKeypair, IdentityKeypair
use super::conversation::{
now_ms, Conversation, ConversationId, ConversationKind, ConversationStore,
};
-use super::state::{load_or_init_state, keystore_path};
+use super::state::load_or_init_state;
/// Runtime state for an interactive REPL session.
pub struct SessionState {
@@ -109,7 +109,7 @@ impl SessionState {
/// Migrate the legacy single-group from StoredState into the conversation DB.
fn migrate_legacy_group(
&mut self,
- state_path: &Path,
+ _state_path: &Path,
group_blob: &Option>,
) -> anyhow::Result<()> {
let blob = match group_blob {
@@ -117,16 +117,22 @@ impl SessionState {
None => return Ok(()),
};
- // Reconstruct GroupMember using the legacy keystore and group blob.
- let ks_path = keystore_path(state_path);
- let ks = DiskKeyStore::persistent(&ks_path)?;
- let group = bincode::deserialize(blob).context("decode legacy group")?;
- let member = GroupMember::new_with_state(
+ // Legacy group blobs used openmls 0.5 serde format. After the 0.8
+ // upgrade the blob format changed to storage-provider state. Attempt
+ // to load from the new format; if that fails, skip the legacy group.
+ let group_id_guess = &blob[..blob.len().min(16)];
+ let member = match GroupMember::new_from_storage_bytes(
Arc::clone(&self.identity),
- ks,
- Some(group),
+ blob,
+ group_id_guess,
false, // legacy groups are classical
- );
+ ) {
+ Ok(m) => m,
+ Err(e) => {
+ tracing::warn!(error = %e, "skipping incompatible legacy group blob (openmls version mismatch)");
+ return Ok(());
+ }
+ };
let group_id_bytes = member.group_id().unwrap_or_default();
@@ -182,26 +188,31 @@ impl SessionState {
/// Create a GroupMember from a stored conversation.
fn create_member_from_conv(&self, conv: &Conversation) -> anyhow::Result {
- let ks_path = self.keystore_path_for(&conv.id);
- let ks = DiskKeyStore::persistent(&ks_path)
- .unwrap_or_else(|e| {
- tracing::warn!(path = %ks_path.display(), error = %e, "DiskKeyStore open failed, falling back to ephemeral");
- DiskKeyStore::ephemeral()
- });
-
- let group = conv
- .mls_group_blob
- .as_ref()
- .map(|b| bincode::deserialize(b))
- .transpose()
- .context("decode MLS group from conversation db")?;
-
- Ok(GroupMember::new_with_state(
- Arc::clone(&self.identity),
- ks,
- group,
- conv.is_hybrid,
- ))
+ if let Some(blob) = conv.mls_group_blob.as_ref() {
+ let group_id = conv.id.0.as_slice();
+ let member = GroupMember::new_from_storage_bytes(
+ Arc::clone(&self.identity),
+ blob,
+ group_id,
+ conv.is_hybrid,
+ )
+ .context("restore MLS state from conversation db")?;
+ Ok(member)
+ } else {
+ // No MLS state — create an empty member.
+ let ks_path = self.keystore_path_for(&conv.id);
+ let ks = DiskKeyStore::persistent(&ks_path)
+ .unwrap_or_else(|e| {
+ tracing::warn!(path = %ks_path.display(), error = %e, "DiskKeyStore open failed, falling back to ephemeral");
+ DiskKeyStore::ephemeral()
+ });
+ Ok(GroupMember::new_with_state(
+ Arc::clone(&self.identity),
+ ks,
+ None,
+ conv.is_hybrid,
+ ))
+ }
}
/// Path for a per-conversation keystore file.
@@ -214,10 +225,8 @@ impl SessionState {
pub fn save_member(&self, conv_id: &ConversationId) -> anyhow::Result<()> {
let member = self.members.get(conv_id).context("no such conversation")?;
let blob = member
- .group_ref()
- .map(bincode::serialize)
- .transpose()
- .context("serialize MLS group")?;
+ .serialize_mls_state()
+ .context("serialize MLS state")?;
let member_keys = member.member_identities();
diff --git a/crates/quicprochat-client/src/client/state.rs b/crates/quicprochat-client/src/client/state.rs
index 07db4bb..dfbd9e3 100644
--- a/crates/quicprochat-client/src/client/state.rs
+++ b/crates/quicprochat-client/src/client/state.rs
@@ -27,18 +27,31 @@ pub struct StoredState {
/// Cached member public keys for group participants.
#[serde(default)]
pub member_keys: Vec>,
+ /// MLS group ID bytes, needed to reload the group from StorageProvider state.
+ #[serde(default)]
+ pub group_id: Option>,
}
impl StoredState {
pub fn into_parts(self, state_path: &Path) -> anyhow::Result<(GroupMember, Option)> {
let identity = Arc::new(IdentityKeypair::from_seed(self.identity_seed));
- let group = self
- .group
- .map(|bytes| bincode::deserialize(&bytes).context("decode group"))
- .transpose()?;
- let key_store = DiskKeyStore::persistent(keystore_path(state_path))?;
let hybrid = self.hybrid_key.is_some();
- let member = GroupMember::new_with_state(identity, key_store, group, hybrid);
+
+ let member = match (self.group.as_ref(), self.group_id.as_ref()) {
+ (Some(storage_bytes), Some(gid)) => {
+ GroupMember::new_from_storage_bytes(
+ identity,
+ storage_bytes,
+ gid,
+ hybrid,
+ )
+ .context("restore MLS state from stored state")?
+ }
+ _ => {
+ let key_store = DiskKeyStore::persistent(keystore_path(state_path))?;
+ GroupMember::new_with_state(identity, key_store, None, hybrid)
+ }
+ };
let hybrid_kp = self
.hybrid_key
@@ -50,15 +63,15 @@ impl StoredState {
pub fn from_parts(member: &GroupMember, hybrid_kp: Option<&HybridKeypair>) -> anyhow::Result {
let group = member
- .group_ref()
- .map(|g| bincode::serialize(g).context("serialize group"))
- .transpose()?;
+ .serialize_mls_state()
+ .context("serialize MLS state")?;
Ok(Self {
identity_seed: *member.identity_seed(),
group,
hybrid_key: hybrid_kp.map(|kp| kp.to_bytes()),
member_keys: Vec::new(),
+ group_id: member.group_id(),
})
}
}
@@ -245,6 +258,7 @@ mod tests {
hybrid_key: None,
group: None,
member_keys: Vec::new(),
+ group_id: None,
};
let password = "test-password";
let plaintext = bincode::serialize(&state).unwrap();
@@ -268,6 +282,7 @@ mod tests {
}),
group: None,
member_keys: Vec::new(),
+ group_id: None,
};
let password = "another-password";
let plaintext = bincode::serialize(&state).unwrap();
@@ -285,6 +300,7 @@ mod tests {
hybrid_key: None,
group: None,
member_keys: Vec::new(),
+ group_id: None,
};
let plaintext = bincode::serialize(&state).unwrap();
let encrypted = encrypt_state("correct", &plaintext).unwrap();
diff --git a/crates/quicprochat-client/src/main.rs b/crates/quicprochat-client/src/main.rs
index 9b346e6..c2a4b73 100644
--- a/crates/quicprochat-client/src/main.rs
+++ b/crates/quicprochat-client/src/main.rs
@@ -28,12 +28,159 @@ use quicprochat_client::{
#[cfg(all(feature = "tui", not(feature = "v2")))]
use quicprochat_client::client::tui::run_tui;
+// ── Config file loading ──────────────────────────────────────────────────────
+//
+// Loads a TOML config file and sets QPQ_* environment variables for values
+// not already set. This runs BEFORE clap parses, so the natural precedence is:
+// CLI flags > environment variables > config file > compiled defaults.
+//
+// Config file search order:
+// 1. --config (parsed manually from argv)
+// 2. $QPC_CONFIG env var
+// 3. $XDG_CONFIG_HOME/qpc/config.toml (usually ~/.config/qpc/config.toml)
+// 4. ~/.qpc.toml
+#[cfg(not(feature = "v2"))]
+mod client_config {
+ use serde::Deserialize;
+ use std::path::PathBuf;
+
+ #[derive(Debug, Default, Deserialize)]
+ pub struct ClientFileConfig {
+ pub server: Option,
+ pub server_name: Option,
+ pub ca_cert: Option,
+ pub username: Option,
+ pub password: Option,
+ pub access_token: Option,
+ pub device_id: Option,
+ pub state_password: Option,
+ pub state: Option,
+ pub danger_accept_invalid_certs: Option,
+ pub no_server: Option,
+ }
+
+ /// Find and load the config file. Returns the parsed config (or default if
+ /// no file is found).
+ pub fn load_client_config() -> ClientFileConfig {
+ let path = find_config_path();
+ let path = match path {
+ Some(p) if p.exists() => p,
+ _ => return ClientFileConfig::default(),
+ };
+
+ match std::fs::read_to_string(&path) {
+ Ok(contents) => match toml::from_str(&contents) {
+ Ok(cfg) => {
+ eprintln!("Loaded config: {}", path.display());
+ cfg
+ }
+ Err(e) => {
+ eprintln!("Warning: failed to parse {}: {e}", path.display());
+ ClientFileConfig::default()
+ }
+ },
+ Err(e) => {
+ eprintln!("Warning: failed to read {}: {e}", path.display());
+ ClientFileConfig::default()
+ }
+ }
+ }
+
+ fn find_config_path() -> Option {
+ // 1. --config from argv (before clap parses).
+ let args: Vec = std::env::args().collect();
+ for i in 0..args.len().saturating_sub(1) {
+ if args[i] == "--config" || args[i] == "-c" {
+ return Some(PathBuf::from(&args[i + 1]));
+ }
+ }
+
+ // 2. $QPC_CONFIG env var.
+ if let Ok(p) = std::env::var("QPC_CONFIG") {
+ return Some(PathBuf::from(p));
+ }
+
+ // 3. $XDG_CONFIG_HOME/qpc/config.toml
+ let xdg = std::env::var("XDG_CONFIG_HOME")
+ .map(PathBuf::from)
+ .unwrap_or_else(|_| {
+ let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
+ PathBuf::from(home).join(".config")
+ });
+ let xdg_path = xdg.join("qpc").join("config.toml");
+ if xdg_path.exists() {
+ return Some(xdg_path);
+ }
+
+ // 4. ~/.qpc.toml
+ if let Ok(home) = std::env::var("HOME") {
+ let home_path = PathBuf::from(home).join(".qpc.toml");
+ if home_path.exists() {
+ return Some(home_path);
+ }
+ }
+
+ None
+ }
+
+ /// Set QPQ_* env vars from config values, but only if they're not already set.
+ pub fn apply_config_to_env(cfg: &ClientFileConfig) {
+ fn set_if_empty(key: &str, val: &str) {
+ if std::env::var(key).is_err() {
+ std::env::set_var(key, val);
+ }
+ }
+
+ if let Some(ref v) = cfg.server {
+ set_if_empty("QPQ_SERVER", v);
+ }
+ if let Some(ref v) = cfg.server_name {
+ set_if_empty("QPQ_SERVER_NAME", v);
+ }
+ if let Some(ref v) = cfg.ca_cert {
+ set_if_empty("QPQ_CA_CERT", v);
+ }
+ if let Some(ref v) = cfg.username {
+ set_if_empty("QPQ_USERNAME", v);
+ }
+ if let Some(ref v) = cfg.password {
+ set_if_empty("QPQ_PASSWORD", v);
+ }
+ if let Some(ref v) = cfg.access_token {
+ set_if_empty("QPQ_ACCESS_TOKEN", v);
+ }
+ if let Some(ref v) = cfg.device_id {
+ set_if_empty("QPQ_DEVICE_ID", v);
+ }
+ if let Some(ref v) = cfg.state_password {
+ set_if_empty("QPQ_STATE_PASSWORD", v);
+ }
+ if let Some(ref v) = cfg.state {
+ set_if_empty("QPQ_STATE", v);
+ }
+ if let Some(v) = cfg.danger_accept_invalid_certs {
+ if v {
+ set_if_empty("QPQ_DANGER_ACCEPT_INVALID_CERTS", "true");
+ }
+ }
+ if let Some(v) = cfg.no_server {
+ if v {
+ set_if_empty("QPQ_NO_SERVER", "true");
+ }
+ }
+ }
+}
+
// ── CLI ───────────────────────────────────────────────────────────────────────
#[cfg(not(feature = "v2"))]
#[derive(Debug, Parser)]
#[command(name = "qpc", about = "quicprochat CLI client", version)]
struct Args {
+ /// Path to a TOML config file (auto-detected from ~/.config/qpc/config.toml or ~/.qpc.toml).
+ #[arg(long, short = 'c', global = true, env = "QPC_CONFIG")]
+ config: Option,
+
/// Path to the server's TLS certificate (self-signed by default).
#[arg(
long,
@@ -540,6 +687,13 @@ async fn main() -> anyhow::Result<()> {
)
.init();
+ // Load config file and apply to env BEFORE clap parses (so config values
+ // act as defaults that env vars and CLI flags can override).
+ {
+ let cfg = client_config::load_client_config();
+ client_config::apply_config_to_env(&cfg);
+ }
+
let args = Args::parse();
if args.danger_accept_invalid_certs {
diff --git a/crates/quicprochat-core/Cargo.toml b/crates/quicprochat-core/Cargo.toml
index d535183..608026e 100644
--- a/crates/quicprochat-core/Cargo.toml
+++ b/crates/quicprochat-core/Cargo.toml
@@ -15,6 +15,7 @@ native = [
"dep:openmls",
"dep:openmls_rust_crypto",
"dep:openmls_traits",
+ "dep:openmls_memory_storage",
"dep:tls_codec",
"dep:opaque-ke",
"dep:bincode",
@@ -49,6 +50,7 @@ opaque-ke = { workspace = true, optional = true }
openmls = { workspace = true, optional = true }
openmls_rust_crypto = { workspace = true, optional = true }
openmls_traits = { workspace = true, optional = true }
+openmls_memory_storage = { workspace = true, optional = true }
tls_codec = { workspace = true, optional = true }
bincode = { workspace = true, optional = true }
diff --git a/crates/quicprochat-core/src/group.rs b/crates/quicprochat-core/src/group.rs
index e420aa4..c7c0d19 100644
--- a/crates/quicprochat-core/src/group.rs
+++ b/crates/quicprochat-core/src/group.rs
@@ -29,7 +29,7 @@
//! # Ratchet tree
//!
//! `use_ratchet_tree_extension = true` so that the ratchet tree is embedded
-//! in Welcome messages. `new_from_welcome` is called with `ratchet_tree = None`;
+//! in Welcome messages. `new_from_welcome` is called without a ratchet_tree;
//! openmls extracts the tree from the Welcome's `GroupInfo` extension.
use std::{path::Path, sync::Arc};
@@ -37,12 +37,13 @@ use std::{path::Path, sync::Arc};
use zeroize::Zeroizing;
use openmls::prelude::{
- Ciphersuite, Credential, CredentialType, CredentialWithKey, CryptoConfig, GroupId, KeyPackage,
- KeyPackageIn, MlsGroup, MlsGroupConfig, MlsMessageInBody, MlsMessageOut,
- ProcessedMessageContent, ProtocolMessage, ProtocolVersion, TlsDeserializeTrait,
- TlsSerializeTrait,
+ BasicCredential, Ciphersuite, Credential, CredentialWithKey, GroupId, KeyPackage,
+ KeyPackageIn, LeafNodeParameters, MlsGroup, MlsGroupCreateConfig, MlsGroupJoinConfig,
+ MlsMessageBodyIn, MlsMessageOut, ProcessedMessageContent, ProtocolMessage,
+ ProtocolVersion, StagedWelcome,
};
-use openmls_traits::OpenMlsCryptoProvider;
+use openmls_traits::OpenMlsProvider;
+use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait};
use crate::{
error::CoreError,
@@ -102,8 +103,10 @@ pub struct GroupMember {
identity: Arc,
/// Active MLS group, if any.
group: Option,
- /// Shared group configuration (wire format, ratchet tree extension, etc.).
- config: MlsGroupConfig,
+ /// Shared group creation configuration (wire format, ratchet tree extension, etc.).
+ create_config: MlsGroupCreateConfig,
+ /// Shared group join configuration (wire format, ratchet tree extension, etc.).
+ join_config: MlsGroupJoinConfig,
/// Whether this member uses hybrid (X25519 + ML-KEM-768) HPKE keys.
hybrid: bool,
}
@@ -139,7 +142,11 @@ impl GroupMember {
group: Option,
hybrid: bool,
) -> Self {
- let config = MlsGroupConfig::builder()
+ let create_config = MlsGroupCreateConfig::builder()
+ .use_ratchet_tree_extension(true)
+ .build();
+
+ let join_config = MlsGroupJoinConfig::builder()
.use_ratchet_tree_extension(true)
.build();
@@ -153,7 +160,8 @@ impl GroupMember {
backend,
identity,
group,
- config,
+ create_config,
+ join_config,
hybrid,
}
}
@@ -175,18 +183,19 @@ impl GroupMember {
///
/// Returns [`CoreError::Mls`] if openmls fails to create the KeyPackage.
pub fn generate_key_package(&mut self) -> Result, CoreError> {
- let credential_with_key = self.make_credential_with_key()?;
+ let credential_with_key = self.make_credential_with_key();
- let key_package = KeyPackage::builder()
+ let key_package_bundle = KeyPackage::builder()
.build(
- CryptoConfig::with_default_version(CIPHERSUITE),
+ CIPHERSUITE,
&self.backend,
self.identity.as_ref(),
credential_with_key,
)
.map_err(|e| CoreError::Mls(format!("{e:?}")))?;
- key_package
+ key_package_bundle
+ .key_package()
.tls_serialize_detached()
.map_err(|e| CoreError::Mls(format!("{e:?}")))
}
@@ -205,13 +214,13 @@ impl GroupMember {
///
/// Returns [`CoreError::Mls`] if the group already exists or openmls fails.
pub fn create_group(&mut self, group_id: &[u8]) -> Result<(), CoreError> {
- let credential_with_key = self.make_credential_with_key()?;
+ let credential_with_key = self.make_credential_with_key();
let mls_id = GroupId::from_slice(group_id);
let group = MlsGroup::new_with_group_id(
&self.backend,
self.identity.as_ref(),
- &self.config,
+ &self.create_config,
mls_id,
credential_with_key,
)
@@ -303,7 +312,7 @@ impl GroupMember {
let leaf_index = group
.members()
- .find(|m| m.credential.identity() == member_identity)
+ .find(|m| m.credential.serialized_content() == member_identity)
.map(|m| m.index)
.ok_or_else(|| CoreError::Mls("member not found in group".into()))?;
@@ -384,7 +393,11 @@ impl GroupMember {
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
let (proposal_out, _ref) = group
- .propose_self_update(&self.backend, self.identity.as_ref(), None)
+ .propose_self_update(
+ &self.backend,
+ self.identity.as_ref(),
+ LeafNodeParameters::default(),
+ )
.map_err(|e| CoreError::Mls(format!("propose_self_update: {e:?}")))?;
proposal_out
@@ -396,7 +409,7 @@ impl GroupMember {
pub fn has_pending_proposals(&self) -> bool {
self.group
.as_ref()
- .map(|g| g.pending_proposals().next().is_some())
+ .map(|g| g.has_pending_proposals())
.unwrap_or(false)
}
@@ -417,16 +430,22 @@ impl GroupMember {
let msg_in = openmls::prelude::MlsMessageIn::tls_deserialize(&mut welcome_bytes)
.map_err(|e| CoreError::Mls(format!("Welcome deserialise: {e:?}")))?;
- // into_welcome() is feature-gated in openmls 0.5; extract() is public.
let welcome = match msg_in.extract() {
- MlsMessageInBody::Welcome(w) => w,
+ MlsMessageBodyIn::Welcome(w) => w,
_ => return Err(CoreError::Mls("expected a Welcome message".into())),
};
- // ratchet_tree = None because use_ratchet_tree_extension = true embeds
- // the tree inside the Welcome's GroupInfo extension.
- let group = MlsGroup::new_from_welcome(&self.backend, &self.config, welcome, None)
- .map_err(|e| CoreError::Mls(format!("new_from_welcome: {e:?}")))?;
+ let staged = StagedWelcome::new_from_welcome(
+ &self.backend,
+ &self.join_config,
+ welcome,
+ None, // ratchet tree extracted from the Welcome's GroupInfo extension
+ )
+ .map_err(|e| CoreError::Mls(format!("new_from_welcome: {e:?}")))?;
+
+ let group = staged
+ .into_group(&self.backend)
+ .map_err(|e| CoreError::Mls(format!("into_group: {e:?}")))?;
self.group = Some(group);
Ok(())
@@ -508,10 +527,9 @@ impl GroupMember {
let msg_in = openmls::prelude::MlsMessageIn::tls_deserialize(&mut bytes)
.map_err(|e| CoreError::Mls(format!("message deserialise: {e:?}")))?;
- // into_protocol_message() is feature-gated; extract() + manual construction is not.
- let protocol_message = match msg_in.extract() {
- MlsMessageInBody::PrivateMessage(m) => ProtocolMessage::PrivateMessage(m),
- MlsMessageInBody::PublicMessage(m) => ProtocolMessage::PublicMessage(m),
+ let protocol_message: ProtocolMessage = match msg_in.extract() {
+ MlsMessageBodyIn::PrivateMessage(m) => m.into(),
+ MlsMessageBodyIn::PublicMessage(m) => m.into(),
_ => return Err(CoreError::Mls("not a protocol message".into())),
};
@@ -519,7 +537,7 @@ impl GroupMember {
.process_message(&self.backend, protocol_message)
.map_err(|e| CoreError::Mls(format!("process_message: {e:?}")))?;
- let sender_identity = processed.credential().identity().to_vec();
+ let sender_identity = processed.credential().serialized_content().to_vec();
match processed.into_content() {
ProcessedMessageContent::ApplicationMessage(app) => {
@@ -545,11 +563,15 @@ impl GroupMember {
}
// Proposals are stored for a later Commit; nothing to return yet.
ProcessedMessageContent::ProposalMessage(proposal) => {
- group.store_pending_proposal(*proposal);
+ group
+ .store_pending_proposal(self.backend.storage(), *proposal)
+ .map_err(|e| CoreError::Mls(format!("store_pending_proposal: {e:?}")))?;
Ok((sender_identity, ReceivedMessage::StateChanged))
}
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
- group.store_pending_proposal(*proposal);
+ group
+ .store_pending_proposal(self.backend.storage(), *proposal)
+ .map_err(|e| CoreError::Mls(format!("store_pending_proposal: {e:?}")))?;
Ok((sender_identity, ReceivedMessage::StateChanged))
}
}
@@ -597,6 +619,69 @@ impl GroupMember {
self.group.as_ref()
}
+ /// Serialize the MLS group state (via the backing `StorageProvider`).
+ ///
+ /// In openmls 0.8 the `MlsGroup` is no longer `Serialize`; its state is
+ /// held inside the `StorageProvider`. This method serializes the full
+ /// provider storage to bytes, which can later be restored with
+ /// [`new_from_storage_bytes`].
+ ///
+ /// Returns `None` if no active group exists.
+ ///
+ /// [`new_from_storage_bytes`]: Self::new_from_storage_bytes
+ pub fn serialize_mls_state(&self) -> Result