commit 6ea8cf9a4791ff593a34c7c3dd02d88546c4de83
parent 8f5707c73e23c9841b84e6f96fe578e38b286e37
Author: triesap <tyson@radroots.org>
Date: Mon, 22 Jun 2026 23:03:24 +0000
simplex: add official ratchet primitives
- add X448 key agreement helpers for official SimpleX ratchets
- add SNTRUP761 key encapsulation helpers and size constants
- add AES-GCM padded payload encryption with official header lengths
- expose root and chain KDF helpers for runtime wiring
Diffstat:
6 files changed, 963 insertions(+), 44 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -40,7 +40,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"generic-array 0.14.7",
]
@@ -56,6 +56,20 @@ dependencies = [
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -394,6 +408,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
+name = "base16ct"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
+
+[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -534,6 +554,15 @@ dependencies = [
]
[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
name = "block-padding"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -672,13 +701,24 @@ dependencies = [
]
[[package]]
+name = "chacha20"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.1",
+]
+
+[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
- "chacha20",
+ "chacha20 0.9.1",
"cipher",
"poly1305",
"zeroize",
@@ -703,7 +743,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"inout",
"zeroize",
]
@@ -760,6 +800,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -819,6 +865,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -910,6 +962,12 @@ dependencies = [
]
[[package]]
+name = "cpubits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
+[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1056,6 +1114,21 @@ dependencies = [
]
[[package]]
+name = "crypto-bigint"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
+dependencies = [
+ "cpubits",
+ "ctutils",
+ "hybrid-array",
+ "num-traits",
+ "rand_core 0.10.1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1067,6 +1140,35 @@ dependencies = [
]
[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+ "subtle",
+]
+
+[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1075,7 +1177,7 @@ dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"curve25519-dalek-derive",
- "digest",
+ "digest 0.10.7",
"fiat-crypto",
"rustc_version",
"subtle",
@@ -1232,12 +1334,22 @@ version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
- "const-oid",
+ "const-oid 0.9.6",
"pem-rfc7468",
"zeroize",
]
[[package]]
+name = "der"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b"
+dependencies = [
+ "const-oid 0.10.2",
+ "zeroize",
+]
+
+[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1297,13 +1409,23 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "block-buffer",
- "const-oid",
- "crypto-common",
+ "block-buffer 0.10.4",
+ "const-oid 0.9.6",
+ "crypto-common 0.1.7",
"subtle",
]
[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "crypto-common 0.2.2",
+]
+
+[[package]]
name = "directories"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1410,13 +1532,13 @@ version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
- "der",
- "digest",
- "elliptic-curve",
+ "der 0.7.10",
+ "digest 0.10.7",
+ "elliptic-curve 0.13.8",
"rfc6979",
"serdect",
"signature",
- "spki",
+ "spki 0.7.3",
]
[[package]]
@@ -1425,7 +1547,7 @@ version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
- "pkcs8",
+ "pkcs8 0.10.2",
"signature",
]
@@ -1444,6 +1566,19 @@ dependencies = [
]
[[package]]
+name = "ed448-goldilocks"
+version = "0.14.0-pre.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8be3c3dac25c52fc0f2f193687d09efa1e4c8981f6b8544acc9faffc1c29bd0"
+dependencies = [
+ "elliptic-curve 0.14.0-rc.35",
+ "hash2curve",
+ "rand_core 0.10.1",
+ "shake",
+ "subtle",
+]
+
+[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1464,22 +1599,41 @@ version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
- "base16ct",
- "crypto-bigint",
- "digest",
- "ff",
+ "base16ct 0.2.0",
+ "crypto-bigint 0.5.5",
+ "digest 0.10.7",
+ "ff 0.13.1",
"generic-array 0.14.7",
- "group",
+ "group 0.13.0",
"pem-rfc7468",
- "pkcs8",
+ "pkcs8 0.10.2",
"rand_core 0.6.4",
- "sec1",
+ "sec1 0.7.3",
"serdect",
"subtle",
"zeroize",
]
[[package]]
+name = "elliptic-curve"
+version = "0.14.0-rc.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51c58d86e2f3cebbf2dfd94c4bf049585c7def71058ba506bfdafcb57652a34b"
+dependencies = [
+ "base16ct 1.0.0",
+ "crypto-bigint 0.7.5",
+ "crypto-common 0.2.2",
+ "ff 0.14.0",
+ "group 0.14.0",
+ "hybrid-array",
+ "pkcs8 0.11.0",
+ "rand_core 0.10.1",
+ "sec1 0.8.1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "embedded-alloc"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1607,6 +1761,16 @@ dependencies = [
]
[[package]]
+name = "ff"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
+dependencies = [
+ "rand_core 0.10.1",
+ "subtle",
+]
+
+[[package]]
name = "ff_derive"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1898,11 +2062,22 @@ dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
+ "rand_core 0.10.1",
"wasip2",
"wasip3",
]
[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1932,12 +2107,23 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
- "ff",
+ "ff 0.13.1",
"rand_core 0.6.4",
"subtle",
]
[[package]]
+name = "group"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
+dependencies = [
+ "ff 0.14.0",
+ "rand_core 0.10.1",
+ "subtle",
+]
+
+[[package]]
name = "h2"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1957,6 +2143,16 @@ dependencies = [
]
[[package]]
+name = "hash2curve"
+version = "0.14.0-rc.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e5e38a1358dfed60ae56c6d62b2d1466853d162d9a518da0673e3670d95fd10"
+dependencies = [
+ "digest 0.11.3",
+ "elliptic-curve 0.14.0-rc.35",
+]
+
+[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2064,7 +2260,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -2122,6 +2318,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
+name = "hybrid-array"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
+dependencies = [
+ "subtle",
+ "typenum",
+ "zeroize",
+]
+
+[[package]]
name = "hyper"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2488,7 +2695,7 @@ checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
dependencies = [
"cfg-if",
"ecdsa",
- "elliptic-curve",
+ "elliptic-curve 0.13.8",
"once_cell",
"serdect",
"sha2",
@@ -2496,6 +2703,16 @@ dependencies = [
]
[[package]]
+name = "keccak"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+]
+
+[[package]]
name = "keyring"
version = "3.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2745,7 +2962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -2855,7 +3072,7 @@ dependencies = [
"bip39",
"bitcoin_hashes",
"cbc",
- "chacha20",
+ "chacha20 0.9.1",
"chacha20poly1305",
"getrandom 0.2.17",
"hex",
@@ -3224,7 +3441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [
"ecdsa",
- "elliptic-curve",
+ "elliptic-curve 0.13.8",
"primeorder",
"sha2",
]
@@ -3263,7 +3480,7 @@ version = "0.4.3-succinct"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2077757c7cb514202ccb5368f521f23f5709c720599e6545c683c66e0a52d2d8"
dependencies = [
- "ff",
+ "ff 0.13.1",
"num-bigint 0.4.6",
"p3-field",
"p3-poseidon2",
@@ -3581,7 +3798,7 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
- "digest",
+ "digest 0.10.7",
"hmac",
]
@@ -3701,8 +3918,18 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
- "der",
- "spki",
+ "der 0.7.10",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
+dependencies = [
+ "der 0.8.0",
+ "spki 0.8.0",
]
[[package]]
@@ -3729,6 +3956,18 @@ dependencies = [
]
[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3774,7 +4013,7 @@ version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
- "elliptic-curve",
+ "elliptic-curve 0.13.8",
]
[[package]]
@@ -4396,12 +4635,15 @@ dependencies = [
name = "radroots_simplex_smp_crypto"
version = "0.1.0-alpha.2"
dependencies = [
+ "aes-gcm",
"ed25519-dalek",
"getrandom 0.2.17",
"hkdf",
"radroots_simplex_smp_proto",
"sha2",
+ "sntrup761",
"x25519-dalek",
+ "x448",
"xsalsa20poly1305",
]
@@ -4520,6 +4762,17 @@ dependencies = [
]
[[package]]
+name = "rand"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
+dependencies = [
+ "chacha20 0.10.0",
+ "getrandom 0.4.2",
+ "rand_core 0.10.1",
+]
+
+[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4540,6 +4793,16 @@ dependencies = [
]
[[package]]
+name = "rand_chacha"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.10.1",
+]
+
+[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4558,6 +4821,12 @@ dependencies = [
]
[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
name = "range-set-blaze"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5039,16 +5308,30 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
- "base16ct",
- "der",
+ "base16ct 0.2.0",
+ "der 0.7.10",
"generic-array 0.14.7",
- "pkcs8",
+ "pkcs8 0.10.2",
"serdect",
"subtle",
"zeroize",
]
[[package]]
+name = "sec1"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
+dependencies = [
+ "base16ct 1.0.0",
+ "ctutils",
+ "der 0.8.0",
+ "hybrid-array",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "secp256k1"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5202,7 +5485,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
dependencies = [
- "base16ct",
+ "base16ct 0.2.0",
"serde",
]
@@ -5240,7 +5523,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -5257,7 +5540,28 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
+ "sha2-asm",
+]
+
+[[package]]
+name = "sha2-asm"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b845214d6175804686b2bd482bcffe96651bb2d1200742b712003504a2dac1ab"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "shake"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
+dependencies = [
+ "digest 0.11.3",
+ "keccak",
+ "sponge-cursor",
]
[[package]]
@@ -5291,7 +5595,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
- "digest",
+ "digest 0.10.7",
"rand_core 0.6.4",
]
@@ -5409,7 +5713,7 @@ version = "6.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1243ca619a3f4c07f536a060d1fd2c6f779d5b0e4cfeee3acee4d1f76ae35cb4"
dependencies = [
- "ff",
+ "ff 0.13.1",
"p3-bn254-fr",
"serde",
"slop-algebra",
@@ -5761,6 +6065,20 @@ dependencies = [
]
[[package]]
+name = "sntrup761"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7fadfbd0a7d18ce7b04ed753c018a3e5d2ac2b3603f70507cd84012e76fa8a8"
+dependencies = [
+ "rand 0.10.1",
+ "rand_chacha 0.10.0",
+ "sha2",
+ "subtle",
+ "thiserror 2.0.18",
+ "zeroize",
+]
+
+[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5951,7 +6269,7 @@ checksum = "0bd90718c62dc06a42b9a47308ca914bb6d3f04f9265c77cb76bc135929cd59d"
dependencies = [
"cfg-if",
"dashu",
- "elliptic-curve",
+ "elliptic-curve 0.13.8",
"generic-array 1.1.0",
"itertools 0.14.0",
"k256",
@@ -6395,10 +6713,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
- "der",
+ "der 0.7.10",
+]
+
+[[package]]
+name = "spki"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
+dependencies = [
+ "der 0.8.0",
]
[[package]]
+name = "sponge-cursor"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
+
+[[package]]
name = "sqlx"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7254,9 +7587,9 @@ dependencies = [
[[package]]
name = "typenum"
-version = "1.19.0"
+version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "ucd-trie"
@@ -7309,7 +7642,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"subtle",
]
@@ -8077,6 +8410,16 @@ dependencies = [
]
[[package]]
+name = "x448"
+version = "0.14.0-pre.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f73ce51d17316f3acbb7b826903ec4cb948dbeee2d8e9821cd2c888847d3443a"
+dependencies = [
+ "ed448-goldilocks",
+ "zeroize",
+]
+
+[[package]]
name = "x509-parser"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -100,6 +100,10 @@ radroots_sp1_guest_trade = { path = "crates/sp1_guest_trade", version = "0.1.0-a
radroots_sp1_host_trade = { path = "crates/sp1_host_trade", version = "0.1.0-alpha.2", default-features = false }
anyhow = { version = "1" }
+aes-gcm = { version = "0.10.3", default-features = false, features = [
+ "aes",
+ "alloc",
+] }
base64 = { version = "0.22", default-features = false, features = ["alloc"] }
bincode = { version = "1.3.3" }
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
@@ -134,6 +138,11 @@ serde = { version = "1", default-features = false, features = [
] }
serde_json = { version = "1", default-features = false, features = ["alloc"] }
sha2 = { version = "0.10", default-features = false }
+sntrup761 = { version = "0.4.0", default-features = false, features = [
+ "dcap",
+ "ecap",
+ "kgen",
+] }
sqlx = { version = "0.8.6", default-features = false }
sp1-build = { version = "6.2.3" }
sp1-sdk = { version = "6.2.3", default-features = false }
@@ -149,6 +158,9 @@ xsalsa20poly1305 = { version = "0.9", default-features = false }
x25519-dalek = { version = "2", default-features = false, features = [
"static_secrets",
] }
+x448 = { version = "0.14.0-pre.10", default-features = false, features = [
+ "static_secrets",
+] }
sled = { version = "0.34" }
tempfile = { version = "3" }
tar = { version = "0.4" }
diff --git a/crates/simplex_smp_crypto/Cargo.toml b/crates/simplex_smp_crypto/Cargo.toml
@@ -15,6 +15,7 @@ readme = "README"
[features]
default = ["std"]
std = [
+ "aes-gcm/std",
"ed25519-dalek/std",
"getrandom/std",
"radroots_simplex_smp_proto/std",
@@ -23,6 +24,10 @@ std = [
]
[dependencies]
+aes-gcm = { workspace = true, default-features = false, features = [
+ "aes",
+ "alloc",
+] }
ed25519-dalek = { workspace = true, default-features = false, features = [
"alloc",
] }
@@ -30,7 +35,15 @@ getrandom = { workspace = true, default-features = false }
hkdf = { workspace = true, default-features = false }
radroots_simplex_smp_proto = { workspace = true, default-features = false }
sha2 = { workspace = true, default-features = false }
+sntrup761 = { workspace = true, default-features = false, features = [
+ "dcap",
+ "ecap",
+ "kgen",
+] }
xsalsa20poly1305 = { workspace = true, default-features = false }
x25519-dalek = { workspace = true, default-features = false, features = [
"static_secrets",
] }
+x448 = { workspace = true, default-features = false, features = [
+ "static_secrets",
+] }
diff --git a/crates/simplex_smp_crypto/src/error.rs b/crates/simplex_smp_crypto/src/error.rs
@@ -21,6 +21,11 @@ pub enum RadrootsSimplexSmpCryptoError {
InvalidSessionIdentifier(String),
InvalidKeyDerivationLength(usize),
InvalidSecretBoxChainKeyLength(usize),
+ AesGcmAuthenticationFailed,
+ InvalidOfficialRatchetVersion(u16),
+ InvalidOfficialRatchetPadding,
+ InvalidPqKeyLength(usize),
+ InvalidPqCiphertextLength(usize),
}
impl From<RadrootsSimplexSmpProtoError> for RadrootsSimplexSmpCryptoError {
@@ -88,6 +93,21 @@ impl fmt::Display for RadrootsSimplexSmpCryptoError {
Self::InvalidSecretBoxChainKeyLength(length) => {
write!(f, "invalid SMP secretbox chain key length {length}")
}
+ Self::AesGcmAuthenticationFailed => {
+ write!(f, "failed to authenticate SMP AES-GCM payload")
+ }
+ Self::InvalidOfficialRatchetVersion(version) => {
+ write!(f, "invalid official SMP ratchet version {version}")
+ }
+ Self::InvalidOfficialRatchetPadding => {
+ write!(f, "invalid official SMP ratchet padding")
+ }
+ Self::InvalidPqKeyLength(length) => {
+ write!(f, "invalid SMP PQ key length {length}")
+ }
+ Self::InvalidPqCiphertextLength(length) => {
+ write!(f, "invalid SMP PQ ciphertext length {length}")
+ }
}
}
}
diff --git a/crates/simplex_smp_crypto/src/lib.rs b/crates/simplex_smp_crypto/src/lib.rs
@@ -6,6 +6,7 @@ extern crate alloc;
pub mod auth;
pub mod error;
pub mod message;
+pub mod official_ratchet;
pub mod ratchet;
pub mod prelude {
@@ -22,6 +23,29 @@ pub mod prelude {
advance_secretbox_chain, decrypt_no_pad, decrypt_padded, derive_shared_secret,
encrypt_no_pad, encrypt_padded, init_secretbox_chain, random_nonce,
};
+ pub use crate::official_ratchet::{
+ RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH, RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH, RADROOTS_SIMPLEX_OFFICIAL_CHAIN_RATCHET_INFO,
+ RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
+ RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION,
+ RADROOTS_SIMPLEX_OFFICIAL_PQ_RATCHET_HEADER_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_RATCHET_HEADER_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_ROOT_RATCHET_INFO,
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PRIVATE_KEY_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_X3DH_INFO, RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH,
+ RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH, RadrootsSimplexOfficialAesGcmPayload,
+ RadrootsSimplexOfficialChainKdfOutput, RadrootsSimplexOfficialRootKdfOutput,
+ RadrootsSimplexOfficialSntrup761Keypair, RadrootsSimplexOfficialX448Keypair,
+ decapsulate_official_sntrup761, derive_official_x448_shared_secret,
+ encapsulate_official_sntrup761, generate_official_sntrup761_keypair,
+ generate_official_x448_keypair, official_aes_gcm_decrypt_padded,
+ official_aes_gcm_encrypt_padded, official_chain_kdf, official_full_header_len,
+ official_ratchet_header_len, official_root_kdf, official_sntrup761_keypair_from_seed,
+ official_x448_keypair_from_seed,
+ };
pub use crate::ratchet::{
RadrootsSimplexSmpRatchetHeader, RadrootsSimplexSmpRatchetRole,
RadrootsSimplexSmpRatchetState,
diff --git a/crates/simplex_smp_crypto/src/official_ratchet.rs b/crates/simplex_smp_crypto/src/official_ratchet.rs
@@ -0,0 +1,507 @@
+use crate::error::RadrootsSimplexSmpCryptoError;
+use aes_gcm::aead::consts::U16;
+use aes_gcm::aead::{Aead, KeyInit, Payload};
+use aes_gcm::{AesGcm, Nonce, aes::Aes256};
+use alloc::vec::Vec;
+use hkdf::Hkdf;
+use sha2::{Digest, Sha256, Sha512};
+
+pub const RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION: u16 = 2;
+pub const RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION: u16 = 3;
+pub const RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION: u16 = 3;
+pub const RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH: usize = 56;
+pub const RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH: usize = 56;
+pub const RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH: usize = 32;
+pub const RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH: usize = 16;
+pub const RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH: usize = 16;
+pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH: usize = sntrup761::PUBLIC_KEY_SIZE;
+pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PRIVATE_KEY_LENGTH: usize =
+ sntrup761::SECRET_KEY_SIZE;
+pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH: usize = sntrup761::CIPHERTEXT_SIZE;
+pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH: usize =
+ sntrup761::SHARED_SECRET_SIZE;
+pub const RADROOTS_SIMPLEX_OFFICIAL_RATCHET_HEADER_LENGTH: usize = 88;
+pub const RADROOTS_SIMPLEX_OFFICIAL_PQ_RATCHET_HEADER_LENGTH: usize = 2_310;
+pub const RADROOTS_SIMPLEX_OFFICIAL_ROOT_RATCHET_INFO: &[u8] = b"SimpleXRootRatchet";
+pub const RADROOTS_SIMPLEX_OFFICIAL_CHAIN_RATCHET_INFO: &[u8] = b"SimpleXChainRatchet";
+pub const RADROOTS_SIMPLEX_OFFICIAL_X3DH_INFO: &[u8] = b"SimpleXX3DH";
+
+const RADROOTS_SIMPLEX_OFFICIAL_HKDF3_OUTPUT_LENGTH: usize =
+ RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH * 3;
+const RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES: usize = 2;
+type RadrootsSimplexOfficialAes256Gcm = AesGcm<Aes256, U16>;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RadrootsSimplexOfficialX448Keypair {
+ pub public_key: Vec<u8>,
+ pub private_key: Vec<u8>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RadrootsSimplexOfficialSntrup761Keypair {
+ pub public_key: Vec<u8>,
+ pub private_key: Vec<u8>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RadrootsSimplexOfficialAesGcmPayload {
+ pub auth_tag: Vec<u8>,
+ pub ciphertext: Vec<u8>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RadrootsSimplexOfficialRootKdfOutput {
+ pub root_key: Vec<u8>,
+ pub chain_key: Vec<u8>,
+ pub next_header_key: Vec<u8>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RadrootsSimplexOfficialChainKdfOutput {
+ pub chain_key: Vec<u8>,
+ pub message_key: Vec<u8>,
+ pub message_iv: [u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH],
+ pub header_iv: [u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH],
+}
+
+pub fn official_ratchet_header_len(
+ version: u16,
+ pq_enabled: bool,
+) -> Result<usize, RadrootsSimplexSmpCryptoError> {
+ if version < RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION
+ || version > RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION
+ {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidOfficialRatchetVersion(version));
+ }
+ Ok(
+ if pq_enabled && version >= RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION {
+ RADROOTS_SIMPLEX_OFFICIAL_PQ_RATCHET_HEADER_LENGTH
+ } else {
+ RADROOTS_SIMPLEX_OFFICIAL_RATCHET_HEADER_LENGTH
+ },
+ )
+}
+
+pub fn official_full_header_len(
+ version: u16,
+ pq_enabled: bool,
+) -> Result<usize, RadrootsSimplexSmpCryptoError> {
+ Ok(2 + 1
+ + official_ratchet_header_len(version, pq_enabled)?
+ + RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
+ + RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH)
+}
+
+pub fn official_x448_keypair_from_seed(seed: &[u8]) -> RadrootsSimplexOfficialX448Keypair {
+ let digest = Sha512::digest(seed);
+ let mut private_key = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH];
+ private_key.copy_from_slice(&digest[..RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH]);
+ official_x448_keypair_from_private(private_key)
+}
+
+pub fn generate_official_x448_keypair()
+-> Result<RadrootsSimplexOfficialX448Keypair, RadrootsSimplexSmpCryptoError> {
+ let mut private_key = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH];
+ getrandom::getrandom(&mut private_key)
+ .map_err(|_| RadrootsSimplexSmpCryptoError::EntropyUnavailable)?;
+ Ok(official_x448_keypair_from_private(private_key))
+}
+
+pub fn derive_official_x448_shared_secret(
+ private_key: &[u8],
+ public_key: &[u8],
+) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
+ let private_key: [u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH] = private_key
+ .try_into()
+ .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(private_key.len()))?;
+ let public_key = x448::PublicKey::from_bytes(public_key).ok_or(
+ RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(public_key.len()),
+ )?;
+ let private = x448::StaticSecret::from(private_key);
+ Ok(private.diffie_hellman(&public_key).as_bytes().to_vec())
+}
+
+pub fn official_sntrup761_keypair_from_seed(
+ seed: &[u8],
+) -> RadrootsSimplexOfficialSntrup761Keypair {
+ let seed = pq_seed(seed);
+ let (public_key, private_key) = sntrup761::generate_key_from_seed(seed);
+ RadrootsSimplexOfficialSntrup761Keypair {
+ public_key: public_key.as_ref().to_vec(),
+ private_key: private_key.as_ref().to_vec(),
+ }
+}
+
+pub fn generate_official_sntrup761_keypair()
+-> Result<RadrootsSimplexOfficialSntrup761Keypair, RadrootsSimplexSmpCryptoError> {
+ let mut seed = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH];
+ getrandom::getrandom(&mut seed)
+ .map_err(|_| RadrootsSimplexSmpCryptoError::EntropyUnavailable)?;
+ Ok(official_sntrup761_keypair_from_seed(&seed))
+}
+
+pub fn encapsulate_official_sntrup761(
+ public_key: &[u8],
+ seed: &[u8],
+) -> Result<(Vec<u8>, Vec<u8>), RadrootsSimplexSmpCryptoError> {
+ let public_key = sntrup761::EncapsulationKey::try_from(public_key)
+ .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPqKeyLength(public_key.len()))?;
+ let (ciphertext, shared_secret) = public_key.encapsulate_deterministic(pq_seed(seed));
+ Ok((
+ ciphertext.as_ref().to_vec(),
+ shared_secret.as_ref().to_vec(),
+ ))
+}
+
+pub fn decapsulate_official_sntrup761(
+ private_key: &[u8],
+ ciphertext: &[u8],
+) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
+ let private_key = sntrup761::DecapsulationKey::try_from(private_key)
+ .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(private_key.len()))?;
+ let ciphertext = sntrup761::Ciphertext::try_from(ciphertext)
+ .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPqCiphertextLength(ciphertext.len()))?;
+ Ok(private_key.decapsulate(&ciphertext).as_ref().to_vec())
+}
+
+pub fn official_root_kdf(
+ root_key: &[u8],
+ dh_shared_secret: &[u8],
+ pq_shared_secret: Option<&[u8]>,
+) -> Result<RadrootsSimplexOfficialRootKdfOutput, RadrootsSimplexSmpCryptoError> {
+ if root_key.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
+ root_key.len(),
+ ));
+ }
+ let mut input =
+ Vec::with_capacity(dh_shared_secret.len() + pq_shared_secret.map_or(0, <[u8]>::len));
+ input.extend_from_slice(dh_shared_secret);
+ if let Some(shared_secret) = pq_shared_secret {
+ input.extend_from_slice(shared_secret);
+ }
+ let (root_key, chain_key, next_header_key) = official_hkdf3(
+ root_key,
+ &input,
+ RADROOTS_SIMPLEX_OFFICIAL_ROOT_RATCHET_INFO,
+ )?;
+ Ok(RadrootsSimplexOfficialRootKdfOutput {
+ root_key,
+ chain_key,
+ next_header_key,
+ })
+}
+
+pub fn official_chain_kdf(
+ chain_key: &[u8],
+) -> Result<RadrootsSimplexOfficialChainKdfOutput, RadrootsSimplexSmpCryptoError> {
+ if chain_key.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
+ chain_key.len(),
+ ));
+ }
+ let (chain_key, message_key, iv_material) =
+ official_hkdf3(b"", chain_key, RADROOTS_SIMPLEX_OFFICIAL_CHAIN_RATCHET_INFO)?;
+ let mut message_iv = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH];
+ let mut header_iv = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH];
+ message_iv.copy_from_slice(&iv_material[..RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH]);
+ header_iv.copy_from_slice(
+ &iv_material
+ [RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH..RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH * 2],
+ );
+ Ok(RadrootsSimplexOfficialChainKdfOutput {
+ chain_key,
+ message_key,
+ message_iv,
+ header_iv,
+ })
+}
+
+pub fn official_aes_gcm_encrypt_padded(
+ key: &[u8],
+ iv: &[u8],
+ plaintext: &[u8],
+ padded_len: usize,
+ associated_data: &[u8],
+) -> Result<RadrootsSimplexOfficialAesGcmPayload, RadrootsSimplexSmpCryptoError> {
+ let padded = official_pad(plaintext, padded_len)?;
+ let encrypted = official_aes_gcm_cipher(key)?
+ .encrypt(
+ official_aes_gcm_nonce(iv)?,
+ Payload {
+ msg: &padded,
+ aad: associated_data,
+ },
+ )
+ .map_err(|_| RadrootsSimplexSmpCryptoError::AesGcmAuthenticationFailed)?;
+ split_official_aes_gcm_payload(&encrypted)
+}
+
+pub fn official_aes_gcm_decrypt_padded(
+ key: &[u8],
+ iv: &[u8],
+ payload: &RadrootsSimplexOfficialAesGcmPayload,
+ associated_data: &[u8],
+) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
+ if payload.auth_tag.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidSignatureLength(
+ payload.auth_tag.len(),
+ ));
+ }
+ let mut encrypted = Vec::with_capacity(payload.ciphertext.len() + payload.auth_tag.len());
+ encrypted.extend_from_slice(&payload.ciphertext);
+ encrypted.extend_from_slice(&payload.auth_tag);
+ let padded = official_aes_gcm_cipher(key)?
+ .decrypt(
+ official_aes_gcm_nonce(iv)?,
+ Payload {
+ msg: &encrypted,
+ aad: associated_data,
+ },
+ )
+ .map_err(|_| RadrootsSimplexSmpCryptoError::AesGcmAuthenticationFailed)?;
+ official_unpad(&padded)
+}
+
+fn official_x448_keypair_from_private(
+ private_key: [u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH],
+) -> RadrootsSimplexOfficialX448Keypair {
+ let private = x448::StaticSecret::from(private_key);
+ let public = x448::PublicKey::from(&private);
+ RadrootsSimplexOfficialX448Keypair {
+ public_key: public.as_bytes().to_vec(),
+ private_key: private.as_bytes().to_vec(),
+ }
+}
+
+fn official_aes_gcm_cipher(
+ key: &[u8],
+) -> Result<RadrootsSimplexOfficialAes256Gcm, RadrootsSimplexSmpCryptoError> {
+ if key.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
+ key.len(),
+ ));
+ }
+ RadrootsSimplexOfficialAes256Gcm::new_from_slice(key)
+ .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(key.len()))
+}
+
+fn official_aes_gcm_nonce(iv: &[u8]) -> Result<&Nonce<U16>, RadrootsSimplexSmpCryptoError> {
+ if iv.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidNonceLength(iv.len()));
+ }
+ Ok(Nonce::<U16>::from_slice(iv))
+}
+
+fn split_official_aes_gcm_payload(
+ encrypted: &[u8],
+) -> Result<RadrootsSimplexOfficialAesGcmPayload, RadrootsSimplexSmpCryptoError> {
+ if encrypted.len() < RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
+ encrypted.len(),
+ ));
+ }
+ let tag_offset = encrypted.len() - RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH;
+ let (ciphertext, auth_tag) = encrypted.split_at(tag_offset);
+ Ok(RadrootsSimplexOfficialAesGcmPayload {
+ auth_tag: auth_tag.to_vec(),
+ ciphertext: ciphertext.to_vec(),
+ })
+}
+
+fn official_pad(
+ plaintext: &[u8],
+ padded_len: usize,
+) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
+ if plaintext.len() > u16::MAX as usize
+ || plaintext
+ .len()
+ .saturating_add(RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES)
+ > padded_len
+ {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidMessageLength {
+ actual: plaintext.len(),
+ padded: padded_len,
+ });
+ }
+ let mut padded = Vec::with_capacity(padded_len);
+ padded.extend_from_slice(&(plaintext.len() as u16).to_be_bytes());
+ padded.extend_from_slice(plaintext);
+ padded.resize(padded_len, b'#');
+ Ok(padded)
+}
+
+fn official_unpad(padded: &[u8]) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
+ if padded.len() < RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidOfficialRatchetPadding);
+ }
+ let length = u16::from_be_bytes([padded[0], padded[1]]) as usize;
+ if length
+ > padded
+ .len()
+ .saturating_sub(RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES)
+ {
+ return Err(RadrootsSimplexSmpCryptoError::InvalidOfficialRatchetPadding);
+ }
+ Ok(padded[RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES
+ ..RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES + length]
+ .to_vec())
+}
+
+fn official_hkdf3(
+ salt: &[u8],
+ ikm: &[u8],
+ info: &[u8],
+) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), RadrootsSimplexSmpCryptoError> {
+ let hkdf = Hkdf::<Sha512>::new(Some(salt), ikm);
+ let mut output = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_HKDF3_OUTPUT_LENGTH];
+ hkdf.expand(info, &mut output).map_err(|_| {
+ RadrootsSimplexSmpCryptoError::InvalidKeyDerivationLength(
+ RADROOTS_SIMPLEX_OFFICIAL_HKDF3_OUTPUT_LENGTH,
+ )
+ })?;
+ Ok((
+ output[..RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH].to_vec(),
+ output[RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
+ ..RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH * 2]
+ .to_vec(),
+ output[RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH * 2..].to_vec(),
+ ))
+}
+
+fn pq_seed(seed: &[u8]) -> [u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH] {
+ let digest = Sha256::digest(seed);
+ let mut value = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH];
+ value.copy_from_slice(&digest);
+ value
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn official_header_lengths_match_upstream_constants() {
+ assert_eq!(official_ratchet_header_len(2, false).unwrap(), 88);
+ assert_eq!(official_ratchet_header_len(3, false).unwrap(), 88);
+ assert_eq!(official_ratchet_header_len(3, true).unwrap(), 2_310);
+ assert_eq!(official_full_header_len(3, false).unwrap(), 123);
+ assert_eq!(official_full_header_len(3, true).unwrap(), 2_345);
+ }
+
+ #[test]
+ fn x448_key_agreement_roundtrips() {
+ let alice = official_x448_keypair_from_seed(b"rr-synth-official-alice-x448");
+ let bob = official_x448_keypair_from_seed(b"rr-synth-official-bob-x448");
+
+ let alice_secret =
+ derive_official_x448_shared_secret(&alice.private_key, &bob.public_key).unwrap();
+ let bob_secret =
+ derive_official_x448_shared_secret(&bob.private_key, &alice.public_key).unwrap();
+
+ assert_eq!(
+ alice.public_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH
+ );
+ assert_eq!(
+ alice.private_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH
+ );
+ assert_eq!(
+ alice_secret.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH
+ );
+ assert_eq!(alice_secret, bob_secret);
+ }
+
+ #[test]
+ fn sntrup761_encapsulation_roundtrips() {
+ let recipient = official_sntrup761_keypair_from_seed(b"rr-synth-official-pq-recipient");
+ let (ciphertext, sender_secret) =
+ encapsulate_official_sntrup761(&recipient.public_key, b"rr-synth-official-pq-send")
+ .unwrap();
+ let receiver_secret =
+ decapsulate_official_sntrup761(&recipient.private_key, &ciphertext).unwrap();
+
+ assert_eq!(
+ recipient.public_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH
+ );
+ assert_eq!(
+ recipient.private_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PRIVATE_KEY_LENGTH
+ );
+ assert_eq!(
+ ciphertext.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH
+ );
+ assert_eq!(
+ sender_secret.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH
+ );
+ assert_eq!(sender_secret, receiver_secret);
+ }
+
+ #[test]
+ fn official_aes_gcm_padding_authenticates_associated_data() {
+ let key = [11_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH];
+ let iv = [12_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH];
+ let associated_data = b"rr-synth-official-associated-data";
+ let payload = official_aes_gcm_encrypt_padded(
+ &key,
+ &iv,
+ b"hello official simplex",
+ 96,
+ associated_data,
+ )
+ .unwrap();
+
+ assert_eq!(
+ payload.auth_tag.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
+ );
+ assert_eq!(payload.ciphertext.len(), 96);
+ assert_ne!(payload.ciphertext, b"hello official simplex");
+ assert_eq!(
+ official_aes_gcm_decrypt_padded(&key, &iv, &payload, associated_data).unwrap(),
+ b"hello official simplex"
+ );
+ assert!(matches!(
+ official_aes_gcm_decrypt_padded(&key, &iv, &payload, b"wrong-ad").unwrap_err(),
+ RadrootsSimplexSmpCryptoError::AesGcmAuthenticationFailed
+ ));
+ }
+
+ #[test]
+ fn official_kdfs_split_root_and_chain_material() {
+ let root = official_root_kdf(
+ &[1_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH],
+ &[2_u8; RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH],
+ Some(&[3_u8; RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH]),
+ )
+ .unwrap();
+ let chain = official_chain_kdf(&root.chain_key).unwrap();
+
+ assert_eq!(
+ root.root_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
+ );
+ assert_eq!(
+ root.chain_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
+ );
+ assert_eq!(
+ root.next_header_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
+ );
+ assert_eq!(
+ chain.chain_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
+ );
+ assert_eq!(
+ chain.message_key.len(),
+ RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
+ );
+ assert_ne!(chain.message_iv, chain.header_iv);
+ }
+}