MOD: decupple this rust project from dart
This commit is contained in:
parent
c1d6086214
commit
db897b5abe
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ resolver = "2"
|
|||
members = [
|
||||
"crates/ccc-crypto-core",
|
||||
"crates/ccc-crypto-wolfssl",
|
||||
"crates/ccc-flutter-bridge",
|
||||
"tests/conformance",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
/target
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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/.
|
||||
|
|
@ -1 +0,0 @@
|
|||
// Nothing when using full_dep=false mode
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub mod simple;
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
@ -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 0–100 (0 = unknown).
|
||||
pub efficiency_score: u8,
|
||||
/// NIST / standards reliability score 0–100 (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
|
|
@ -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;
|
||||
|
|
@ -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 0–100
|
||||
``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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
----
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)}%'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)';
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
@ -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)');
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue