lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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:
MCargo.lock | 431+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
MCargo.toml | 12++++++++++++
Mcrates/simplex_smp_crypto/Cargo.toml | 13+++++++++++++
Mcrates/simplex_smp_crypto/src/error.rs | 20++++++++++++++++++++
Mcrates/simplex_smp_crypto/src/lib.rs | 24++++++++++++++++++++++++
Acrates/simplex_smp_crypto/src/official_ratchet.rs | 507+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); + } +}