diff --git a/lib/core/bridge.dart b/lib/core/bridge.dart index dfa22807..d3c9d0f4 100644 --- a/lib/core/bridge.dart +++ b/lib/core/bridge.dart @@ -9,7 +9,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; // These functions are ignored because they are not marked as `pub`: `get_twonly_flutter` // These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `TwonlyFlutter` -Future initializeTwonlyFlutter({required TwonlyConfig config}) => +Future initializeTwonlyFlutter({required InitConfig config}) => RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config); class AnnouncedUser { @@ -36,6 +36,27 @@ class AnnouncedUser { publicId == other.publicId; } +class InitConfig { + final String databaseDir; + final String dataDir; + + const InitConfig({ + required this.databaseDir, + required this.dataDir, + }); + + @override + int get hashCode => databaseDir.hashCode ^ dataDir.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is InitConfig && + runtimeType == other.runtimeType && + databaseDir == other.databaseDir && + dataDir == other.dataDir; +} + class OtherPromotion { final int promotionId; final PlatformInt64 publicId; @@ -74,24 +95,3 @@ class OtherPromotion { announcementShare == other.announcementShare && publicKeyVerifiedTimestamp == other.publicKeyVerifiedTimestamp; } - -class TwonlyConfig { - final String databasePath; - final String dataDirectory; - - const TwonlyConfig({ - required this.databasePath, - required this.dataDirectory, - }); - - @override - int get hashCode => databasePath.hashCode ^ dataDirectory.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is TwonlyConfig && - runtimeType == other.runtimeType && - databasePath == other.databasePath && - dataDirectory == other.dataDirectory; -} diff --git a/lib/core/context.dart b/lib/core/context.dart new file mode 100644 index 00000000..7294be61 --- /dev/null +++ b/lib/core/context.dart @@ -0,0 +1,28 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class InitConfig { + final String databasePath; + final String dataDirectory; + + const InitConfig({ + required this.databasePath, + required this.dataDirectory, + }); + + @override + int get hashCode => databasePath.hashCode ^ dataDirectory.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is InitConfig && + runtimeType == other.runtimeType && + databasePath == other.databasePath && + dataDirectory == other.dataDirectory; +} diff --git a/lib/core/frb_generated.dart b/lib/core/frb_generated.dart index 28dac796..e4be04f0 100644 --- a/lib/core/frb_generated.dart +++ b/lib/core/frb_generated.dart @@ -152,9 +152,7 @@ abstract class RustLibApi extends BaseApi { userDiscoveryGetContactPromotion, }); - Future crateBridgeInitializeTwonlyFlutter({ - required TwonlyConfig config, - }); + Future crateBridgeInitializeTwonlyFlutter({required InitConfig config}); } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -556,13 +554,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @override Future crateBridgeInitializeTwonlyFlutter({ - required TwonlyConfig config, + required InitConfig config, }) { return handler.executeNormal( NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); - sse_encode_box_autoadd_twonly_config(config, serializer); + sse_encode_box_autoadd_init_config(config, serializer); pdeCallFfi( generalizedFrbRustBinding, serializer, @@ -1180,9 +1178,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } @protected - TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw) { + InitConfig dco_decode_box_autoadd_init_config(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs - return dco_decode_twonly_config(raw); + return dco_decode_init_config(raw); } @protected @@ -1200,6 +1198,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dcoDecodeI64(raw); } + @protected + InitConfig dco_decode_init_config(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return InitConfig( + databaseDir: dco_decode_String(arr[0]), + dataDir: dco_decode_String(arr[1]), + ); + } + @protected PlatformInt64 dco_decode_isize(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1276,18 +1286,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } - @protected - TwonlyConfig dco_decode_twonly_config(dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - final arr = raw as List; - if (arr.length != 2) - throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); - return TwonlyConfig( - databasePath: dco_decode_String(arr[0]), - dataDirectory: dco_decode_String(arr[1]), - ); - } - @protected int dco_decode_u_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1375,11 +1373,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } @protected - TwonlyConfig sse_decode_box_autoadd_twonly_config( - SseDeserializer deserializer, - ) { + InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs - return (sse_decode_twonly_config(deserializer)); + return (sse_decode_init_config(deserializer)); } @protected @@ -1396,6 +1392,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getPlatformInt64(); } + @protected + InitConfig sse_decode_init_config(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_databaseDir = sse_decode_String(deserializer); + var var_dataDir = sse_decode_String(deserializer); + return InitConfig(databaseDir: var_databaseDir, dataDir: var_dataDir); + } + @protected PlatformInt64 sse_decode_isize(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1526,17 +1530,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } - @protected - TwonlyConfig sse_decode_twonly_config(SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - var var_databasePath = sse_decode_String(deserializer); - var var_dataDirectory = sse_decode_String(deserializer); - return TwonlyConfig( - databasePath: var_databasePath, - dataDirectory: var_dataDirectory, - ); - } - @protected int sse_decode_u_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1820,12 +1813,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } @protected - void sse_encode_box_autoadd_twonly_config( - TwonlyConfig self, + void sse_encode_box_autoadd_init_config( + InitConfig self, SseSerializer serializer, ) { // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_twonly_config(self, serializer); + sse_encode_init_config(self, serializer); } @protected @@ -1842,6 +1835,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putPlatformInt64(self); } + @protected + void sse_encode_init_config(InitConfig self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.databaseDir, serializer); + sse_encode_String(self.dataDir, serializer); + } + @protected void sse_encode_isize(PlatformInt64 self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1976,13 +1976,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } - @protected - void sse_encode_twonly_config(TwonlyConfig self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_String(self.databasePath, serializer); - sse_encode_String(self.dataDirectory, serializer); - } - @protected void sse_encode_u_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/lib/core/frb_generated.io.dart b/lib/core/frb_generated.io.dart index e5c71e93..d71edb15 100644 --- a/lib/core/frb_generated.io.dart +++ b/lib/core/frb_generated.io.dart @@ -117,7 +117,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); @protected - TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw); + InitConfig dco_decode_box_autoadd_init_config(dynamic raw); @protected FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw); @@ -125,6 +125,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected PlatformInt64 dco_decode_i_64(dynamic raw); + @protected + InitConfig dco_decode_init_config(dynamic raw); + @protected PlatformInt64 dco_decode_isize(dynamic raw); @@ -158,9 +161,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion dco_decode_other_promotion(dynamic raw); - @protected - TwonlyConfig dco_decode_twonly_config(dynamic raw); - @protected int dco_decode_u_32(dynamic raw); @@ -202,9 +202,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); @protected - TwonlyConfig sse_decode_box_autoadd_twonly_config( - SseDeserializer deserializer, - ); + InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer); @protected FlutterUserDiscovery sse_decode_flutter_user_discovery( @@ -214,6 +212,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + @protected + InitConfig sse_decode_init_config(SseDeserializer deserializer); + @protected PlatformInt64 sse_decode_isize(SseDeserializer deserializer); @@ -257,9 +258,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer); - @protected - TwonlyConfig sse_decode_twonly_config(SseDeserializer deserializer); - @protected int sse_decode_u_32(SseDeserializer deserializer); @@ -394,8 +392,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { ); @protected - void sse_encode_box_autoadd_twonly_config( - TwonlyConfig self, + void sse_encode_box_autoadd_init_config( + InitConfig self, SseSerializer serializer, ); @@ -408,6 +406,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + @protected + void sse_encode_init_config(InitConfig self, SseSerializer serializer); + @protected void sse_encode_isize(PlatformInt64 self, SseSerializer serializer); @@ -468,9 +469,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); - @protected - void sse_encode_twonly_config(TwonlyConfig self, SseSerializer serializer); - @protected void sse_encode_u_32(int self, SseSerializer serializer); diff --git a/lib/core/frb_generated.web.dart b/lib/core/frb_generated.web.dart index 8c2f83d1..2febda82 100644 --- a/lib/core/frb_generated.web.dart +++ b/lib/core/frb_generated.web.dart @@ -119,7 +119,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); @protected - TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw); + InitConfig dco_decode_box_autoadd_init_config(dynamic raw); @protected FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw); @@ -127,6 +127,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected PlatformInt64 dco_decode_i_64(dynamic raw); + @protected + InitConfig dco_decode_init_config(dynamic raw); + @protected PlatformInt64 dco_decode_isize(dynamic raw); @@ -160,9 +163,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion dco_decode_other_promotion(dynamic raw); - @protected - TwonlyConfig dco_decode_twonly_config(dynamic raw); - @protected int dco_decode_u_32(dynamic raw); @@ -204,9 +204,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); @protected - TwonlyConfig sse_decode_box_autoadd_twonly_config( - SseDeserializer deserializer, - ); + InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer); @protected FlutterUserDiscovery sse_decode_flutter_user_discovery( @@ -216,6 +214,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + @protected + InitConfig sse_decode_init_config(SseDeserializer deserializer); + @protected PlatformInt64 sse_decode_isize(SseDeserializer deserializer); @@ -259,9 +260,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer); - @protected - TwonlyConfig sse_decode_twonly_config(SseDeserializer deserializer); - @protected int sse_decode_u_32(SseDeserializer deserializer); @@ -396,8 +394,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { ); @protected - void sse_encode_box_autoadd_twonly_config( - TwonlyConfig self, + void sse_encode_box_autoadd_init_config( + InitConfig self, SseSerializer serializer, ); @@ -410,6 +408,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + @protected + void sse_encode_init_config(InitConfig self, SseSerializer serializer); + @protected void sse_encode_isize(PlatformInt64 self, SseSerializer serializer); @@ -470,9 +471,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); - @protected - void sse_encode_twonly_config(TwonlyConfig self, SseSerializer serializer); - @protected void sse_encode_u_32(int self, SseSerializer serializer); diff --git a/lib/main.dart b/lib/main.dart index ddd6f46f..1b82d399 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -55,9 +55,9 @@ Future twonlyMinimumInitialization() async { Log.info('twonlyMinimumInitialization: bridge.initializeTwonlyFlutter()'); await bridge.initializeTwonlyFlutter( - config: bridge.TwonlyConfig( - databasePath: '${AppEnvironment.supportDir}/twonly.sqlite', - dataDirectory: AppEnvironment.supportDir, + config: bridge.InitConfig( + databaseDir: AppEnvironment.supportDir, + dataDir: AppEnvironment.supportDir, ), ); Log.info('twonlyMinimumInitialization: finished'); diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0fccb096..23a0e4a6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -17,6 +17,41 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -43,6 +78,24 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-native-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c6349ddff23194f8fdce2ea8849380f5a4868c1648965b70e801e104cba9b3" +dependencies = [ + "base64", + "jni", + "keyring-core", + "log", + "ndk-context", + "regex", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "android_log-sys" version = "0.3.2" @@ -75,6 +128,43 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "apple-native-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7be2f067ccd8d4b4d4a66ddafe0f32a5dff31732f32dbff85fefc40929b1f72" +dependencies = [ + "keyring-core", + "log", + "security-framework", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atoi" version = "2.0.0" @@ -90,6 +180,15 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -111,6 +210,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -123,6 +228,40 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bip39" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +dependencies = [ + "bitcoin_hashes", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + [[package]] name = "bitflags" version = "2.11.1" @@ -161,6 +300,21 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blurhash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc" + [[package]] name = "build-target" version = "0.4.0" @@ -185,12 +339,27 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.60" @@ -201,12 +370,29 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.0" @@ -218,6 +404,19 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.44" @@ -225,16 +424,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", + "serde", + "wasm-bindgen", "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + [[package]] name = "cmov" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -266,12 +504,33 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-models" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4" +dependencies = [ + "hax-lib", + "pastey", + "rand 0.9.4", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -305,6 +564,21 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -314,6 +588,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -329,6 +622,18 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -336,6 +641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -348,6 +654,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "ctutils" version = "0.4.2" @@ -357,6 +672,33 @@ dependencies = [ "cmov", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dart-sys" version = "4.1.5" @@ -408,6 +750,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -451,6 +805,45 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -460,6 +853,39 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "env_filter" version = "0.1.4" @@ -521,12 +947,55 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-thumbhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5243a22cce29dff488db8ef03d8e0e54dd06cd3e6d98475160dff390fa414de2" + [[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -539,6 +1008,16 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.1" @@ -720,6 +1199,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -729,8 +1209,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", ] [[package]] @@ -741,18 +1235,58 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.1", "wasip2", "wasip3", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -796,6 +1330,57 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "hax-lib" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -814,6 +1399,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -850,6 +1444,75 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "hpke-rs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ad6a58eb3e0ee30be8bfc7a9770ae98adcfa1d9bc820a5847732ce84f70837" +dependencies = [ + "hpke-rs-crypto", + "hpke-rs-libcrux", + "hpke-rs-rust-crypto", + "libcrux-sha3", + "log", + "rand_core 0.9.5", + "serde", + "subtle", + "tls_codec", + "zeroize", +] + +[[package]] +name = "hpke-rs-crypto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a73a99d9008010d73289f41335a3f6e14fb8c04eaf60e9111b450463b1bbc7f" +dependencies = [ + "rand_core 0.9.5", + "zeroize", +] + +[[package]] +name = "hpke-rs-libcrux" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ce6b7e54aebe540faee869c67ee253bede44ea6cb67c6e72c7847d6c59f1df" +dependencies = [ + "hpke-rs-crypto", + "libcrux-aead", + "libcrux-ecdh", + "libcrux-hkdf", + "libcrux-kem", + "libcrux-traits", + "rand 0.10.1", + "rand_chacha 0.10.0", + "rand_core 0.10.1", + "zeroize", +] + +[[package]] +name = "hpke-rs-rust-crypto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b28be6cba9081c7feda2651d51c2a900029798e78b4c1e093e792f4571a870" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "hkdf", + "hpke-rs-crypto", + "k256", + "p256", + "p384", + "rand 0.8.6", + "rand_chacha 0.3.1", + "rand_core 0.10.1", + "rand_core 0.6.4", + "sha2 0.10.9", + "subtle", + "x25519-dalek", + "zeroize", +] + [[package]] name = "humantime" version = "2.3.0" @@ -998,6 +1661,34 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1010,6 +1701,28 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "is-terminal" version = "0.4.17" @@ -1036,6 +1749,50 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.95" @@ -1048,6 +1805,34 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "elliptic-curve", +] + +[[package]] +name = "kamadak-exif" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837" +dependencies = [ + "mutate_once", +] + +[[package]] +name = "keyring-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e621458ca9c51aa110bd0339d4751a056b9576bf1253aee1aa560dda0fc9d" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1069,6 +1854,222 @@ version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +[[package]] +name = "libcrux-aead" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13297ce29869a5c0edab0378837b0fc5f88bf99a843712d9201c3b1150b3b476" +dependencies = [ + "libcrux-aesgcm", + "libcrux-chacha20poly1305", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-aesgcm" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f2a019dab4097585a7d4f5b9deebe46cd1e628b16a5bc4cb0ce35e1da334e6" +dependencies = [ + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-chacha20poly1305" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc08d044676af21343b32b988411fa98dbb5cf65a03c9df478ced221bbdfdb1b" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-poly1305", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-curve25519" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1e5fd8476a6ed609d24ef42aee5ab6f99f7c65d054f92412da9f499e423299" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-ecdh" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65f73ce79337c762eb38bbac91e4c9b9e60cf318e8501b812750c640814d45e" +dependencies = [ + "libcrux-curve25519", + "libcrux-p256", + "rand 0.9.4", +] + +[[package]] +name = "libcrux-hacl-rs" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2637dc87d158e1f1b550fd9b226443e84153fded4de69028d897b534d16d22e6" +dependencies = [ + "libcrux-macros", +] + +[[package]] +name = "libcrux-hkdf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1a89ca0c89be3a268a921e47105fb7873badf7267f5e3ebf4ea46baedd73ef" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-hmac", + "libcrux-secrets", +] + +[[package]] +name = "libcrux-hmac" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7a242707d65960770bd7e14e4f18a92bdf0b967777dd404887db8d087a643b" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-sha2", +] + +[[package]] +name = "libcrux-intrinsics" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1b5db005ff8001e026b73a6842ee81bbef8ec5ff0e1915a67ae65fd2a9fafa5" +dependencies = [ + "core-models", + "hax-lib", +] + +[[package]] +name = "libcrux-kem" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12631592f491d22fd1a176d32b2c6edfb673998fd3987e9d95f8fa79ad2a737b" +dependencies = [ + "libcrux-curve25519", + "libcrux-ecdh", + "libcrux-ml-kem", + "libcrux-p256", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.4", +] + +[[package]] +name = "libcrux-macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "libcrux-ml-kem" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14ab3e477de9df6ee1273a114018ff62c4996ca9220070c4e5cb1743f94a67d" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.4", + "tls_codec", +] + +[[package]] +name = "libcrux-p256" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4778ba25cb08bb8a96bd100e19ed9aecf78337198fd176036e21042b2dd99bc" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-sha2", + "libcrux-traits", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-poly1305" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02491808ee5b9db8cb65fad64ae0be812db64beef179d945c00c7787dc7dfcf9" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha2" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d253473f259fc74a280c43f29c464f7e374abdf28b4942234dc707f529d4b7" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-traits", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ae0b7d0e1cc4793a609fd0ff2ca3b3a3fabae523770c619a3d4bc86417b0d7" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] + +[[package]] +name = "libcrux-traits" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e4fa89f3f5e34b47f928b22b1b78395a0d4ec23b1f583db635f128159d65f" +dependencies = [ + "libcrux-secrets", + "rand 0.9.4", +] + [[package]] name = "libm" version = "0.2.16" @@ -1089,9 +2090,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" dependencies = [ "cc", "pkg-config", @@ -1144,6 +2145,77 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "mdk-core" +version = "0.8.0" +source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" +dependencies = [ + "base64", + "blurhash", + "chacha20poly1305", + "fast-thumbhash", + "hex", + "hkdf", + "image", + "kamadak-exif", + "mdk-macros", + "mdk-storage-traits", + "nostr", + "openmls", + "openmls_basic_credential", + "openmls_rust_crypto", + "openmls_traits", + "serde", + "sha2 0.10.9", + "thiserror 2.0.18", + "tls_codec", + "tracing", + "zeroize", +] + +[[package]] +name = "mdk-macros" +version = "0.8.0" +source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" + +[[package]] +name = "mdk-sqlite-storage" +version = "0.8.0" +source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" +dependencies = [ + "base64", + "getrandom 0.4.2", + "hex", + "keyring-core", + "mdk-storage-traits", + "nostr", + "openmls", + "openmls_traits", + "refinery", + "rusqlite", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "mdk-storage-traits" +version = "0.8.0" +source = "git+https://github.com/marmot-protocol/mdk?rev=7f809f8549458a0d7f7d885bcdd694023abf299c#7f809f8549458a0d7f7d885bcdd694023abf299c" +dependencies = [ + "nostr", + "openmls", + "openmls_traits", + "pastey", + "postcard", + "serde", + "serde_json", + "thiserror 2.0.18", + "zeroize", +] + [[package]] name = "memchr" version = "2.8.0" @@ -1157,6 +2229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1170,12 +2243,58 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "multimap" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "mutate_once" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nostr" +version = "0.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1" +dependencies = [ + "base64", + "bech32", + "bip39", + "bitcoin_hashes", + "cbc", + "chacha20 0.9.1", + "chacha20poly1305", + "getrandom 0.2.17", + "hex", + "instant", + "scrypt", + "secp256k1", + "serde", + "serde_json", + "unicode-normalization", + "url", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1185,6 +2304,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -1262,6 +2391,85 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openmls" +version = "0.8.1" +source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" +dependencies = [ + "log", + "openmls_traits", + "rayon", + "serde", + "serde_bytes", + "thiserror 2.0.18", + "tls_codec", + "zeroize", +] + +[[package]] +name = "openmls_basic_credential" +version = "0.5.0" +source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" +dependencies = [ + "ed25519-dalek", + "openmls_traits", + "p256", + "rand 0.8.6", + "serde", + "tls_codec", +] + +[[package]] +name = "openmls_memory_storage" +version = "0.5.0" +source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" +dependencies = [ + "log", + "openmls_traits", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "openmls_rust_crypto" +version = "0.5.1" +source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "ed25519-dalek", + "hkdf", + "hmac 0.12.1", + "hpke-rs", + "hpke-rs-crypto", + "hpke-rs-rust-crypto", + "openmls_memory_storage", + "openmls_traits", + "p256", + "rand 0.8.6", + "rand_chacha 0.3.1", + "serde", + "sha2 0.10.9", + "thiserror 2.0.18", + "tls_codec", +] + +[[package]] +name = "openmls_traits" +version = "0.5.0" +source = "git+https://github.com/openmls/openmls?rev=04c50d7fb12d52f4f9aee26de5f5234f3df29fa8#04c50d7fb12d52f4f9aee26de5f5234f3df29fa8" +dependencies = [ + "serde", + "tls_codec", +] + [[package]] name = "oslog" version = "0.2.0" @@ -1273,6 +2481,28 @@ dependencies = [ "log", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "elliptic-curve", + "primeorder", +] + [[package]] name = "parking" version = "2.2.1" @@ -1302,12 +2532,39 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1373,12 +2630,61 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -1423,6 +2729,37 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1497,11 +2834,23 @@ dependencies = [ "serde_json", "sha2 0.11.0", "sqlx", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", ] +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.45" @@ -1511,6 +2860,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1524,17 +2879,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20", + "chacha20 0.10.0", "getrandom 0.4.2", "rand_core 0.10.1", ] @@ -1549,6 +2914,26 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" +dependencies = [ + "ppv-lite86", + "rand_core 0.10.1", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -1558,12 +2943,41 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_core" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1582,6 +2996,49 @@ dependencies = [ "bitflags", ] +[[package]] +name = "refinery" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee5133e5b207e5703c2a4a9dc9bd8c8f2cc74c4ac04ca5510acaa907012c77ac" +dependencies = [ + "refinery-core", + "refinery-macros", +] + +[[package]] +name = "refinery-core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a2a96d959c9b5b5da78e965bfdb1363b365bf5e84531a67d0eee827a702a3" +dependencies = [ + "async-trait", + "cfg-if", + "log", + "regex", + "rusqlite", + "serde", + "siphasher", + "thiserror 2.0.18", + "time", + "toml", + "url", + "walkdir", +] + +[[package]] +name = "refinery-macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56c2e960c8e47c7c5c30ad334afea8b5502da796a59e34d640d6239d876d924" +dependencies = [ + "proc-macro2", + "quote", + "refinery-core", + "regex", + "syn", +] + [[package]] name = "regex" version = "1.12.3" @@ -1611,6 +3068,16 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "rsa" version = "0.9.10" @@ -1631,23 +3098,54 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust_lib_twonly" version = "0.1.0" dependencies = [ + "aes-gcm", + "android-native-keyring-store", + "apple-native-keyring-store", + "chrono", "flutter_rust_bridge", + "hex", + "hkdf", + "keyring-core", + "libsqlite3-sys", + "mdk-core", + "mdk-sqlite-storage", + "mdk-storage-traits", "paste", + "postcard", "pretty_env_logger", "prost-build", "protocols", "rand 0.10.1", + "serde", + "sha2 0.10.9", "sqlx", "tempfile", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "tracing-appender", "tracing-subscriber", + "walkdir", + "zeroize", + "zip", ] [[package]] @@ -1656,6 +3154,15 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.4" @@ -1681,12 +3188,99 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2 0.10.9", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand 0.8.6", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.28" @@ -1703,6 +3297,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1736,6 +3340,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1816,6 +3429,18 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.12" @@ -1901,7 +3526,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -1942,7 +3567,7 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn", - "thiserror", + "thiserror 2.0.18", "tokio", "url", ] @@ -1984,7 +3609,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -2021,7 +3646,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -2046,7 +3671,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.18", "tracing", "url", ] @@ -2124,13 +3749,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2218,6 +3863,28 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "serde", + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.52.1" @@ -2257,6 +3924,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tracing" version = "0.1.44" @@ -2277,7 +3985,7 @@ checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", "symlink", - "thiserror", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -2371,6 +4079,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "url" version = "2.5.8" @@ -2381,6 +4099,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -2389,6 +4108,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2407,6 +4137,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2536,6 +4276,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "whoami" version = "1.6.1" @@ -2614,13 +4360,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2632,34 +4387,67 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2672,30 +4460,63 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2796,6 +4617,18 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "yoke" version = "0.8.2" @@ -2913,8 +4746,52 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 47ab6cf6..abc70558 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -18,13 +18,37 @@ sqlx = { version = "0.9.0-alpha.1", default-features = false, features = [ "derive", "json", ] } +mdk-core = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c", features = [ + "mip04", + "mip05", +] } +mdk-sqlite-storage = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c" } +mdk-storage-traits = { version = "0.8.0", git = "https://github.com/marmot-protocol/mdk", rev = "7f809f8549458a0d7f7d885bcdd694023abf299c" } +libsqlite3-sys = { version = "0.35.0", features = ["bundled", "sqlcipher"] } tokio = { version = "1.44", features = ["full"] } tracing = "0.1.44" rand = "0.10.1" protocols = { path = "../rust_dependencies/protocols" } +hkdf = "0.12.4" +sha2 = "0.10.8" +aes-gcm = "0.10.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-appender = "0.2.5" paste = "1.0.15" +serde = { version = "1.0", features = ["derive"] } +zeroize = { version = "1.8", features = ["derive"] } +hex = "0.4.3" +keyring-core = "1" +postcard = { version = "1.0", features = ["alloc"] } +chrono = { version = "0.4", features = ["serde"] } +zip = { version = "2.2.2", default-features = false, features = ["deflate"] } +walkdir = "2.5.0" +[target.'cfg(target_os = "ios")'.dependencies] +# iOS backend: Requires the 'protected' feature for Data Protection Keychain +apple-native-keyring-store = { version = "1", features = ["protected"] } +[target.'cfg(target_os = "android")'.dependencies] +# Android backend: Interfaces with the Android Keystore +android-native-keyring-store = "1" [dev-dependencies] pretty_env_logger = "0.5.0" diff --git a/rust/src/backup/backup_archive.rs b/rust/src/backup/backup_archive.rs new file mode 100644 index 00000000..1afdac6b --- /dev/null +++ b/rust/src/backup/backup_archive.rs @@ -0,0 +1,258 @@ +use crate::context::Context; +use crate::database::Database; +use crate::error::Result; +use crate::keys::DatabaseKey; +use std::fs::{remove_file, File}; +use std::io::{copy, Cursor}; +use std::path::PathBuf; +use walkdir::WalkDir; +use zeroize::Zeroize; +use zip::write::SimpleFileOptions; +use zip::{CompressionMethod, ZipArchive, ZipWriter}; + +struct BackupArchive {} + +impl BackupArchive { + fn get_backup_files(ctx: &Context) -> Result)>> { + let config = ctx.get_config()?; + let database_dir = PathBuf::from(&config.database_dir); + let data_dir = PathBuf::from(&config.data_dir); + let keys = ctx.get_key_manager()?; + let rust_db_key = keys.main_key.get_database_key(DatabaseKey::RustDb); + + Ok(vec![ + ("twonly.sqlite", database_dir.clone(), true, None), + ("rust_db.sqlite", database_dir, true, Some(rust_db_key)), + ("user_discovery_config.json", data_dir, false, None), + ]) + } + + pub(crate) async fn create_backup(ctx: &Context) -> Result { + let config = ctx.get_config()?; + let data_dir = PathBuf::from(&config.data_dir); + + let backup_data_dir = data_dir.join("temp_backup_dir"); + if backup_data_dir.is_dir() { + std::fs::remove_dir_all(&backup_data_dir)?; + } + std::fs::create_dir_all(&backup_data_dir)?; + + for (file_name, source_dir, is_db, mut encryption_key) in Self::get_backup_files(ctx)? { + let file_path = source_dir.join(&file_name); + if !file_path.exists() { + tracing::warn!( + "Could not backup {} as it does not exist.", + file_path.display() + ); + continue; + } + + if is_db { + let db = Database::new( + &file_path.display().to_string(), + encryption_key.as_deref(), + encryption_key.is_none(), + ) + .await?; + let backup_database_file = backup_data_dir.join(&file_name).display().to_string(); + db.create_backup(backup_database_file.as_str(), encryption_key.as_deref()) + .await?; + } else { + let file_backup = backup_data_dir.join(&file_name); + std::fs::copy(file_path, file_backup)?; + } + encryption_key.zeroize(); + } + + let mut zip_data = Vec::new(); + + { + let mut zip = ZipWriter::new(Cursor::new(&mut zip_data)); + let options = + SimpleFileOptions::default().compression_method(CompressionMethod::Deflated); + + for entry in WalkDir::new(&backup_data_dir) { + let entry = entry?; + let path = entry.path(); + + if !path.is_file() { + continue; + } + + if let Ok(name) = path.strip_prefix(&backup_data_dir) { + zip.start_file(name.to_string_lossy(), options)?; + copy(&mut File::open(path)?, &mut zip)?; + } + } + zip.finish()?; + } + + let mut keys = ctx.get_key_manager()?; + + let zip_path = data_dir.join("temp_backup.zip"); + std::fs::write(&zip_path, keys.main_key.encrypt_backup(&zip_data))?; + + std::fs::remove_dir_all(&backup_data_dir)?; + keys.zeroize(); + + Ok(zip_path) + } + + pub(crate) async fn restore_from_backup(ctx: &Context, file_path: &PathBuf) -> Result<()> { + let data_dir = PathBuf::from(&ctx.get_config()?.data_dir); + + let mut keys = ctx.get_key_manager()?; + + let encrypted_zip = std::fs::read(file_path)?; + let zip_content = keys.main_key.decrypt_backup(&encrypted_zip)?; + + let restore_temp_dir = data_dir.join("restore_temp"); + + if restore_temp_dir.exists() { + std::fs::remove_dir_all(&restore_temp_dir)?; + } + + std::fs::create_dir_all(&restore_temp_dir)?; + + let mut archive = ZipArchive::new(Cursor::new(zip_content))?; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + + if file.is_file() { + let enclosed_name = file.enclosed_name(); + if let Some(name) = enclosed_name.as_ref().and_then(|p| p.file_name()) { + let restored_file = restore_temp_dir.join(name); + copy(&mut file, &mut File::create(&restored_file)?)?; + }; + } + } + + for (file_name, target_dir, is_db, _) in Self::get_backup_files(ctx)? { + let src = restore_temp_dir.join(&file_name); + if src.exists() { + let dst = target_dir.join(&file_name); + if is_db { + // Remove existing database and its temporary files (WAL, SHM) + let _ = remove_file(&dst); + let _ = remove_file(target_dir.join(format!("{}-wal", file_name))); + let _ = remove_file(target_dir.join(format!("{}-shm", file_name))); + } + + std::fs::copy(src, dst)?; + } + } + + keys.zeroize(); + std::fs::remove_dir_all(&restore_temp_dir)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[tokio::test] + async fn test_backup_and_restore() { + let _ = pretty_env_logger::try_init(); + + let temp_dir = tempdir().unwrap(); + + let ctx = Context::init_for_testing( + temp_dir.path().join("database"), + temp_dir.path().join("data"), + ) + .await + .unwrap(); + + // 1. Add some data + { + let config = ctx.get_config().unwrap(); + let rust_db_path = PathBuf::from(&config.database_dir).join("rust_db.sqlite"); + let key_manager = ctx.get_key_manager().unwrap(); + let db = Database::new( + &rust_db_path.display().to_string(), + Some(&key_manager.main_key.get_database_key(DatabaseKey::RustDb)), + false, + ) + .await + .unwrap(); + + crate::database::tables::received_messages::ReceivedMessage::insert( + &db.pool, + "sender1", + b"original message", + ) + .await + .unwrap(); + + // Add a file + let config_file = PathBuf::from(&config.data_dir).join("user_discovery_config.json"); + std::fs::write(config_file, "original config").unwrap(); + } + + // 2. Create backup + let backup_path = BackupArchive::create_backup(&ctx).await.unwrap(); + assert!(backup_path.exists()); + + // 3. Modify data (to simulate state before restore) + { + let config = ctx.get_config().unwrap(); + let rust_db_path = PathBuf::from(&config.database_dir).join("rust_db.sqlite"); + let key_manager = ctx.get_key_manager().unwrap(); + let db = Database::new( + &rust_db_path.display().to_string(), + Some(&key_manager.main_key.get_database_key(DatabaseKey::RustDb)), + false, + ) + .await + .unwrap(); + + crate::database::tables::received_messages::ReceivedMessage::insert( + &db.pool, + "sender2", + b"new message", + ) + .await + .unwrap(); + + let config_file = PathBuf::from(&config.data_dir).join("user_discovery_config.json"); + std::fs::write(config_file, "new config").unwrap(); + } + + // 4. Restore backup + BackupArchive::restore_from_backup(&ctx, &backup_path) + .await + .unwrap(); + + // 5. Verify restored data + { + let config = ctx.get_config().unwrap(); + let rust_db_path = PathBuf::from(&config.database_dir).join("rust_db.sqlite"); + let key_manager = ctx.get_key_manager().unwrap(); + let db = Database::new( + &rust_db_path.display().to_string(), + Some(&key_manager.main_key.get_database_key(DatabaseKey::RustDb)), + false, + ) + .await + .unwrap(); + + let messages = + crate::database::tables::received_messages::ReceivedMessage::get_all(&db.pool) + .await + .unwrap(); + // Should only have the original message because restore overwrites + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].sender_id, "sender1"); + assert_eq!(messages[0].content, b"original message"); + + let config_file = PathBuf::from(&config.data_dir).join("user_discovery_config.json"); + let config_content = std::fs::read_to_string(config_file).unwrap(); + assert_eq!(config_content, "original config"); + } + } +} diff --git a/rust/src/backup/mod.rs b/rust/src/backup/mod.rs new file mode 100644 index 00000000..e2e6fa43 --- /dev/null +++ b/rust/src/backup/mod.rs @@ -0,0 +1 @@ +mod backup_archive; diff --git a/rust/src/bridge/callbacks.rs b/rust/src/bridge/callbacks.rs index a2c81111..7db2b450 100644 --- a/rust/src/bridge/callbacks.rs +++ b/rust/src/bridge/callbacks.rs @@ -5,12 +5,10 @@ pub(crate) mod user_discovery; use flutter_rust_bridge::DartFnFuture; use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion}; -use super::error::Result; +use crate::error::{Result, TwonlyError}; use crate::{callback_generator, frb_generated::StreamSink}; use std::sync::{Arc, OnceLock}; -use crate::bridge::error::TwonlyError; - static FLUTTER_CALLBACKS: OnceLock = OnceLock::new(); // This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks diff --git a/rust/src/bridge/callbacks/user_discovery.rs b/rust/src/bridge/callbacks/user_discovery.rs index 1a24fb94..c9b390b7 100644 --- a/rust/src/bridge/callbacks/user_discovery.rs +++ b/rust/src/bridge/callbacks/user_discovery.rs @@ -1,6 +1,6 @@ use crate::bridge::callbacks::get_callbacks; -use crate::bridge::error::TwonlyError; use crate::bridge::get_twonly_flutter; +use crate::error::TwonlyError; use protocols::user_discovery::error::{Result, UserDiscoveryError}; use protocols::user_discovery::traits::UserDiscoveryUtils; use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore}; @@ -47,8 +47,7 @@ impl UserDiscoveryUtils for UserDiscoveryUtilsFlutter { impl UserDiscoveryStore for UserDiscoveryStoreFlutter { async fn get_config(&self) -> Result { let ws = get_twonly_flutter()?; - let config_path = - PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json"); + let config_path = PathBuf::from(&ws.config.data_dir).join("user_discovery_config.json"); if !config_path.is_file() { return Err(UserDiscoveryError::NotInitialized); @@ -60,8 +59,7 @@ impl UserDiscoveryStore for UserDiscoveryStoreFlutter { async fn update_config(&self, update: String) -> Result<()> { tracing::debug!("Updating configuration file."); let ws = get_twonly_flutter()?; - let config_path = - PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json"); + let config_path = PathBuf::from(&ws.config.data_dir).join("user_discovery_config.json"); std::fs::write(config_path, &update)?; Ok(()) } diff --git a/rust/src/bridge/mod.rs b/rust/src/bridge/mod.rs index dd4a7aa0..6bb8dd98 100644 --- a/rust/src/bridge/mod.rs +++ b/rust/src/bridge/mod.rs @@ -1,24 +1,30 @@ #![allow(unexpected_cfgs)] pub mod callbacks; -pub mod error; -pub mod log; pub mod wrapper; +use std::path::Path; +use std::sync::Arc; + use crate::bridge::callbacks::user_discovery::{ UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter, }; -use crate::bridge::log::init_tracing; +use crate::context::Context; +use crate::database::Database; +use crate::error::Result; +use crate::error::TwonlyError; +use crate::secure_storage::SecureStorage; use crate::utils::Shared; -use error::Result; -use error::TwonlyError; use flutter_rust_bridge::frb; use protocols::user_discovery::UserDiscovery; -use std::path::PathBuf; -use tokio::sync::OnceCell; pub use protocols::user_discovery::traits::AnnouncedUser; pub use protocols::user_discovery::traits::OtherPromotion; +pub struct InitConfig { + pub database_dir: String, + pub data_dir: String, +} + #[frb(mirror(OtherPromotion))] pub struct _OtherPromotion { pub promotion_id: u32, @@ -36,58 +42,25 @@ pub struct _AnnouncedUser { pub public_id: i64, } -pub struct TwonlyConfig { - pub database_path: String, - pub data_directory: String, -} - pub(crate) struct TwonlyFlutter { #[allow(dead_code)] - pub(crate) config: TwonlyConfig, - // /// Rust runs in the same process as drift, the database can only be opened in readonly mode - // pub(crate) twonly_db_readonly: Arc, + pub(crate) config: InitConfig, pub(crate) user_discovery: Shared>, + pub(crate) rust_db: Arc, + pub(crate) secure_storage: SecureStorage, } -static GLOBAL_TWONLY: OnceCell = OnceCell::const_new(); - pub(super) fn get_twonly_flutter() -> Result<&'static TwonlyFlutter> { - GLOBAL_TWONLY.get().ok_or(TwonlyError::Initialization) + let ctx = Context::get_static()?; + if let Context::Flutter(twonly) = ctx { + return Ok(twonly); + } else { + return Err(TwonlyError::Initialization); + } } -pub async fn initialize_twonly_flutter(config: TwonlyConfig) -> Result<()> { - if GLOBAL_TWONLY.initialized() { - tracing::info!("twonly already initialized."); - return Ok(()); - } - let log_dir = PathBuf::from(&config.data_directory).join("log"); - init_tracing(&log_dir, true).await; - tracing::info!("Initialized twonly workspace."); - let twonly_res: Result<&'static TwonlyFlutter> = GLOBAL_TWONLY - .get_or_try_init(|| async { - // let database_dir = PathBuf::from(&config.database_path.clone()); - // let Some(rust_db_path) = database_dir.parent() else { - // return Err(TwonlyError::DatabaseNotFound); - // }; - // let rust_db_path = rust_db_path.join("rust_db.sqlite").display().to_string(); - - // let twonly_db_readonly = Arc::new(Database::new(&config.database_path, true).await?); - // let rust_db = Arc::new(Database::new(&rust_db_path, false).await?); - - Ok(TwonlyFlutter { - config, - // twonly_db_readonly, - // rust_db, - user_discovery: Shared::new(UserDiscovery::new( - UserDiscoveryStoreFlutter {}, - UserDiscoveryUtilsFlutter {}, - )?), - }) - }) - .await; - - twonly_res?; - +pub async fn initialize_twonly_flutter(config: InitConfig) -> Result<()> { + Context::init_flutter(config).await?; Ok(()) } diff --git a/rust/src/bridge/wrapper/user_discovery.rs b/rust/src/bridge/wrapper/user_discovery.rs index 6c50a7f8..4ecb59c2 100644 --- a/rust/src/bridge/wrapper/user_discovery.rs +++ b/rust/src/bridge/wrapper/user_discovery.rs @@ -1,5 +1,5 @@ -use crate::bridge::error::Result; use crate::bridge::get_twonly_flutter; +use crate::error::Result; pub struct FlutterUserDiscovery {} diff --git a/rust/src/context.rs b/rust/src/context.rs new file mode 100644 index 00000000..425cb76a --- /dev/null +++ b/rust/src/context.rs @@ -0,0 +1,169 @@ +use crate::{ + bridge::{ + callbacks::user_discovery::{UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter}, + InitConfig, + }, + database::Database, + error::{Result, TwonlyError}, + keys::{DatabaseKey, KeyManager}, + log::init_tracing, + utils::Shared, +}; +use protocols::user_discovery::UserDiscovery; +use std::{path::PathBuf, sync::Arc}; +use tokio::sync::OnceCell; +use zeroize::Zeroize; + +use crate::{bridge::TwonlyFlutter, secure_storage::SecureStorage, standalone::TwonlyStandalone}; + +pub(crate) enum Context { + Flutter(TwonlyFlutter), + Standalone(TwonlyStandalone), +} + +impl Context { + #[cfg(test)] + pub(crate) fn from_standalone(standalone: TwonlyStandalone) -> Self { + Self::Standalone(standalone) + } +} + +static GLOBAL_CONTEXT: OnceCell = OnceCell::const_new(); + +impl Context { + pub(crate) async fn init_flutter(config: InitConfig) -> Result<()> { + Self::init_common(config, true).await + } + + pub(crate) async fn init_standalone(config: InitConfig) -> Result<()> { + Self::init_common(config, false).await + } + + #[cfg(test)] + pub(crate) async fn init_for_testing( + database_dir: PathBuf, + data_dir: PathBuf, + ) -> Result { + std::fs::create_dir_all(&database_dir)?; + std::fs::create_dir_all(&data_dir)?; + + let config = InitConfig { + database_dir: database_dir.display().to_string(), + data_dir: data_dir.display().to_string(), + }; + + // Initialize tracing and secure storage if not already done + let _ = SecureStorage::init(); + let secure_storage = SecureStorage::new("eu.twonly.testing"); + + let key_manager = KeyManager::generate()?; + key_manager.store_to_keychain(&secure_storage)?; + + let rust_db_path = database_dir.join("rust_db.sqlite"); + let rust_db = Arc::new( + Database::new( + &rust_db_path.display().to_string(), + Some(&key_manager.main_key.get_database_key(DatabaseKey::RustDb)), + false, + ) + .await?, + ); + + Ok(Context::from_standalone(TwonlyStandalone { + config, + rust_db, + secure_storage, + })) + } + + async fn init_common(config: InitConfig, is_flutter: bool) -> Result<()> { + if GLOBAL_CONTEXT.initialized() { + tracing::info!("twonly already initialized."); + return Ok(()); + } + + let log_dir = PathBuf::from(&config.data_dir).join("log"); + init_tracing(&log_dir, is_flutter).await; + + SecureStorage::init()?; + let secure_storage = SecureStorage::new("eu.twonly"); + + let database_dir = PathBuf::from(&config.database_dir.clone()); + let rust_db_path = database_dir.join("rust_db.sqlite"); + + tracing::info!("Initialized twonly workspace."); + let _: Result<&'static Context> = GLOBAL_CONTEXT + .get_or_try_init(|| async { + let key_manager = match KeyManager::try_from_keychain(&secure_storage) { + Ok(key) => key, + Err(err) => { + tracing::error!("{err}"); + if rust_db_path.exists() { + tracing::error!("Rust Database exsist, while the key manager not"); + return Err(TwonlyError::SecureStorageError); + } + tracing::info!("Generating a new key manager."); + let new = KeyManager::generate()?; + new.store_to_keychain(&secure_storage)?; + new + } + }; + + let mut rust_db_key = key_manager.main_key.get_database_key(DatabaseKey::RustDb); + + let rust_db = Arc::new( + Database::new( + &rust_db_path.display().to_string(), + Some(rust_db_key.as_str()), + false, + ) + .await?, + ); + + rust_db_key.zeroize(); + + if is_flutter { + Ok(Context::Flutter(TwonlyFlutter { + config, + secure_storage, + rust_db, + user_discovery: Shared::new(UserDiscovery::new( + UserDiscoveryStoreFlutter {}, + UserDiscoveryUtilsFlutter {}, + )?), + })) + } else { + Ok(Context::Standalone(TwonlyStandalone { + config, + rust_db, + secure_storage, + })) + } + }) + .await; + + Ok(()) + } + + pub(super) fn get_static() -> Result<&'static Context> { + GLOBAL_CONTEXT.get().ok_or(TwonlyError::Initialization) + } + + pub(crate) fn get_secure_storage(&self) -> Result<&SecureStorage> { + match self { + Self::Flutter(twonly) => Ok(&twonly.secure_storage), + Self::Standalone(twonly) => Ok(&twonly.secure_storage), + } + } + + pub(crate) fn get_config(&self) -> Result<&InitConfig> { + match self { + Self::Flutter(twonly) => Ok(&twonly.config), + Self::Standalone(twonly) => Ok(&twonly.config), + } + } + + pub(crate) fn get_key_manager(&self) -> Result { + KeyManager::try_from_keychain(self.get_secure_storage()?) + } +} diff --git a/rust/src/database/migrations/0001_initial.sql b/rust/src/database/migrations/0001_initial.sql new file mode 100644 index 00000000..84945152 --- /dev/null +++ b/rust/src/database/migrations/0001_initial.sql @@ -0,0 +1,16 @@ +-- Initial migration: Create received_messages and sending_messages tables + +CREATE TABLE IF NOT EXISTS received_messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sender_id TEXT NOT NULL, + content BLOB NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, +); + +CREATE TABLE IF NOT EXISTS sending_messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + recipient_id TEXT NOT NULL, + content BLOB NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + status TEXT DEFAULT 'pending' +); diff --git a/rust/src/database/mod.rs b/rust/src/database/mod.rs index ee29f7e9..3ebc7af0 100644 --- a/rust/src/database/mod.rs +++ b/rust/src/database/mod.rs @@ -1,65 +1,180 @@ -// use crate::bridge::error::{Result, TwonlyError}; -// use sqlx::migrate::MigrateDatabase; -// use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; -// use sqlx::{ConnectOptions, Sqlite, SqlitePool}; -// use std::time::Duration; +use crate::error::{Result, TwonlyError}; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; +use sqlx::{ConnectOptions, SqlitePool}; +use std::time::Duration; -// pub(crate) struct Database { -// pub(crate) pool: SqlitePool, -// } +pub(crate) mod tables; -// impl Database { -// pub(crate) async fn new(db_path: &String, read_only: bool) -> Result { -// let db_url = format!("sqlite://{}", db_path); +pub(crate) struct Database { + pub(crate) pool: SqlitePool, +} -// match Sqlite::database_exists(&db_url).await { -// Ok(true) => { -// tracing::debug!("database exists"); -// } -// Ok(false) => { -// tracing::error!("could not open the sqlite3 database"); -// return Err(TwonlyError::DatabaseNotFound); -// } -// Err(e) => { -// tracing::error!( -// "Could not check if database exists: {:?}, attempting to create", -// e -// ); -// return Err(TwonlyError::DatabaseNotFound); -// } -// } +impl Database { + pub(crate) async fn new( + db_path: &String, + encryption_key: Option<&str>, + read_only: bool, + ) -> Result { + let db_url = format!("sqlite://{}", db_path); -// tracing::debug!("Creating database connection pool"); + let log_statements_level = if std::env::var("SQLX_LOG_STATEMENTS").is_ok() { + tracing::log::LevelFilter::Info + } else { + tracing::log::LevelFilter::Off + }; -// let log_statements_level = if std::env::var("SQLX_LOG_STATEMENTS").is_ok() { -// tracing::log::LevelFilter::Info -// } else { -// tracing::log::LevelFilter::Off -// }; + let mut connect_options = format!("{db_url}?mode=rwc") + .parse::()? + .log_statements(log_statements_level) + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .foreign_keys(true) + .read_only(read_only) + .busy_timeout(Duration::from_millis(5000)) + .pragma("recursive_triggers", "ON") + .log_slow_statements(tracing::log::LevelFilter::Warn, Duration::from_millis(500)); -// let connect_options = format!("{db_url}?mode=rwc") -// .parse::()? -// .log_statements(log_statements_level) -// .read_only(read_only) -// .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) -// .foreign_keys(true) -// .busy_timeout(Duration::from_millis(5000)) -// .pragma("recursive_triggers", "ON") -// .log_slow_statements(tracing::log::LevelFilter::Warn, Duration::from_millis(500)); + if let Some(encryption_key) = encryption_key { + connect_options = connect_options.pragma("key", format!("'{}'", encryption_key)); + } -// let pool = SqlitePoolOptions::new() -// .acquire_timeout(Duration::from_secs(5)) -// .max_connections(10) -// .connect_with(connect_options) -// .await?; + let pool = SqlitePoolOptions::new() + .acquire_timeout(Duration::from_secs(5)) + .max_connections(10) + .connect_with(connect_options) + .await?; -// let row: (String, String) = sqlx::query_as("SELECT sqlite_version(), sqlite_source_id()") -// .fetch_one(&pool) -// .await?; + sqlx::migrate!("./src/database/migrations") + .run(&pool) + .await + .map_err(|e| { + tracing::error!("migration error: {:?}", e); + TwonlyError::Generic(format!("Migration error: {}", e)) + })?; -// tracing::info!("Rust SQLite Version: {}", row.0); -// tracing::info!("Rust SQLite Source ID: {}", row.1); + Ok(Self { pool }) + } -// Ok(Self { pool: pool }) -// } -// } + pub(crate) async fn create_backup( + &self, + output_path: &str, + encryption_key: Option<&str>, + ) -> Result<()> { + if let Some(key) = encryption_key { + let mut conn = self + .pool + .acquire() + .await + .map_err(|e| TwonlyError::Generic(e.to_string()))?; + + sqlx::query("ATTACH DATABASE ? AS backup KEY ?") + .bind(output_path) + .bind(key) + .execute(&mut *conn) + .await + .map_err(|e| TwonlyError::Generic(format!("Attach failed: {}", e)))?; + + sqlx::query("SELECT sqlcipher_export('backup')") + .execute(&mut *conn) + .await + .map_err(|e| TwonlyError::Generic(format!("Export failed: {}", e)))?; + + sqlx::query("DETACH DATABASE backup") + .execute(&mut *conn) + .await + .map_err(|e| TwonlyError::Generic(format!("Detach failed: {}", e)))?; + } else { + sqlx::query("VACUUM INTO ?") + .bind(output_path) + .execute(&self.pool) + .await + .map_err(|e| TwonlyError::Generic(format!("Backup failed: {}", e)))?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::database::tables::received_messages::ReceivedMessage; + + use super::*; + use tempfile::tempdir; + + #[tokio::test] + async fn test_database_encryption_and_migrations() { + let _ = pretty_env_logger::try_init(); + let dir = tempdir().unwrap(); + let db_path = dir.path().join("test.sqlite").display().to_string(); + let key = "secure_password"; + + // 1. Create and initialize database with key + let db = Database::new(&db_path, Some(key), false).await.unwrap(); + ReceivedMessage::insert(&db.pool, "sender1", b"hello world") + .await + .unwrap(); + + // 2. Try to open with WRONG key + let result = Database::new(&db_path, Some("wrong_password"), false).await; + assert!( + result.is_err(), + "Opening with wrong key should fail. If this passes, the database might not be encrypted!" + ); + + // 3. Open with CORRECT key again + let db = Database::new(&db_path, Some(key), false).await.unwrap(); + let messages = ReceivedMessage::get_all(&db.pool).await.unwrap(); + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].sender_id, "sender1"); + assert_eq!(messages[0].content, b"hello world"); + } + + #[tokio::test] + async fn test_database_backup_encrypted() { + let _ = pretty_env_logger::try_init(); + let dir = tempdir().unwrap(); + let db_path = dir.path().join("test_enc.sqlite").display().to_string(); + let backup_path = dir.path().join("backup_enc.sqlite").display().to_string(); + let key = "secure_password"; + + let db = Database::new(&db_path, Some(key), false).await.unwrap(); + ReceivedMessage::insert(&db.pool, "sender1", b"hello world") + .await + .unwrap(); + + db.create_backup(&backup_path, Some(key)).await.unwrap(); + + // 1. Verify it cannot be opened with wrong key + let result = Database::new(&backup_path, Some("wrong_password"), false).await; + assert!( + result.is_err(), + "Encrypted backup should fail with wrong key" + ); + + // 2. Open backup with correct key and verify data + let backup_db = Database::new(&backup_path, Some(key), false).await.unwrap(); + let messages = ReceivedMessage::get_all(&backup_db.pool).await.unwrap(); + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].sender_id, "sender1"); + } + + #[tokio::test] + async fn test_database_backup_plaintext() { + let _ = pretty_env_logger::try_init(); + let dir = tempdir().unwrap(); + let db_path = dir.path().join("test_plain.sqlite").display().to_string(); + let backup_path = dir.path().join("backup_plain.sqlite").display().to_string(); + + let db = Database::new(&db_path, None, false).await.unwrap(); + ReceivedMessage::insert(&db.pool, "sender1", b"hello world") + .await + .unwrap(); + + db.create_backup(&backup_path, None).await.unwrap(); + + // Open backup and verify + let backup_db = Database::new(&backup_path, None, false).await.unwrap(); + let messages = ReceivedMessage::get_all(&backup_db.pool).await.unwrap(); + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].sender_id, "sender1"); + } +} diff --git a/rust/src/database/tables/mod.rs b/rust/src/database/tables/mod.rs new file mode 100644 index 00000000..b87cf5a5 --- /dev/null +++ b/rust/src/database/tables/mod.rs @@ -0,0 +1,2 @@ +pub mod received_messages; +pub mod sending_messages; diff --git a/rust/src/database/tables/received_messages.rs b/rust/src/database/tables/received_messages.rs new file mode 100644 index 00000000..800ab79f --- /dev/null +++ b/rust/src/database/tables/received_messages.rs @@ -0,0 +1,34 @@ +use crate::error::Result; +use chrono::{DateTime, Utc}; +use sqlx::{FromRow, SqlitePool}; + +#[derive(Debug, FromRow)] +pub struct ReceivedMessage { + pub id: i64, + pub sender_id: String, + pub content: Vec, + pub timestamp: DateTime, +} + +impl ReceivedMessage { + pub async fn insert(pool: &SqlitePool, sender_id: &str, content: &[u8]) -> Result { + let result = + sqlx::query("INSERT INTO received_messages (sender_id, content) VALUES (?, ?)") + .bind(sender_id) + .bind(content) + .execute(pool) + .await?; + + Ok(result.last_insert_rowid()) + } + + pub async fn get_all(pool: &SqlitePool) -> Result> { + let messages = sqlx::query_as::<_, Self>( + "SELECT id, sender_id, content, timestamp FROM received_messages ORDER BY timestamp DESC", + ) + .fetch_all(pool) + .await?; + + Ok(messages) + } +} diff --git a/rust/src/database/tables/sending_messages.rs b/rust/src/database/tables/sending_messages.rs new file mode 100644 index 00000000..d9341266 --- /dev/null +++ b/rust/src/database/tables/sending_messages.rs @@ -0,0 +1,35 @@ +use crate::error::Result; +use chrono::{DateTime, Utc}; +use sqlx::{FromRow, SqlitePool}; + +#[derive(Debug, FromRow)] +pub struct SendingMessage { + pub id: i64, + pub recipient_id: String, + pub content: Vec, + pub timestamp: DateTime, + pub status: String, +} + +impl SendingMessage { + pub async fn insert(pool: &SqlitePool, recipient_id: &str, content: &[u8]) -> Result { + let result = + sqlx::query("INSERT INTO sending_messages (recipient_id, content) VALUES (?, ?)") + .bind(recipient_id) + .bind(content) + .execute(pool) + .await?; + + Ok(result.last_insert_rowid()) + } + + pub async fn get_all(pool: &SqlitePool) -> Result> { + let messages = sqlx::query_as::<_, Self>( + "SELECT id, recipient_id, content, timestamp, status FROM sending_messages ORDER BY timestamp DESC", + ) + .fetch_all(pool) + .await?; + + Ok(messages) + } +} diff --git a/rust/src/bridge/error.rs b/rust/src/error.rs similarity index 57% rename from rust/src/bridge/error.rs rename to rust/src/error.rs index 7ad74628..53a6f4a5 100644 --- a/rust/src/bridge/error.rs +++ b/rust/src/error.rs @@ -1,5 +1,6 @@ use protocols::user_discovery::error::UserDiscoveryError; use thiserror::Error; +use zip::result::ZipError; pub type Result = core::result::Result; @@ -7,6 +8,8 @@ pub type Result = core::result::Result; pub enum TwonlyError { #[error("global twonly is not initialized")] Initialization, + #[error("Tried to access the wrong context")] + WrongContext, #[error("init_flutter_callbacks was not called")] MissingCallbackInitialization, #[error("Could not find the given database")] @@ -15,8 +18,28 @@ pub enum TwonlyError { UserDiscoveryError(#[from] UserDiscoveryError), #[error("Error in dart callback")] DartError, + #[error( + "Storage error: database exists but master key could not be loaded from secure storage" + )] + SecureStorageError, #[error("{0}")] SqliteError(#[from] sqlx::Error), + #[error("{0}")] + Generic(String), + #[error("{0}")] + IoError(#[from] std::io::Error), + + #[error("{0}")] + ZipError(#[from] ZipError), + + #[error("{0}")] + Walkdir(#[from] walkdir::Error), +} + +impl From for TwonlyError { + fn from(error: String) -> Self { + TwonlyError::Generic(error) + } } impl From for UserDiscoveryError { diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 5e0cb33a..48193e15 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -195,7 +195,7 @@ fn wire__crate__bridge__initialize_twonly_flutter_impl( }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); - let api_config = ::sse_decode(&mut deserializer); + let api_config = ::sse_decode(&mut deserializer); deserializer.end(); move |context| async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( @@ -743,6 +743,18 @@ impl SseDecode for i64 { } } +impl SseDecode for crate::bridge::InitConfig { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_databaseDir = ::sse_decode(deserializer); + let mut var_dataDir = ::sse_decode(deserializer); + return crate::bridge::InitConfig { + database_dir: var_databaseDir, + data_dir: var_dataDir, + }; + } +} + impl SseDecode for isize { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -863,18 +875,6 @@ impl SseDecode for crate::bridge::OtherPromotion { } } -impl SseDecode for crate::bridge::TwonlyConfig { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut var_databasePath = ::sse_decode(deserializer); - let mut var_dataDirectory = ::sse_decode(deserializer); - return crate::bridge::TwonlyConfig { - database_path: var_databasePath, - data_directory: var_dataDirectory, - }; - } -} - impl SseDecode for u32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -985,6 +985,22 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { + [ + self.database_dir.into_into_dart().into_dart(), + self.data_dir.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::bridge::InitConfig {} +impl flutter_rust_bridge::IntoIntoDart for crate::bridge::InitConfig { + fn into_into_dart(self) -> crate::bridge::InitConfig { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for FrbWrapper { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ @@ -1012,24 +1028,6 @@ impl flutter_rust_bridge::IntoIntoDart self.into() } } -// Codec=Dco (DartCObject based), see doc to use other codecs -impl flutter_rust_bridge::IntoDart for crate::bridge::TwonlyConfig { - fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { - [ - self.database_path.into_into_dart().into_dart(), - self.data_directory.into_into_dart().into_dart(), - ] - .into_dart() - } -} -impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::bridge::TwonlyConfig {} -impl flutter_rust_bridge::IntoIntoDart - for crate::bridge::TwonlyConfig -{ - fn into_into_dart(self) -> crate::bridge::TwonlyConfig { - self - } -} impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1087,6 +1085,14 @@ impl SseEncode for i64 { } } +impl SseEncode for crate::bridge::InitConfig { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.database_dir, serializer); + ::sse_encode(self.data_dir, serializer); + } +} + impl SseEncode for isize { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1189,14 +1195,6 @@ impl SseEncode for crate::bridge::OtherPromotion { } } -impl SseEncode for crate::bridge::TwonlyConfig { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - ::sse_encode(self.database_path, serializer); - ::sse_encode(self.data_directory, serializer); - } -} - impl SseEncode for u32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { diff --git a/rust/src/key_manager.rs b/rust/src/key_manager.rs deleted file mode 100644 index 6423303e..00000000 --- a/rust/src/key_manager.rs +++ /dev/null @@ -1,32 +0,0 @@ -struct TwonlyIdentity {} - -struct NostrIdentity {} - -struct KeyManager { - main_key: [u8; 32], -} - -impl KeyManager { - fn try_from_keychain() -> KeyManager { - todo!(); - } - - fn create_new() { - // generates main_key - - // generates signal identity - // generates nostr identity - } - - fn get_signal_identity() {} - - fn recover_from_trusted_friends() { - // - } - - fn generate_backup_key() {} - - fn recover_from_backup() { - // - } -} diff --git a/rust/src/keys/README.md b/rust/src/keys/README.md new file mode 100644 index 00000000..e801a428 --- /dev/null +++ b/rust/src/keys/README.md @@ -0,0 +1,73 @@ +# Cryptographic Architecture + +## 1. Main Key +A cryptographically secure, immutable master key. Loss of this key, in the absence of valid backups, results in permanent loss of account access. + +*Key Derivation*: Utilizes HKDF to derive subordinate keys. + +- Authentication Token: Uploaded to the server for session authentication. +- Backup Key: Used to encrypt a backup + - Backup Content: The encrypted backup encompasses + - Main Key + - Identity keys + - Local database (including contacts, public identities, memories (only references), and messages). + - Lifecycle: Backups are refreshed daily and deleted after one year. +- Media Main Key: Used to wrap media-specific keys. + - A new, cryptographically secure key is generated for every media file. + - The media key is wrapped using AES-GCM and the Main Media Key and stored in the online database along side to the uploaded media file database entry. + - The original media file is encrypted using AES-GCM and uploaded to the designated storage bucket. + +## 3. Identity Keys +- Signal Identity + - Generates a private and public key pair for secure communication. +- Nostr Identity + - Generates a private and public key pair for Nostr network interactions. + + +## 1. Backup Keys +Independent, securely generated keys used to wrap the primary backup key. + +### 1.1. Password-Based Backup +1. Derivation + - Utilizes scrypt with the username as the salt (cost 65536) to derive a 64-byte sequence. +2. Allocation: + - 32 bytes: Backup ID, used as the identifier to locate the backup on the server. + - 32 bytes: Backup wrapper key. +3. Content + - The payload contains the main key required to generate the auth token and the backup key. +4. Operation + - The backup wrapper key encrypts the main key. The ciphertext is uploaded anonymously to the server, indexed by the Backup ID. +5. Security Measures + - The server enforces strict rate limiting per IP address to prevent brute-force attacks. +6. Lifecycle + - These backup keys require a monthly refresh; otherwise, they are scheduled for deletion after two years. + +### 1.2. Trusted Friends Keys (Passwordless Recovery) +1. Initiation + - The recovering user generates a temporary ID (TempID) and a new ephemeral asymmetric key pair. +2. Request + - A recovery request containing the TempID and the public key is transmitted to a trusted contact via a secure link. +3. Verification + - The contact manually verifies the requestor's identity within their application to mitigate phishing risks. +4. Share Transmission + - The contact encrypts a trusted friend share using the provided public key. This share includes the user IDs, the minimum threshold required for decryption, and the cryptographic share (utilizing Shamir's Secret Sharing). +5. Reconstruction + - Upon receiving the required threshold of shares, the user reconstructs the shared secret data. +6. Second Factor (Optional) + - The shared secret data may mandate an additional factor (PIN or Email). For a PIN factor, an unlock token and a PIN seed are used to securely retrieve the remaining share from the server without exposing the raw PIN. +7. Final Recovery + - The decrypted recovery data provides the User ID, private key, and the backup master key necessary to restore the account and its backups. + +## 4. Web Portal Upload Protocol +1. Initialization + - The web portal generates a cryptographically secure symmetric key for end-to-end encrypted (E2EE) communication with the mobile application, alongside a newly registered session token. +2. Handshake + - The mobile application scans the QR code containing the session token and the symmetric key. +3. Authorization + - The application signals readiness via the server using the session token and securely provisions a temporary authentication token for media uploads over the established symmetric E2EE channel. +4. Key Exchange + - The web portal encrypts the media file using a newly generated media key. It transmits this media key to the application (encrypted via the E2EE symmetric key) and receives the wrapped media key in return. +5. Upload + - The web portal uploads the encrypted media file to the server, assigning it a device ID of 0. +6. Synchronization + - Finally, the application requests all memories with a device ID lower than its current one (the device ID increments after a backup restoration). diff --git a/rust/src/keys/backup_password.rs b/rust/src/keys/backup_password.rs new file mode 100644 index 00000000..e69de29b diff --git a/rust/src/keys/backup_passwordless/mod.rs b/rust/src/keys/backup_passwordless/mod.rs new file mode 100644 index 00000000..6f8b3dee --- /dev/null +++ b/rust/src/keys/backup_passwordless/mod.rs @@ -0,0 +1 @@ +mod types; diff --git a/rust/src/keys/backup_passwordless/types.rs b/rust/src/keys/backup_passwordless/types.rs new file mode 100644 index 00000000..77952828 --- /dev/null +++ b/rust/src/keys/backup_passwordless/types.rs @@ -0,0 +1,83 @@ +use serde::{Deserialize, Serialize}; + +/// Send from the person who tries to recover their account. +/// This can be done via a link, which will then be opened in the app of the contact. +/// The contact then has to manually select from which user he got the request. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RecoveryRequest { + pub temp_id: i64, + pub public_key: Vec, +} + +/// Used as envelope for TrustedFriendShare and RecoveryData +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EncryptedEnvelope { + pub encrypted_data: Vec, + pub iv: Vec, + pub mac: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct User { + pub user_id: i64, + pub display_name: String, + pub avatar: Vec, +} + +/// Send from the trusted friend. +/// This is encrypted with the received public key. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TrustedFriendShare { + /// This allows to display the user which user has send him his recovery data. + pub trusted_friend: User, + /// This allows to display the userdata, showing that he is recovering the correct person. + pub share_user: User, + /// The minimum threshold required to decrypt the shares. + pub threshold: i32, + /// The actual share which will become: SecretSharedData + pub share: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SecondFactorPin { + /// Required to try the PIN to get the share from the server. + /// This prevents that someone else can lock the pin, as the server only + /// allows 3 tries then after 1 day again 3 tries until the key is deleted. + pub unlock_token: Vec, + /// This never is send to the server but used to hash the pin before sending it to the server. + /// This prevents that the server every knows the short 4-digit PIN. + pub pin_seed: Vec, + /// The recovery data in case a second factor was used + /// The decryption key is loaded from the server either using the PIN or the MAIL + pub recovery_data_encrypted: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SecondFactorMail { + /// The users selected mail which will be send to the server + /// To this mail the encryption key for the recovery_data is send + pub mail: String, + /// Required to try the PIN to get the share from the server. + /// This prevents that someone else can lock the pin, as the server only + /// allows 3 tries then after 1 day again 3 tries until the key is deleted. + pub unlock_token: Vec, + /// The recovery data in case a second factor was used + /// The decryption key is loaded from the server either using the PIN or the MAIL + pub recovery_data_encrypted: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum SecretSharedData { + None(RecoveryData), + Mail(SecondFactorMail), + Pin(SecondFactorPin), +} + +/// The data which is recovered at the end. +/// The backup_master_key allows to recover the actual backup uploaded in the background to the server. +/// In case the backup is not available any more the user can use its user_id and his private_key to register as a new user. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RecoveryData { + pub user_id: i64, + pub master_key: Vec, +} diff --git a/rust/src/keys/identity_key.rs b/rust/src/keys/identity_key.rs new file mode 100644 index 00000000..3e6b24b6 --- /dev/null +++ b/rust/src/keys/identity_key.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub(crate) enum IdentityKey { + Nost(), + Signal(), +} diff --git a/rust/src/keys/main_key.rs b/rust/src/keys/main_key.rs new file mode 100644 index 00000000..ddf63e20 --- /dev/null +++ b/rust/src/keys/main_key.rs @@ -0,0 +1,256 @@ +use crate::error::Result; +use aes_gcm::aead::rand_core::RngCore; +use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng}; +use aes_gcm::{Aes256Gcm, Key, Nonce}; +use hkdf::Hkdf; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// `MainKey` is responsible for handling the cryptographically secure, immutable master key. +/// It uses HKDF to derive subordinate keys (Authentication Token, Backup Key, Media Main Key). +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub struct MainKey { + /// The 32-byte main master key + main_key: [u8; 32], +} + +#[derive(Debug)] +pub(crate) enum DatabaseKey { + RustDb, +} + +impl MainKey { + /// Generates a new cryptographically secure MainKey. + pub fn generate() -> Self { + let mut main_key = [0u8; 32]; + OsRng.fill_bytes(&mut main_key); + Self { main_key } + } + + /// Initializes a MainKey from an existing main key. + pub fn from_main_key(main_key: [u8; 32]) -> Self { + Self { main_key } + } + + /// Derives the database encryption key. + pub(crate) fn get_database_key(&self, db: DatabaseKey) -> String { + let db_name = match db { + DatabaseKey::RustDb => b"rust_db", + }; + let info = [b"database_key_", db_name as &[u8]].concat(); + let key = self.derive_key(&info); + hex::encode(key) + } + + /// Derives the authentication token uploaded to the server for session authentication. + pub fn get_authentication_token(&self) -> [u8; 32] { + self.derive_key(b"auth_token") + } + + /// Encrypts a backup payload. + /// The backup key is derived using HKDF from the main key. + pub fn encrypt_backup(&self, backup_payload: &[u8]) -> Vec { + self.encrypt_with_info(b"backup_key", backup_payload) + } + + /// Decrypts a backup payload. + pub fn decrypt_backup(&self, encrypted_backup: &[u8]) -> Result> { + self.decrypt_with_info(b"backup_key", encrypted_backup) + } + + /// Encrypts a newly generated media key using the derived Media Main Key. + pub fn encrypt_media_key(&self, media_key: &[u8; 32]) -> Vec { + self.encrypt_with_info(b"media_main_key", media_key) + } + + /// Decrypts a wrapped media key using the derived Media Main Key. + pub fn decrypt_media_key(&self, wrapped_media_key: &[u8]) -> Result<[u8; 32]> { + let decrypted = self.decrypt_with_info(b"media_main_key", wrapped_media_key)?; + + if decrypted.len() != 32 { + return Err("Invalid decrypted key length".to_string())?; + } + + let mut result = [0u8; 32]; + result.copy_from_slice(&decrypted); + Ok(result) + } + + fn derive_key(&self, info: &[u8]) -> [u8; 32] { + let hk = Hkdf::::new(None, &self.main_key); + let mut okm = [0u8; 32]; + hk.expand(info, &mut okm).expect("HKDF expand failed"); + okm + } + + fn encrypt_with_info(&self, info: &[u8], payload: &[u8]) -> Vec { + let derived_key = self.derive_key(info); + let key = Key::::from_slice(&derived_key); + let cipher = Aes256Gcm::new(key); + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + let ciphertext = cipher + .encrypt(&nonce, payload) + .expect("encryption failure!"); + + let mut result = nonce.to_vec(); + result.extend_from_slice(&ciphertext); + result + } + + fn decrypt_with_info(&self, info: &[u8], encrypted_data: &[u8]) -> Result> { + if encrypted_data.len() < 12 { + return Err("Invalid encrypted data length".to_string())?; + } + + let derived_key = self.derive_key(info); + let key = Key::::from_slice(&derived_key); + let cipher = Aes256Gcm::new(key); + let nonce = Nonce::from_slice(&encrypted_data[..12]); + let ciphertext = &encrypted_data[12..]; + + Ok(cipher + .decrypt(nonce, ciphertext) + .map_err(|_| "Decryption failure".to_string())?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_and_from_main_key() { + let km = MainKey::generate(); + let km2 = MainKey::from_main_key(km.main_key); + assert_eq!(km.main_key, km2.main_key); + } + + #[test] + fn test_get_authentication_token() { + let km1 = MainKey::generate(); + let token1 = km1.get_authentication_token(); + + let km2 = MainKey::from_main_key(km1.main_key); + let token2 = km2.get_authentication_token(); + + // Tokens derived from the same main key should match + assert_eq!(token1, token2); + + let km3 = MainKey::generate(); + let token3 = km3.get_authentication_token(); + + // Different main keys should produce different tokens + assert_ne!(token1, token3); + } + + #[test] + fn test_backup_encryption_decryption_success() { + let km = MainKey::generate(); + let payload = b"this is a secret backup payload"; + + let encrypted = km.encrypt_backup(payload); + let decrypted = km.decrypt_backup(&encrypted).unwrap(); + + assert_eq!(payload.as_slice(), decrypted.as_slice()); + } + + #[test] + fn test_backup_decryption_tampered_payload_fails() { + let km = MainKey::generate(); + let payload = b"this is a secret backup payload"; + let mut encrypted = km.encrypt_backup(payload); + + // Tamper with the ciphertext (assuming length > 12) + let last_idx = encrypted.len() - 1; + encrypted[last_idx] ^= 1; // Flip a bit + + let result = km.decrypt_backup(&encrypted); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "Decryption failure"); + } + + #[test] + fn test_backup_decryption_too_short_fails() { + let km = MainKey::generate(); + let short_payload = vec![0u8; 10]; // Less than 12 bytes nonce + + let result = km.decrypt_backup(&short_payload); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid encrypted data length" + ); + } + + #[test] + fn test_media_key_encryption_decryption_success() { + let km = MainKey::generate(); + let mut media_key = [0u8; 32]; + OsRng.fill_bytes(&mut media_key); + + let encrypted = km.encrypt_media_key(&media_key); + let decrypted = km.decrypt_media_key(&encrypted).unwrap(); + + assert_eq!(media_key, decrypted); + } + + #[test] + fn test_media_key_decryption_tampered_payload_fails() { + let km = MainKey::generate(); + let mut media_key = [0u8; 32]; + OsRng.fill_bytes(&mut media_key); + + let mut encrypted = km.encrypt_media_key(&media_key); + + // Tamper with the ciphertext + let last_idx = encrypted.len() - 1; + encrypted[last_idx] ^= 1; + + let result = km.decrypt_media_key(&encrypted); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "Decryption failure"); + } + + #[test] + fn test_media_key_decryption_too_short_fails() { + let km = MainKey::generate(); + let short_payload = vec![0u8; 10]; // Less than 12 bytes nonce + + let result = km.decrypt_media_key(&short_payload); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid encrypted data length" + ); + } + + #[test] + fn test_media_key_decryption_wrong_decrypted_length_fails() { + let km = MainKey::generate(); + + // Manually encrypt a 31 byte payload + let hk = Hkdf::::new(None, &km.main_key); + let mut media_main_key = [0u8; 32]; + hk.expand(b"media_main_key", &mut media_main_key) + .expect("HKDF expand failed"); + + let key = Key::::from_slice(&media_main_key); + let cipher = Aes256Gcm::new(key); + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + let payload = vec![0u8; 31]; + let ciphertext = cipher + .encrypt(&nonce, payload.as_ref()) + .expect("encryption failure"); + + let mut encrypted = nonce.to_vec(); + encrypted.extend_from_slice(&ciphertext); + + let result = km.decrypt_media_key(&encrypted); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid decrypted key length" + ); + } +} diff --git a/rust/src/keys/mod.rs b/rust/src/keys/mod.rs new file mode 100644 index 00000000..88d36044 --- /dev/null +++ b/rust/src/keys/mod.rs @@ -0,0 +1,52 @@ +mod backup_password; +mod backup_passwordless; +mod identity_key; +mod main_key; + +pub(crate) use crate::keys::main_key::{DatabaseKey, MainKey}; +use crate::secure_storage::SecureStorage; +use crate::{error::Result, keys::identity_key::IdentityKey}; +use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +const KEY_MANAGER_ID: &str = "twonly_key_manager"; + +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub(crate) struct KeyManager { + pub(crate) main_key: MainKey, + pub(crate) identity_keys: Vec, +} + +impl KeyManager { + pub fn generate() -> Result { + Ok(KeyManager { + main_key: MainKey::generate(), + identity_keys: vec![], + }) + } + + /// Tries to load the KeyManager from the secure keychain/local storage. + pub fn try_from_keychain(storage: &SecureStorage) -> Result { + let hex_key = storage + .read(KEY_MANAGER_ID)? + .ok_or_else(|| "Main key not found in keychain".to_string())?; + + let bytes = hex::decode(hex_key).map_err(|e| format!("Failed to decode hex key: {}", e))?; + + let main_key: KeyManager = postcard::from_bytes(&bytes) + .map_err(|e| format!("Failed to deserialize KeyManager: {}", e))?; + + Ok(main_key) + } + + /// Stores the main key into the secure keychain/local storage. + pub fn store_to_keychain(&self, storage: &SecureStorage) -> Result<()> { + let serialized = postcard::to_allocvec(self) + .map_err(|e| format!("Failed to serialize KeyManager: {}", e))?; + + let hex_key = hex::encode(serialized); + storage.write(KEY_MANAGER_ID, &hex_key)?; + + Ok(()) + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 39c49fb0..255e19a3 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,5 +1,11 @@ +mod backup; pub mod bridge; +mod context; mod database; +mod error; mod frb_generated; +mod keys; +mod log; +mod secure_storage; mod standalone; mod utils; diff --git a/rust/src/bridge/log.rs b/rust/src/log.rs similarity index 97% rename from rust/src/bridge/log.rs rename to rust/src/log.rs index fb41ffd6..884aa04f 100644 --- a/rust/src/bridge/log.rs +++ b/rust/src/log.rs @@ -68,7 +68,7 @@ fn build_writers(logs_dir: &std::path::Path) -> (NonBlocking, NonBlocking) { } Err(e) => { eprintln!("Failed to create file appender: {}", e); - let (nb, guard) = tracing_appender::non_blocking(std::io::sink()); + let (nb, _guard) = tracing_appender::non_blocking(std::io::sink()); (nb, None) } }; diff --git a/rust/src/secure_storage.rs b/rust/src/secure_storage.rs new file mode 100644 index 00000000..0d8ad4d4 --- /dev/null +++ b/rust/src/secure_storage.rs @@ -0,0 +1,132 @@ +use keyring_core::{Entry, Error as KeyringError}; + +/// A simple wrapper around `keyring-core` for secure storage on iOS, Android, and other platforms. +/// +/// IMPORTANT: This struct assumes that a `keyring-core` default store has been initialized +/// (e.g., via `keyring_core::set_default_store`). In the White Noise project, this is handled +/// during application startup in `Whitenoise::initialize_keyring_store`. +pub struct SecureStorage { + service_name: String, +} + +impl SecureStorage { + /// Creates a new `SecureStorage` instance with the specified service name. + /// The service name is used as a namespace in the system keyring. + pub fn new(service_name: &str) -> Self { + Self { + service_name: service_name.to_string(), + } + } + + /// Initializes the platform-native secure storage backend for iOS and Android. + /// + /// # Arguments + /// * `group_id` - (iOS only) Optional App Group ID to allow cross-process keychain access. + /// + /// This function registers the appropriate credential store (Protected Store for iOS, + /// Keystore for Android) with `keyring-core`. It is safe to call multiple times. + pub fn init() -> Result<(), String> { + if keyring_core::get_default_store().is_some() { + return Ok(()); + } + + #[cfg(target_os = "ios")] + { + let group = "CN332ZUGRP.eu.twonly.shared"; + let store = apple_native_keyring_store::protected::Store::with_application_group(group) + .map_err(|e| format!("Failed to init iOS Protected Store: {}", e))?; + keyring_core::set_default_store(store); + } + + #[cfg(target_os = "android")] + { + let store = android_native_keyring_store::Store::new() + .map_err(|e| format!("Failed to init Android Store: {}", e))?; + keyring_core::set_default_store(store); + } + + #[cfg(not(any(target_os = "ios", target_os = "android")))] + { + let store = keyring_core::mock::Store::new() + .map_err(|e| format!("Failed to init Mock Store: {}", e))?; + keyring_core::set_default_store(store); + tracing::warn!("Using mock store as default keyring store!"); + } + + Ok(()) + } + + /// Writes a secret value to the secure keyring associated with the given key. + /// + /// # Arguments + /// * `key` - The identifier (account name) for the secret. + /// * `value` - The secret string to store. + pub fn write(&self, key: &str, value: &str) -> Result<(), String> { + let entry = Entry::new(&self.service_name, key) + .map_err(|e| format!("Failed to create keyring entry: {}", e))?; + + entry + .set_password(value) + .map_err(|e| format!("Failed to write secret to keyring: {}", e))?; + + Ok(()) + } + + /// Reads a secret value from the secure keyring associated with the given key. + /// + /// Returns `Ok(Some(String))` if the key exists, `Ok(None)` if it doesn't, + /// or an `Err` if a system error occurs. + pub fn read(&self, key: &str) -> Result, String> { + let entry = Entry::new(&self.service_name, key) + .map_err(|e| format!("Failed to create keyring entry: {}", e))?; + + match entry.get_password() { + Ok(password) => Ok(Some(password)), + Err(KeyringError::NoEntry) => Ok(None), + Err(e) => Err(format!("Failed to read secret from keyring: {}", e)), + } + } + + /// Deletes the secret associated with the given key from the secure keyring. + /// + /// If the key does not exist, this function returns `Ok(())` (idempotent). + pub fn delete(&self, key: &str) -> Result<(), String> { + let entry = Entry::new(&self.service_name, key) + .map_err(|e| format!("Failed to create keyring entry: {}", e))?; + + match entry.delete_credential() { + Ok(()) => Ok(()), + Err(KeyringError::NoEntry) => Ok(()), + Err(e) => Err(format!("Failed to delete secret from keyring: {}", e)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_secure_storage_flow() { + // Initialize the store (will use MockStore on non-mobile platforms) + SecureStorage::init().unwrap(); + + let storage = SecureStorage::new("eu.twonly.test"); + let key = "test_secret_key"; + let secret = "my_awesome_secret_123"; + + // 1. Write the secret + storage.write(key, secret).expect("Failed to write secret"); + + // 2. Read the secret and verify it matches + let read_val = storage.read(key).expect("Failed to read secret"); + assert_eq!(read_val, Some(secret.to_string())); + + // 3. Delete the secret + storage.delete(key).expect("Failed to delete secret"); + + // 4. Verify the secret is gone + let after_delete = storage.read(key).expect("Failed to read after delete"); + assert_eq!(after_delete, None); + } +} diff --git a/rust/src/standalone.rs b/rust/src/standalone.rs new file mode 100644 index 00000000..b351d324 --- /dev/null +++ b/rust/src/standalone.rs @@ -0,0 +1,11 @@ +use crate::bridge::InitConfig; +use crate::database::Database; +use crate::secure_storage::SecureStorage; +use std::sync::Arc; + +pub(crate) struct TwonlyStandalone { + #[allow(dead_code)] + pub(crate) config: InitConfig, + pub(crate) rust_db: Arc, + pub(crate) secure_storage: SecureStorage, +} diff --git a/rust/src/standalone/mod.rs b/rust/src/standalone/mod.rs deleted file mode 100644 index c5ece6b4..00000000 --- a/rust/src/standalone/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -// use crate::{bridge::TwonlyConfig, database::Database}; -// use std::sync::Arc; - -// pub(crate) struct TwonlyStandalone { -// #[allow(dead_code)] -// pub(crate) config: TwonlyConfig, -// /// Because Rust is called from a different process it is safe to write to the twonly_db. -// pub(crate) twonly_db: Arc, -// }