MOD: decupple this rust project from dart

This commit is contained in:
JohnE 2026-02-24 18:47:25 -08:00
parent c1d6086214
commit db897b5abe
34 changed files with 269 additions and 6937 deletions

524
Cargo.lock generated
View File

@ -2,21 +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 = "aho-corasick"
version = "1.1.4"
@ -26,34 +11,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "allo-isolate"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550"
dependencies = [
"anyhow",
"atomic",
"backtrace",
]
[[package]]
name = "android_log-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
[[package]]
name = "android_logger"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3"
dependencies = [
"android_log-sys",
"env_filter 0.1.4",
"log",
]
[[package]]
name = "anstream"
version = "0.6.21"
@ -123,27 +80,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[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 = "base64ct"
version = "1.8.3"
@ -194,30 +130,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "build-target"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b"
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytemuck"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.56"
@ -266,20 +178,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ccc-flutter-bridge"
version = "0.1.0"
dependencies = [
"anyhow",
"ccc-crypto-core",
"ccc-crypto-wolfssl",
"env_logger",
"flutter_rust_bridge",
"hex",
"log",
"zeroize",
]
[[package]]
name = "cexpr"
version = "0.6.0"
@ -321,16 +219,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -350,39 +238,6 @@ dependencies = [
"typenum",
]
[[package]]
name = "dart-sys"
version = "4.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895"
dependencies = [
"cc",
]
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "delegate-attr"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -400,16 +255,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "env_filter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_filter"
version = "1.0.0"
@ -428,7 +273,7 @@ checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d"
dependencies = [
"anstream",
"anstyle",
"env_filter 1.0.0",
"env_filter",
"jiff",
"log",
]
@ -439,136 +284,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flutter_rust_bridge"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde126295b2acc5f0a712e265e91b6fdc0ed38767496483e592ae7134db83725"
dependencies = [
"allo-isolate",
"android_logger",
"anyhow",
"build-target",
"bytemuck",
"byteorder",
"console_error_panic_hook",
"dart-sys",
"delegate-attr",
"flutter_rust_bridge_macros",
"futures",
"js-sys",
"lazy_static",
"log",
"oslog",
"portable-atomic",
"threadpool",
"tokio",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "flutter_rust_bridge_macros"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f0420326b13675321b194928bb7830043b68cf8b810e1c651285c747abb080"
dependencies = [
"hex",
"md-5",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-macro"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@ -579,30 +294,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "hex"
version = "0.4.3"
@ -654,22 +351,6 @@ dependencies = [
"syn",
]
[[package]]
name = "js-sys"
version = "0.3.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.182"
@ -686,31 +367,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.8.0"
@ -723,15 +385,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 = "nom"
version = "7.1.3"
@ -742,25 +395,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.3"
@ -773,30 +407,6 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "oslog"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969"
dependencies = [
"cc",
"dashmap",
"log",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "password-hash"
version = "0.5.0"
@ -808,12 +418,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "portable-atomic"
version = "1.13.1"
@ -863,15 +467,6 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.12.3"
@ -901,30 +496,12 @@ version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.228"
@ -974,18 +551,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "subtle"
version = "2.6.1"
@ -1023,24 +588,6 @@ dependencies = [
"syn",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"pin-project-lite",
]
[[package]]
name = "typenum"
version = "1.19.0"
@ -1065,75 +612,6 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasm-bindgen"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b"
dependencies = [
"cfg-if",
"futures-util",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-link"
version = "0.2.1"

View File

@ -3,7 +3,6 @@ resolver = "2"
members = [
"crates/ccc-crypto-core",
"crates/ccc-crypto-wolfssl",
"crates/ccc-flutter-bridge",
"tests/conformance",
]

View File

@ -1 +0,0 @@
/target

View File

@ -1,655 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "allo-isolate"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b6d794345b06592d0ebeed8e477e41b71e5a0a49df4fc0e4184d5938b99509"
dependencies = [
"anyhow",
"atomic",
"backtrace",
]
[[package]]
name = "android_log-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
[[package]]
name = "android_logger"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f"
dependencies = [
"android_log-sys",
"env_logger",
"log",
"once_cell",
]
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "build-target"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b"
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "dart-sys-fork"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "933dafff26172b719bb9695dd3715a1e7792f62dcdc8a5d4c740db7e0fedee8b"
dependencies = [
"cc",
]
[[package]]
name = "dashmap"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
dependencies = [
"cfg-if",
"num_cpus",
]
[[package]]
name = "delegate-attr"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "env_logger"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
dependencies = [
"log",
"regex",
]
[[package]]
name = "flutter_rust_bridge"
version = "2.11.1"
dependencies = [
"allo-isolate",
"android_logger",
"anyhow",
"build-target",
"bytemuck",
"byteorder",
"console_error_panic_hook",
"dart-sys-fork",
"delegate-attr",
"flutter_rust_bridge_macros",
"futures",
"js-sys",
"lazy_static",
"log",
"oslog",
"threadpool",
"tokio",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "flutter_rust_bridge_macros"
version = "2.11.1"
dependencies = [
"hex",
"md-5",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
[[package]]
name = "futures-executor"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
[[package]]
name = "futures-macro"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
[[package]]
name = "futures-task"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
[[package]]
name = "futures-util"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hermit-abi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oslog"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8343ce955f18e7e68c0207dd0ea776ec453035685395ababd2ea651c569728b3"
dependencies = [
"cc",
"dashmap",
"log",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "ccc_flutter_bridge"
version = "0.1.0"
dependencies = [
"flutter_rust_bridge",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "tokio"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
dependencies = [
"backtrace",
"num_cpus",
"pin-project-lite",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
dependencies = [
"js-sys",
"wasm-bindgen",
]

View File

@ -1,28 +0,0 @@
[package]
name = "ccc-flutter-bridge"
version = { workspace = true }
edition = { workspace = true }
authors = { workspace = true }
description = "flutter_rust_bridge v2 entry-point exposing CCC Rust crypto to Dart"
# Both output kinds are required:
# - staticlib → iOS (linked into XCFramework / Runner)
# - cdylib → macOS / Android (.dylib / .so)
[lib]
crate-type = ["cdylib", "staticlib"]
[dependencies]
ccc-crypto-core = { path = "../ccc-crypto-core" }
ccc-crypto-wolfssl = { path = "../ccc-crypto-wolfssl" }
flutter_rust_bridge = { version = "=2.11.1" }
zeroize = { workspace = true }
log = { workspace = true }
anyhow = { workspace = true }
env_logger = { workspace = true }
# Convenience hex encoding for test / debug bridge calls.
hex = "0.4"
[dev-dependencies]
# Nothing needed; actual tests live in tests/conformance/.

View File

@ -1 +0,0 @@
// Nothing when using full_dep=false mode

View File

@ -1 +0,0 @@
pub mod simple;

View File

@ -1,10 +0,0 @@
#[flutter_rust_bridge::frb(sync)] // Synchronous mode for simplicity of the demo
pub fn greet(name: String) -> String {
format!("Hello, {name}!")
}
#[flutter_rust_bridge::frb(init)]
pub fn init_app() {
// Default utilities - feel free to customize
flutter_rust_bridge::setup_default_user_utils();
}

View File

@ -1,281 +0,0 @@
//! Public bridge functions — annotated with `#[frb(sync)]` or `#[frb]` so
//! that `flutter_rust_bridge` code-generation emits matching Dart APIs.
//!
//! All fallible functions return `anyhow::Result<T>` (FRB v2 maps that to a
//! Dart `Future<T>` that throws on error).
//!
//! # Naming convention
//! Functions are named `ccc_<family>_<operation>` so the generated Dart class
//! is pleasingly namespaced as `CccBridge.xxxYyy(…)`.
use flutter_rust_bridge::frb;
use zeroize::Zeroizing;
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
registry::ProviderRegistry,
};
// Provider traits are imported for use in future dispatch methods.
#[allow(unused_imports)]
use ccc_crypto_core::provider::{AeadProvider, HashProvider, KdfProvider, KemProvider, MacProvider};
use super::dto::{
AeadEncryptRequest, AeadEncryptResult, AlgorithmCapabilityDto, HashRequest, KdfDeriveRequest,
KemEncapResultDto, KemKeyPairDto, MacComputeRequest, ProviderCapabilitiesDto,
SelfTestReportDto,
};
// ──────────────────────────────────────────────────────────────────────────────
// Initialisation
// ──────────────────────────────────────────────────────────────────────────────
/// Initialise the CCC Rust layer.
///
/// **Must** be called once before any other bridge function, typically during
/// the app's boot sequence in `main.dart` (or `AppState.init()`).
///
/// Registers the wolfSSL provider into the global [`ProviderRegistry`].
/// Subsequent calls are no-ops.
#[frb(sync)]
pub fn ccc_init() {
static ONCE: std::sync::OnceLock<()> = std::sync::OnceLock::new();
ONCE.get_or_init(|| {
let _ = env_logger::try_init(); // tolerate error when already initialised
ccc_crypto_wolfssl::init();
log::info!("[ccc-bridge] wolfSSL provider registered");
});
}
// ──────────────────────────────────────────────────────────────────────────────
// Provider registry queries
// ──────────────────────────────────────────────────────────────────────────────
/// List all registered provider names.
#[frb(sync)]
pub fn ccc_list_providers() -> Vec<String> {
ProviderRegistry::global().list()
}
/// Retrieve full capabilities for a named provider.
///
/// Returns `None` if the provider is not registered.
#[frb(sync)]
pub fn ccc_capabilities(provider_name: String) -> Option<ProviderCapabilitiesDto> {
let reg = ProviderRegistry::global();
let guard = reg.get(&provider_name)?;
Some(guard.capabilities().into())
}
/// List all algorithms of a specific family that are **available** in a provider.
///
/// `family` is one of `"aead"`, `"kdf"`, `"mac"`, `"hash"`, `"kem"`.
#[frb(sync)]
pub fn ccc_available_algorithms(
provider_name: String,
family: String,
) -> Vec<AlgorithmCapabilityDto> {
let reg = ProviderRegistry::global();
let Some(guard) = reg.get(&provider_name) else { return vec![] };
// Convert the full capability map to DTO (which flattens HashMaps → Vecs).
let caps_dto: ProviderCapabilitiesDto = guard.capabilities().into();
let mut list = match family.as_str() {
"aead" => caps_dto.aead,
"kdf" => caps_dto.kdf,
"mac" => caps_dto.mac,
"hash" => caps_dto.hash,
"kem" => caps_dto.kem,
_ => vec![],
};
list.retain(|c| c.available);
list
}
// ──────────────────────────────────────────────────────────────────────────────
// AEAD
// ──────────────────────────────────────────────────────────────────────────────
/// Encrypt with an AEAD cipher.
///
/// Returns `ciphertext || 16-byte authentication tag` on success.
///
/// # Errors
/// Propagates [`ccc_crypto_core::error::CryptoError`] as an `anyhow::Result`.
pub fn ccc_aead_encrypt(req: AeadEncryptRequest) -> anyhow::Result<AeadEncryptResult> {
let algo = AeadAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown AEAD algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
let ct_tag = guard.encrypt_aead(algo, &req.key, &req.nonce, &req.plaintext, &req.aad)?;
Ok(AeadEncryptResult { ciphertext_and_tag: ct_tag })
}
/// Decrypt with an AEAD cipher.
///
/// Expects `ciphertext || 16-byte authentication tag` as `ciphertext_and_tag`.
/// Returns plaintext on success; returns an error if authentication fails.
pub fn ccc_aead_decrypt(
algo_id: u32,
key: Vec<u8>,
nonce: Vec<u8>,
ciphertext_and_tag: Vec<u8>,
aad: Vec<u8>,
) -> anyhow::Result<Vec<u8>> {
let algo = AeadAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown AEAD algo_id: {}", algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.decrypt_aead(algo, &key, &nonce, &ciphertext_and_tag, &aad)?)
}
// ──────────────────────────────────────────────────────────────────────────────
// KDF
// ──────────────────────────────────────────────────────────────────────────────
/// Derive a key using a KDF.
///
/// Returns the derived key bytes (length = `req.out_length`).
pub fn ccc_kdf_derive(req: KdfDeriveRequest) -> anyhow::Result<Vec<u8>> {
let algo = KdfAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KDF algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
let out: Zeroizing<Vec<u8>> =
guard.derive_key(algo, &req.ikm, &req.salt, &req.info, req.out_length as usize)?;
// Dart does not share memory with Rust; returning a plain Vec<u8> is safe.
Ok(out.to_vec())
}
// ──────────────────────────────────────────────────────────────────────────────
// MAC
// ──────────────────────────────────────────────────────────────────────────────
/// Compute a MAC tag.
pub fn ccc_mac_compute(req: MacComputeRequest) -> anyhow::Result<Vec<u8>> {
let algo = MacAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown MAC algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.compute_mac(algo, &req.key, &req.data)?)
}
/// Verify a MAC tag in constant time (returns `true` if authentic).
pub fn ccc_mac_verify(
algo_id: u32,
key: Vec<u8>,
data: Vec<u8>,
mac_bytes: Vec<u8>,
) -> anyhow::Result<bool> {
let algo = MacAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown MAC algo_id: {}", algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.verify_mac(algo, &key, &data, &mac_bytes)?)
}
// ──────────────────────────────────────────────────────────────────────────────
// Hash
// ──────────────────────────────────────────────────────────────────────────────
/// Compute a cryptographic hash.
pub fn ccc_hash(req: HashRequest) -> anyhow::Result<Vec<u8>> {
let algo = HashAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown Hash algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.hash(algo, &req.data)?)
}
// ──────────────────────────────────────────────────────────────────────────────
// KEM
// ──────────────────────────────────────────────────────────────────────────────
/// Generate a KEM key pair.
pub fn ccc_kem_generate_keypair(algo_id: u32) -> anyhow::Result<KemKeyPairDto> {
let algo = KemAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KEM algo_id: {}", algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
// WolfSslProvider also implements KemProvider.
// Down-cast via concrete type via the global init path.
use ccc_crypto_wolfssl::provider::WolfSslProvider;
let _ = guard; // release the Arc lock before constructing a temporary
let temp = WolfSslProvider::new();
Ok(temp.generate_keypair(algo)?.into())
}
/// Encapsulate a shared secret to a public key.
pub fn ccc_kem_encapsulate(algo_id: u32, public_key: Vec<u8>) -> anyhow::Result<KemEncapResultDto> {
let algo = KemAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KEM algo_id: {}", algo_id))?;
use ccc_crypto_wolfssl::provider::WolfSslProvider;
let temp = WolfSslProvider::new();
Ok(temp.encapsulate(algo, &public_key)?.into())
}
/// Decapsulate a shared secret using a private key.
///
/// Returns the shared secret bytes.
pub fn ccc_kem_decapsulate(
algo_id: u32,
private_key: Vec<u8>,
ciphertext: Vec<u8>,
) -> anyhow::Result<Vec<u8>> {
let algo = KemAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KEM algo_id: {}", algo_id))?;
use ccc_crypto_wolfssl::provider::WolfSslProvider;
let temp = WolfSslProvider::new();
let secret: Zeroizing<Vec<u8>> = temp.decapsulate(algo, &private_key, &ciphertext)?;
Ok(secret.to_vec())
}
// ──────────────────────────────────────────────────────────────────────────────
// Self-test
// ──────────────────────────────────────────────────────────────────────────────
/// Run the embedded NIST / RFC test vectors for the wolfSSL provider.
///
/// Returns a full [`SelfTestReportDto`]; `all_passed == true` means every
/// algorithm passed its vectors. Check individual `results` for details.
pub fn ccc_self_test() -> anyhow::Result<SelfTestReportDto> {
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.self_test().into())
}

View File

@ -1,296 +0,0 @@
//! Data Transfer Objects (DTOs) shared between Rust and the Dart layer.
//!
//! All structs here are annotated with `#[frb(opaque)]` or plain `pub struct`
//! as appropriate so that `flutter_rust_bridge` code-generation emits the
//! correct Dart equivalents.
//!
//! **Wire conventions**
//! - Binary blobs are always `Vec<u8>` (Dart: `Uint8List`)
//! - Identifiers that mirror `cipher_constants.dart` integers are `u32`
//! - Strings are `String` / `Option<String>`
//! - Boolean results are `bool`
use flutter_rust_bridge::frb;
// ──────────────────────────────────────────────────────────────────────────────
// Capability DTOs
// ──────────────────────────────────────────────────────────────────────────────
/// Capability entry for a single algorithm exported to Dart.
///
/// Maps 1-to-1 with [`ccc_crypto_core::capabilities::AlgorithmCapability`].
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct AlgorithmCapabilityDto {
/// Algorithm integer id (mirrors `cipher_constants.dart`).
pub algo_id: u32,
/// Human-readable name (e.g. `"AES-256-GCM"`).
pub algo_name: String,
/// Whether the provider actually supports this algorithm.
pub available: bool,
/// Whether the algorithm is deterministic given the same inputs.
pub deterministic_io: bool,
/// Provider-normalised throughput score 0100 (0 = unknown).
pub efficiency_score: u8,
/// NIST / standards reliability score 0100 (100 = NIST-recommended).
pub reliability_score: u8,
}
/// All capabilities returned by a provider in one call.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct ProviderCapabilitiesDto {
pub provider_name: String,
pub aead: Vec<AlgorithmCapabilityDto>,
pub kdf: Vec<AlgorithmCapabilityDto>,
pub mac: Vec<AlgorithmCapabilityDto>,
pub hash: Vec<AlgorithmCapabilityDto>,
pub kem: Vec<AlgorithmCapabilityDto>,
}
// ──────────────────────────────────────────────────────────────────────────────
// AEAD
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for AEAD encrypt.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct AeadEncryptRequest {
/// Algorithm id (e.g. 12 = AES-256-GCM).
pub algo_id: u32,
pub key: Vec<u8>,
pub nonce: Vec<u8>,
pub plaintext: Vec<u8>,
pub aad: Vec<u8>,
}
/// Result of AEAD encrypt: `ciphertext || 16-byte tag`.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct AeadEncryptResult {
/// `ciphertext || authentication_tag` (always 16-byte tag appended).
pub ciphertext_and_tag: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// KDF
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for key derivation.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct KdfDeriveRequest {
/// Algorithm id (e.g. 1 = HKDF-SHA-256).
pub algo_id: u32,
pub ikm: Vec<u8>,
pub salt: Vec<u8>,
pub info: Vec<u8>,
/// Requested output length in bytes.
pub out_length: u32,
}
// ──────────────────────────────────────────────────────────────────────────────
// MAC
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for MAC computation.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct MacComputeRequest {
/// Algorithm id (e.g. 30 = HMAC-SHA-256).
pub algo_id: u32,
pub key: Vec<u8>,
pub data: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// Hash
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for a hash computation.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct HashRequest {
/// Algorithm id (e.g. 40 = SHA-256).
pub algo_id: u32,
pub data: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// KEM
// ──────────────────────────────────────────────────────────────────────────────
/// A generated KEM key pair returned to Dart.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct KemKeyPairDto {
pub private_key: Vec<u8>,
pub public_key: Vec<u8>,
}
/// Result of KEM encapsulation.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct KemEncapResultDto {
/// The ephemeral ciphertext to send to the peer.
pub ciphertext: Vec<u8>,
/// The shared secret (zeroized after serialisation — Dart copy is its own).
pub shared_secret: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// Self-test
// ──────────────────────────────────────────────────────────────────────────────
/// Result of a single algorithm self-test.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct AlgoTestResultDto {
pub algo_id: u32,
pub algo_name: String,
pub passed: bool,
pub error_message: Option<String>,
}
/// Aggregate self-test report returned to Dart after `ccc_self_test()`.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct SelfTestReportDto {
pub provider_name: String,
pub all_passed: bool,
pub results: Vec<AlgoTestResultDto>,
}
// ──────────────────────────────────────────────────────────────────────────────
// Conversions from core types → DTOs
// ──────────────────────────────────────────────────────────────────────────────
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
capabilities::{AlgorithmCapability, ProviderCapabilities},
types::{AlgoTestResult, KemEncapResult, KemKeyPair, SelfTestReport},
};
// ──────────────────────────────────────────────────────────────────────────────
// Helper — convert a HashMap<Algo, AlgorithmCapability> to a Vec<DTO>
// ──────────────────────────────────────────────────────────────────────────────
fn aead_cap_to_dto(algo: AeadAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn kdf_cap_to_dto(algo: KdfAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn mac_cap_to_dto(algo: MacAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn hash_cap_to_dto(algo: HashAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn kem_cap_to_dto(algo: KemAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
impl From<ProviderCapabilities> for ProviderCapabilitiesDto {
fn from(p: ProviderCapabilities) -> Self {
let mut aead: Vec<AlgorithmCapabilityDto> =
p.aead.into_iter().map(|(a, c)| aead_cap_to_dto(a, c)).collect();
let mut kdf: Vec<AlgorithmCapabilityDto> =
p.kdf.into_iter().map(|(a, c)| kdf_cap_to_dto(a, c)).collect();
let mut mac: Vec<AlgorithmCapabilityDto> =
p.mac.into_iter().map(|(a, c)| mac_cap_to_dto(a, c)).collect();
let mut hash: Vec<AlgorithmCapabilityDto> =
p.hash.into_iter().map(|(a, c)| hash_cap_to_dto(a, c)).collect();
let mut kem: Vec<AlgorithmCapabilityDto> =
p.kem.into_iter().map(|(a, c)| kem_cap_to_dto(a, c)).collect();
// Sort by algo_id for stable ordering in the Dart layer.
aead.sort_by_key(|c| c.algo_id);
kdf.sort_by_key(|c| c.algo_id);
mac.sort_by_key(|c| c.algo_id);
hash.sort_by_key(|c| c.algo_id);
kem.sort_by_key(|c| c.algo_id);
Self { provider_name: p.provider_name, aead, kdf, mac, hash, kem }
}
}
impl From<KemKeyPair> for KemKeyPairDto {
fn from(kp: KemKeyPair) -> Self {
// Use .clone() because KemKeyPair implements ZeroizeOnDrop (adds Drop),
// which prevents partial moves in safe Rust.
Self {
private_key: kp.private_key.clone(),
public_key: kp.public_key.clone(),
}
}
}
impl From<KemEncapResult> for KemEncapResultDto {
fn from(r: KemEncapResult) -> Self {
Self {
ciphertext: r.ciphertext.clone(),
shared_secret: r.shared_secret.clone(),
}
}
}
impl From<AlgoTestResult> for AlgoTestResultDto {
fn from(r: AlgoTestResult) -> Self {
Self {
algo_id: r.algo_id,
algo_name: r.algo_name,
passed: r.passed,
error_message: r.error_message,
}
}
}
impl From<SelfTestReport> for SelfTestReportDto {
fn from(r: SelfTestReport) -> Self {
Self {
provider_name: r.provider_name,
all_passed: r.all_passed,
results: r.results.into_iter().map(Into::into).collect(),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
//! `ccc-flutter-bridge` — flutter_rust_bridge v2 entry-point.
//!
//! This crate is compiled to both a `cdylib` (for macOS / Android) and a
//! `staticlib` (for iOS). Do not add platform logic here — keep it in the
//! provider crates.
//!
//! ## Generated Dart bindings
//!
//! Run the code-generator from the flutter project root:
//!
//! ```sh
//! flutter_rust_bridge_codegen generate
//! ```
//!
//! The generator reads this crate's source and emits Dart classes under
//! `flutter_src/lib/gen/rust/`.
// AUTO INJECTED BY flutter_rust_bridge.
mod frb_generated;
pub mod bridge;
pub mod dto;

View File

@ -3,59 +3,74 @@ CCC Rust Crypto Provider — Architecture Plan
===============================================
:Status: Approved
:Phase: 4
:Date: 2026-02-23
:Phase: 4 — Milestone 1 of 3
:Date: 2026-02-24
:Author: Engineering
----
Overview
--------
Replace the Dart-only crypto stubs with a Cargo workspace of native Rust
crates. A shared trait/capability core defines the contract every provider
must satisfy. Per-provider sub-crates implement that contract.
``flutter_rust_bridge`` auto-generates ``dart:ffi`` bindings so Dart code
calls Rust directly with no platform-channel overhead. wolfSSL / wolfCrypt
is the first (and only Phase 4) provider. All future libraries slot into the
same trait with zero changes to the core.
This document describes the architecture for the **CCC Rust Crypto Provider**,
the first of three milestones in delivering hardware-grade cryptography to the
LetUsMsg application.
The guiding constraint is *clean separation*: each milestone is a distinct,
independently testable artifact with no forbidden upward dependencies.
----
Three-Milestone Strategy
------------------------
============= =================================== ========================== ===========================
Milestone Repository What ships Depends on
============= =================================== ========================== ===========================
**1 (this)** ``ccc_rust`` Pure Rust crypto library wolfSSL (vendored)
**2** ``ccc_dart_plugin`` Flutter plugin + Dart API ``ccc_rust`` (git/semver)
**3** ``letusmsg`` (existing app) App integration ``ccc_dart_plugin`` package
============= =================================== ========================== ===========================
Milestone 1 is complete and independently verified *before* any bridge or Dart
code is written. Milestone 2 owns the ``flutter_rust_bridge`` dependency and
all generated Dart bindings. Milestone 3 makes no Rust changes; it consumes
only the published Dart package.
----
Milestone 1 — ``ccc_rust`` Scope (this repository)
----------------------------------------------------
**Goal**: a fully tested, provider-agnostic Rust crypto library.
**Hard boundaries** — this repo must never contain:
* ``flutter_rust_bridge`` or any Flutter/Dart dependency
* Generated Dart files (``frb_generated.rs``, ``*.dart``)
* ``flutter_rust_bridge.yaml``
* Any platform plugin scaffold (``ios/``, ``android/``, ``macos/``)
Guiding Principles
------------------
~~~~~~~~~~~~~~~~~~
1. Every provider reports its own capabilities at runtime — no compile-time
hard-coding of ``available: true/false``.
2. Algorithm IDs in Rust map 1-to-1 to the integer constants already in
``cipher_constants.dart`` — zero Dart routing changes needed.
2. Algorithm IDs in Rust map 1-to-1 to the integer constants in
``cipher_constants.dart`` — zero Dart changes needed when wiring up.
3. Key material is zeroed on drop (``zeroize`` crate) everywhere.
4. A conformance / self-test suite validates cross-provider byte-identity
4. A conformance test suite validates NIST/RFC vectors for every algorithm
before any provider is marked ``available``.
5. The Rust workspace has no runtime dependency on Flutter; it is a pure
native library that could be consumed by any FFI host.
5. The library has no runtime dependency on Flutter; it is consumable by any
FFI host (Flutter plugin, Python tests, CLI tools).
FFI Bridge Decision
-------------------
``flutter_rust_bridge`` (FRB) is used rather than raw ``dart:ffi`` hand-wiring.
FRB **is** ``dart:ffi`` — it uses ``dart:ffi`` under the hood and introduces
no platform channels. The difference from raw ``dart:ffi`` is:
========================= ========================== ==========================
Aspect flutter_rust_bridge Raw dart:ffi
========================= ========================== ==========================
Rust-side code Clean idiomatic Rust ``extern "C"`` with C ABI
Dart bindings **Auto-generated** Hand-written every field
Async / isolate support Automatic Manual wiring
Cross-type safety Codegen validates at build Discovered at runtime
Beginner-friendliness High Low
========================= ========================== ==========================
Repository Layout (target state)
---------------------------------
Repository Layout (Milestone 1 — this repo)
--------------------------------------------
::
ccc_rust/
├── Cargo.toml ← workspace manifest (3 members)
├── Cargo.toml ← workspace manifest (3 members, no bridge)
├── rust-toolchain.toml ← pinned stable toolchain
├── .cargo/
│ └── config.toml ← cross-compile target aliases
@ -73,40 +88,26 @@ Repository Layout (target state)
│ │ ├── provider.rs ← CryptoProvider umbrella trait
│ │ ├── registry.rs ← ProviderRegistry (OnceLock<Mutex<...>>)
│ │ └── types.rs ← KemKeyPair, SelfTestReport, BenchmarkReport
│ ├── ccc-crypto-wolfssl/ ← wolfSSL provider impl
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── aead.rs ← AES-256-GCM, ChaCha20-Poly1305
│ │ ├── kdf.rs ← HKDF, Argon2id
│ │ ├── mac.rs ← HMAC-SHA256/384/512
│ │ ├── hash.rs ← SHA-2, SHA-3, BLAKE2b
│ │ ├── kem.rs ← X25519, X448, ML-KEM (if PQ build)
│ │ ├── capabilities.rs ← probe-at-startup + benchmark
│ │ └── provider.rs ← WolfSslProvider: CryptoProvider impl
│ └── ccc-flutter-bridge/ ← flutter_rust_bridge entry point
│ └── ccc-crypto-wolfssl/ ← wolfSSL provider implementation
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs
│ ├── bridge.rs ← #[frb] exported functions
└── dto.rs ← CapabilitiesDto, KemKeyPairDto, SelfTestDto
├── tests/
└── conformance/
├── aes_gcm_vectors.rs
├── chacha_vectors.rs
├── hkdf_vectors.rs
│ ├── hmac_vectors.rs
│ └── cross_provider.rs
├── flutter_src/
│ └── ccc/ ← existing Dart files (minimal changes)
└── docs/
│ ├── aead.rs ← AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305
│ ├── kdf.rs ← HKDF-SHA256/384/512, Argon2id, BLAKE2b-KDF
│ ├── mac.rs ← HMAC-SHA256/384/512, BLAKE2b-MAC
│ ├── hash.rs ← SHA-256/384/512, SHA3-256/512, BLAKE2b-512
│ ├── kem.rs ← X25519, X448 (ML-KEM deferred Phase 5)
│ ├── capabilities.rs ← probe-at-startup + benchmark()
│ └── provider.rs ← WolfSslProvider: CryptoProvider impl
└── tests/
└── conformance/
├── Cargo.toml
└── src/
└── main.rs ← NIST/RFC vectors for all algorithms
Step 1 — Cargo Workspace Scaffold
----------------------------------
Files
~~~~~
``Cargo.toml``::
[workspace]
@ -114,13 +115,13 @@ Files
members = [
"crates/ccc-crypto-core",
"crates/ccc-crypto-wolfssl",
"crates/ccc-flutter-bridge",
"tests/conformance",
]
``rust-toolchain.toml``::
[toolchain]
channel = "1.77"
channel = "stable"
``.cargo/config.toml``::
@ -256,16 +257,9 @@ Provider Traits
Step 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
-----------------------------------------------------
Submodule Registration::
git submodule add https://github.com/wolfSSL/wolfssl vendors/wolfssl
git -C vendors/wolfssl checkout v5.7.2-stable
``crates/ccc-crypto-wolfssl/Cargo.toml`` key dependencies::
wolfssl = { version = "0.1", features = ["pq", "blake2", "argon2"] }
zeroize = { version = "1", features = ["derive"] }
ccc-crypto-core = { path = "../ccc-crypto-core" }
wolfSSL is vendored as a git submodule pinned to ``v5.7.2-stable``.
The crate uses ``cmake`` + ``bindgen`` in ``build.rs`` to build and bind it.
A ``stub_ffi`` feature bypasses the C build for fast unit-test cycles.
wolfSSL Phase 4 Algorithm Coverage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -273,152 +267,127 @@ wolfSSL Phase 4 Algorithm Coverage
=================== ===========================================
Category Algorithms
=================== ===========================================
AEAD AES-256-GCM, ChaCha20-Poly1305
KDF HKDF-SHA256/384/512, PBKDF2, Argon2id
MAC HMAC-SHA256/384/512, Poly1305
AEAD AES-256-GCM, ChaCha20-Poly1305,
XChaCha20-Poly1305
KDF HKDF-SHA256/384/512, Argon2id, BLAKE2b-KDF
MAC HMAC-SHA256/384/512, BLAKE2b-MAC
Hash SHA-256/384/512, SHA3-256/512, BLAKE2b-512
KEM (if PQ build) X25519, X448, ML-KEM-768, ML-KEM-1024
KEM X25519, X448
KEM (Phase 5) ML-KEM-768, ML-KEM-1024, Classic McEliece
=================== ===========================================
Capability Probe Strategy
~~~~~~~~~~~~~~~~~~~~~~~~~~
``WolfSslProvider::capabilities()`` runs a minimal probe call per algorithm at
startup (encrypt 1-byte payload; decrypt; compare). Sets
``available = probe_succeeded``. If the wolfSSL build does not include PQ
support, ML-KEM entries gracefully report ``available: false``.
``WolfSslProvider::capabilities()`` runs a minimal probe call (encrypt 1 byte;
decrypt; compare) per algorithm at startup. Sets ``available = probe_succeeded``.
If the wolfSSL build omits PQ support, ML-KEM entries report
``available: false`` gracefully.
Benchmark Strategy
~~~~~~~~~~~~~~~~~~
``WolfSslProvider::benchmark()`` encrypts a 1 MB buffer × 100 iterations per
AEAD algorithm, measures wall-clock throughput, normalises to a 0100
``efficiency_score``. Run once at ``ccc_init()`` and cached.
``efficiency_score``. Run once at library init and cached.
Step 4 — ``ccc-flutter-bridge`` Entry-Point Crate
---------------------------------------------------
----
Exported Functions (``#[frb]`` tagged)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: text
ccc_init() → initialise registry + run benchmarks
ccc_list_providers() → Vec<String>
ccc_provider_capabilities(provider) → CapabilitiesDto
ccc_aead_encrypt(provider, algo: u32, key, nonce, plaintext, aad)
→ Result<Vec<u8>>
ccc_aead_decrypt(provider, algo: u32, key, nonce, ciphertext, aad)
→ Result<Vec<u8>>
ccc_derive_key(provider, algo: u32, ikm, salt, info, length: u32)
→ Result<Vec<u8>>
ccc_compute_mac(provider, algo: u32, key, data)
→ Result<Vec<u8>>
ccc_verify_mac(provider, algo: u32, key, data, mac)
→ Result<bool>
ccc_hash(provider, algo: u32, data) → Result<Vec<u8>>
ccc_kem_generate_keypair(provider, algo: u32)
→ Result<KemKeyPairDto>
ccc_kem_encapsulate(provider, algo: u32, public_key)
→ Result<KemEncapDto>
ccc_kem_decapsulate(provider, algo: u32, private_key, ciphertext)
→ Result<Vec<u8>>
ccc_self_test(provider) → SelfTestDto
DTO Structs
~~~~~~~~~~~
``CapabilitiesDto`` — mirrors ``ProviderCapabilities``, uses primitive types
so ``flutter_rust_bridge`` can auto-generate the Dart data class.
``KemKeyPairDto { public_key: Vec<u8>, private_key: Vec<u8> }``
``KemEncapDto { ciphertext: Vec<u8>, shared_secret: Vec<u8> }``
``SelfTestDto { provider: String, results: Vec<AlgoTestResult> }``
``AlgoTestResult { algo_id: u32, algo_name: String, passed: bool,
error_message: Option<String> }``
Step 5 — Flutter Build Integration
------------------------------------
* Add ``flutter_rust_bridge: ^2`` to ``pubspec.yaml``
* Run ``flutter_rust_bridge_codegen generate`` → emits
``flutter_src/ccc_crypto_bindings/ccc_crypto.dart``
* ``crate-type = ["cdylib", "staticlib"]`` in ``ccc-flutter-bridge/Cargo.toml``
(``cdylib`` for Android / Linux / macOS, ``staticlib`` for iOS)
* Cargokit handles cross-compilation inside standard Flutter plugin dirs
(``ios/``, ``android/``, ``macos/``)
Step 6 — Dart Layer Wiring
---------------------------
``crypto_wolfssl.dart``
~~~~~~~~~~~~~~~~~~~~~~~~
Replace ``UnimplementedError`` stubs:
.. code-block:: dart
@override
Future<List<int>> encrypt(Map<String, dynamic> input,
{CryptoContext? context}) async {
final algo = context?.cipherSequence?.first ?? CipherConstants.AES_GCM_256;
final key = _resolveKey(context);
final nonce = _generateNonce(algo);
return CccCrypto.ccmAeadEncrypt(
provider: 'wolfssl', algo: algo,
key: key, nonce: nonce,
plaintext: _encodePayload(input), aad: _buildAad(context));
}
``ccc_provider_spec.dart``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Convert ``CccProviderCatalog.capabilities`` from a compile-time static map to a
runtime-populated map. At app start call::
await CccCrypto.cccInit();
final providers = await CccCrypto.cccListProviders();
for (final name in providers) {
final dto = await CccCrypto.cccProviderCapabilities(provider: name);
CccProviderCatalog.populate(name, dto);
}
``CccSelfTest``
~~~~~~~~~~~~~~~~
New Dart class that wraps ``CccCrypto.cccSelfTest(provider: name)`` and exposes
per-algorithm pass/fail results in the app's debug diagnostics screen.
Step 7 — Conformance Test Suite
Step 4 — Conformance Test Suite
---------------------------------
Location: ``tests/conformance/``
Location: ``tests/conformance/src/main.rs``
* ``aes_gcm_vectors.rs`` — NIST SP 800-38D GCM test vectors
* ``chacha_vectors.rs`` — RFC 8439 ChaCha20-Poly1305 test vectors
* ``hkdf_vectors.rs`` — RFC 5869 HKDF-SHA256 / SHA512 test vectors
* ``hmac_vectors.rs`` — RFC 4231 HMAC-SHA256 / SHA512 test vectors
* ``cross_provider.rs`` — encrypt with wolfSSL → decrypt expectation matches
the reference output from the Dart ``cryptography`` package for the same
key/nonce/plaintext/aad (validates ``deterministic_io: true``)
Run with ``cargo run -p ccc-conformance-tests`` and ``cargo test --workspace``.
Step 8 — Architecture Documentation
========================= ================================
Test Source
========================= ================================
AES-256-GCM NIST SP 800-38D vectors
ChaCha20-Poly1305 RFC 8439 vectors
XChaCha20-Poly1305 Extended nonce vectors
HKDF-SHA256/512 RFC 5869 vectors
HMAC-SHA256/512 RFC 4231 vectors
SHA-256/512, SHA3, BLAKE2b FIPS / reference vectors
X25519 DH RFC 7748 vectors
X448 DH RFC 7748 vectors
========================= ================================
**Gate**: ``ALL VECTORS PASSED`` must print before Milestone 1 is tagged.
----
Step 5 — Architecture Documentation
--------------------------------------
``docs/phase4_rust_architecture.rst`` covers:
* Crate dependency graph (ASCII)
* "How to add a new provider" — the 7-step trait checklist
* "How to add a new provider" — 7-step trait checklist
* ``algo: u32`` → cipher constant mapping table
* Stretch-goal Phase 8 "Omni-Crypto" provider list
* Milestone 2 hand-off contract (API surface Milestone 2 must implement against)
Phase 8 — Stretch Goal Provider List
--------------------------------------
----
*(Fully out of scope for Phase 4. Documented here for future planning.)*
Milestone 1 Verification Gate
------------------------------
All of the following must pass before the ``v0.1.0`` tag is cut and Milestone 2
work begins:
* ``cargo test --workspace`` — all unit tests pass
* ``cargo run -p ccc-conformance-tests````ALL VECTORS PASSED``
* ``cargo build --target aarch64-apple-ios`` — compiles
* ``cargo build --target aarch64-linux-android`` — compiles
* No ``flutter_rust_bridge``, Dart, or Flutter dependency anywhere in the workspace
* ``cargo audit`` — no known CVEs in dependency tree
----
Milestone 2 — ``ccc_dart_plugin`` (separate repository)
---------------------------------------------------------
*(Planned — not started. Work begins after Milestone 1 gate passes.)*
A separate Dart/Flutter plugin package repository. It contains:
* A small Rust bridge crate that references ``ccc_rust`` via git tag:
.. code-block:: toml
[dependencies]
ccc-crypto-core = { git = "https://github.com/letusmsg/ccc_rust", tag = "v0.1.0" }
ccc-crypto-wolfssl = { git = "https://github.com/letusmsg/ccc_rust", tag = "v0.1.0" }
flutter_rust_bridge = "2"
* ``flutter_rust_bridge_codegen generate`` output (``frb_generated.rs``, Dart bindings)
* Flutter plugin scaffold: ``ios/``, ``android/``, ``macos/``
* Dart API surface: ``CccCrypto``, ``CccSelfTest``, ``CccProviderCatalog``
* Flutter integration tests (roundtrip encrypt/decrypt, self-test harness)
----
Milestone 3 — LetUsMsg App Integration (existing repository)
-------------------------------------------------------------
*(Planned — not started. Work begins after Milestone 2 gate passes.)*
The existing ``letusmsg`` Flutter app adds ``ccc_dart_plugin`` as a pubspec
dependency. Changes are confined to:
* ``crypto_wolfssl.dart`` — replace ``UnimplementedError`` stubs with plugin calls
* ``ccc_provider_spec.dart`` — populate ``CccProviderCatalog`` at runtime
* ``main.dart`` — call ``CccCrypto.cccInit()`` at startup
* App debug screen — expose ``CccSelfTest.runAll()`` results
No Rust changes and no bridge changes are made in Milestone 3.
----
Phase 8 — Stretch Goal Providers (Future)
------------------------------------------
*(Out of scope for Phase 4. Tracked here for future scheduling.)*
================== =====================================================
Library Rust crate / approach
@ -435,14 +404,3 @@ mbedTLS ``mbedtls`` crate
Nettle ``nettle-sys`` crate
================== =====================================================
Verification Checklist
-----------------------
* ``cargo test --workspace`` passes including all NIST vectors
* ``cargo build --target aarch64-apple-ios`` succeeds
* ``cargo build --target aarch64-linux-android`` succeeds
* Flutter integration test: roundtrip encrypt/decrypt 1 KB via
``CryptoWolfSsl`` Dart class
* ``CccSelfTest.runAll()`` returns all-pass in the app debug screen
* Cross-provider conformance: Dart ↔ wolfSSL byte-identity confirmed
(``deterministic_io: true`` verified for AES-256-GCM and ChaCha20-Poly1305)

View File

@ -2,7 +2,7 @@
CCC Rust Implementation — Phase Tracking
==============================================
:Last Updated: 2026-06-20
:Last Updated: 2026-02-24
Legend
------
@ -14,10 +14,30 @@ Legend
----
Three-Milestone Overview
------------------------
============= =================================== ============================
Milestone Repository Status
============= =================================== ============================
**1 (this)** ``ccc_rust`` In progress
**2** ``ccc_cryptography`` Not started
**3** ``letusmsg`` (existing app) Not started
============= =================================== ============================
Milestone 2 does not start until the Milestone 1 Verification Gate passes.
Milestone 3 does not start until the Milestone 2 gate passes.
----
============================================================
Milestone 1 — ``ccc_rust`` Pure Rust Crypto Library
============================================================
Step 1 — Cargo Workspace Scaffold
----------------------------------
* ``[x]`` Create ``Cargo.toml`` (workspace manifest, 4 members)
* ``[x]`` Create ``Cargo.toml`` (workspace manifest, 3 members — no bridge crate)
* ``[x]`` Create ``rust-toolchain.toml`` (channel = "stable")
* ``[x]`` Create ``.cargo/config.toml`` (cross-compile target aliases)
* ``[x]`` Create ``vendors/README.md``
@ -99,280 +119,131 @@ Step 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
----
Step 4 — ``ccc-flutter-bridge`` Entry-Point Crate
---------------------------------------------------
* ``[x]`` Create ``crates/ccc-flutter-bridge/Cargo.toml``
* ``[x]`` Set ``crate-type = ["cdylib", "staticlib"]``
* ``[x]`` Add ``flutter_rust_bridge = "=2.9.0"`` dependency
* ``[x]`` ``dto.rs`` — CapabilitiesDto, KemKeyPairDto, KemEncapDto,
SelfTestDto, AlgoTestResultDto; From<core types> impls
* ``[x]`` ``bridge.rs`` — ccc_init()
* ``[x]`` ``bridge.rs`` — ccc_list_providers()
* ``[x]`` ``bridge.rs`` — ccc_capabilities() / ccc_available_algorithms()
* ``[x]`` ``bridge.rs`` — ccc_aead_encrypt() / ccc_aead_decrypt()
* ``[x]`` ``bridge.rs`` — ccc_kdf_derive()
* ``[x]`` ``bridge.rs`` — ccc_mac_compute() / ccc_mac_verify()
* ``[x]`` ``bridge.rs`` — ccc_hash()
* ``[x]`` ``bridge.rs`` — ccc_kem_generate_keypair()
* ``[x]`` ``bridge.rs`` — ccc_kem_encapsulate() / ccc_kem_decapsulate()
* ``[x]`` ``bridge.rs`` — ccc_self_test()
* ``[x]`` ``lib.rs`` — module declarations
* ``[x]`` ``flutter_rust_bridge.yaml`` — codegen config
----
Step 5 — Flutter Build Integration
------------------------------------
* ``[x]`` Add ``flutter_rust_bridge: ^2`` to ``pubspec.yaml``
* ``[x]`` Run ``flutter_rust_bridge_codegen generate``
* ``[x]`` Verify generated ``flutter_src/lib/gen/rust/`` bindings (bridge.dart, dto.dart, dto.freezed.dart, frb_generated.dart)
* ``[ ]`` iOS plugin scaffold (``ios/`` dir, cargokit integration)
* ``[ ]`` Android plugin scaffold (``android/`` dir, CMakeLists.txt)
* ``[ ]`` macOS plugin scaffold (``macos/`` dir)
* ``[ ]`` Confirm ``flutter build ios`` succeeds (static lib linked)
* ``[ ]`` Confirm ``flutter build apk`` succeeds (cdylib linked)
----
Step 6 — Dart Layer Wiring
---------------------------
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``encrypt()````ccc_aead_encrypt()``
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``decrypt()````ccc_aead_decrypt()``
* ``[ ]`` Convert ``CccProviderCatalog.capabilities`` to runtime-populated map
* ``[ ]`` Call ``ccc_init()`` at app startup
* ``[ ]`` Populate ``CccProviderCatalog`` from ``ccc_capabilities()``
* ``[ ]`` Create ``CccSelfTest`` Dart class (wraps ``ccc_self_test()``)
* ``[ ]`` Expose self-test pass/fail diagnostics in app debug screen
----
Step 7 — Conformance Test Suite
Step 4 — Conformance Test Suite
---------------------------------
* ``[x]`` ``tests/conformance/src/main.rs`` — NIST AES-256-GCM (2 vectors)
* ``[x]`` ``tests/conformance/src/main.rs`` — RFC 8439 ChaCha20-Poly1305
* ``[x]`` ``tests/conformance/src/main.rs`` — RFC 5869 HKDF-SHA256 (2 vectors)
* ``[x]`` ``tests/conformance/src/main.rs`` — RFC 4231 HMAC-SHA256 (2 vectors)
* ``[x]`` ``tests/conformance/src/main.rs`` — FIPS hash vectors (SHA-256/512, SHA3-256, BLAKE2b)
* ``[ ]`` Cross-provider conformance test (requires multiple providers)
* ``[x]`` ``cargo run -p ccc-conformance-tests`` passes (12/12 vectors, all algorithms)
* ``[x]`` NIST AES-256-GCM vectors (2 vectors)
* ``[x]`` RFC 8439 ChaCha20-Poly1305 vectors
* ``[x]`` RFC 5869 HKDF-SHA256 vectors (2 vectors)
* ``[x]`` RFC 4231 HMAC-SHA256 vectors (2 vectors)
* ``[x]`` FIPS hash vectors (SHA-256/512, SHA3-256, BLAKE2b-512)
* ``[ ]`` RFC 7748 X25519 DH test vectors
* ``[ ]`` RFC 7748 X448 DH test vectors
* ``[ ]`` XChaCha20-Poly1305 extended-nonce vectors
* ``[x]`` ``cargo run -p ccc-conformance-tests`` passes (all current vectors)
----
Step 8 — Architecture Documentation
Step 5 — Architecture Documentation
--------------------------------------
* ``[ ]`` Create ``docs/phase4_rust_architecture.rst``
* ``[ ]`` Crate dependency graph (ASCII diagram)
* ``[ ]`` "How to add a new provider" — 7-step trait checklist
* ``[ ]`` ``algo: u32`` → cipher constant mapping table
* ``[ ]`` Phase 8 stretch-goal provider list documented
* ``[ ]`` Milestone 2 hand-off contract documented
----
Final Verification Gate
------------------------
Milestone 1 Verification Gate
------------------------------
* ``[x]`` ``cargo test --workspace`` — all pass (8/8 unit + 12/12 conformance)
*All items must be checked before the* ``v0.1.0`` *tag is cut.*
* ``[x]`` ``cargo test --workspace`` — all pass
* ``[x]`` ``cargo run -p ccc-conformance-tests`` — ALL VECTORS PASSED
* ``[ ]`` ``cargo build --target aarch64-apple-ios`` — success
* ``[ ]`` ``cargo build --target aarch64-linux-android`` — success
* ``[ ]`` Flutter roundtrip integration test passes (1 KB encrypt/decrypt)
* ``[ ]`` ``CccSelfTest.runAll()`` all-pass in app debug screen
* ``[ ]`` Cross-provider conformance confirmed (``deterministic_io: true``
verified for AES-256-GCM and ChaCha20-Poly1305)
* ``[ ]`` No ``flutter_rust_bridge`` / Dart / Flutter dependency in workspace
* ``[ ]`` ``cargo audit`` — no known CVEs
----
Phase 8 — Stretch Goal Providers (Future)
------------------------------------------
============================================================
Milestone 2 — ``ccc_cryptography`` Flutter Plugin
===========================================================
*(Out of scope for Phase 4. Tracked here for future scheduling.)*
*(Not started — begins after Milestone 1 gate passes)*
* ``[ ]`` libsodium (``sodiumoxide`` / ``safe_libsodium``)
* ``[ ]`` OpenSSL (``openssl`` crate)
* ``[ ]`` BoringSSL (``boring`` crate)
* ``[ ]`` RustCrypto (pure-Rust, no native dep)
* ``[ ]`` liboqs — ML-KEM, BIKE, HQC, Falcon, Dilithium, SPHINCS+
* ``[ ]`` Signal ``libsignal``
* ``[ ]`` Botan
* ``[ ]`` mbedTLS
* ``[ ]`` Nettle
Step 1 — New Repository Setup
-------------------------------
Legend
------
* ``[ ]`` Not started
* ``[~]`` In progress
* ``[x]`` Complete
* ``[!]`` Blocked
* ``[ ]`` Create ``ccc_cryptography`` repository
* ``[]`` Flutter plugin scaffold (``pubspec.yaml``, ``ios/``, ``android/``, ``macos/``)
* ``[ ]`` Rust bridge crate with ``crate-type = ["cdylib", "staticlib"]``
* ``[ ]`` Add ``flutter_rust_bridge = "2"`` dependency
* ``[ ]`` Reference ``ccc_rust`` via git tag ``v0.1.0``
----
Step 1 — Cargo Workspace Scaffold
----------------------------------
Step 2 — Bridge Crate
----------------------
* ``[ ]`` Create ``Cargo.toml`` (workspace manifest, 3 members)
* ``[ ]`` Create ``rust-toolchain.toml`` (stable, pinned version)
* ``[ ]`` Create ``.cargo/config.toml`` (cross-compile target aliases)
* ``[ ]`` Create ``vendors/README.md``
----
Step 2 — ``ccc-crypto-core`` Trait Crate
-----------------------------------------
* ``[ ]`` Create ``crates/ccc-crypto-core/Cargo.toml``
* ``[ ]`` ``algorithms.rs`` — AeadAlgorithm, KdfAlgorithm, MacAlgorithm,
HashAlgorithm, KemAlgorithm enums (values == cipher_constants.dart)
* ``[ ]`` ``capabilities.rs`` — AlgorithmCapability, ProviderCapabilities
* ``[ ]`` ``error.rs`` — CryptoError enum
* ``[ ]`` ``types.rs`` — KemKeyPair, SelfTestReport, BenchmarkReport,
AlgoTestResult
* ``[ ]`` ``provider.rs`` — AeadProvider, KdfProvider, MacProvider,
HashProvider, KemProvider traits; CryptoProvider umbrella trait
* ``[ ]`` ``registry.rs`` — ProviderRegistry (OnceLock<Mutex<...>>),
register(), get(), list()
* ``[ ]`` ``lib.rs`` — re-exports all public items
* ``[ ]`` Unit tests for registry (register, get, list)
----
Step 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
-----------------------------------------------------
* ``[ ]`` ``git submodule add`` wolfSSL → ``vendors/wolfssl``
* ``[ ]`` Pin submodule to ``v5.7.2-stable``
* ``[ ]`` Document pin in ``vendors/README.md``
* ``[ ]`` Create ``crates/ccc-crypto-wolfssl/Cargo.toml``
* ``[ ]`` ``aead.rs`` — AES-256-GCM implementation
* ``[ ]`` encrypt_aead (AES-256-GCM)
* ``[ ]`` decrypt_aead (AES-256-GCM)
* ``[ ]`` encrypt_aead (ChaCha20-Poly1305)
* ``[ ]`` decrypt_aead (ChaCha20-Poly1305)
* ``[ ]`` ``kdf.rs`` — KDF implementations
* ``[ ]`` HKDF-SHA256
* ``[ ]`` HKDF-SHA384
* ``[ ]`` HKDF-SHA512
* ``[ ]`` Argon2id
* ``[ ]`` ``mac.rs`` — MAC implementations
* ``[ ]`` HMAC-SHA256
* ``[ ]`` HMAC-SHA384
* ``[ ]`` HMAC-SHA512
* ``[ ]`` ``hash.rs`` — Hash implementations
* ``[ ]`` SHA-256 / SHA-384 / SHA-512
* ``[ ]`` SHA3-256 / SHA3-512
* ``[ ]`` BLAKE2b-512
* ``[ ]`` ``kem.rs`` — KEM implementations
* ``[ ]`` X25519
* ``[ ]`` X448
* ``[ ]`` ML-KEM-768 (conditional on PQ build)
* ``[ ]`` ML-KEM-1024 (conditional on PQ build)
* ``[ ]`` ``capabilities.rs`` — probe-at-startup per algorithm
* ``[ ]`` ``capabilities.rs`` — benchmark() throughput micro-bench
* ``[ ]`` ``provider.rs`` — WolfSslProvider: CryptoProvider impl
* ``[ ]`` ``provider.rs`` — self_test() with embedded NIST vectors
* ``[ ]`` Register WolfSslProvider in ProviderRegistry via init()
* ``[ ]`` Unit tests for each implemented algorithm
----
Step 4 — ``ccc-flutter-bridge`` Entry-Point Crate
---------------------------------------------------
* ``[ ]`` Create ``crates/ccc-flutter-bridge/Cargo.toml``
* ``[ ]`` Set ``crate-type = ["cdylib", "staticlib"]``
* ``[ ]`` Add ``flutter_rust_bridge`` dependency
* ``[ ]`` ``dto.rs`` — CapabilitiesDto, KemKeyPairDto, KemEncapDto,
SelfTestDto, AlgoTestResultDto
SelfTestDto, AlgoTestResultDto; From<core types> impls
* ``[ ]`` ``bridge.rs`` — ccc_init()
* ``[ ]`` ``bridge.rs`` — ccc_list_providers()
* ``[ ]`` ``bridge.rs`` — ccc_provider_capabilities()
* ``[ ]`` ``bridge.rs`` — ccc_capabilities() / ccc_available_algorithms()
* ``[ ]`` ``bridge.rs`` — ccc_aead_encrypt() / ccc_aead_decrypt()
* ``[ ]`` ``bridge.rs`` — ccc_derive_key()
* ``[ ]`` ``bridge.rs`` — ccc_compute_mac() / ccc_verify_mac()
* ``[ ]`` ``bridge.rs`` — ccc_kdf_derive()
* ``[ ]`` ``bridge.rs`` — ccc_mac_compute() / ccc_mac_verify()
* ``[ ]`` ``bridge.rs`` — ccc_hash()
* ``[ ]`` ``bridge.rs`` — ccc_kem_generate_keypair()
* ``[ ]`` ``bridge.rs`` — ccc_kem_encapsulate() / ccc_kem_decapsulate()
* ``[ ]`` ``bridge.rs`` — ccc_self_test()
* ``[ ]`` ``lib.rs`` — frb_generated module import
----
Step 5 — Flutter Build Integration
------------------------------------
* ``[ ]`` Add ``flutter_rust_bridge: ^2`` to ``pubspec.yaml``
* ``[ ]`` Run ``flutter_rust_bridge_codegen generate``
* ``[ ]`` Verify generated ``flutter_src/ccc_crypto_bindings/ccc_crypto.dart``
* ``[ ]`` iOS plugin scaffold (``ios/`` dir, cargokit integration)
* ``[ ]`` Android plugin scaffold (``android/`` dir, CMakeLists.txt)
* ``[ ]`` macOS plugin scaffold (``macos/`` dir)
* ``[ ]`` Confirm ``flutter build ios`` succeeds (static lib linked)
* ``[ ]`` Confirm ``flutter build apk`` succeeds (cdylib linked)
----
Step 6 — Dart Layer Wiring
---------------------------
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``encrypt()````ccc_aead_encrypt()``
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``decrypt()````ccc_aead_decrypt()``
* ``[ ]`` Convert ``CccProviderCatalog.capabilities`` to runtime-populated map
* ``[ ]`` Call ``ccc_init()`` at app startup
* ``[ ]`` Populate ``CccProviderCatalog`` from ``ccc_provider_capabilities()``
* ``[ ]`` Create ``CccSelfTest`` Dart class (wraps ``ccc_self_test()``)
* ``[ ]`` Expose self-test pass/fail diagnostics in app debug screen
----
Step 7 — Conformance Test Suite
Step 3 — Codegen + Plugin Build
---------------------------------
* ``[ ]`` ``tests/conformance/aes_gcm_vectors.rs`` — NIST SP 800-38D vectors
* ``[ ]`` ``tests/conformance/chacha_vectors.rs`` — RFC 8439 vectors
* ``[ ]`` ``tests/conformance/hkdf_vectors.rs`` — RFC 5869 vectors
* ``[ ]`` ``tests/conformance/hmac_vectors.rs`` — RFC 4231 vectors
* ``[ ]`` ``tests/conformance/cross_provider.rs`` — wolfSSL output matches
Dart ``cryptography`` reference output (byte-identity)
* ``[ ]`` ``cargo test --workspace`` all pass
* ``[ ]`` Run ``flutter_rust_bridge_codegen generate``
* ``[ ]`` Verify generated Dart bindings compile
* ``[ ]`` ``flutter build ios`` succeeds (static lib linked)
* ``[ ]`` ``flutter build apk`` succeeds (cdylib linked)
* ``[ ]`` ``flutter build macos`` succeeds
----
Step 8 — Architecture Documentation
--------------------------------------
* ``[ ]`` Create ``docs/phase4_rust_architecture.rst``
* ``[ ]`` Crate dependency graph (ASCII diagram)
* ``[ ]`` "How to add a new provider" — 7-step trait checklist
* ``[ ]`` ``algo: u32`` → cipher constant mapping table
* ``[ ]`` Phase 8 stretch-goal provider list documented
----
Final Verification Gate
Step 4 — Dart API Layer
------------------------
* ``[ ]`` ``cargo test --workspace`` — all pass
* ``[ ]`` ``cargo build --target aarch64-apple-ios`` — success
* ``[ ]`` ``cargo build --target aarch64-linux-android`` — success
* ``[ ]`` Flutter roundtrip integration test passes (1 KB encrypt/decrypt)
* ``[ ]`` ``CccSelfTest.runAll()`` all-pass in app debug screen
* ``[ ]`` Cross-provider conformance confirmed (``deterministic_io: true``
verified for AES-256-GCM and ChaCha20-Poly1305)
* ``[ ]`` ``CccCrypto`` class (wraps all bridge calls)
* ``[ ]`` ``CccSelfTest`` class (wraps ccc_self_test())
* ``[ ]`` ``CccProviderCatalog`` (runtime-populated from ccc_capabilities())
----
Step 5 — Flutter Integration Tests
------------------------------------
* ``[ ]`` Roundtrip encrypt/decrypt 1 KB (AES-256-GCM)
* ``[ ]`` Roundtrip encrypt/decrypt 1 KB (ChaCha20-Poly1305)
* ``[ ]`` ``CccSelfTest.runAll()`` — all-pass
----
Milestone 2 Verification Gate
------------------------------
* ``[ ]`` All Flutter integration tests pass on iOS simulator
* ``[ ]`` All Flutter integration tests pass on Android emulator
* ``[ ]`` Package published / tagged ``v0.1.0``
----
============================================================
Milestone 3 — LetUsMsg App Integration
============================================================
*(Not started — begins after Milestone 2 gate passes)*
* ``[ ]`` Add ``ccc_cryptography`` to ``letusmsg`` `pubspec.yaml``
* ``[ ]`` Wire ``crypto_wolfssl.dart`` encrypt/decrypt → bridge calls
* ``[ ]`` Call ``CccCrypto.cccInit()`` at app startup
* ``[ ]`` Populate ``CccProviderCatalog`` from runtime capabilities
* ``[ ]`` Expose ``CccSelfTest.runAll()`` in app debug screen
* ``[ ]`` End-to-end integration test (send + receive encrypted message)
----

View File

@ -1,24 +0,0 @@
# flutter_rust_bridge v2.11.1 codegen configuration
# Run from the ccc_rust/ workspace root:
# flutter_rust_bridge_codegen generate
#
# All paths are relative to this file (the workspace root).
# Crate directory containing the bridge Cargo.toml.
rust_root: "crates/ccc-flutter-bridge"
# Module paths within the crate that expose #[frb] functions.
# Comma-separated, using Rust module path syntax.
rust_input: "crate::bridge,crate::dto"
# Where to write the generated Dart bindings.
dart_output: "../letusmsg_app/flutter_src/lib/gen/rust"
# Where to write the generated Rust glue (frb_generated.rs).
rust_output: "crates/ccc-flutter-bridge/src/frb_generated.rs"
# C header for iOS/macOS static-lib linkage.
c_output: "crates/ccc-flutter-bridge/include/ccc_bridge.h"
# Dart class name exposed to the Flutter app.
dart_entrypoint_class_name: "CccCrypto"

View File

@ -1,17 +0,0 @@
/// Attachment crypto context for BG attachment read/write operations.
///
/// This data container carries the resolved channel cipher sequence and the
/// key-schedule-enriched cipher parameters so attachment crypto calls can use
/// one consistent context.
class AttachmentCryptoContext {
/// Ordered cipher chain applied by CCC for this attachment operation.
final List<int> cipherSequence;
/// Effective cipher parameters, including key-schedule metadata.
final Map<String, dynamic> cipherParams;
const AttachmentCryptoContext({
required this.cipherSequence,
required this.cipherParams,
});
}

View File

@ -1,321 +0,0 @@
///
/// Channel-level CCC profile derivation.
///
import 'package:letusmsg/ccc/ccc_kdf.dart';
import 'package:letusmsg/ccc/cipher_constants.dart';
import 'package:letusmsg/ccc/ccc_provider_spec.dart';
import 'package:letusmsg/data/ccc_data.dart';
/// One resolved cipher execution step for a channel CCC route.
///
/// The step identifies a primary provider and optional ordered fallbacks.
class CccRouteStep {
final int cipher;
final CccCryptoProvider provider;
final List<CccCryptoProvider> fallbackProviders;
const CccRouteStep({
required this.cipher,
required this.provider,
required this.fallbackProviders,
});
Map<String, dynamic> toMap() {
return {
'cipher': cipher,
'provider': provider.name,
'fallbackProviders': fallbackProviders.map((item) => item.name).toList(growable: false),
};
}
}
/// Concrete cipher configuration derived from per-channel `CCCData`.
class ChannelCccProfile {
final int combo;
final int iterations;
final CccExecutionMode executionMode;
final bool allowFallback;
final List<CccProviderSpec> providers;
final List<CccRouteStep> route;
final List<int> cipherSequence;
final Map<String, dynamic> cipherParams;
const ChannelCccProfile({
required this.combo,
required this.iterations,
required this.executionMode,
required this.allowFallback,
required this.providers,
required this.route,
required this.cipherSequence,
required this.cipherParams,
});
static const int _maxIterations = 16;
factory ChannelCccProfile.fromCccData(CCCData? cccData) {
final combo = cccData?.combo ?? 0;
final rawIterations = cccData?.iterrations ?? 0;
final kdfFunctionValue = normalizeCccKdfFunctionValue(cccData?.kdfFunction);
final kdfFunction = cccKdfFunctionFromInt(kdfFunctionValue);
final executionMode = cccExecutionModeFromString(cccData?.executionMode ?? 'auto');
final iterations = rawIterations <= 0
? 1
: rawIterations.clamp(1, _maxIterations);
final allowFallback = executionMode != CccExecutionMode.strict;
final providers = _providersForCombo(combo);
final route = _resolveRoute(providers, executionMode: executionMode, allowFallback: allowFallback);
final baseSequence = route.map((item) => item.cipher).toList(growable: false);
final expandedSequence = <int>[];
for (var i = 0; i < iterations; i++) {
expandedSequence.addAll(baseSequence);
}
final params = <String, dynamic>{
...CipherConstants.DEFAULT_CIPHER_PARAMS,
'ccc_combo': combo,
'ccc_iterations': iterations,
'ccc_kdf_function': kdfFunction.name,
'ccc_kdf_function_value': kdfFunction.value,
'ccc_execution_mode': executionMode.name,
'ccc_allow_fallback': allowFallback,
'ccc_providers': providers.map((provider) => provider.toMap()).toList(growable: false),
'ccc_route': route.map((step) => step.toMap()).toList(growable: false),
// Phase 3: length-hiding padding parameters
'ccc_min_padding_bytes': cccData?.minPaddingBytes ?? 32,
'ccc_block_size': cccData?.blockSize ?? 256,
};
return ChannelCccProfile(
combo: combo,
iterations: iterations,
executionMode: executionMode,
allowFallback: allowFallback,
providers: providers,
route: route,
cipherSequence: expandedSequence,
cipherParams: params,
);
}
static List<CccRouteStep> _resolveRoute(
List<CccProviderSpec> providers, {
required CccExecutionMode executionMode,
required bool allowFallback,
}) {
final strictRoute = <CccRouteStep>[];
for (final provider in providers) {
for (final cipher in provider.ciphers) {
strictRoute.add(CccRouteStep(
cipher: cipher,
provider: provider.provider,
fallbackProviders: const [],
));
}
}
if (executionMode == CccExecutionMode.strict) {
return strictRoute;
}
final optimizedRoute = <CccRouteStep>[];
for (final step in strictRoute) {
final ordered = _orderedProviderCandidates(
cipher: step.cipher,
configuredProviders: providers.map((item) => item.provider).toSet(),
executionMode: executionMode,
);
final primary = ordered.isNotEmpty ? ordered.first : step.provider;
final fallbacks = allowFallback
? ordered.where((provider) => provider != primary).toList(growable: false)
: const <CccCryptoProvider>[];
optimizedRoute.add(CccRouteStep(
cipher: step.cipher,
provider: primary,
fallbackProviders: fallbacks,
));
}
return optimizedRoute;
}
static List<CccCryptoProvider> _orderedProviderCandidates({
required int cipher,
required Set<CccCryptoProvider> configuredProviders,
required CccExecutionMode executionMode,
}) {
final configured = configuredProviders.where((provider) {
return CccProviderCatalog.supports(provider, cipher);
}).toList(growable: false);
if (executionMode == CccExecutionMode.efficient) {
final rankedConfigured = List<CccCryptoProvider>.from(configured)
..sort((a, b) {
final capA = CccProviderCatalog.capability(a, cipher);
final capB = CccProviderCatalog.capability(b, cipher);
final perf = (capB?.efficiencyScore ?? 0).compareTo(capA?.efficiencyScore ?? 0);
if (perf != 0) return perf;
return (capB?.reliabilityScore ?? 0).compareTo(capA?.reliabilityScore ?? 0);
});
return rankedConfigured;
}
final availableRanked = CccProviderCatalog.providersSupporting(cipher, availableOnly: true)
.where(configuredProviders.contains)
.toList(growable: false);
final unavailableRanked = CccProviderCatalog.providersSupporting(cipher, availableOnly: false)
.where((provider) => configuredProviders.contains(provider) && !availableRanked.contains(provider))
.toList(growable: false);
return [
...availableRanked,
...unavailableRanked,
];
}
static List<CccProviderSpec> _providersForCombo(int combo) {
switch (combo) {
case 0:
// Combo 0 is reserved as the plaintext / unencrypted legacy combo.
// It produces an empty cipher sequence so the CCC pipeline serializes
// and deserializes JSON only, with no encryption layers applied.
// Backwards-compatible with all channels created before Normal Mode.
// When Normal Mode is complete, CCCData.blank() will be updated to a
// real encrypted combo (e.g. combo 5 or higher) and combo 0 will
// remain available only for migration/legacy channels.
return [];
case 1:
return [
CccProviderSpec(
provider: CccCryptoProvider.wolfssl,
ciphers: [
CipherConstants.AES_GCM_256,
CipherConstants.CHACHA20_POLY1305,
],
),
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: [
CipherConstants.HMAC_SHA512,
CipherConstants.BLAKE2B,
],
),
];
case 2:
return [
CccProviderSpec(
provider: CccCryptoProvider.boringssl,
ciphers: [
CipherConstants.CHACHA20_POLY1305,
CipherConstants.XCHACHA20_POLY1305,
],
),
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: [
CipherConstants.AES_GCM_256,
],
),
];
case 3:
return [
CccProviderSpec(
provider: CccCryptoProvider.openssl,
ciphers: [
CipherConstants.AES_GCM_256,
],
),
CccProviderSpec(
provider: CccCryptoProvider.wolfssl,
ciphers: [
CipherConstants.CHACHA20_POLY1305,
],
),
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: [
CipherConstants.BLAKE2B,
],
),
];
case 4:
return [
CccProviderSpec(
provider: CccCryptoProvider.wolfssl,
ciphers: [
CipherConstants.XCHACHA20_POLY1305,
],
),
CccProviderSpec(
provider: CccCryptoProvider.openssl,
ciphers: [
CipherConstants.HMAC_SHA512,
],
),
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: [
CipherConstants.BLAKE2B,
],
),
];
// --- Basic / user-selectable combos (5-9) ---
case 5:
// Basic: single AES-256-GCM
return [
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: List<int>.from(CipherConstants.BASIC_AES_SEQUENCE),
),
];
case 6:
// Basic: single ChaCha20-Poly1305
return [
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: List<int>.from(CipherConstants.BASIC_CHACHA_SEQUENCE),
),
];
case 7:
// Basic: single XChaCha20-Poly1305
return [
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: List<int>.from(CipherConstants.BASIC_XCHACHA_SEQUENCE),
),
];
case 8:
// Dual AEAD: AES-256-GCM + ChaCha20-Poly1305
return [
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: List<int>.from(CipherConstants.DUAL_AEAD_SEQUENCE),
),
];
case 9:
// Triple AEAD: AES + ChaCha20 + XChaCha20
return [
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: List<int>.from(CipherConstants.TRIPLE_AEAD_SEQUENCE),
),
];
default:
// Unknown combo falls back to standard 5-layer
return [
CccProviderSpec(
provider: CccCryptoProvider.cryptography,
ciphers: List<int>.from(CipherConstants.PHASE1_SEQUENCE),
),
];
}
}
}

View File

@ -1,632 +0,0 @@
///
/// Cipher implementations for isolate workers
/// Uses cryptography package for all crypto operations
///
import 'dart:convert';
import 'dart:isolate';
import 'dart:math';
import 'dart:typed_data';
import 'package:cryptography/cryptography.dart';
import 'ccc_iso_operation.dart';
import 'ccc_iso_result.dart';
import 'cipher_constants.dart';
/// Main crypto worker function that runs in isolates
@pragma('vm:entry-point')
Future<Map<String, dynamic>> cryptoWorkerFunction(Map<String, dynamic> data) async {
final startTime = DateTime.now();
// Use isolate hash code as a stable worker identifier
final isolateHash = Isolate.current.hashCode.abs() % 10000;
final workerId = 'isolate_$isolateHash';
try {
// Deserialize operation
final operation = CryptoOperation.fromMap(data);
// Execute cipher chain
final resultData = await _executeCipherChain(
operation.data,
operation.cipherSequence,
operation.params,
operation.type,
);
final processingTime = DateTime.now().difference(startTime);
// Create success result
final result = CryptoResult.success(
operationId: operation.id,
data: resultData,
processingTime: processingTime,
workerId: workerId,
);
return result.toMap();
} catch (error, stackTrace) {
final processingTime = DateTime.now().difference(startTime);
// Create error result
final result = CryptoResult.error(
operationId: data['id'] as String? ?? 'unknown',
error: error.toString(),
stackTrace: stackTrace.toString(),
processingTime: processingTime,
workerId: workerId,
);
return result.toMap();
}
}
/// Execute cipher chain based on operation type
Future<List<int>> _executeCipherChain(List<int> inputData, List<int> cipherSequence,
Map<String, dynamic> params, OperationType operationType) async {
List<int> data = inputData;
final associatedData = _buildAssociatedDataBytes(params);
if (operationType == OperationType.encrypt) {
// Apply length-hiding padding before encryption
data = _applyPadding(data, params);
// Forward execution for encryption
for (var i = 0; i < cipherSequence.length; i++) {
data = await _executeCipher(
data,
cipherSequence[i],
params,
true,
associatedData: associatedData,
layerIndex: i,
);
}
} else {
// Reverse execution for decryption peel outermost layer first
for (var i = cipherSequence.length - 1; i >= 0; i--) {
data = await _executeCipher(
data,
cipherSequence[i],
params,
false,
associatedData: associatedData,
layerIndex: i,
);
}
// Strip length-hiding padding after decryption
data = _stripPadding(data);
}
return data;
}
/// Execute single cipher operation.
///
/// [layerIndex] identifies this step's position in the cipher sequence.
/// Combined with [cipherConstant], it allows deterministic per-layer key
/// derivation when `phase1_root_key_b64` is present in [params].
Future<List<int>> _executeCipher(List<int> data, int cipherConstant,
Map<String, dynamic> params, bool isEncrypt,
{List<int>? associatedData, int layerIndex = 0}) async {
switch (cipherConstant) {
// Key Derivation Functions
case CipherConstants.ARGON2ID:
return await _executeArgon2id(data, params);
// AEAD Ciphers
case CipherConstants.AES_GCM_256:
return await _executeAesGcm256(data, params, isEncrypt,
associatedData: associatedData, layerIndex: layerIndex);
case CipherConstants.CHACHA20_POLY1305:
return await _executeChacha20Poly1305(data, params, isEncrypt,
associatedData: associatedData, layerIndex: layerIndex);
case CipherConstants.XCHACHA20_POLY1305:
return await _executeXChacha20Poly1305(data, params, isEncrypt,
associatedData: associatedData, layerIndex: layerIndex);
// MAC Algorithms
case CipherConstants.HMAC_SHA512:
return await _executeHmacSha512(data, params, isEncrypt, layerIndex: layerIndex);
case CipherConstants.BLAKE2B:
return await _executeBlake2b(data, params, isEncrypt);
default:
throw UnsupportedError('Cipher not implemented: ${CipherConstants.getCipherName(cipherConstant)}');
}
}
/// Argon2id key derivation (always applied, regardless of encrypt/decrypt)
Future<List<int>> _executeArgon2id(List<int> data, Map<String, dynamic> params) async {
final algorithm = Argon2id(
memory: params['argon2_memory'] as int? ?? 64 * 1024,
parallelism: params['argon2_parallelism'] as int? ?? 4,
iterations: params['argon2_iterations'] as int? ?? 3,
hashLength: params['argon2_hash_length'] as int? ?? 32,
);
// Use first 16 bytes as salt, or generate if data is too short
Uint8List salt;
if (data.length >= 16) {
salt = Uint8List.fromList(data.take(16).toList());
} else {
salt = Uint8List.fromList(List.generate(16, (i) => i));
}
final secretKey = await algorithm.deriveKeyFromPassword(
password: String.fromCharCodes(data),
nonce: salt.toList(),
);
final keyBytes = await secretKey.extractBytes();
return keyBytes;
}
/// AES-256-GCM encryption/decryption.
///
/// When `phase1_root_key_b64` is present in [params], derives a deterministic
/// per-layer key from the channel root key (real E2E encryption).
/// Output format: `[12B nonce][ciphertext][16B MAC]`.
///
/// Legacy mode (no root key): generates a random key per encryption and
/// embeds it in the output: `[32B key][12B nonce][ciphertext][16B MAC]`.
Future<List<int>> _executeAesGcm256(
List<int> data,
Map<String, dynamic> params,
bool isEncrypt, {
List<int>? associatedData,
int layerIndex = 0,
}) async {
final algorithm = AesGcm.with256bits();
final useDerivedKey = params.containsKey('phase1_root_key_b64');
if (isEncrypt) {
final SecretKey secretKey;
if (useDerivedKey) {
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.AES_GCM_256, 32);
secretKey = SecretKeyData(keyBytes);
} else {
secretKey = await algorithm.newSecretKey();
}
final secretBox = await algorithm.encrypt(
data,
secretKey: secretKey,
aad: associatedData ?? const <int>[],
);
if (useDerivedKey) {
// Derived-key format: [nonce][ciphertext][mac]
return [...secretBox.nonce, ...secretBox.cipherText, ...secretBox.mac.bytes];
}
// Legacy format: [key][nonce][ciphertext][mac]
final keyBytes = await secretKey.extractBytes();
return [...keyBytes, ...secretBox.nonce, ...secretBox.cipherText, ...secretBox.mac.bytes];
} else {
if (useDerivedKey) {
// Derived-key format: [12B nonce][ciphertext][16B mac]
if (data.length < 12 + 16) {
throw ArgumentError('Invalid AES-GCM derived-key data length');
}
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.AES_GCM_256, 32);
final nonce = data.sublist(0, 12);
final cipherText = data.sublist(12, data.length - 16);
final macBytes = data.sublist(data.length - 16);
return await algorithm.decrypt(
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
secretKey: SecretKeyData(keyBytes),
aad: associatedData ?? const <int>[],
);
}
// Legacy format: [32B key][12B nonce][ciphertext][16B mac]
if (data.length < 32 + 12 + 16) {
throw ArgumentError('Invalid AES-GCM data length');
}
final keyBytes = data.sublist(0, 32);
final nonce = data.sublist(32, 44);
final cipherText = data.sublist(44, data.length - 16);
final macBytes = data.sublist(data.length - 16);
return await algorithm.decrypt(
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
secretKey: SecretKeyData(keyBytes),
aad: associatedData ?? const <int>[],
);
}
}
/// ChaCha20-Poly1305 encryption/decryption.
///
/// Derived-key output: `[12B nonce][ciphertext][16B MAC]`.
/// Legacy output: `[32B key][12B nonce][ciphertext][16B MAC]`.
Future<List<int>> _executeChacha20Poly1305(
List<int> data,
Map<String, dynamic> params,
bool isEncrypt, {
List<int>? associatedData,
int layerIndex = 0,
}) async {
final algorithm = Chacha20.poly1305Aead();
final useDerivedKey = params.containsKey('phase1_root_key_b64');
if (isEncrypt) {
final SecretKey secretKey;
if (useDerivedKey) {
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.CHACHA20_POLY1305, 32);
secretKey = SecretKeyData(keyBytes);
} else {
secretKey = await algorithm.newSecretKey();
}
final secretBox = await algorithm.encrypt(
data,
secretKey: secretKey,
aad: associatedData ?? const <int>[],
);
if (useDerivedKey) {
return [...secretBox.nonce, ...secretBox.cipherText, ...secretBox.mac.bytes];
}
final keyBytes = await secretKey.extractBytes();
return [...keyBytes, ...secretBox.nonce, ...secretBox.cipherText, ...secretBox.mac.bytes];
} else {
if (useDerivedKey) {
if (data.length < 12 + 16) {
throw ArgumentError('Invalid ChaCha20-Poly1305 derived-key data length');
}
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.CHACHA20_POLY1305, 32);
final nonce = data.sublist(0, 12);
final cipherText = data.sublist(12, data.length - 16);
final macBytes = data.sublist(data.length - 16);
return await algorithm.decrypt(
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
secretKey: SecretKeyData(keyBytes),
aad: associatedData ?? const <int>[],
);
}
if (data.length < 32 + 12 + 16) {
throw ArgumentError('Invalid ChaCha20-Poly1305 data length');
}
final keyBytes = data.sublist(0, 32);
final nonce = data.sublist(32, 44);
final cipherText = data.sublist(44, data.length - 16);
final macBytes = data.sublist(data.length - 16);
return await algorithm.decrypt(
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
secretKey: SecretKeyData(keyBytes),
aad: associatedData ?? const <int>[],
);
}
}
/// XChaCha20-Poly1305 encryption/decryption.
///
/// Derived-key output: `[24B nonce][ciphertext][16B MAC]`.
/// Legacy output: `[32B key][24B nonce][ciphertext][16B MAC]`.
Future<List<int>> _executeXChacha20Poly1305(
List<int> data,
Map<String, dynamic> params,
bool isEncrypt, {
List<int>? associatedData,
int layerIndex = 0,
}) async {
final algorithm = Xchacha20.poly1305Aead();
final useDerivedKey = params.containsKey('phase1_root_key_b64');
if (isEncrypt) {
final SecretKey secretKey;
if (useDerivedKey) {
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.XCHACHA20_POLY1305, 32);
secretKey = SecretKeyData(keyBytes);
} else {
secretKey = await algorithm.newSecretKey();
}
final secretBox = await algorithm.encrypt(
data,
secretKey: secretKey,
aad: associatedData ?? const <int>[],
);
if (useDerivedKey) {
return [...secretBox.nonce, ...secretBox.cipherText, ...secretBox.mac.bytes];
}
final keyBytes = await secretKey.extractBytes();
return [...keyBytes, ...secretBox.nonce, ...secretBox.cipherText, ...secretBox.mac.bytes];
} else {
if (useDerivedKey) {
if (data.length < 24 + 16) {
throw ArgumentError('Invalid XChaCha20-Poly1305 derived-key data length');
}
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.XCHACHA20_POLY1305, 32);
final nonce = data.sublist(0, 24);
final cipherText = data.sublist(24, data.length - 16);
final macBytes = data.sublist(data.length - 16);
return await algorithm.decrypt(
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
secretKey: SecretKeyData(keyBytes),
aad: associatedData ?? const <int>[],
);
}
if (data.length < 32 + 24 + 16) {
throw ArgumentError('Invalid XChaCha20-Poly1305 data length');
}
final keyBytes = data.sublist(0, 32);
final nonce = data.sublist(32, 56);
final cipherText = data.sublist(56, data.length - 16);
final macBytes = data.sublist(data.length - 16);
return await algorithm.decrypt(
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
secretKey: SecretKeyData(keyBytes),
aad: associatedData ?? const <int>[],
);
}
}
/// Build canonical associated-data bytes from Phase 1 params.
///
/// The worker expects `phase1_associated_data` in params and canonicalizes it
/// to stable UTF-8 JSON so encrypt/decrypt use identical AEAD AAD bytes.
///
/// Phase 1 exclusions (both removed so sender/receiver produce identical AD):
/// - `direction`: sender uses 'out', receiver uses 'in' for the same ciphertext.
/// - `message_sequence`: sender encrypts before the relay assigns server_seq,
/// so the receiver's relay-assigned sequence would mismatch. Sequence binding
/// will be re-enabled in Phase 2 when the ratchet provides agreed-upon counters.
List<int>? _buildAssociatedDataBytes(Map<String, dynamic> params) {
final phase1 = params['phase1_associated_data'];
if (phase1 == null) return null;
final canonical = _canonicalizeJsonSafe(phase1);
if (canonical == null) return null;
if (canonical is Map<String, dynamic>) {
final normalized = Map<String, dynamic>.from(canonical)
..remove('direction')
..remove('message_sequence');
return utf8.encode(jsonEncode(normalized));
}
if (canonical is List) {
return utf8.encode(jsonEncode(canonical));
}
return utf8.encode(canonical.toString());
}
dynamic _canonicalizeJsonSafe(dynamic value) {
if (value is Map) {
final keys = value.keys.map((item) => item.toString()).toList()..sort();
final normalized = <String, dynamic>{};
for (final key in keys) {
normalized[key] = _canonicalizeJsonSafe(value[key]);
}
return normalized;
}
if (value is List) {
return value.map(_canonicalizeJsonSafe).toList(growable: false);
}
return value;
}
// ---------------------------------------------------------------------------
// Derived-key helpers
// ---------------------------------------------------------------------------
/// Derive a deterministic per-layer encryption key from the channel root key.
///
/// Uses SHA-512 (always, regardless of channel KDF selection) to ensure
/// sufficient output length for all cipher types:
/// - AEAD ciphers (AES-256-GCM, ChaCha20, XChaCha20): 32 bytes
/// - HMAC-SHA512: 64 bytes
///
/// The derivation is deterministic: identical inputs on sender and receiver
/// produce the same key, enabling real E2E encryption without embedding keys
/// in the ciphertext.
///
/// Formula: `SHA-512(rootKeyBytes || "|lk|{layerIndex}|c|{cipherConstant}")`
/// truncated to [keyLength] bytes.
Future<List<int>> _deriveLayerKey(
Map<String, dynamic> params,
int layerIndex,
int cipherConstant,
int keyLength,
) async {
final rootKeyB64 = params['phase1_root_key_b64'] as String;
final rootKeyBytes = _decodeBase64UrlNoPad(rootKeyB64);
// Derive: SHA-512(rootKey || domain-separation-label)
final input = [
...rootKeyBytes,
...utf8.encode('|lk|$layerIndex|c|$cipherConstant'),
];
final hash = await Sha512().hash(input);
return hash.bytes.sublist(0, keyLength);
}
/// Decode a base64url string that may lack padding characters.
List<int> _decodeBase64UrlNoPad(String encoded) {
final padded = encoded + '=' * ((4 - encoded.length % 4) % 4);
return base64Url.decode(padded);
}
/// Constant-time MAC/hash comparison to prevent timing side-channels.
void _verifyMacBytes(List<int> expected, List<int> computed) {
if (expected.length != computed.length) {
throw ArgumentError('MAC verification failed');
}
int diff = 0;
for (int i = 0; i < expected.length; i++) {
diff |= expected[i] ^ computed[i];
}
if (diff != 0) {
throw ArgumentError('MAC verification failed');
}
}
/// HMAC-SHA512 authentication/verification.
///
/// Derived-key output: `[data][64B MAC]`.
/// Legacy output: `[64B key][data][64B MAC]`.
Future<List<int>> _executeHmacSha512(List<int> data, Map<String, dynamic> params, bool isEncrypt,
{int layerIndex = 0}) async {
final algorithm = Hmac.sha512();
final useDerivedKey = params.containsKey('phase1_root_key_b64');
if (isEncrypt) {
final SecretKey secretKey;
final List<int>? legacyKeyBytes;
if (useDerivedKey) {
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.HMAC_SHA512, 64);
secretKey = SecretKeyData(keyBytes);
legacyKeyBytes = null;
} else {
final keyBytes = List.generate(64, (i) => DateTime.now().microsecond % 256);
secretKey = SecretKeyData(keyBytes);
legacyKeyBytes = keyBytes;
}
final mac = await algorithm.calculateMac(data, secretKey: secretKey);
if (useDerivedKey) {
// Derived-key format: [data][mac]
return [...data, ...mac.bytes];
}
// Legacy format: [key][data][mac]
return [...legacyKeyBytes!, ...data, ...mac.bytes];
} else {
if (useDerivedKey) {
// Derived-key format: [data][64B mac]
if (data.length < 64) {
throw ArgumentError('Invalid HMAC-SHA512 derived-key data length');
}
final macBytes = data.sublist(data.length - 64);
final originalData = data.sublist(0, data.length - 64);
final keyBytes = await _deriveLayerKey(params, layerIndex, CipherConstants.HMAC_SHA512, 64);
final computedMac = await algorithm.calculateMac(originalData, secretKey: SecretKeyData(keyBytes));
_verifyMacBytes(macBytes, computedMac.bytes);
return originalData;
}
// Legacy format: [64B key][data][64B mac]
if (data.length < 64 + 64) {
throw ArgumentError('Invalid HMAC-SHA512 data length');
}
final keyBytes = data.sublist(0, 64);
final macBytes = data.sublist(data.length - 64);
final originalData = data.sublist(64, data.length - 64);
final computedMac = await algorithm.calculateMac(originalData, secretKey: SecretKeyData(keyBytes));
_verifyMacBytes(macBytes, computedMac.bytes);
return originalData;
}
}
/// BLAKE2b hashing/verification (keyless integrity check).
Future<List<int>> _executeBlake2b(List<int> data, Map<String, dynamic> params, bool isEncrypt) async {
final algorithm = Blake2b();
if (isEncrypt) {
final hash = await algorithm.hash(data);
return [...data, ...hash.bytes];
} else {
if (data.length < 64) {
throw ArgumentError('Invalid BLAKE2b data length');
}
final hashBytes = data.sublist(data.length - 64);
final originalData = data.sublist(0, data.length - 64);
final computedHash = await algorithm.hash(originalData);
_verifyMacBytes(hashBytes, computedHash.bytes);
return originalData;
}
}
// ---------------------------------------------------------------------------
// Length-Hiding Padding (Phase 3)
// ---------------------------------------------------------------------------
/// Default minimum random padding bytes.
const int _paddingDefaultMinBytes = 32;
/// Default block size for length-hiding alignment.
const int _paddingDefaultBlockSize = 256;
/// Apply length-hiding padding to plaintext before encryption.
///
/// Format: `[plaintext][random_padding][4-byte padding_length_LE]`
///
/// The padding ensures:
/// 1. At least [minPaddingBytes] random bytes are appended (defeats exact
/// length correlation even for known plaintexts).
/// 2. The total padded length is rounded up to the next multiple of
/// [blockSize] (groups all messages into fixed-size buckets so an
/// eavesdropper cannot distinguish "hi" from "hey").
///
/// The last 4 bytes always store the total padding length as a little-endian
/// uint32, allowing deterministic stripping on the receiver side.
///
/// When both `ccc_min_padding_bytes` and `ccc_block_size` are absent from
/// [params] (legacy channels), padding is still applied with conservative
/// defaults (32B min, 256B block) so all new encryptions are length-hidden.
List<int> _applyPadding(List<int> plaintext, Map<String, dynamic> params) {
final minPadding = (params['ccc_min_padding_bytes'] as int?) ?? _paddingDefaultMinBytes;
final blockSize = (params['ccc_block_size'] as int?) ?? _paddingDefaultBlockSize;
// 4 bytes reserved for the padding-length trailer
const trailerSize = 4;
// Minimum total size: plaintext + minPadding + trailer
final minTotal = plaintext.length + minPadding + trailerSize;
// Round up to next block boundary (block size of 0 or 1 means no alignment)
final effectiveBlock = blockSize > 1 ? blockSize : 1;
final paddedTotal = ((minTotal + effectiveBlock - 1) ~/ effectiveBlock) * effectiveBlock;
// Total padding = everything after plaintext
final totalPadding = paddedTotal - plaintext.length;
// Fill random padding bytes (all except last 4 which are the trailer)
final rng = Random.secure();
final randomLen = totalPadding - trailerSize;
final randomBytes = List<int>.generate(randomLen, (_) => rng.nextInt(256));
// Encode totalPadding as 4-byte little-endian
final trailer = [
totalPadding & 0xFF,
(totalPadding >> 8) & 0xFF,
(totalPadding >> 16) & 0xFF,
(totalPadding >> 24) & 0xFF,
];
return [...plaintext, ...randomBytes, ...trailer];
}
/// Strip length-hiding padding after decryption.
///
/// Reads the 4-byte little-endian trailer at the end of [paddedData] to
/// determine how many bytes of padding to remove.
///
/// Throws [ArgumentError] if the trailer claims a padding length larger than
/// the data itself (corruption or tamper indicator the AEAD layer should
/// already have caught tampering, but defense-in-depth).
List<int> _stripPadding(List<int> paddedData) {
const trailerSize = 4;
if (paddedData.length < trailerSize) {
// Data too short to contain a padding trailer return as-is for
// backward compatibility with pre-Phase-3 messages that have no padding.
return paddedData;
}
final len = paddedData.length;
final totalPadding = paddedData[len - 4] |
(paddedData[len - 3] << 8) |
(paddedData[len - 2] << 16) |
(paddedData[len - 1] << 24);
// Sanity: padding must be at least trailerSize and at most the full data
if (totalPadding < trailerSize || totalPadding > len) {
// Not a padded message (pre-Phase-3 legacy) return unchanged.
return paddedData;
}
return paddedData.sublist(0, len - totalPadding);
}

View File

@ -1,247 +0,0 @@
///
/// Crypto Isolate Manager
/// Manages worker pool for encryption/decryption operations
///
import 'dart:async';
import 'package:worker_manager/worker_manager.dart';
import 'ccc_iso_operation.dart';
import 'ccc_iso_result.dart';
import 'ccc_iso_operation_id.dart';
import 'ccc_iso.dart';
import 'cipher_constants.dart';
/// Manages isolate workers for cryptographic operations
///
/// CCC (Copius Cipher Chain) Architecture:
/// - Single encryption system for ALL data (messages, attachments, thumbnails)
/// - Uses channel UUID for key derivation scope
/// - Same key encrypts message + its attachments (bundled together)
/// - Used for both over-the-wire AND local storage encryption
///
/// FUTURE WORK: Ratchet System
/// - Per-message key derivation using Double Ratchet algorithm
/// - Keys derived from: channelUuid + messageSequence + rootKey
/// - Forward secrecy: compromise of one key doesn't reveal past messages
/// - Break-in recovery: new keys derived after compromise
///
/// Current State: Full CCC encryption with isolate workers
/// Future: Ratchet key derivation integration
class CryptoIsolateManager {
// Performance tracking
final CryptoMetrics _metrics = CryptoMetrics();
// Configuration
static const Duration OPERATION_TIMEOUT = Duration(seconds: 30);
static const int WORKER_POOL_SIZE = 4; // Fixed pool of 4 workers
bool _initialized = false;
bool _disposed = false;
/// Initialize the crypto isolate manager
Future<void> initialize() async {
if (_initialized) return;
// Initialize operation ID generator
CryptoOperationId.initialize();
// Configure worker manager with fixed pool size
await workerManager.init(isolatesCount: WORKER_POOL_SIZE);
_initialized = true;
}
/// Encrypt data with specified cipher sequence
/// In passthrough mode, returns data unchanged (for development)
///
/// [channelUuid] - Channel UUID for key derivation scope
/// [messageSequence] - Message sequence number for per-message key derivation (future: ratchet)
/// [isUserMessage] - Priority flag for worker queue
///
/// FUTURE: When ratchet is implemented, key = derive(channelRootKey, messageSequence)
/// Same key will encrypt: MsgData + all attachment files + all thumbnails for that message
Future<CryptoResult> encrypt(
List<int> plaintext, {
required String channelUuid,
int? messageSequence, // Future: used for ratchet key derivation
List<int>? cipherSequence,
Map<String, dynamic>? params,
bool isUserMessage = false,
}) async {
_ensureInitialized();
final operation = CryptoOperation.encrypt(
plaintext: plaintext,
cipherSequence: cipherSequence ?? CipherConstants.PHASE1_SEQUENCE,
params: params ?? CipherConstants.DEFAULT_CIPHER_PARAMS,
channelUuid: channelUuid,
messageSequence: messageSequence,
isUserMessage: isUserMessage,
);
return await _executeOperation(operation);
}
/// Decrypt data with specified cipher sequence
/// In passthrough mode, returns data unchanged (for development)
///
/// [channelUuid] - Channel UUID for key derivation scope
/// [messageSequence] - Message sequence number for per-message key derivation (future: ratchet)
///
/// FUTURE: When ratchet is implemented, key = derive(channelRootKey, messageSequence)
Future<CryptoResult> decrypt(
List<int> ciphertext, {
required String channelUuid,
int? messageSequence, // Future: used for ratchet key derivation
List<int>? cipherSequence,
Map<String, dynamic>? params,
}) async {
_ensureInitialized();
final operation = CryptoOperation.decrypt(
ciphertext: ciphertext,
cipherSequence: cipherSequence ?? CipherConstants.PHASE1_SEQUENCE,
params: params ?? CipherConstants.DEFAULT_CIPHER_PARAMS,
channelUuid: channelUuid,
messageSequence: messageSequence,
);
return await _executeOperation(operation);
}
/// Execute a crypto operation
Future<CryptoResult> _executeOperation(CryptoOperation operation) async {
_ensureInitialized();
if (_disposed) {
throw StateError('CryptoIsolateManager has been disposed');
}
try {
// Execute using the global workerManager with correct API
final resultMap = await workerManager.execute<Map<String, dynamic>>(
() => cryptoWorkerFunction(operation.toMap()),
priority: operation.priority,
).timeout(OPERATION_TIMEOUT);
// Deserialize result
final result = CryptoResult.fromMap(resultMap);
// Update metrics
_updateMetrics(operation, result);
return result;
} catch (error, stackTrace) {
// Create error result
final result = CryptoResult.error(
operationId: operation.id,
error: error.toString(),
stackTrace: stackTrace.toString(),
processingTime: operation.age,
workerId: 'manager_error',
);
// Update metrics
_metrics.recordError();
return result;
}
}
/// Update performance metrics
void _updateMetrics(CryptoOperation operation, CryptoResult result) {
if (result.success) {
if (operation.type == OperationType.encrypt) {
_metrics.recordEncryption(result.processingTime, operation.isHighPriority);
} else {
_metrics.recordDecryption(result.processingTime);
}
} else {
_metrics.recordError();
}
}
/// Get current performance metrics
CryptoMetrics get metrics => _metrics;
/// Get worker manager stats
String get workerStats {
return 'Crypto Manager Stats: ${_metrics.toString()}';
}
/// Check if manager is ready
bool get isReady => _initialized && !_disposed;
/// Ensure manager is initialized
void _ensureInitialized() {
if (!_initialized) {
throw StateError('CryptoIsolateManager must be initialized before use');
}
if (_disposed) {
throw StateError('CryptoIsolateManager has been disposed');
}
}
/// Dispose the isolate manager and clean up resources
Future<void> dispose() async {
if (_disposed) return;
_disposed = true;
// Dispose global worker manager
try {
await workerManager.dispose();
} catch (e) {
// Ignore disposal errors
}
// Clear metrics
_metrics.reset();
}
/// Create a test operation (for debugging)
Future<CryptoResult> testOperation({
String testData = 'Hello, Crypto World!',
String channelUuid = 'test-channel-uuid',
}) async {
final plaintext = testData.codeUnits;
// Encrypt
final encryptResult = await encrypt(
plaintext,
channelUuid: channelUuid,
isUserMessage: true,
);
if (!encryptResult.success) {
return encryptResult;
}
// Decrypt
final decryptResult = await decrypt(
encryptResult.data,
channelUuid: channelUuid,
);
if (!decryptResult.success) {
return decryptResult;
}
// Verify roundtrip
final decryptedText = String.fromCharCodes(decryptResult.data);
if (decryptedText == testData) {
return CryptoResult.success(
operationId: 'test_roundtrip',
data: decryptResult.data,
processingTime: encryptResult.processingTime + decryptResult.processingTime,
workerId: 'test_manager',
);
} else {
return CryptoResult.error(
operationId: 'test_roundtrip',
error: 'Roundtrip failed: expected "$testData", got "$decryptedText"',
processingTime: encryptResult.processingTime + decryptResult.processingTime,
workerId: 'test_manager',
);
}
}
}

View File

@ -1,190 +0,0 @@
///
/// Data structure for crypto operations in isolates
/// Handles serialization for isolate communication
///
import 'package:worker_manager/worker_manager.dart';
import 'ccc_iso_operation_id.dart';
import 'cipher_constants.dart';
/// Type of cryptographic operation
enum OperationType {
encrypt,
decrypt;
@override
String toString() => name;
static OperationType fromString(String value) {
return OperationType.values.firstWhere(
(type) => type.name == value,
orElse: () => throw ArgumentError('Invalid OperationType: $value'),
);
}
}
/// Cryptographic operation data for isolate workers
///
/// Key derivation (future ratchet implementation):
/// - channelUuid: Scope for key derivation (each channel has unique root key)
/// - messageSequence: Per-message key derivation index
/// - Final key = ratchet(channelRootKey, messageSequence)
///
/// Same derived key encrypts: MsgData + attachment files + thumbnails
class CryptoOperation {
final String id;
final List<int> data;
final List<int> cipherSequence;
final Map<String, dynamic> params;
final OperationType type;
final WorkPriority priority;
final String channelUuid; // Channel UUID for key derivation scope
final int? messageSequence; // Future: ratchet index for per-message key
final DateTime timestamp;
/// Create new crypto operation with auto-generated ID
CryptoOperation({
required this.data,
required this.cipherSequence,
required this.params,
required this.type,
required this.channelUuid,
this.messageSequence,
this.priority = WorkPriority.high,
}) : id = CryptoOperationId.generate(),
timestamp = DateTime.now();
/// Create crypto operation with custom ID (for testing)
CryptoOperation.withId({
required this.id,
required this.data,
required this.cipherSequence,
required this.params,
required this.type,
required this.channelUuid,
this.messageSequence,
this.priority = WorkPriority.high,
}) : timestamp = DateTime.now();
/// Create operation for encryption
factory CryptoOperation.encrypt({
required List<int> plaintext,
required List<int> cipherSequence,
required Map<String, dynamic> params,
required String channelUuid,
int? messageSequence,
bool isUserMessage = false,
}) {
return CryptoOperation(
data: plaintext,
cipherSequence: cipherSequence,
params: params,
type: OperationType.encrypt,
channelUuid: channelUuid,
messageSequence: messageSequence,
priority: isUserMessage ? WorkPriority.immediately : WorkPriority.high,
);
}
/// Create operation for decryption
factory CryptoOperation.decrypt({
required List<int> ciphertext,
required List<int> cipherSequence,
required Map<String, dynamic> params,
required String channelUuid,
int? messageSequence,
}) {
return CryptoOperation(
data: ciphertext,
cipherSequence: cipherSequence,
params: params,
type: OperationType.decrypt,
channelUuid: channelUuid,
messageSequence: messageSequence,
priority: WorkPriority.high,
);
}
/// Serialize for isolate communication
Map<String, dynamic> toMap() {
return {
'id': id,
'data': data,
'cipherSequence': cipherSequence,
'params': params,
'type': type.name,
'priority': priority.index,
'channelUuid': channelUuid,
'messageSequence': messageSequence,
'timestamp': timestamp.millisecondsSinceEpoch,
};
}
/// Deserialize from isolate communication
static CryptoOperation fromMap(Map<String, dynamic> map) {
return CryptoOperation.withId(
id: map['id'] as String,
data: List<int>.from(map['data'] as List),
cipherSequence: List<int>.from(map['cipherSequence'] as List),
params: Map<String, dynamic>.from(map['params'] as Map),
type: OperationType.fromString(map['type'] as String),
channelUuid: map['channelUuid'] as String,
messageSequence: map['messageSequence'] as int?,
priority: WorkPriority.values[map['priority'] as int],
);
}
/// Get cipher sequence as human-readable string
String get cipherSequenceDescription {
return CipherConstants.getSequenceDescription(cipherSequence);
}
/// Validate cipher sequence
bool get hasValidCipherSequence {
return CipherConstants.isValidSequence(cipherSequence);
}
/// Get data size in bytes
int get dataSize => data.length;
/// Get operation age
Duration get age => DateTime.now().difference(timestamp);
/// Check if operation is high priority
bool get isHighPriority => priority == WorkPriority.immediately;
/// Copy with new priority
CryptoOperation copyWithPriority(WorkPriority newPriority) {
return CryptoOperation.withId(
id: id,
data: data,
cipherSequence: cipherSequence,
params: params,
type: type,
channelUuid: channelUuid,
messageSequence: messageSequence,
priority: newPriority,
);
}
@override
String toString() {
return 'CryptoOperation('
'id: $id, '
'type: $type, '
'dataSize: ${dataSize}B, '
'ciphers: ${cipherSequence.length}, '
'priority: $priority, '
'channel: $channelUuid'
')';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CryptoOperation &&
runtimeType == other.runtimeType &&
id == other.id;
@override
int get hashCode => id.hashCode;
}

View File

@ -1,96 +0,0 @@
///
///
///
/// Fast sequence-based ID generator for crypto operations
/// Optimized for high-frequency crypto tasks with minimal overhead
class CryptoOperationId {
static int _counter = 0;
static late final String _sessionId;
static bool _initialized = false;
/// Initialize the ID system with a new session
static void initialize() {
if (_initialized) return; // Don't reinitialize
_sessionId = DateTime.now().millisecondsSinceEpoch.toString();
_counter = 0;
_initialized = true;
}
/// Generate next operation ID
/// Format: "timestamp_sequence"
/// Example: "1692123456789_1"
static String generate() {
if (!_initialized) {
initialize();
}
return '${_sessionId}_${++_counter}';
}
/// Get current session ID (for debugging)
static String get currentSession {
if (!_initialized) {
initialize();
}
return _sessionId;
}
/// Get total operations generated in this session
static int get operationCount => _counter;
/// Reset counter (useful for testing)
static void reset() {
_counter = 0;
}
/// Parse operation ID to extract session and sequence
static OperationIdInfo? parse(String operationId) {
final parts = operationId.split('_');
if (parts.length != 2) return null;
final sessionId = parts[0];
final sequence = int.tryParse(parts[1]);
if (sequence == null) return null;
return OperationIdInfo(
sessionId: sessionId,
sequence: sequence,
operationId: operationId,
);
}
}
/// Information extracted from an operation ID
class OperationIdInfo {
final String sessionId;
final int sequence;
final String operationId;
const OperationIdInfo({
required this.sessionId,
required this.sequence,
required this.operationId,
});
/// Get session timestamp
DateTime? get sessionTimestamp {
final timestamp = int.tryParse(sessionId);
if (timestamp == null) return null;
return DateTime.fromMillisecondsSinceEpoch(timestamp);
}
@override
String toString() => 'OperationIdInfo(session: $sessionId, sequence: $sequence)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is OperationIdInfo &&
runtimeType == other.runtimeType &&
sessionId == other.sessionId &&
sequence == other.sequence;
@override
int get hashCode => sessionId.hashCode ^ sequence.hashCode;
}

View File

@ -1,220 +0,0 @@
///
/// Result structure for crypto operations
/// Handles success/error states and performance metrics
///
/// Result of a cryptographic operation
class CryptoResult {
final String operationId;
final List<int> data;
final bool success;
final String? error;
final String? stackTrace;
final Duration processingTime;
final String workerId;
final DateTime completedAt;
/// Create successful result
CryptoResult.success({
required this.operationId,
required this.data,
required this.processingTime,
required this.workerId,
}) : success = true,
error = null,
stackTrace = null,
completedAt = DateTime.now();
/// Create error result
CryptoResult.error({
required this.operationId,
required this.error,
this.stackTrace,
required this.processingTime,
required this.workerId,
}) : success = false,
data = const [],
completedAt = DateTime.now();
/// Serialize for isolate communication
Map<String, dynamic> toMap() {
return {
'operationId': operationId,
'data': data,
'success': success,
'error': error,
'stackTrace': stackTrace,
'processingTimeMs': processingTime.inMilliseconds,
'workerId': workerId,
'completedAtMs': completedAt.millisecondsSinceEpoch,
};
}
/// Deserialize from isolate communication
static CryptoResult fromMap(Map<String, dynamic> map) {
final success = map['success'] as bool;
final processingTime = Duration(milliseconds: map['processingTimeMs'] as int);
final workerId = map['workerId'] as String;
final operationId = map['operationId'] as String;
if (success) {
return CryptoResult.success(
operationId: operationId,
data: List<int>.from(map['data'] as List),
processingTime: processingTime,
workerId: workerId,
);
} else {
return CryptoResult.error(
operationId: operationId,
error: map['error'] as String?,
stackTrace: map['stackTrace'] as String?,
processingTime: processingTime,
workerId: workerId,
);
}
}
/// Get result data size in bytes
int get dataSize => data.length;
/// Get processing speed in bytes per second
double get bytesPerSecond {
final seconds = processingTime.inMilliseconds / 1000.0;
if (seconds <= 0) return 0.0;
return dataSize / seconds;
}
/// Get human-readable processing speed
String get formattedSpeed {
final bps = bytesPerSecond;
if (bps >= 1024 * 1024) {
return '${(bps / (1024 * 1024)).toStringAsFixed(2)} MB/s';
} else if (bps >= 1024) {
return '${(bps / 1024).toStringAsFixed(2)} KB/s';
} else {
return '${bps.toStringAsFixed(2)} B/s';
}
}
/// Check if operation was slow
bool get isSlow => processingTime.inMilliseconds > 1000; // > 1 second
/// Check if operation was fast
bool get isFast => processingTime.inMilliseconds < 100; // < 100ms
@override
String toString() {
if (success) {
return 'CryptoResult.success('
'id: $operationId, '
'dataSize: ${dataSize}B, '
'time: ${processingTime.inMilliseconds}ms, '
'speed: $formattedSpeed, '
'worker: $workerId'
')';
} else {
return 'CryptoResult.error('
'id: $operationId, '
'error: $error, '
'time: ${processingTime.inMilliseconds}ms, '
'worker: $workerId'
')';
}
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CryptoResult &&
runtimeType == other.runtimeType &&
operationId == other.operationId &&
success == other.success;
@override
int get hashCode => operationId.hashCode ^ success.hashCode;
}
/// Performance metrics for crypto operations
class CryptoMetrics {
int encryptionCount = 0;
int decryptionCount = 0;
Duration totalEncryptionTime = Duration.zero;
Duration totalDecryptionTime = Duration.zero;
int errorCount = 0;
int priorityOperations = 0;
DateTime? lastOperationTime;
/// Record successful encryption
void recordEncryption(Duration time, bool wasPriority) {
encryptionCount++;
totalEncryptionTime += time;
if (wasPriority) priorityOperations++;
lastOperationTime = DateTime.now();
}
/// Record successful decryption
void recordDecryption(Duration time) {
decryptionCount++;
totalDecryptionTime += time;
lastOperationTime = DateTime.now();
}
/// Record error
void recordError() {
errorCount++;
lastOperationTime = DateTime.now();
}
/// Get total operations
int get totalOperations => encryptionCount + decryptionCount;
/// Get average encryption time
Duration get averageEncryptionTime {
if (encryptionCount == 0) return Duration.zero;
return Duration(milliseconds: totalEncryptionTime.inMilliseconds ~/ encryptionCount);
}
/// Get average decryption time
Duration get averageDecryptionTime {
if (decryptionCount == 0) return Duration.zero;
return Duration(milliseconds: totalDecryptionTime.inMilliseconds ~/ decryptionCount);
}
/// Get error rate (0.0 to 1.0)
double get errorRate {
final total = totalOperations + errorCount;
if (total == 0) return 0.0;
return errorCount / total;
}
/// Get priority operation percentage
double get priorityPercentage {
if (totalOperations == 0) return 0.0;
return priorityOperations / totalOperations;
}
/// Reset all metrics
void reset() {
encryptionCount = 0;
decryptionCount = 0;
totalEncryptionTime = Duration.zero;
totalDecryptionTime = Duration.zero;
errorCount = 0;
priorityOperations = 0;
lastOperationTime = null;
}
@override
String toString() {
return 'CryptoMetrics('
'operations: $totalOperations, '
'encryptions: $encryptionCount, '
'decryptions: $decryptionCount, '
'errors: $errorCount, '
'avgEncTime: ${averageEncryptionTime.inMilliseconds}ms, '
'avgDecTime: ${averageDecryptionTime.inMilliseconds}ms, '
'errorRate: ${(errorRate * 100).toStringAsFixed(1)}%'
')';
}
}

View File

@ -1,40 +0,0 @@
/// CCC Key-Derivation Function (KDF) definitions and helpers.
///
/// This module owns:
/// - stable int-backed KDF identifiers for persistence,
/// - normalization/parsing helpers,
/// - conversion from persisted int to enum.
///
/// Keeping this in `ccc/` preserves separation of concerns so data models remain
/// focused on storage shape only.
enum CccKdfFunction {
sha256(1),
sha384(2),
sha512(3),
blake2b512(4);
final int value;
const CccKdfFunction(this.value);
}
/// Default persisted KDF selector.
const int cccDefaultKdfFunctionValue = 1;
/// Normalize arbitrary input to a supported KDF function int value.
int normalizeCccKdfFunctionValue(dynamic raw) {
final parsed = raw is int ? raw : int.tryParse(raw?.toString() ?? '');
if (parsed == null) return cccDefaultKdfFunctionValue;
for (final option in CccKdfFunction.values) {
if (option.value == parsed) return parsed;
}
return cccDefaultKdfFunctionValue;
}
/// Resolve an enum value from persisted int representation.
CccKdfFunction cccKdfFunctionFromInt(int raw) {
for (final option in CccKdfFunction.values) {
if (option.value == raw) return option;
}
return CccKdfFunction.sha256;
}

View File

@ -1,148 +0,0 @@
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
/// local imports
import 'package:letusmsg/ccc/ccc_kdf.dart';
import 'package:letusmsg/data/ccc_data.dart';
/// Phase 1 key schedule helper.
///
/// This utility derives deterministic channel-root and per-message key material
/// with a selectable KDF/hash option so message/attachment flows can share one
/// key-schedule contract before full ratchet rollout.
class CccKeySchedule {
static const String version = 'phase1-v1';
/// Derive the deterministic 32-byte root key for a channel.
///
/// This is the shared secret that all channel members can independently
/// compute from the channel UUID and CCCData configuration. Used both
/// by the Phase 1 static key schedule and as the seed for Phase 2
/// ratchet initialization via [RatchetEngine.initializeFromRoot].
///
/// Returns a 32-byte root key (truncated from the full hash output).
static Future<List<int>> deriveRootKey({
required String channelUuid,
required CCCData? cccData,
}) async {
final combo = cccData?.combo ?? 0;
final iterations = cccData?.iterrations ?? 0;
final kdfFunctionValue = normalizeCccKdfFunctionValue(
cccData?.kdfFunction,
);
final kdfFunction = cccKdfFunctionFromInt(kdfFunctionValue);
final fullHash = await _hashUtf8(
'lum|$version|root|$channelUuid|combo:$combo|iter:$iterations',
kdfFunction,
);
return fullHash.sublist(0, 32);
}
/// Build Phase 1 key-schedule params for one channel/message operation.
///
/// The returned params are additive and safe to merge with existing CCC params.
static Future<CccPhase1KeyMaterial> buildMessageParams({
required String channelUuid,
required CCCData? cccData,
required Map<String, dynamic> baseCipherParams,
required bool isUserMessage,
int? messageSequence,
}) async {
final combo = cccData?.combo ?? 0;
final iterations = cccData?.iterrations ?? 0;
final kdfFunctionValue = normalizeCccKdfFunctionValue(
cccData?.kdfFunction,
);
final kdfFunction = cccKdfFunctionFromInt(kdfFunctionValue);
final effectiveSequence = messageSequence ?? 0;
final rootKey = await _hashUtf8(
'lum|$version|root|$channelUuid|combo:$combo|iter:$iterations',
kdfFunction,
);
final direction = isUserMessage ? 'out' : 'in';
final messageKey = await _hashBytes(
[
...rootKey,
...utf8.encode('|msg|$direction|seq:$effectiveSequence'),
],
kdfFunction,
);
// Note: direction and message_sequence are included in the AD map for
// key-schedule metadata completeness, but are stripped by the isolate
// worker's _buildAssociatedDataBytes() before AEAD binding. This is
// intentional for Phase 1: the sender does not know the relay-assigned
// server_seq at encryption time, and direction differs between sender
// ('out') and receiver ('in'). Phase 2 ratchet will re-enable sequence
// binding once both sides share agreed-upon counters.
final associatedData = <String, dynamic>{
'v': version,
'channel_uuid': channelUuid,
'ccc_combo': combo,
'ccc_iterations': iterations,
'message_sequence': effectiveSequence,
'direction': direction,
'kdf_function': kdfFunction.name,
'kdf_function_value': kdfFunction.value,
};
return CccPhase1KeyMaterial(
effectiveMessageSequence: effectiveSequence,
cipherParams: {
...baseCipherParams,
'phase1_key_schedule_version': version,
'phase1_kdf_function': kdfFunction.name,
'phase1_kdf_function_value': kdfFunction.value,
'phase1_effective_sequence': effectiveSequence,
'phase1_root_key_b64': _toBase64UrlNoPad(rootKey),
'phase1_message_key_b64': _toBase64UrlNoPad(messageKey),
'phase1_associated_data': associatedData,
},
);
}
/// Hash UTF-8 text to bytes using selected KDF/hash function.
static Future<List<int>> _hashUtf8(
String value,
CccKdfFunction kdfFunction,
) async {
return _hashBytes(utf8.encode(value), kdfFunction);
}
/// Hash bytes to bytes using selected KDF/hash function.
static Future<List<int>> _hashBytes(
List<int> bytes,
CccKdfFunction kdfFunction,
) async {
switch (kdfFunction) {
case CccKdfFunction.sha256:
return (await Sha256().hash(bytes)).bytes;
case CccKdfFunction.sha384:
return (await Sha384().hash(bytes)).bytes;
case CccKdfFunction.sha512:
return (await Sha512().hash(bytes)).bytes;
case CccKdfFunction.blake2b512:
return (await Blake2b().hash(bytes)).bytes;
}
}
static String _toBase64UrlNoPad(List<int> bytes) {
return base64UrlEncode(bytes).replaceAll('=', '');
}
}
/// Derived key material metadata for Phase 1 message encryption scheduling.
class CccPhase1KeyMaterial {
/// Effective message sequence used for this derivation.
final int effectiveMessageSequence;
/// Parameters merged into CCC cipher params for runtime usage.
final Map<String, dynamic> cipherParams;
const CccPhase1KeyMaterial({
required this.effectiveMessageSequence,
required this.cipherParams,
});
}

View File

@ -1,182 +0,0 @@
///
/// CCC provider specification models.
///
import 'package:letusmsg/ccc/cipher_constants.dart';
/// Execution strategy for provider routing inside CCC.
enum CccExecutionMode {
strict,
efficient,
auto,
}
CccExecutionMode cccExecutionModeFromString(String raw) {
switch (raw.trim().toLowerCase()) {
case 'strict':
return CccExecutionMode.strict;
case 'efficient':
return CccExecutionMode.efficient;
case 'auto':
return CccExecutionMode.auto;
default:
throw ArgumentError('Unsupported CCC execution mode: $raw');
}
}
/// Logical provider identities used by CCC channel profiles.
enum CccCryptoProvider {
cryptography,
wolfssl,
openssl,
boringssl,
}
/// Provider entry in a CCC provider chain.
///
/// Each provider advertises the ciphers it contributes to the channel plan.
class CccProviderSpec {
final CccCryptoProvider provider;
final List<int> ciphers;
const CccProviderSpec({
required this.provider,
required this.ciphers,
});
Map<String, dynamic> toMap() {
return {
'provider': provider.name,
'ciphers': ciphers,
};
}
}
/// Per-provider, per-cipher capability metadata.
class CccCipherCapability {
/// Whether the provider is currently available in this codebase/runtime.
///
/// `true` means the provider can be selected as primary in `auto` mode.
/// `false` means it can still appear in route planning metadata but should
/// not be chosen as active primary until availability changes.
final bool available;
/// Whether input/output format is identical across providers for this cipher.
///
/// This guards fallback safety. If this is `false`, fallback should be
/// treated as incompatible for that cipher unless a migration adapter exists.
final bool deterministicIo;
/// Relative efficiency score from 0..100 (higher = faster/cheaper).
///
/// Used by `efficient` mode to rank candidate providers for a cipher.
final int efficiencyScore;
/// Relative reliability score from 0..100 (higher = more trusted/stable).
///
/// Used as tie-breaker when efficiency scores are equal.
final int reliabilityScore;
/// Creates immutable provider capability metadata used by CCC route planning.
///
/// Parameter meanings:
/// - [available]: runtime/provider readiness flag.
/// - [deterministicIo]: indicates safe cross-provider fallback compatibility.
/// - [efficiencyScore]: performance rank (0..100), higher is better.
/// - [reliabilityScore]: confidence rank (0..100), higher is better.
const CccCipherCapability({
required this.available,
required this.deterministicIo,
required this.efficiencyScore,
required this.reliabilityScore,
});
}
/// Runtime/provider capability registry used by CCC route planning.
class CccProviderCatalog {
/// Placeholder score used until benchmark pipeline is implemented.
///
/// TODO(j3g): Replace with measured values from automated perf/reliability
/// benchmark suite per provider/cipher.
///
/// Example efficiency calculation (normalized 0..100):
/// efficiency = normalize(throughputMBps / latencyMs)
///
/// Example reliability calculation (normalized 0..100):
/// reliability = normalize((1 - errorRate) * 100 - crashPenalty - cvePenalty)
static const int pendingEfficiencyScore = 50;
static const int pendingReliabilityScore = 50;
static const CccCipherCapability _availableDeterministicPending = CccCipherCapability(
available: true,
deterministicIo: true,
efficiencyScore: pendingEfficiencyScore,
reliabilityScore: pendingReliabilityScore,
);
static const CccCipherCapability _unavailableDeterministicPending = CccCipherCapability(
available: false,
deterministicIo: true,
efficiencyScore: pendingEfficiencyScore,
reliabilityScore: pendingReliabilityScore,
);
static const Map<CccCryptoProvider, Map<int, CccCipherCapability>> capabilities = {
CccCryptoProvider.cryptography: {
CipherConstants.AES_GCM_256: _availableDeterministicPending,
CipherConstants.CHACHA20_POLY1305: _availableDeterministicPending,
CipherConstants.XCHACHA20_POLY1305: _availableDeterministicPending,
CipherConstants.HMAC_SHA512: _availableDeterministicPending,
CipherConstants.BLAKE2B: _availableDeterministicPending,
},
CccCryptoProvider.wolfssl: {
CipherConstants.AES_GCM_256: _unavailableDeterministicPending,
CipherConstants.CHACHA20_POLY1305: _unavailableDeterministicPending,
CipherConstants.XCHACHA20_POLY1305: _unavailableDeterministicPending,
CipherConstants.HMAC_SHA512: _unavailableDeterministicPending,
CipherConstants.BLAKE2B: _unavailableDeterministicPending,
},
CccCryptoProvider.openssl: {
CipherConstants.AES_GCM_256: _unavailableDeterministicPending,
CipherConstants.CHACHA20_POLY1305: _unavailableDeterministicPending,
CipherConstants.HMAC_SHA512: _unavailableDeterministicPending,
CipherConstants.BLAKE2B: _unavailableDeterministicPending,
},
CccCryptoProvider.boringssl: {
CipherConstants.AES_GCM_256: _unavailableDeterministicPending,
CipherConstants.CHACHA20_POLY1305: _unavailableDeterministicPending,
CipherConstants.XCHACHA20_POLY1305: _unavailableDeterministicPending,
CipherConstants.HMAC_SHA512: _unavailableDeterministicPending,
CipherConstants.BLAKE2B: _unavailableDeterministicPending,
},
};
static CccCipherCapability? capability(CccCryptoProvider provider, int cipher) {
return capabilities[provider]?[cipher];
}
static bool supports(CccCryptoProvider provider, int cipher) {
return capability(provider, cipher) != null;
}
static List<CccCryptoProvider> providersSupporting(
int cipher, {
bool availableOnly = true,
}) {
final ranked = <({CccCryptoProvider provider, CccCipherCapability capability})>[];
for (final entry in capabilities.entries) {
final cap = entry.value[cipher];
if (cap == null) continue;
if (availableOnly && !cap.available) continue;
ranked.add((provider: entry.key, capability: cap));
}
ranked.sort((a, b) {
final perf = b.capability.efficiencyScore.compareTo(a.capability.efficiencyScore);
if (perf != 0) return perf;
return b.capability.reliabilityScore.compareTo(a.capability.reliabilityScore);
});
return ranked.map((item) => item.provider).toList(growable: false);
}
}

View File

@ -1,209 +0,0 @@
///
/// Cipher Constants for Copious Cipher Chain
/// Integer constants for efficient cipher sequence storage and processing
///
class CipherConstants {
// Key Derivation Functions
static const int ARGON2ID = 1;
static const int PBKDF2 = 2;
static const int HKDF = 3;
static const int HCHACHA20 = 4;
// Symmetric Ciphers (AEAD)
static const int AES_GCM_128 = 10;
static const int AES_GCM_192 = 11;
static const int AES_GCM_256 = 12;
static const int CHACHA20_POLY1305 = 13;
static const int XCHACHA20_POLY1305 = 14;
// Symmetric Ciphers (Non-AEAD)
static const int AES_CBC_128 = 20;
static const int AES_CBC_192 = 21;
static const int AES_CBC_256 = 22;
static const int AES_CTR_128 = 23;
static const int AES_CTR_192 = 24;
static const int AES_CTR_256 = 25;
static const int CHACHA20 = 26;
static const int XCHACHA20 = 27;
// MAC Algorithms
static const int HMAC_SHA256 = 30;
static const int HMAC_SHA384 = 31;
static const int HMAC_SHA512 = 32;
static const int BLAKE2B = 33;
static const int BLAKE2S = 34;
static const int POLY1305 = 35;
// Hash Algorithms (for integrity verification)
static const int SHA256 = 40;
static const int SHA384 = 41;
static const int SHA512 = 42;
static const int BLAKE2B_HASH = 43;
static const int BLAKE2S_HASH = 44;
// Phase 1 Default Cipher Sequence (5 layers - Argon2id removed for proper reversibility)
static const List<int> PHASE1_SEQUENCE = [
AES_GCM_256, // Primary AEAD encryption
CHACHA20_POLY1305, // Stream cipher AEAD
XCHACHA20_POLY1305, // Extended nonce AEAD
HMAC_SHA512, // Additional authentication
BLAKE2B, // Final integrity check
];
// Complete sequence with key derivation (for future use)
static const List<int> PHASE1_COMPLETE_SEQUENCE = [
ARGON2ID, // Key strengthening (one-way - use for key derivation only)
AES_GCM_256, // Primary AEAD encryption
CHACHA20_POLY1305, // Stream cipher AEAD
XCHACHA20_POLY1305, // Extended nonce AEAD
HMAC_SHA512, // Additional authentication
BLAKE2B, // Final integrity check
];
// --- Basic / user-selectable cipher sequences (combo 5-9) ----------------
/// Single-layer AES-256-GCM (combo 5).
static const List<int> BASIC_AES_SEQUENCE = [AES_GCM_256];
/// Single-layer ChaCha20-Poly1305 (combo 6).
static const List<int> BASIC_CHACHA_SEQUENCE = [CHACHA20_POLY1305];
/// Single-layer XChaCha20-Poly1305 (combo 7).
static const List<int> BASIC_XCHACHA_SEQUENCE = [XCHACHA20_POLY1305];
/// Dual AEAD: AES-256-GCM + ChaCha20-Poly1305 (combo 8).
static const List<int> DUAL_AEAD_SEQUENCE = [AES_GCM_256, CHACHA20_POLY1305];
/// Triple AEAD: AES + ChaCha20 + XChaCha20 (combo 9).
static const List<int> TRIPLE_AEAD_SEQUENCE = [AES_GCM_256, CHACHA20_POLY1305, XCHACHA20_POLY1305];
// Default cipher parameters
static const Map<String, dynamic> DEFAULT_CIPHER_PARAMS = {
// Argon2id parameters
'argon2_memory': 64 * 1024, // 64 MB
'argon2_parallelism': 4, // 4 CPU cores
'argon2_iterations': 3, // 3 iterations
'argon2_hash_length': 32, // 256-bit output
// AES parameters
'aes_key_size': 256, // 256-bit keys
'aes_nonce_size': 12, // 96-bit nonces for GCM
// ChaCha parameters
'chacha_nonce_size': 12, // 96-bit nonces
'xchacha_nonce_size': 24, // 192-bit nonces
// HMAC parameters
'hmac_key_size': 64, // 512-bit keys
// BLAKE2B parameters
'blake2b_hash_size': 64, // 512-bit hashes
};
// Cipher name mapping for debugging
static const Map<int, String> CIPHER_NAMES = {
// Key Derivation
ARGON2ID: 'Argon2id',
PBKDF2: 'PBKDF2',
HKDF: 'HKDF',
HCHACHA20: 'HChaCha20',
// AEAD Ciphers
AES_GCM_128: 'AES-128-GCM',
AES_GCM_192: 'AES-192-GCM',
AES_GCM_256: 'AES-256-GCM',
CHACHA20_POLY1305: 'ChaCha20-Poly1305',
XCHACHA20_POLY1305: 'XChaCha20-Poly1305',
// Non-AEAD Ciphers
AES_CBC_128: 'AES-128-CBC',
AES_CBC_192: 'AES-192-CBC',
AES_CBC_256: 'AES-256-CBC',
AES_CTR_128: 'AES-128-CTR',
AES_CTR_192: 'AES-192-CTR',
AES_CTR_256: 'AES-256-CTR',
CHACHA20: 'ChaCha20',
XCHACHA20: 'XChaCha20',
// MAC Algorithms
HMAC_SHA256: 'HMAC-SHA256',
HMAC_SHA384: 'HMAC-SHA384',
HMAC_SHA512: 'HMAC-SHA512',
BLAKE2B: 'BLAKE2b',
BLAKE2S: 'BLAKE2s',
POLY1305: 'Poly1305',
// Hash Algorithms
SHA256: 'SHA256',
SHA384: 'SHA384',
SHA512: 'SHA512',
BLAKE2B_HASH: 'BLAKE2b-Hash',
BLAKE2S_HASH: 'BLAKE2s-Hash',
};
/// Get human-readable name for cipher constant
static String getCipherName(int cipherConstant) {
return CIPHER_NAMES[cipherConstant] ?? 'Unknown Cipher ($cipherConstant)';
}
/// Get human-readable sequence description
static String getSequenceDescription(List<int> sequence) {
return sequence.map((cipher) => getCipherName(cipher)).join(' -> ');
}
/// Validate cipher sequence
static bool isValidSequence(List<int> sequence) {
// Empty sequence is valid it represents the plaintext/legacy combo 0.
if (sequence.isEmpty) return true;
// Check all ciphers are known
for (final cipher in sequence) {
if (!CIPHER_NAMES.containsKey(cipher)) {
return false;
}
}
return true;
}
// --- Combo metadata -------------------------------------------------------
/// Human-readable names for each combo value.
///
/// Combos 0-4 are multi-layer / multi-provider configurations.
/// Combos 5-9 are user-selectable "basic" through "triple AEAD" options.
static const Map<int, String> COMBO_NAMES = {
0: 'Plaintext (legacy / unencrypted)',
1: 'Multi-Provider: wolfSSL + CCC',
2: 'Multi-Provider: BoringSSL + CCC',
3: 'Multi-Provider: OpenSSL + wolfSSL + CCC',
4: 'Multi-Provider: wolfSSL + OpenSSL + CCC',
5: 'Basic: AES-256-GCM',
6: 'Basic: ChaCha20-Poly1305',
7: 'Basic: XChaCha20-Poly1305',
8: 'Dual AEAD: AES + ChaCha20',
9: 'Triple AEAD: AES + ChaCha20 + XChaCha20',
};
/// Cipher sequence for each combo value.
static const Map<int, List<int>> COMBO_SEQUENCES = {
0: [], // plaintext / legacy empty sequence = no cipher layers
5: BASIC_AES_SEQUENCE,
6: BASIC_CHACHA_SEQUENCE,
7: BASIC_XCHACHA_SEQUENCE,
8: DUAL_AEAD_SEQUENCE,
9: TRIPLE_AEAD_SEQUENCE,
};
/// Maximum supported combo value.
static const int MAX_COMBO = 9;
/// Whether [combo] is a valid, known combo value.
static bool isValidCombo(int combo) => COMBO_NAMES.containsKey(combo);
/// Get the human-readable name for a combo, or a fallback string.
static String getComboName(int combo) {
return COMBO_NAMES[combo] ?? 'Unknown Combo ($combo)';
}
}

View File

@ -1,116 +0,0 @@
///
///
///
import 'ccc_iso_manager.dart';
import 'ccc_iso_result.dart';
import 'cipher_constants.dart';
/// Copious Cipher Chain - Phase 1 with Isolate Support
/// Now supports async operations using CryptoIsolateManager
class CopiousCipherChain {
final CryptoIsolateManager? _isolateManager; // New isolate-based crypto
final List<int> _cipherSequence; // Integer-based cipher sequence
final Map<String, dynamic> _cipherParams; // Cipher parameters
/// New constructor with isolate manager (Phase 1)
CopiousCipherChain.withIsolateManager({
required CryptoIsolateManager isolateManager,
List<int>? cipherSequence,
Map<String, dynamic>? cipherParams,
}) : _isolateManager = isolateManager,
_cipherSequence = cipherSequence ?? CipherConstants.PHASE1_SEQUENCE,
_cipherParams = cipherParams ?? CipherConstants.DEFAULT_CIPHER_PARAMS;
/// Async encrypt with isolate support
Future<List<int>> encryptAsync(
List<int> data, {
required String channelUuid,
int? messageSequence,
bool isUserMessage = false,
List<int>? customCipherSequence,
Map<String, dynamic>? customParams,
}) async {
if (_isolateManager == null) {
throw StateError('Isolate manager not available. Use legacy encrypt() or create with withIsolateManager()');
}
final result = await _isolateManager.encrypt(
data,
channelUuid: channelUuid,
messageSequence: messageSequence,
cipherSequence: customCipherSequence ?? _cipherSequence,
params: customParams ?? _cipherParams,
isUserMessage: isUserMessage,
);
if (!result.success) {
throw Exception('Encryption failed: ${result.error}');
}
return result.data;
}
/// Async decrypt with isolate support
Future<List<int>> decryptAsync(
List<int> data, {
required String channelUuid,
int? messageSequence,
List<int>? customCipherSequence,
Map<String, dynamic>? customParams,
}) async {
if (_isolateManager == null) {
throw StateError('Isolate manager not available. Use legacy decrypt() or create with withIsolateManager()');
}
final result = await _isolateManager.decrypt(
data,
channelUuid: channelUuid,
messageSequence: messageSequence,
cipherSequence: customCipherSequence ?? _cipherSequence,
params: customParams ?? _cipherParams,
);
if (!result.success) {
throw Exception('Decryption failed: ${result.error}');
}
return result.data;
}
/// Test the cipher chain with a roundtrip operation
Future<bool> testCipherChain({
String testData = 'Test message for cipher chain validation',
String channelUuid = 'test-channel-999',
}) async {
if (_isolateManager == null) {
return false; // Cannot test without isolate manager
}
try {
final result = await _isolateManager.testOperation(
testData: testData,
channelUuid: channelUuid,
);
return result.success;
} catch (e) {
return false;
}
}
/// Get cipher sequence description
String get cipherSequenceDescription {
return CipherConstants.getSequenceDescription(_cipherSequence);
}
/// Get performance metrics (if using isolate manager)
CryptoMetrics? get metrics => _isolateManager?.metrics;
/// Check if using isolate manager
bool get usesIsolateManager => _isolateManager != null;
/// Get current cipher sequence
List<int> get cipherSequence => List.from(_cipherSequence);
/// Get current cipher parameters
Map<String, dynamic> get cipherParams => Map.from(_cipherParams);
}

View File

@ -1,69 +0,0 @@
///
/// Core crypto contract used by message/attachment pipelines.
///
/// Generic encryption/decryption contract.
///
/// Implementations include:
/// - plaintext/testing providers,
/// - CCC isolate-backed providers,
/// - native FFI-backed providers.
abstract class CryptoAbstract<T> {
/// Human readable provider name for logs/diagnostics.
String get providerName;
/// True when provider does not apply real cryptographic protection.
bool get isPlaintextMode;
/// Encrypt typed input into bytes.
Future<List<int>> encrypt(T input, {CryptoContext? context});
/// Decrypt bytes back to typed data.
Future<T> decrypt(List<int> data, {CryptoContext? context});
}
/// Per-operation context for crypto providers.
///
/// Providers that require channel-scoped or message-scoped key derivation
/// can consume these values. Plaintext/testing providers can ignore them.
class CryptoContext {
/// Stable channel UUID used for channel-scoped keying.
final String? channelUuid;
/// Optional message sequence used for per-message key derivation.
final int? messageSequence;
/// Priority hint for provider internals.
final bool isUserMessage;
/// Optional channel-derived cipher sequence override.
final List<int>? cipherSequence;
/// Optional channel-derived cipher params override.
final Map<String, dynamic>? cipherParams;
const CryptoContext({
this.channelUuid,
this.messageSequence,
this.isUserMessage = false,
this.cipherSequence,
this.cipherParams,
});
}
CryptoProviderMode cryptoProviderModeFromString(String raw) {
final value = raw.trim().toLowerCase();
switch (value) {
case 'plaintext':
return CryptoProviderMode.plaintext;
case 'ccc':
return CryptoProviderMode.ccc;
default:
throw ArgumentError('Unsupported crypto provider mode: $raw');
}
}
enum CryptoProviderMode {
plaintext,
ccc,
}

View File

@ -1,81 +0,0 @@
///
/// CCC isolate-backed crypto provider for message payloads.
///
import 'dart:convert';
import 'package:letusmsg/ccc/ccc_iso_manager.dart';
import 'package:letusmsg/ccc/crypto_abstract.dart';
/// Real crypto provider backed by `CryptoIsolateManager`.
///
/// Notes:
/// - Requires `context.channelUuid` for channel-scoped operation.
/// - Uses JSON map serialization for current message payload format.
class CryptoCcc implements CryptoAbstract<Map<String, dynamic>> {
final CryptoIsolateManager _manager;
bool _initialized = false;
CryptoCcc(this._manager);
@override
String get providerName => 'CryptoCcc';
@override
bool get isPlaintextMode => false;
Future<void> _ensureInitialized() async {
if (_initialized) return;
await _manager.initialize();
_initialized = true;
}
String _requireChannelUuid(CryptoContext? context) {
final channelUuid = context?.channelUuid;
if (channelUuid == null || channelUuid.isEmpty) {
throw ArgumentError('CryptoCcc requires a non-empty channelUuid in CryptoContext');
}
return channelUuid;
}
@override
Future<List<int>> encrypt(Map<String, dynamic> input, {CryptoContext? context}) async {
await _ensureInitialized();
final channelUuid = _requireChannelUuid(context);
final plainBytes = utf8.encode(jsonEncode(input));
final result = await _manager.encrypt(
plainBytes,
channelUuid: channelUuid,
messageSequence: context?.messageSequence,
cipherSequence: context?.cipherSequence,
params: context?.cipherParams,
isUserMessage: context?.isUserMessage ?? false,
);
if (!result.success) {
throw Exception('CryptoCcc encrypt failed: ${result.error ?? 'unknown error'}');
}
return result.data;
}
@override
Future<Map<String, dynamic>> decrypt(List<int> data, {CryptoContext? context}) async {
await _ensureInitialized();
final channelUuid = _requireChannelUuid(context);
final result = await _manager.decrypt(
data,
channelUuid: channelUuid,
messageSequence: context?.messageSequence,
cipherSequence: context?.cipherSequence,
params: context?.cipherParams,
);
if (!result.success) {
throw Exception('CryptoCcc decrypt failed: ${result.error ?? 'unknown error'}');
}
return jsonDecode(utf8.decode(result.data));
}
}

View File

@ -1,13 +0,0 @@
///
/// Preferred plaintext provider naming.
///
import 'package:letusmsg/ccc/enc_dec_json.dart';
/// Plaintext/testing provider alias.
///
/// Keeps compatibility with existing EncDecJson behavior while making
/// provider intent explicit in pipeline wiring.
class CryptoPlaintext extends EncDecJson {
@override
String get providerName => 'CryptoPlaintext';
}

View File

@ -1,26 +0,0 @@
///
/// Native wolfSSL provider scaffold.
///
import 'package:letusmsg/ccc/crypto_abstract.dart';
/// Phase-0 scaffold for future wolfSSL FFI integration.
///
/// This provider is intentionally not implemented yet. It exists so pipeline
/// wiring and provider selection can be completed before FFI delivery.
class CryptoWolfSsl implements CryptoAbstract<Map<String, dynamic>> {
@override
String get providerName => 'CryptoWolfSsl';
@override
bool get isPlaintextMode => false;
@override
Future<List<int>> encrypt(Map<String, dynamic> input, {CryptoContext? context}) {
throw UnimplementedError('CryptoWolfSsl.encrypt is not implemented yet (Phase 3)');
}
@override
Future<Map<String, dynamic>> decrypt(List<int> data, {CryptoContext? context}) {
throw UnimplementedError('CryptoWolfSsl.decrypt is not implemented yet (Phase 3)');
}
}

View File

@ -1,43 +0,0 @@
///
/// Enc/Dec JSON implementation
///
import 'dart:convert';
import 'package:letusmsg/ccc/crypto_abstract.dart';
/// Plaintext JSON codec used for testing/development flows.
///
/// This class intentionally does not provide cryptographic protection.
class EncDecJson implements CryptoAbstract<Map<String, dynamic>> {
@override
String get providerName => 'EncDecJson';
@override
bool get isPlaintextMode => true;
// @override
// List<int> pre(String input) {
// return utf8.encode(input);
// }
@override
Future<List<int>> encrypt(Map<String, dynamic> data, {CryptoContext? context}) async {
// Simulate encryption delay
// await Future.delayed(Duration(milliseconds: 100));
return utf8.encode( jsonEncode(data) );
}
@override
Future<Map<String, dynamic>> decrypt(List<int> encdata, {CryptoContext? context}) async {
// Simulate decryption delay
// await Future.delayed(Duration(milliseconds: 100));
return jsonDecode(utf8.decode(encdata));
}
// @override
// String post(List<int> output) {
// return utf8.decode(output);
// }
}

View File

@ -1,497 +0,0 @@
///
/// Ratchet Engine Double Ratchet Core for Normal Mode
///
/// Pure-logic class with static methods that operate on [RatchetState] +
/// [CCCData]. Designed to run on the background isolate alongside the
/// existing CCC cipher chain.
///
/// Phase 2 of the Hydra Apocalypse Ratchet.
///
/// ## Per-Sender Chain Separation
///
/// Each participant holds a separate [RatchetState] per sender in the
/// channel. The chain key is derived as:
///
/// ```text
/// rootKey
/// KDF("chain|channelUuid|senderIdentifier") initial chainKey
///
/// For each message:
/// chainKey = KDF(chainKey, "next|channelUuid") // symmetric ratchet step
/// messageKey = KDF(chainKey, counter + channelUuid) // per-message key
/// ```
///
/// Including `senderIdentifier` in the initial derivation guarantees that
/// two senders sharing the same root key and channel produce different
/// chains, preventing key reuse when both send at the same counter.
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
import 'package:letusmsg/ccc/ccc_kdf.dart';
import 'package:letusmsg/data/ccc_data.dart';
import 'package:letusmsg/data/ratchet_state_data.dart';
/// Result of a ratchet advance operation.
///
/// Contains the derived 32-byte base message key and the message counter
/// it corresponds to.
class RatchetAdvanceResult {
/// The derived 32-byte base message key for AEAD encryption/decryption.
///
/// This key is passed into the isolate worker as the root key for
/// per-layer key derivation (replacing the Phase 1 static root key).
final List<int> baseMessageKey;
/// The message counter this key corresponds to.
final int counter;
/// Ephemeral X25519 public key bytes (32 bytes) when an asymmetric ratchet
/// step was performed on this message. The caller must embed this in the
/// encrypted payload (``rk`` field) so receivers can apply the same reset.
///
/// Null when no asymmetric ratchet step was triggered.
final List<int>? asymmetricRatchetPublicKey;
const RatchetAdvanceResult({
required this.baseMessageKey,
required this.counter,
this.asymmetricRatchetPublicKey,
});
}
/// Core ratchet engine implementing the symmetric Double Ratchet.
///
/// All methods are static and pure they take [RatchetState] and [CCCData]
/// as input, mutate the state in place, and return derived key material.
///
/// The engine does **not** handle persistence; callers must save the updated
/// [RatchetState] via [RatchetStateManager] after each operation.
///
/// ## Key Derivation Chain
///
/// ```text
/// rootKey
/// KDF("chain|channelUuid|senderIdentifier") initial chainKey
///
/// For each message:
/// chainKey = KDF(chainKey, "next|channelUuid") // symmetric ratchet step
/// messageKey = KDF(chainKey, counter + channelUuid) // per-message key
/// ```
class RatchetEngine {
/// Default checkpoint interval (messages between chain key snapshots).
static const int defaultCheckpointInterval = 500;
/// Default asymmetric ratchet frequency (messages between KEM steps).
static const int defaultRatchetFrequency = 50;
// ---------------------------------------------------------------------------
// Initialization
// ---------------------------------------------------------------------------
/// Initialize ratchet state from a shared root secret.
///
/// Called once when a channel is created or an invite is accepted.
/// All parties must call this with the same [rootKey] to establish
/// identical starting states critical for N-party group channels.
///
/// Each participant creates one state per sender (including themselves).
/// The [senderIdentifier] is mixed into the chain derivation so that
/// two senders sharing the same root and channel produce **different**
/// chain keys preventing key reuse when both send the same counter.
///
/// The [rootKey] should be a 32-byte cryptographically random secret
/// shared securely during the invite flow.
///
/// Returns a fully initialized [RatchetState] with:
/// - A chain key unique to this (channel, sender) pair
/// - Counters at 0
/// - Genesis checkpoint at counter 0
static Future<RatchetState> initializeFromRoot({
required List<int> rootKey,
required String channelUuid,
required String senderIdentifier,
required CCCData cccData,
}) async {
// Derive the chain key from root with sender-specific domain separation.
// Including senderIdentifier ensures that two participants sharing the
// same rootKey + channelUuid produce different chains, eliminating any
// key-reuse risk when both send at the same counter value.
final chainKey = await _kdf(
cccData,
rootKey,
'chain|$channelUuid|$senderIdentifier',
);
final state = RatchetState(
encryptedRootKey: List<int>.from(rootKey), // TODO: encrypt under device master key
chainKey: chainKey,
channelUuid: channelUuid,
senderIdentifier: senderIdentifier,
);
// Genesis checkpoint
state.checkpoints[0] = List<int>.from(chainKey);
return state;
}
// ---------------------------------------------------------------------------
// Symmetric Chain Advance
// ---------------------------------------------------------------------------
/// Advance the chain and derive the base message key for sending.
///
/// This is the "click" of the ratchet. When
/// [CCCData.symmetricRatchetEveryMessage] is true (default), the chain key
/// is replaced with its KDF successor and the old value is permanently lost.
///
/// The returned [RatchetAdvanceResult.baseMessageKey] is a unique 32-byte
/// key for this specific message, suitable for passing into the isolate
/// worker as the root key for per-layer AEAD derivation.
///
/// **Side effects**: mutates [state] (chain key, counter, checkpoints).
/// Caller must persist the updated state.
static Future<RatchetAdvanceResult> advanceSending({
required RatchetState state,
required CCCData cccData,
}) async {
final counter = state.lastSentCounter + 1;
// Symmetric ratchet step: derive new chain key from current
if (cccData.symmetricRatchetEveryMessage) {
state.chainKey = await _kdf(
cccData,
state.chainKey,
'next|${state.channelUuid}',
);
}
// Derive per-message base key
final baseMessageKey = await _deriveMessageKey(
cccData,
state.chainKey,
counter,
state.channelUuid,
);
// Update counter
state.lastSentCounter = counter;
// Checkpoint if needed
_maybeCheckpoint(
state.checkpoints,
counter,
state.chainKey,
cccData.checkpointInterval,
);
// Asymmetric ratchet step: generates fresh X25519 keypair and mixes
// entropy into chain. The returned public key must be embedded in the
// message payload ("rk" field) so receivers can apply the same reset.
List<int>? asymmetricPublicKey;
if (_isAsymmetricRatchetDue(counter, cccData.ratchetFrequency)) {
asymmetricPublicKey = await performAsymmetricRatchet(
state: state,
cccData: cccData,
);
}
return RatchetAdvanceResult(
baseMessageKey: baseMessageKey,
counter: counter,
asymmetricRatchetPublicKey: asymmetricPublicKey,
);
}
/// Advance the chain to match the incoming message counter
/// and derive the base message key for decryption.
///
/// The chain is fast-forwarded from its current position to
/// [incomingCounter], applying the symmetric ratchet step at each
/// intermediate position.
///
/// **Side effects**: mutates [state] (chain key, counter, checkpoints).
/// Caller must persist the updated state.
static Future<RatchetAdvanceResult> advanceReceiving({
required RatchetState state,
required CCCData cccData,
required int incomingCounter,
}) async {
// Fast-forward chain to match incoming counter
while (state.lastReceivedCounter < incomingCounter) {
if (cccData.symmetricRatchetEveryMessage) {
state.chainKey = await _kdf(
cccData,
state.chainKey,
'next|${state.channelUuid}',
);
}
state.lastReceivedCounter += 1;
// Checkpoint at intermediate steps too
_maybeCheckpoint(
state.checkpoints,
state.lastReceivedCounter,
state.chainKey,
cccData.checkpointInterval,
);
}
// Derive per-message base key
final baseMessageKey = await _deriveMessageKey(
cccData,
state.chainKey,
incomingCounter,
state.channelUuid,
);
return RatchetAdvanceResult(
baseMessageKey: baseMessageKey,
counter: incomingCounter,
);
}
// ---------------------------------------------------------------------------
// Old Message Decryption (Random Access via Checkpoints)
// ---------------------------------------------------------------------------
/// Derive the base message key for an older message by replaying the chain
/// from the nearest checkpoint.
///
/// This is the "scroll up" scenario the user wants to read a message
/// that was received in the past. Instead of replaying from genesis
/// (which could be millions of steps), we start from the nearest saved
/// checkpoint and replay at most [checkpointInterval] steps.
///
/// **Does not mutate** the main chain keys or counters in [state].
/// May add new checkpoints to [state.checkpoints].
static Future<RatchetAdvanceResult> deriveKeyForOldMessage({
required RatchetState state,
required CCCData cccData,
required int targetCounter,
}) async {
// Find nearest checkpoint at or before targetCounter
int checkpointCounter = 0;
List<int> chainKey = List<int>.from(state.checkpoints[0] ?? state.chainKey);
final sortedKeys = state.checkpoints.keys.toList()..sort();
for (final cp in sortedKeys) {
if (cp <= targetCounter) {
checkpointCounter = cp;
chainKey = List<int>.from(state.checkpoints[cp]!);
} else {
break;
}
}
// Replay chain forward from checkpoint to target
int current = checkpointCounter;
while (current < targetCounter) {
if (cccData.symmetricRatchetEveryMessage) {
chainKey = await _kdf(
cccData,
chainKey,
'next|${state.channelUuid}',
);
}
current += 1;
}
// Derive base_message_key at target
final baseMessageKey = await _deriveMessageKey(
cccData,
chainKey,
targetCounter,
state.channelUuid,
);
// Optionally save a new checkpoint
final interval = cccData.checkpointInterval > 0
? cccData.checkpointInterval
: defaultCheckpointInterval;
if (targetCounter % interval == 0 &&
!state.checkpoints.containsKey(targetCounter)) {
state.checkpoints[targetCounter] = List<int>.from(chainKey);
}
return RatchetAdvanceResult(
baseMessageKey: baseMessageKey,
counter: targetCounter,
);
}
// ---------------------------------------------------------------------------
// Asymmetric Ratchet (KEM) Phase 3 X25519
// ---------------------------------------------------------------------------
/// Perform an asymmetric ratchet step on the **sending** chain.
///
/// Generates a fresh X25519 keypair and mixes the public key bytes into
/// the chain key via KDF. The public key is returned so the caller can
/// embed it in the encrypted payload (``rk`` field). All receivers
/// extract those same bytes and call [applyRemoteAsymmetricRatchet] to
/// perform the identical chain reset.
///
/// ```text
/// freshKeyPair = X25519.newKeyPair()
/// publicKeyBytes = freshKeyPair.publicKey.bytes // 32 bytes
///
/// new_chain_key = KDF(
/// old_chain_key || publicKeyBytes,
/// "asymmetric-reset|" + channelUuid,
/// )
/// ```
///
/// The DH private key is persisted in [state] for potential future
/// pairwise DH key agreement (Phase 4 enhancement for 1-to-1 channels).
///
/// Returns the 32-byte X25519 public key that must be embedded in the
/// message payload.
static Future<List<int>> performAsymmetricRatchet({
required RatchetState state,
required CCCData cccData,
}) async {
// 1. Generate fresh X25519 keypair (32 bytes, OS-level CSPRNG)
final x25519 = X25519();
final keyPair = await x25519.newKeyPair();
final publicKey = await keyPair.extractPublicKey();
final privateKeyBytes = await keyPair.extractPrivateKeyBytes();
final publicKeyBytes = publicKey.bytes;
// 2. Mix public-key entropy into the chain
// Using the public key ensures all receivers (group-compatible) can
// compute the same new chain key from information in the payload.
state.chainKey = await _kdf(
cccData,
[...state.chainKey, ...publicKeyBytes],
'asymmetric-reset|${state.channelUuid}',
);
// 3. Persist DH keys for future pairwise DH (Phase 4)
state.dhPrivateKey = privateKeyBytes;
state.dhPublicKey = List<int>.from(publicKeyBytes);
state.lastAsymmetricRatchetCounter = state.lastSentCounter;
return List<int>.from(publicKeyBytes);
}
/// Apply a remote asymmetric ratchet step on the **receiving** chain.
///
/// Called by the message processor after successful decryption when
/// the payload contains an ``rk`` (ratchet-key) field. Applies the
/// same chain-key mixing as [performAsymmetricRatchet] so sender and
/// receiver chains stay in sync.
///
/// [remotePublicKeyBytes] is the 32-byte X25519 public key extracted
/// from the decrypted payload.
static Future<void> applyRemoteAsymmetricRatchet({
required RatchetState state,
required CCCData cccData,
required List<int> remotePublicKeyBytes,
}) async {
// Mix the same public-key entropy that the sender used
state.chainKey = await _kdf(
cccData,
[...state.chainKey, ...remotePublicKeyBytes],
'asymmetric-reset|${state.channelUuid}',
);
// Store remote public key for future DH (Phase 4)
state.remoteDhPublicKey = List<int>.from(remotePublicKeyBytes);
state.lastAsymmetricRatchetCounter = state.lastReceivedCounter;
}
/// Check whether an asymmetric ratchet step is due at the given counter.
static bool _isAsymmetricRatchetDue(int counter, int ratchetFrequency) {
if (ratchetFrequency <= 0) return false; // disabled or every-message (handled separately)
return counter > 0 && counter % ratchetFrequency == 0;
}
// ---------------------------------------------------------------------------
// Key Derivation Functions (Internal)
// ---------------------------------------------------------------------------
/// Core KDF: derive 32-byte key material from input key + info string.
///
/// Uses the KDF function selected in [CCCData.kdfFunction]
/// (SHA-256/384/512/BLAKE2b-512). Output is always truncated to 32 bytes.
static Future<List<int>> _kdf(
CCCData cccData,
List<int> inputKey,
String info,
) async {
final kdfFunction = cccKdfFunctionFromInt(
normalizeCccKdfFunctionValue(cccData.kdfFunction),
);
final hashInput = [...inputKey, ...utf8.encode(info)];
final fullHash = await _hashBytes(hashInput, kdfFunction);
return fullHash.sublist(0, 32); // truncate to 32 bytes
}
/// Derive per-message encryption key from chain key + counter + channel ID.
///
/// Formula: ``KDF(chainKey, counter_bytes(8) || channelUuid)``
///
/// The counter is encoded as 8-byte big-endian to ensure deterministic
/// byte representation across platforms.
static Future<List<int>> _deriveMessageKey(
CCCData cccData,
List<int> chainKey,
int counter,
String channelUuid,
) async {
final counterBytes = _intToBytes8(counter);
final info = '$channelUuid|msg|${String.fromCharCodes(counterBytes)}';
return _kdf(cccData, chainKey, info);
}
/// Hash bytes using the specified KDF/hash function.
static Future<List<int>> _hashBytes(
List<int> bytes,
CccKdfFunction kdfFunction,
) async {
switch (kdfFunction) {
case CccKdfFunction.sha256:
return (await Sha256().hash(bytes)).bytes;
case CccKdfFunction.sha384:
return (await Sha384().hash(bytes)).bytes;
case CccKdfFunction.sha512:
return (await Sha512().hash(bytes)).bytes;
case CccKdfFunction.blake2b512:
return (await Blake2b().hash(bytes)).bytes;
}
}
/// Convert an integer to 8-byte big-endian representation.
static List<int> _intToBytes8(int value) {
return [
(value >> 56) & 0xFF,
(value >> 48) & 0xFF,
(value >> 40) & 0xFF,
(value >> 32) & 0xFF,
(value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF,
];
}
// ---------------------------------------------------------------------------
// Checkpoint Helpers
// ---------------------------------------------------------------------------
/// Save a checkpoint if the counter is at a checkpoint boundary.
static void _maybeCheckpoint(
Map<int, List<int>> checkpoints,
int counter,
List<int> chainKey,
int checkpointInterval,
) {
final interval =
checkpointInterval > 0 ? checkpointInterval : defaultCheckpointInterval;
if (counter % interval == 0) {
checkpoints[counter] = List<int>.from(chainKey);
}
}
}

View File

@ -1,154 +0,0 @@
///
/// Ratchet State Manager Hive Persistence for Ratchet Chain State
///
/// Manages the lifecycle of [RatchetState] objects in a dedicated Hive box.
/// Each entry is keyed by ``'{channelUuid}_{senderIdentifier}'``.
///
/// Phase 2 of the Hydra Apocalypse Ratchet.
///
import 'package:hive_ce/hive.dart';
import 'package:letusmsg/data/ratchet_state_data.dart';
import 'package:letusmsg/utilz/Llog.dart';
/// Manages persistence of [RatchetState] objects in Hive.
///
/// Provides CRUD operations for ratchet states and channel-level cleanup.
/// The box is opened lazily on first access and remains open for the app
/// lifetime.
///
/// ## Key Format
/// ``'{channelUuid}_{senderIdentifier}'`` deterministic from the
/// [RatchetState.hiveKey] property.
///
/// ## Usage
/// ```dart
/// final manager = RatchetStateManager();
/// await manager.open();
///
/// // Save after ratchet advance
/// await manager.save(state);
///
/// // Load for decrypt
/// final state = await manager.load('chan-uuid', 'sender-id');
/// ```
class RatchetStateManager {
static const String boxName = 'ratchet_state';
Box<RatchetState>? _box;
/// Whether the Hive box is currently open.
bool get isOpen => _box?.isOpen ?? false;
/// Open the ratchet state Hive box.
///
/// Safe to call multiple times returns immediately if already open.
Future<void> open() async {
if (_box != null && _box!.isOpen) return;
try {
_box = await Hive.openBox<RatchetState>(boxName);
} catch (ex) {
Llog.log.e('[RatchetStateManager] Failed to open box: $ex');
rethrow;
}
}
/// Ensure the box is open, opening it if necessary.
Future<Box<RatchetState>> _ensureBox() async {
if (_box == null || !_box!.isOpen) {
await open();
}
return _box!;
}
/// Load ratchet state for a specific channel + sender.
///
/// Returns ``null`` if no state exists (channel not yet initialized with
/// ratchet, or pre-Phase 2 channel).
Future<RatchetState?> load(
String channelUuid,
String senderIdentifier,
) async {
final box = await _ensureBox();
final key = _buildKey(channelUuid, senderIdentifier);
return box.get(key);
}
/// Save ratchet state after advancing the chain.
///
/// Must be called after every send/receive operation to persist the
/// updated chain keys and counters.
Future<void> save(RatchetState state) async {
final box = await _ensureBox();
await box.put(state.hiveKey, state);
}
/// Delete ratchet state for a specific channel + sender.
Future<void> delete(
String channelUuid,
String senderIdentifier,
) async {
final box = await _ensureBox();
final key = _buildKey(channelUuid, senderIdentifier);
await box.delete(key);
}
/// Delete all ratchet states for a channel (all senders).
///
/// Called when a channel is deleted or left. Iterates all keys in the box
/// and removes those matching the channel prefix.
Future<void> deleteChannel(String channelUuid) async {
final box = await _ensureBox();
final prefix = '${channelUuid}_';
final keysToDelete = <dynamic>[];
for (final key in box.keys) {
if (key.toString().startsWith(prefix)) {
keysToDelete.add(key);
}
}
if (keysToDelete.isNotEmpty) {
await box.deleteAll(keysToDelete);
Llog.log.d('[RatchetStateManager] Deleted ${keysToDelete.length} '
'ratchet state(s) for channel $channelUuid');
}
}
/// List all ratchet states for a channel (all senders).
///
/// Useful for debugging and testing.
Future<List<RatchetState>> listForChannel(String channelUuid) async {
final box = await _ensureBox();
final prefix = '${channelUuid}_';
final results = <RatchetState>[];
for (final key in box.keys) {
if (key.toString().startsWith(prefix)) {
final state = box.get(key);
if (state != null) results.add(state);
}
}
return results;
}
/// Check if a ratchet state exists for a channel + sender.
Future<bool> exists(
String channelUuid,
String senderIdentifier,
) async {
final box = await _ensureBox();
return box.containsKey(_buildKey(channelUuid, senderIdentifier));
}
/// Close the Hive box.
///
/// Called during app shutdown or cleanup.
Future<void> close() async {
if (_box != null && _box!.isOpen) {
await _box!.close();
}
_box = null;
}
/// Build the composite Hive key from channel + sender identifiers.
static String _buildKey(String channelUuid, String senderIdentifier) {
return '${channelUuid}_$senderIdentifier';
}
}