Compare commits

..

No commits in common. "abd8e1b885105d42d94dea868b01cc29078a864a" and "e0cc19284107669385fa8cf5072fb991439839c6" have entirely different histories.

29 changed files with 463 additions and 1389 deletions

327
Cargo.lock generated
View file

@ -68,23 +68,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
[[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 = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64ct"
version = "1.8.0"
@ -139,21 +122,11 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cc"
version = "1.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "chacha20"
@ -219,9 +192,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.49"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
dependencies = [
"clap_builder",
"clap_derive",
@ -229,9 +202,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.49"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
dependencies = [
"anstream",
"anstyle",
@ -241,9 +214,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.49"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck",
"proc-macro2",
@ -253,9 +226,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.6"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
@ -375,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@ -390,17 +363,11 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "generic-array"
version = "0.14.9"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
@ -413,50 +380,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
"wasi 0.11.1+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.4"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "gloo-storage"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
dependencies = [
"gloo-utils",
"js-sys",
"serde",
"serde_json",
"thiserror",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "gloo-utils"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
dependencies = [
"js-sys",
"serde",
"serde_json",
"wasm-bindgen",
"web-sys",
"wasi 0.14.7+wasi-0.2.4",
]
[[package]]
@ -527,12 +464,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.81"
@ -545,9 +476,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.177"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "linux-raw-sys"
@ -561,22 +492,6 @@ version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minicov"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
dependencies = [
"cc",
"walkdir",
]
[[package]]
name = "once_cell"
version = "1.21.3"
@ -719,7 +634,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@ -728,21 +643,6 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[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 = "semver"
version = "1.0.27"
@ -789,19 +689,6 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
name = "sha2"
version = "0.10.9"
@ -815,19 +702,14 @@ dependencies = [
[[package]]
name = "sharenet-passport"
version = "0.3.0"
version = "0.2.0"
dependencies = [
"async-trait",
"base64",
"bip39",
"chacha20poly1305",
"ciborium",
"ed25519-dalek",
"getrandom 0.2.16",
"gloo-storage",
"hex",
"hkdf",
"js-sys",
"rand",
"rand_core",
"serde",
@ -836,9 +718,6 @@ dependencies = [
"tempfile",
"thiserror",
"uuid",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"web-time",
"zeroize",
]
@ -855,12 +734,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signature"
version = "2.2.0"
@ -910,10 +783,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@ -994,7 +867,7 @@ version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
"getrandom 0.3.4",
"getrandom 0.3.3",
"js-sys",
"wasm-bindgen",
]
@ -1005,22 +878,21 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
@ -1057,19 +929,6 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.104"
@ -1102,64 +961,11 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865"
dependencies = [
"js-sys",
"minicov",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "web-sys"
version = "0.3.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "windows-link"
version = "0.2.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-sys"
@ -1185,16 +991,7 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
"windows-targets 0.53.4",
]
[[package]]
@ -1215,19 +1012,19 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.53.5"
version = "0.53.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
@ -1238,9 +1035,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
@ -1250,9 +1047,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
@ -1262,9 +1059,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
@ -1274,9 +1071,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
@ -1286,9 +1083,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
@ -1298,9 +1095,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
@ -1310,9 +1107,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
@ -1322,9 +1119,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen"

View file

@ -1,15 +0,0 @@
[build]
# Default to native target
rustflags = ["-C", "target-cpu=native"]
[target.wasm32-unknown-unknown]
# Use wasm-bindgen-test for WASM testing
runner = "wasm-bindgen-test-runner"
[target.wasm32-wasi]
# Use wasmtime for WASI testing
runner = "wasmtime"
[env]
# Environment variables for development
RUST_BACKTRACE = "1"

View file

@ -1,149 +0,0 @@
# Sharenet Passport Architecture
## Overview
The Sharenet Passport library provides a unified API for cryptographic operations and file storage that works seamlessly across both native and WASM targets. The architecture uses Rust's conditional compilation to automatically select the appropriate implementation based on the target platform.
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Public API Surface │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Bip39MnemonicGenerator, Ed25519KeyDeriver, │ │
│ │ XChaCha20FileEncryptor, FileSystemStorage │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ Crypto │ │ Storage │ │ RNG │ │
│ │ │ │ │ │ Time │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Target-Specific Implementations │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Native (std) │ WASM │ │
│ │ • OsRng │ • getrandom(js) │ │
│ │ • File system │ • LocalStorage │ │
│ │ • SystemTime │ • web-time │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Module Structure
### Core Modules
- **`domain/`**: Domain entities, traits, and error types
- **`application/`**: Use cases and application logic
- **`infrastructure/`**: Platform-specific implementations
### Infrastructure Layer
- **`infrastructure/crypto/`**: Unified cryptographic API
- **`shared/`**: Shared utilities and constants
- **`native/`**: Native implementations using std
- **`wasm/`**: WASM implementations using browser APIs
- **`infrastructure/storage/`**: Unified storage API
- **`native/`**: File system storage
- **`wasm/`**: Browser LocalStorage
- **`infrastructure/rng/`**: Random number generation abstraction
- **`infrastructure/time/`**: Time abstraction
## Target Selection
The library automatically selects implementations based on the target architecture:
- **Native targets** (`x86_64`, `aarch64`, etc.): Use native implementations
- **WASM targets** (`wasm32-unknown-unknown`): Use WASM implementations
### Conditional Compilation
```rust
// In infrastructure/crypto/mod.rs
#[cfg(any(not(target_arch = "wasm32"), feature = "force-native"))]
mod native;
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
mod wasm;
```
## Optional Override Features
For testing and special cases, optional features allow manual override:
- **`force-wasm`**: Use WASM implementation even on native targets
- **`force-native`**: Use native implementation even on WASM targets
These features are mutually exclusive and will trigger a compile error if both are enabled.
## Testing Strategy
### Target-Specific Tests
- **Native tests**: Located in `src/infrastructure/crypto/native_test.rs`
- **WASM tests**: Located in `src/infrastructure/crypto/wasm_test.rs`
### Test Runners
- **Native**: Standard `cargo test`
- **WASM**: `wasm-bindgen-test-runner` configured in `.cargo/config.toml`
## Dependencies
### Target-Specific Dependencies
- **WASM-only**: `gloo-storage`, `web-time`, `getrandom` with JS feature
- **Native-only**: `getrandom` with std feature
- **Shared**: `bip39`, `ed25519-dalek`, `chacha20poly1305`
## Public API Consistency
The public API remains identical regardless of target:
```rust
// Same API on both targets
use sharenet_passport::{
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
};
```
## Development Workflow
### Testing Both Targets
```bash
# Test native
cargo test
# Test WASM
cargo test --target wasm32-unknown-unknown
# Test with override features
cargo test --features force-wasm
cargo test --features force-native
```
### Building for Different Targets
```bash
# Build native
cargo build
# Build WASM
cargo build --target wasm32-unknown-unknown
```
## Adding New Target-Specific Code
1. Add shared logic to the appropriate `shared/` module
2. Implement native version in `native/` module
3. Implement WASM version in `wasm/` module
4. Update the aggregator module to export the unified API
5. Add target-specific tests
## Cryptographic Consistency
All implementations produce identical cryptographic outputs for the same inputs, ensuring cross-platform compatibility.

View file

@ -1,6 +1,6 @@
[package]
name = "sharenet-passport"
version = "0.3.0"
version = "0.2.0"
publish = ["sharenet-sh-forgejo"] # Set this to whichever Cargo registry you are publishing to
edition = "2021"
description = "Core library for Sharenet Passport creation and management"
@ -25,40 +25,16 @@ thiserror = "1.0"
zeroize = { version = "1.7", features = ["zeroize_derive"] }
hex = "0.4"
ciborium = "0.2"
# Core async support
async-trait = "0.1"
# Dependencies needed for WASM implementation (available for all targets)
base64 = "0.21"
# WASM-specific dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
uuid = { version = "1.10", features = ["v7", "js"] }
web-time = "1.1"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
gloo-storage = "0.3"
# Native dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
getrandom = { version = "0.2", features = ["std"] }
uuid = { version = "1.10", features = ["v7", "rng"] }
# Dev dependencies for WASM testing
[dev-dependencies]
tempfile = "3.8"
wasm-bindgen-test = "0.3"
uuid = { version = "1.8", features = ["v7"] }
[lib]
crate-type = ["cdylib", "rlib"] # Support both native and WASM
[features]
default = []
std = [] # Standard library support (for native targets)
default = ["std"]
std = [] # Standard library support
alloc = [] # No-std with alloc support
wasm = ["alloc"] # WASM target support
# Optional override features for manual platform selection
force-wasm = [] # Force WASM implementation even on native targets
force-native = [] # Force native implementation even on WASM targets
[dev-dependencies]
tempfile = "3.8"

View file

@ -18,12 +18,15 @@ A secure Rust library for creating and managing Sharenet Passport files (.spf) f
```toml
[dependencies]
sharenet-passport = { version = "0.2.0", registry = "sharenet-sh-forgejo" }
sharenet-passport = { version = "0.1.0", registry = "sharenet-sh-forgejo", features = ["std"] }
```
Platform selection is automatic based on your compilation target:
- **Native targets** (x86_64, aarch64, etc.): Use native implementations
- **WASM targets** (wasm32-unknown-unknown): Use WASM implementations
### For WASM Projects
```toml
[dependencies]
sharenet-passport = { version = "0.1.0", registry = "sharenet-sh-forgejo", features = ["wasm"] }
```
## Usage
@ -97,29 +100,11 @@ Built with Clean Architecture principles:
- **Application Layer**: Use cases (CreatePassport, ImportFromRecovery, SignCard, etc.)
- **Infrastructure Layer**: Crypto implementations, file storage
## Targets
## Feature Flags
The library automatically selects the appropriate implementation based on your compilation target:
### Native Targets
- **Platforms**: Linux, macOS, Windows, etc.
- **Storage**: File system
- **RNG**: System entropy (OsRng)
- **Time**: System time
### WASM Targets
- **Platforms**: Web browsers, Node.js
- **Storage**: Browser LocalStorage
- **RNG**: Web Crypto API via getrandom
- **Time**: JavaScript Date API
### Optional Override Features
For testing and special cases:
- `force-wasm`: Use WASM implementation even on native targets
- `force-native`: Use native implementation even on WASM targets
These features are mutually exclusive and will trigger a compile error if both are enabled.
- `std` (default): Standard library support for CLI and server applications
- `wasm`: WebAssembly support for web applications
- `alloc`: No-std with allocator support
## Security Features
@ -143,25 +128,22 @@ Sharenet Passport Files (.spf) are encrypted containers that store:
### Running Tests
```bash
# Test native implementation
# Run all tests
cargo test
# Test WASM implementation
cargo test --target wasm32-unknown-unknown
# Test with override features
cargo test --features force-wasm
cargo test --features force-native
# Test specific features
cargo test --features std
cargo test --features wasm
```
### Building for Different Targets
### Building for WASM
```bash
# Build for native
cargo build
# Install wasm-pack if needed
cargo install wasm-pack
# Build for WASM
cargo build --target wasm32-unknown-unknown
# Build for web
wasm-pack build --target web --features wasm
```
## License

View file

@ -1,2 +1,5 @@
pub mod use_cases;
pub mod error;
pub mod error;
#[cfg(test)]
mod use_cases_test;

View file

@ -2,7 +2,7 @@ use crate::domain::entities::*;
use crate::domain::traits::*;
use crate::application::error::ApplicationError;
use ed25519_dalek::Signer;
use crate::infrastructure::time;
use std::time::{SystemTime, UNIX_EPOCH};
pub struct CreatePassportUseCase<MG, KD, FE, FS>
where
@ -425,8 +425,10 @@ where
let existing_profile = passport.user_profile_by_id(id)
.ok_or_else(|| ApplicationError::UseCaseError("User profile not found".to_string()))?;
let now = time::now_seconds()
.map_err(|e| ApplicationError::UseCaseError(format!("Time error: {}", e)))?;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| ApplicationError::UseCaseError(format!("Time error: {}", e)))?
.as_secs();
// Use existing hub_did (cannot change hub_did via update)
let profile = UserProfile {

View file

@ -1,9 +1,8 @@
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use crate::application::use_cases::{CreatePassportUseCase, ImportFromRecoveryUseCase, ImportFromFileUseCase, ExportPassportUseCase, SignCardUseCase, CreateUserProfileUseCase, UpdateUserProfileUseCase, DeleteUserProfileUseCase};
// Note: These domain entities are used indirectly through the use cases
use crate::infrastructure::native::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
use crate::infrastructure::storage::FileSystemStorage;
use tempfile::NamedTempFile;

View file

@ -1,10 +1,8 @@
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::infrastructure::time;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoveryPhrase {
words: Vec<String>,
@ -268,7 +266,10 @@ impl UserProfile {
identity: UserIdentity,
preferences: UserPreferences,
) -> Self {
let now = time::now_seconds().unwrap_or_default();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Self {
id: Uuid::now_v7().to_string(),

View file

@ -1,5 +1,3 @@
//! Native (std) cryptographic implementations
use bip39::Mnemonic;
use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH};
use chacha20poly1305::{aead::{Aead, KeyInit}, XChaCha20Poly1305, Key, XNonce};
@ -9,9 +7,8 @@ use rand::{RngCore, rngs::OsRng};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::domain::entities::*;
use crate::domain::error::DomainError;
use crate::domain::traits::*;
use super::shared::*;
use crate::domain::error::DomainError;
#[derive(Clone)]
pub struct Bip39MnemonicGenerator;
@ -45,8 +42,6 @@ impl KeyDeriver for Ed25519KeyDeriver {
type Error = DomainError;
fn derive_from_seed(&self, seed: &Seed) -> Result<(PublicKey, PrivateKey), Self::Error> {
validate_seed_length(seed.as_bytes())?;
let signing_key = SigningKey::from_bytes(&seed.as_bytes()[..SECRET_KEY_LENGTH].try_into()
.map_err(|_| DomainError::CryptographicError("Invalid seed length".to_string()))?);
@ -65,14 +60,7 @@ impl KeyDeriver for Ed25519KeyDeriver {
// Use univ_id as passphrase to bind seed to universe
let bip39_seed = bip39_mnemonic.to_seed(univ_id);
// BIP39 produces 64-byte seed, but we only need 32 bytes for Ed25519
// Use the first 32 bytes of the BIP39 seed
let ed25519_seed: [u8; 32] = bip39_seed[..32]
.try_into()
.map_err(|_| DomainError::CryptographicError("Failed to extract 32-byte seed from BIP39 seed".to_string()))?;
Ok(Seed::new(ed25519_seed.to_vec()))
Ok(Seed::new(bip39_seed.to_vec()))
}
}
@ -92,22 +80,22 @@ impl FileEncryptor for XChaCha20FileEncryptor {
user_profiles: &[UserProfile],
) -> Result<PassportFile, Self::Error> {
// Generate salt and nonce
let mut salt = [0u8; SALT_LENGTH];
let mut nonce_bytes = [0u8; NONCE_LENGTH];
let mut salt = [0u8; 32];
let mut nonce_bytes = [0u8; 24];
OsRng.fill_bytes(&mut salt);
OsRng.fill_bytes(&mut nonce_bytes);
// Derive KEK from password using HKDF
let hk = Hkdf::<Sha256>::new(Some(&salt), password.as_bytes());
let mut kek = [0u8; 32];
hk.expand(KDF_INFO, &mut kek)
hk.expand(b"sharenet-passport-kek", &mut kek)
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
// Encrypt seed
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
let cipher = XChaCha20Poly1305::new(Key::from_slice(&kek));
let nonce = XNonce::from_slice(&nonce_bytes);
let enc_seed = cipher
.encrypt(&nonce, seed.as_bytes())
.encrypt(nonce, seed.as_bytes())
.map_err(|e| DomainError::CryptographicError(format!("Encryption failed: {}", e)))?;
// Serialize and encrypt user profiles
@ -115,7 +103,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
let user_profiles_bytes = serde_cbor::to_vec(&user_profiles_vec)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize user profiles: {}", e)))?;
let enc_user_profiles = cipher
.encrypt(&nonce, &*user_profiles_bytes)
.encrypt(nonce, &*user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
// Get current timestamp
@ -126,8 +114,8 @@ impl FileEncryptor for XChaCha20FileEncryptor {
Ok(PassportFile {
enc_seed,
kdf: KDF_HKDF_SHA256.to_string(),
cipher: CIPHER_XCHACHA20_POLY1305.to_string(),
kdf: "HKDF-SHA256".to_string(),
cipher: "XChaCha20-Poly1305".to_string(),
salt: salt.to_vec(),
nonce: nonce_bytes.to_vec(),
public_key: public_key.0.clone(),
@ -145,19 +133,23 @@ impl FileEncryptor for XChaCha20FileEncryptor {
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
// Validate file format
validate_file_format(&file.kdf, &file.cipher)?;
if file.kdf != "HKDF-SHA256" || file.cipher != "XChaCha20-Poly1305" {
return Err(DomainError::InvalidFileFormat(
"Unsupported KDF or cipher".to_string(),
));
}
// Derive KEK from password
let hk = Hkdf::<Sha256>::new(Some(&file.salt), password.as_bytes());
let mut kek = [0u8; 32];
hk.expand(KDF_INFO, &mut kek)
hk.expand(b"sharenet-passport-kek", &mut kek)
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
// Decrypt seed
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
let cipher = XChaCha20Poly1305::new(Key::from_slice(&kek));
let nonce = XNonce::from_slice(&file.nonce);
let seed_bytes = cipher
.decrypt(&nonce, &*file.enc_seed)
.decrypt(nonce, &*file.enc_seed)
.map_err(|e| DomainError::CryptographicError(format!("Decryption failed: {}", e)))?;
let seed = Seed::new(seed_bytes);
@ -175,7 +167,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
// Decrypt user profiles
let user_profiles_bytes = cipher
.decrypt(&nonce, &*file.enc_user_profiles)
.decrypt(nonce, &*file.enc_user_profiles)
.map_err(|e| DomainError::CryptographicError(format!("User profiles decryption failed: {}", e)))?;
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;

View file

@ -1,32 +0,0 @@
//! Unified cryptographic API with target-specific implementations
// Check for mutually exclusive override features
#[cfg(all(feature = "force-wasm", feature = "force-native"))]
compile_error!("Features 'force-wasm' and 'force-native' are mutually exclusive");
// Shared helper functions and types
mod shared;
// Platform-specific implementations
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
mod native;
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
mod wasm;
// Re-export the unified API
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
pub use native::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
pub use wasm::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
// Target-specific tests
#[cfg(all(test, all(not(target_arch = "wasm32"), not(feature = "force-wasm"))))]
mod native_test;
#[cfg(all(test, any(target_arch = "wasm32", feature = "force-wasm")))]
mod wasm_test;
// Re-export shared types if any
pub use shared::*;

View file

@ -1,83 +0,0 @@
//! Native-specific cryptographic tests
#[cfg(test)]
mod tests {
use crate::domain::entities::*;
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
#[test]
fn test_native_bip39_generator_creates_valid_mnemonic() {
let generator = Bip39MnemonicGenerator;
let phrase = generator.generate().unwrap();
// Should have 24 words
assert_eq!(phrase.words().len(), 24);
// Should be valid BIP-39
generator.validate(phrase.words()).unwrap();
}
#[test]
fn test_native_key_deriver_creates_consistent_keys() {
let deriver = Ed25519KeyDeriver;
// Create a test seed
let test_seed = Seed::new(vec![1; 32]);
let (public_key, private_key) = deriver.derive_from_seed(&test_seed).unwrap();
// Public key should be 32 bytes
assert_eq!(public_key.0.len(), 32);
// Private key should be 32 bytes
assert_eq!(private_key.0.len(), 32);
}
#[test]
fn test_native_file_encryptor_round_trip() {
let encryptor = XChaCha20FileEncryptor;
let key_deriver = Ed25519KeyDeriver;
let seed = Seed::new(vec![1; 32]);
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
let did = Did::new(&public_key);
let password = "test-password";
// Encrypt
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
// Verify file structure
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
assert_eq!(encrypted_file.salt.len(), 32);
assert_eq!(encrypted_file.nonce.len(), 24);
assert_eq!(encrypted_file.public_key, public_key.0);
assert_eq!(encrypted_file.did, did.0);
// Decrypt
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
// Verify decryption
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
assert_eq!(decrypted_public_key.0, public_key.0);
}
#[test]
fn test_native_file_encryptor_wrong_password_fails() {
let encryptor = XChaCha20FileEncryptor;
let seed = Seed::new(vec![1, 2, 3, 4, 5]);
let public_key = PublicKey(vec![1; 32]);
let did = Did::new(&public_key);
// Encrypt with one password
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
// Try to decrypt with wrong password
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
// Should fail
assert!(result.is_err());
}
}

View file

@ -1,35 +0,0 @@
//! Shared cryptographic utilities and constants
/// Cryptographic constants used across implementations
pub const SEED_LENGTH: usize = 32;
pub const PUBLIC_KEY_LENGTH: usize = 32;
pub const PRIVATE_KEY_LENGTH: usize = 32;
pub const SALT_LENGTH: usize = 32;
pub const NONCE_LENGTH: usize = 24;
/// KDF and cipher identifiers
pub const KDF_HKDF_SHA256: &str = "HKDF-SHA256";
pub const CIPHER_XCHACHA20_POLY1305: &str = "XChaCha20-Poly1305";
/// HKDF info strings
pub const KDF_INFO: &[u8] = b"sharenet-passport-kek";
/// Helper function to validate seed length
pub fn validate_seed_length(seed_bytes: &[u8]) -> Result<(), crate::domain::error::DomainError> {
if seed_bytes.len() != SEED_LENGTH {
return Err(crate::domain::error::DomainError::CryptographicError(
format!("Invalid seed length: expected {}, got {}", SEED_LENGTH, seed_bytes.len())
));
}
Ok(())
}
/// Helper function to validate file format
pub fn validate_file_format(kdf: &str, cipher: &str) -> Result<(), crate::domain::error::DomainError> {
if kdf != KDF_HKDF_SHA256 || cipher != CIPHER_XCHACHA20_POLY1305 {
return Err(crate::domain::error::DomainError::InvalidFileFormat(
"Unsupported KDF or cipher".to_string()
));
}
Ok(())
}

View file

@ -1,185 +0,0 @@
//! WASM-compatible cryptographic implementations
use bip39::Mnemonic;
use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH};
use chacha20poly1305::{aead::{Aead, KeyInit}, XChaCha20Poly1305, Key, XNonce};
use hkdf::Hkdf;
use sha2::Sha256;
use crate::domain::entities::*;
use crate::domain::error::DomainError;
use crate::domain::traits::*;
use crate::infrastructure::rng;
use crate::infrastructure::time;
use super::shared::*;
#[derive(Clone)]
pub struct Bip39MnemonicGenerator;
impl MnemonicGenerator for Bip39MnemonicGenerator {
type Error = DomainError;
fn generate(&self) -> Result<RecoveryPhrase, Self::Error> {
let mut entropy = [0u8; 32];
let mut rng = rng::new_rng();
rng.fill_bytes(&mut entropy)?;
let mnemonic = Mnemonic::from_entropy(&entropy)
.map_err(|e| DomainError::CryptographicError(format!("Failed to generate mnemonic: {}", e)))?;
let words: Vec<String> = mnemonic.words().into_iter().map(|s| s.to_string()).collect();
Ok(RecoveryPhrase::new(words))
}
fn validate(&self, words: &[String]) -> Result<(), Self::Error> {
let phrase = words.join(" ");
Mnemonic::parse(&phrase)
.map_err(|e| DomainError::InvalidMnemonic(format!("Invalid mnemonic: {}", e)))?;
Ok(())
}
}
#[derive(Clone)]
pub struct Ed25519KeyDeriver;
impl KeyDeriver for Ed25519KeyDeriver {
type Error = DomainError;
fn derive_from_seed(&self, seed: &Seed) -> Result<(PublicKey, PrivateKey), Self::Error> {
validate_seed_length(seed.as_bytes())?;
let signing_key = SigningKey::from_bytes(&seed.as_bytes()[..SECRET_KEY_LENGTH].try_into()
.map_err(|_| DomainError::CryptographicError("Invalid seed length".to_string()))?);
let verifying_key = signing_key.verifying_key();
Ok((
PublicKey(verifying_key.to_bytes().to_vec()),
PrivateKey(signing_key.to_bytes().to_vec()),
))
}
fn derive_from_mnemonic(&self, mnemonic: &RecoveryPhrase, univ_id: &str) -> Result<Seed, Self::Error> {
let phrase = mnemonic.words().join(" ");
let bip39_mnemonic = Mnemonic::parse(&phrase)
.map_err(|e| DomainError::InvalidMnemonic(format!("Invalid mnemonic: {}", e)))?;
// Use univ_id as passphrase to bind seed to universe
let bip39_seed = bip39_mnemonic.to_seed(univ_id);
// BIP39 produces 64-byte seed, but we only need 32 bytes for Ed25519
// Use the first 32 bytes of the BIP39 seed
let ed25519_seed: [u8; 32] = bip39_seed[..32]
.try_into()
.map_err(|_| DomainError::CryptographicError("Failed to extract 32-byte seed from BIP39 seed".to_string()))?;
Ok(Seed::new(ed25519_seed.to_vec()))
}
}
#[derive(Clone)]
pub struct XChaCha20FileEncryptor;
impl FileEncryptor for XChaCha20FileEncryptor {
type Error = DomainError;
fn encrypt(
&self,
seed: &Seed,
password: &str,
public_key: &PublicKey,
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
) -> Result<PassportFile, Self::Error> {
// Generate salt and nonce using WASM-compatible RNG
let mut salt = [0u8; SALT_LENGTH];
let mut nonce_bytes = [0u8; NONCE_LENGTH];
let mut rng = rng::new_rng();
rng.fill_bytes(&mut salt)?;
rng.fill_bytes(&mut nonce_bytes)?;
// Derive KEK from password using HKDF
let hk = Hkdf::<Sha256>::new(Some(&salt), password.as_bytes());
let mut kek = [0u8; 32];
hk.expand(KDF_INFO, &mut kek)
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
// Encrypt seed
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
let nonce = XNonce::from_slice(&nonce_bytes);
let enc_seed = cipher
.encrypt(&nonce, seed.as_bytes())
.map_err(|e| DomainError::CryptographicError(format!("Encryption failed: {}", e)))?;
// Serialize and encrypt user profiles
let user_profiles_vec: Vec<UserProfile> = user_profiles.to_vec();
let user_profiles_bytes = serde_cbor::to_vec(&user_profiles_vec)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize user profiles: {}", e)))?;
let enc_user_profiles = cipher
.encrypt(&nonce, &*user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
// Get current timestamp using WASM-compatible time
let created_at = time::now_seconds()?;
Ok(PassportFile {
enc_seed,
kdf: KDF_HKDF_SHA256.to_string(),
cipher: CIPHER_XCHACHA20_POLY1305.to_string(),
salt: salt.to_vec(),
nonce: nonce_bytes.to_vec(),
public_key: public_key.0.clone(),
did: did.0.clone(),
univ_id: univ_id.to_string(),
created_at,
version: "1.0.0".to_string(),
enc_user_profiles,
})
}
fn decrypt(
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
// Validate file format
validate_file_format(&file.kdf, &file.cipher)?;
// Derive KEK from password
let hk = Hkdf::<Sha256>::new(Some(&file.salt), password.as_bytes());
let mut kek = [0u8; 32];
hk.expand(KDF_INFO, &mut kek)
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
// Decrypt seed
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
let nonce = XNonce::from_slice(&file.nonce);
let seed_bytes = cipher
.decrypt(&nonce, &*file.enc_seed)
.map_err(|e| DomainError::CryptographicError(format!("Decryption failed: {}", e)))?;
let seed = Seed::new(seed_bytes);
// Re-derive keys from seed to verify
let key_deriver = Ed25519KeyDeriver;
let (public_key, private_key) = key_deriver.derive_from_seed(&seed)?;
// Verify public key matches
if public_key.0 != file.public_key {
return Err(DomainError::CryptographicError(
"Public key mismatch - wrong password?".to_string(),
));
}
// Decrypt user profiles
let user_profiles_bytes = cipher
.decrypt(&nonce, &*file.enc_user_profiles)
.map_err(|e| DomainError::CryptographicError(format!("User profiles decryption failed: {}", e)))?;
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
// Note: univ_id is stored in the PassportFile and will be used when creating the Passport
Ok((seed, public_key, private_key, user_profiles))
}
}

View file

@ -1,83 +0,0 @@
//! WASM-specific cryptographic tests
#[cfg(test)]
mod tests {
use crate::domain::entities::*;
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
#[test]
fn test_wasm_bip39_generator_creates_valid_mnemonic() {
let generator = Bip39MnemonicGenerator;
let phrase = generator.generate().unwrap();
// Should have 24 words
assert_eq!(phrase.words().len(), 24);
// Should be valid BIP-39
generator.validate(phrase.words()).unwrap();
}
#[test]
fn test_wasm_key_deriver_creates_consistent_keys() {
let deriver = Ed25519KeyDeriver;
// Create a test seed
let test_seed = Seed::new(vec![1; 32]);
let (public_key, private_key) = deriver.derive_from_seed(&test_seed).unwrap();
// Public key should be 32 bytes
assert_eq!(public_key.0.len(), 32);
// Private key should be 32 bytes
assert_eq!(private_key.0.len(), 32);
}
#[test]
fn test_wasm_file_encryptor_round_trip() {
let encryptor = XChaCha20FileEncryptor;
let key_deriver = Ed25519KeyDeriver;
let seed = Seed::new(vec![1; 32]);
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
let did = Did::new(&public_key);
let password = "test-password";
// Encrypt
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
// Verify file structure
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
assert_eq!(encrypted_file.salt.len(), 32);
assert_eq!(encrypted_file.nonce.len(), 24);
assert_eq!(encrypted_file.public_key, public_key.0);
assert_eq!(encrypted_file.did, did.0);
// Decrypt
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
// Verify decryption
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
assert_eq!(decrypted_public_key.0, public_key.0);
}
#[test]
fn test_wasm_file_encryptor_wrong_password_fails() {
let encryptor = XChaCha20FileEncryptor;
let seed = Seed::new(vec![1, 2, 3, 4, 5]);
let public_key = PublicKey(vec![1; 32]);
let did = Did::new(&public_key);
// Encrypt with one password
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
// Try to decrypt with wrong password
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
// Should fail
assert!(result.is_err());
}
}

View file

@ -0,0 +1,255 @@
#[cfg(test)]
mod tests {
use crate::domain::entities::*;
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
use crate::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
#[test]
fn test_bip39_generator_creates_valid_mnemonic() {
let generator = Bip39MnemonicGenerator;
let phrase = generator.generate().unwrap();
// Should have 24 words
assert_eq!(phrase.words().len(), 24);
// Should be valid BIP-39
generator.validate(phrase.words()).unwrap();
}
#[test]
fn test_bip39_generator_validates_correct_mnemonic() {
let generator = Bip39MnemonicGenerator;
// This is a valid test mnemonic
let _valid_words = vec![
"abandon".to_string(), "abandon".to_string(), "abandon".to_string(),
"abandon".to_string(), "abandon".to_string(), "abandon".to_string(),
"abandon".to_string(), "abandon".to_string(), "abandon".to_string(),
"abandon".to_string(), "abandon".to_string(), "about".to_string(),
];
// Note: This test mnemonic is actually invalid (checksum fails)
// For a real test, we'd need a properly generated mnemonic
// For now, we'll test that invalid mnemonics are rejected
let invalid_words = vec!["invalid".to_string(); 12];
assert!(generator.validate(&invalid_words).is_err());
}
#[test]
fn test_key_deriver_creates_consistent_keys() {
let deriver = Ed25519KeyDeriver;
// Create a test seed
let test_seed = Seed::new(vec![1; 32]);
let (public_key, private_key) = deriver.derive_from_seed(&test_seed).unwrap();
// Public key should be 32 bytes
assert_eq!(public_key.0.len(), 32);
// Private key should be 32 bytes
assert_eq!(private_key.0.len(), 32);
}
#[test]
fn test_file_encryptor_round_trip() {
let encryptor = XChaCha20FileEncryptor;
let key_deriver = Ed25519KeyDeriver;
let seed = Seed::new(vec![1; 32]);
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
let did = Did::new(&public_key);
let password = "test-password";
// Encrypt
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "univ:test:crypto", &[]).unwrap();
// Verify file structure
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
assert_eq!(encrypted_file.salt.len(), 32);
assert_eq!(encrypted_file.nonce.len(), 24);
assert_eq!(encrypted_file.public_key, public_key.0);
assert_eq!(encrypted_file.did, did.0);
// Decrypt
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
// Verify decryption
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
assert_eq!(decrypted_public_key.0, public_key.0);
}
#[test]
fn test_file_encryptor_wrong_password_fails() {
let encryptor = XChaCha20FileEncryptor;
let seed = Seed::new(vec![1, 2, 3, 4, 5]);
let public_key = PublicKey(vec![1; 32]);
let did = Did::new(&public_key);
// Encrypt with one password
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "univ:test:crypto", &[]).unwrap();
// Try to decrypt with wrong password
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
// Should fail
assert!(result.is_err());
}
#[test]
fn test_file_encryptor_invalid_file_format() {
let encryptor = XChaCha20FileEncryptor;
let mut invalid_file = PassportFile {
enc_seed: vec![1, 2, 3],
kdf: "Invalid-KDF".to_string(),
cipher: "Invalid-Cipher".to_string(),
salt: vec![0; 32],
nonce: vec![0; 24],
public_key: vec![1; 32],
did: "test-did".to_string(),
univ_id: "univ:test:crypto".to_string(),
created_at: 0,
version: "1.0.0".to_string(),
enc_user_profiles: vec![],
};
// Test with invalid KDF
let result = encryptor.decrypt(&invalid_file, "password");
assert!(result.is_err());
// Test with invalid cipher
invalid_file.kdf = "HKDF-SHA256".to_string();
invalid_file.cipher = "Invalid-Cipher".to_string();
let result = encryptor.decrypt(&invalid_file, "password");
assert!(result.is_err());
}
#[test]
fn test_file_encryptor_with_user_profiles() {
let encryptor = XChaCha20FileEncryptor;
let key_deriver = Ed25519KeyDeriver;
let seed = Seed::new(vec![1; 32]);
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
let did = Did::new(&public_key);
let password = "test-password";
// Create test user profiles
let user_profiles = vec![
UserProfile {
id: "test-uuid-default".to_string(),
hub_did: None,
identity: UserIdentity {
handle: Some("defaultuser".to_string()),
display_name: Some("Default User".to_string()),
first_name: Some("Default".to_string()),
last_name: Some("User".to_string()),
email: Some("default@example.com".to_string()),
avatar_url: None,
bio: Some("Default bio".to_string()),
},
preferences: UserPreferences {
theme: Some("dark".to_string()),
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: true,
},
created_at: 1234567890,
updated_at: 1234567890,
},
UserProfile {
id: "test-uuid-hub".to_string(),
hub_did: Some("h:hub1".to_string()),
identity: UserIdentity {
handle: Some("hubuser".to_string()),
display_name: Some("Hub User".to_string()),
first_name: Some("Hub".to_string()),
last_name: Some("User".to_string()),
email: Some("hub@example.com".to_string()),
avatar_url: Some("https://example.com/avatar.png".to_string()),
bio: Some("Hub bio".to_string()),
},
preferences: UserPreferences {
theme: Some("light".to_string()),
language: Some("fr".to_string()),
notifications_enabled: false,
auto_sync: false,
},
created_at: 1234567891,
updated_at: 1234567892,
},
];
// Encrypt
let encrypted_file = encryptor.encrypt(
&seed,
password,
&public_key,
&did,
"univ:test:crypto",
&user_profiles
).unwrap();
// Verify file structure includes user profiles
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
assert!(!encrypted_file.enc_user_profiles.is_empty());
// Decrypt
let (decrypted_seed, decrypted_public_key, _, decrypted_profiles) =
encryptor.decrypt(&encrypted_file, password).unwrap();
// Verify decryption
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
assert_eq!(decrypted_public_key.0, public_key.0);
// Verify user profiles
assert_eq!(decrypted_profiles.len(), 2);
// Verify default profile
let default_profile = decrypted_profiles.iter().find(|p| p.is_default()).unwrap();
assert_eq!(default_profile.identity.handle, Some("defaultuser".to_string()));
assert_eq!(default_profile.identity.display_name, Some("Default User".to_string()));
assert_eq!(default_profile.identity.email, Some("default@example.com".to_string()));
assert_eq!(default_profile.preferences.theme, Some("dark".to_string()));
// Verify hub profile
let hub_profile = decrypted_profiles.iter().find(|p| p.hub_did == Some("h:hub1".to_string())).unwrap();
assert_eq!(hub_profile.identity.handle, Some("hubuser".to_string()));
assert_eq!(hub_profile.identity.display_name, Some("Hub User".to_string()));
assert_eq!(hub_profile.identity.email, Some("hub@example.com".to_string()));
assert_eq!(hub_profile.preferences.language, Some("fr".to_string()));
assert!(!hub_profile.preferences.notifications_enabled);
}
#[test]
fn test_file_encryptor_with_empty_user_profiles() {
let encryptor = XChaCha20FileEncryptor;
let key_deriver = Ed25519KeyDeriver;
let seed = Seed::new(vec![1; 32]);
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
let did = Did::new(&public_key);
let password = "test-password";
// Encrypt with empty user profiles
let encrypted_file = encryptor.encrypt(
&seed,
password,
&public_key,
&did,
"univ:test:crypto",
&[]
).unwrap();
// Decrypt
let (_, _, _, decrypted_profiles) = encryptor.decrypt(&encrypted_file, password).unwrap();
// Should have empty profiles
assert_eq!(decrypted_profiles.len(), 0);
}
}

View file

@ -1,14 +1,15 @@
// Core abstractions for all platforms
pub mod traits;
pub mod rng;
pub mod time;
#[cfg(feature = "std")]
pub mod crypto;
#[cfg(feature = "std")]
pub mod storage;
// Export platform-appropriate implementations
pub use crypto::*;
pub use storage::*;
#[cfg(feature = "std")]
pub use crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
#[cfg(feature = "std")]
pub use storage::FileSystemStorage;
// Re-export traits for convenience
pub use traits::*;
#[cfg(all(test, feature = "std"))]
mod crypto_test;
#[cfg(all(test, feature = "std"))]
mod storage_test;

View file

@ -1,49 +0,0 @@
//! Random number generation abstraction for WASM compatibility
use crate::domain::error::DomainError;
/// Random number generator trait
pub trait RngCore {
/// Fill a buffer with random bytes
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), DomainError>;
}
/// Standard library RNG using OsRng
#[cfg(not(target_arch = "wasm32"))]
pub struct StdRng;
#[cfg(not(target_arch = "wasm32"))]
impl RngCore for StdRng {
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), DomainError> {
use rand::{rngs::OsRng, RngCore};
OsRng
.try_fill_bytes(dest)
.map_err(|e| DomainError::CryptographicError(format!("RNG error: {}", e)))
}
}
/// WASM-compatible RNG using getrandom
#[cfg(target_arch = "wasm32")]
pub struct WasmRng;
#[cfg(target_arch = "wasm32")]
impl RngCore for WasmRng {
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), DomainError> {
getrandom::getrandom(dest)
.map_err(|e| DomainError::CryptographicError(format!("WASM RNG error: {}", e)))
}
}
/// Create a new RNG instance based on the current architecture
pub fn new_rng() -> Box<dyn RngCore> {
#[cfg(not(target_arch = "wasm32"))]
{
Box::new(StdRng)
}
#[cfg(target_arch = "wasm32")]
{
Box::new(WasmRng)
}
}

View file

@ -1,13 +1,10 @@
//! Native (std) file system storage implementation
use std::fs;
use std::path::Path;
use crate::domain::entities::*;
use crate::domain::entities::PassportFile;
use crate::domain::traits::FileStorage;
use crate::domain::error::DomainError;
use crate::domain::traits::*;
/// Native file system storage
#[derive(Clone)]
pub struct FileSystemStorage;

View file

@ -1,22 +0,0 @@
//! Unified storage API with target-specific implementations
// Platform-specific implementations
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
mod native;
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
mod wasm;
// Re-export the unified API
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
pub use native::FileSystemStorage;
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
pub use wasm::BrowserStorage as FileSystemStorage;
// Target-specific tests
#[cfg(all(test, all(not(target_arch = "wasm32"), not(feature = "force-wasm"))))]
mod native_test;
#[cfg(all(test, any(target_arch = "wasm32", feature = "force-wasm")))]
mod wasm_test;

View file

@ -1,58 +0,0 @@
//! Native-specific storage tests
#[cfg(test)]
mod tests {
use crate::domain::entities::*;
use crate::domain::traits::FileStorage;
use crate::infrastructure::storage::FileSystemStorage;
#[test]
fn test_native_storage_save_and_load() {
let storage = FileSystemStorage;
let temp_dir = tempfile::tempdir().unwrap();
let file_path = temp_dir.path().join("test-passport.spf");
// Create a test passport file
let test_file = PassportFile {
kdf: "HKDF-SHA256".to_string(),
cipher: "XChaCha20-Poly1305".to_string(),
salt: vec![1; 32],
nonce: vec![2; 24],
enc_seed: vec![3; 32],
public_key: vec![4; 32],
did: "did:sharenet:test".to_string(),
univ_id: "u:Test Universe:12345678-1234-1234-1234-123456789012".to_string(),
created_at: 1234567890,
version: "1.0.0".to_string(),
enc_user_profiles: vec![],
};
// Save the file
storage.save(&test_file, file_path.to_str().unwrap()).unwrap();
// Load the file
let loaded_file = storage.load(file_path.to_str().unwrap()).unwrap();
// Verify the loaded file matches the original
assert_eq!(loaded_file.kdf, test_file.kdf);
assert_eq!(loaded_file.cipher, test_file.cipher);
assert_eq!(loaded_file.salt, test_file.salt);
assert_eq!(loaded_file.nonce, test_file.nonce);
assert_eq!(loaded_file.enc_seed, test_file.enc_seed);
assert_eq!(loaded_file.public_key, test_file.public_key);
assert_eq!(loaded_file.did, test_file.did);
assert_eq!(loaded_file.univ_id, test_file.univ_id);
assert_eq!(loaded_file.created_at, test_file.created_at);
assert_eq!(loaded_file.version, test_file.version);
assert_eq!(loaded_file.enc_user_profiles, test_file.enc_user_profiles);
}
#[test]
fn test_native_storage_load_nonexistent_file_fails() {
let storage = FileSystemStorage;
let result = storage.load("/nonexistent/path/file.spf");
// Should fail
assert!(result.is_err());
}
}

View file

@ -1,110 +0,0 @@
//! WASM-compatible storage using browser LocalStorage
use crate::domain::entities::*;
use crate::domain::error::DomainError;
use crate::domain::traits::*;
/// WASM storage using browser LocalStorage
#[derive(Clone)]
pub struct BrowserStorage;
// Mock storage for testing on native targets
#[cfg(not(target_arch = "wasm32"))]
use std::collections::HashMap;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::{Mutex, OnceLock};
#[cfg(not(target_arch = "wasm32"))]
static MOCK_STORAGE: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
impl FileStorage for BrowserStorage {
type Error = DomainError;
fn save(&self, file: &PassportFile, path: &str) -> Result<(), Self::Error> {
// Real implementation for WASM targets
#[cfg(target_arch = "wasm32")]
{
use base64::Engine;
use gloo_storage::Storage;
// Serialize to CBOR
let data = serde_cbor::to_vec(file)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to serialize file: {}", e)))?;
// Convert to base64 for storage
let base64_data = base64::engine::general_purpose::STANDARD.encode(&data);
// Store in browser localStorage using gloo-storage (synchronous)
gloo_storage::LocalStorage::set(path, base64_data)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to store in localStorage: {}", e)))?;
Ok(())
}
// Mock implementation for testing on native targets
#[cfg(not(target_arch = "wasm32"))]
{
// For testing purposes, we'll use a simple in-memory storage
// In a real browser environment, this would use actual localStorage
use base64::Engine;
// Serialize to CBOR
let data = serde_cbor::to_vec(file)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to serialize file: {}", e)))?;
// Convert to base64 for storage
let base64_data = base64::engine::general_purpose::STANDARD.encode(&data);
// Store in mock storage
let mock_storage = MOCK_STORAGE.get_or_init(|| Mutex::new(HashMap::new()));
mock_storage.lock().unwrap().insert(path.to_string(), base64_data);
Ok(())
}
}
fn load(&self, path: &str) -> Result<PassportFile, Self::Error> {
// Real implementation for WASM targets
#[cfg(target_arch = "wasm32")]
{
use base64::Engine;
use gloo_storage::Storage;
// Load from browser localStorage (synchronous)
let base64_data: String = gloo_storage::LocalStorage::get(path)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to load from localStorage: {}", e)))?;
// Decode from base64
let data = base64::engine::general_purpose::STANDARD.decode(&base64_data)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to decode base64: {}", e)))?;
// Deserialize from CBOR
let file = serde_cbor::from_slice(&data)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to deserialize file: {}", e)))?;
Ok(file)
}
// Mock implementation for testing on native targets
#[cfg(not(target_arch = "wasm32"))]
{
use base64::Engine;
// Load from mock storage
let mock_storage = MOCK_STORAGE.get_or_init(|| Mutex::new(HashMap::new()));
let mock_storage = mock_storage.lock().unwrap();
let base64_data = mock_storage.get(path)
.ok_or_else(|| DomainError::InvalidFileFormat(format!("Key not found: {}", path)))?;
// Decode from base64
let data = base64::engine::general_purpose::STANDARD.decode(base64_data)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to decode base64: {}", e)))?;
// Deserialize from CBOR
let file = serde_cbor::from_slice(&data)
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to deserialize file: {}", e)))?;
Ok(file)
}
}
}

View file

@ -1,57 +0,0 @@
//! WASM-specific storage tests
#[cfg(test)]
mod tests {
use crate::domain::entities::*;
use crate::domain::traits::FileStorage;
use crate::infrastructure::storage::FileSystemStorage;
#[test]
fn test_wasm_storage_save_and_load() {
let storage = FileSystemStorage;
let test_key = "test-passport-key";
// Create a test passport file
let test_file = PassportFile {
kdf: "HKDF-SHA256".to_string(),
cipher: "XChaCha20-Poly1305".to_string(),
salt: vec![1; 32],
nonce: vec![2; 24],
enc_seed: vec![3; 32],
public_key: vec![4; 32],
did: "did:sharenet:test".to_string(),
univ_id: "u:Test Universe:12345678-1234-1234-1234-123456789012".to_string(),
created_at: 1234567890,
version: "1.0.0".to_string(),
enc_user_profiles: vec![],
};
// Save the file
storage.save(&test_file, test_key).unwrap();
// Load the file
let loaded_file = storage.load(test_key).unwrap();
// Verify the loaded file matches the original
assert_eq!(loaded_file.kdf, test_file.kdf);
assert_eq!(loaded_file.cipher, test_file.cipher);
assert_eq!(loaded_file.salt, test_file.salt);
assert_eq!(loaded_file.nonce, test_file.nonce);
assert_eq!(loaded_file.enc_seed, test_file.enc_seed);
assert_eq!(loaded_file.public_key, test_file.public_key);
assert_eq!(loaded_file.did, test_file.did);
assert_eq!(loaded_file.univ_id, test_file.univ_id);
assert_eq!(loaded_file.created_at, test_file.created_at);
assert_eq!(loaded_file.version, test_file.version);
assert_eq!(loaded_file.enc_user_profiles, test_file.enc_user_profiles);
}
#[test]
fn test_wasm_storage_load_nonexistent_key_fails() {
let storage = FileSystemStorage;
let result = storage.load("nonexistent-key");
// Should fail
assert!(result.is_err());
}
}

View file

@ -0,0 +1,65 @@
#[cfg(test)]
mod tests {
use tempfile::NamedTempFile;
use crate::domain::traits::FileStorage;
use crate::{FileSystemStorage, PassportFile};
#[test]
fn test_file_storage_round_trip() {
let storage = FileSystemStorage;
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
let test_file = PassportFile {
enc_seed: vec![1, 2, 3, 4, 5],
kdf: "HKDF-SHA256".to_string(),
cipher: "XChaCha20-Poly1305".to_string(),
salt: vec![0; 32],
nonce: vec![0; 24],
public_key: vec![1; 32],
did: "test-did".to_string(),
univ_id: "univ:test:storage".to_string(),
created_at: 1234567890,
version: "1.0.0".to_string(),
enc_user_profiles: vec![],
};
// Save file
storage.save(&test_file, file_path).unwrap();
// Load file
let loaded_file = storage.load(file_path).unwrap();
// Verify data integrity
assert_eq!(loaded_file.enc_seed, test_file.enc_seed);
assert_eq!(loaded_file.kdf, test_file.kdf);
assert_eq!(loaded_file.cipher, test_file.cipher);
assert_eq!(loaded_file.salt, test_file.salt);
assert_eq!(loaded_file.nonce, test_file.nonce);
assert_eq!(loaded_file.public_key, test_file.public_key);
assert_eq!(loaded_file.did, test_file.did);
assert_eq!(loaded_file.created_at, test_file.created_at);
assert_eq!(loaded_file.version, test_file.version);
}
#[test]
fn test_file_storage_nonexistent_file() {
let storage = FileSystemStorage;
let result = storage.load("/nonexistent/path/file.spf");
assert!(result.is_err());
}
#[test]
fn test_file_storage_invalid_cbor() {
let storage = FileSystemStorage;
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Write invalid CBOR data
std::fs::write(file_path, b"invalid cbor data").unwrap();
let result = storage.load(file_path);
assert!(result.is_err());
}
}

View file

@ -1,52 +0,0 @@
//! Time abstraction for WASM compatibility
use crate::domain::error::DomainError;
/// Time provider trait for abstracting time operations
pub trait TimeProvider {
/// Get current timestamp in seconds since Unix epoch
fn now_seconds() -> Result<u64, DomainError>;
}
/// Standard library time provider
#[cfg(not(target_arch = "wasm32"))]
pub struct StdTimeProvider;
#[cfg(not(target_arch = "wasm32"))]
impl TimeProvider for StdTimeProvider {
fn now_seconds() -> Result<u64, DomainError> {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| DomainError::CryptographicError(format!("Time error: {}", e)))
.map(|d| d.as_secs())
}
}
/// WASM time provider
#[cfg(target_arch = "wasm32")]
pub struct WasmTimeProvider;
#[cfg(target_arch = "wasm32")]
impl TimeProvider for WasmTimeProvider {
fn now_seconds() -> Result<u64, DomainError> {
// Use JavaScript Date API via js_sys
// This will work when compiled to WASM
let timestamp = js_sys::Date::now() / 1000.0; // Convert from milliseconds to seconds
Ok(timestamp as u64)
}
}
/// Get the current timestamp using the appropriate time provider
pub fn now_seconds() -> Result<u64, DomainError> {
#[cfg(not(target_arch = "wasm32"))]
{
StdTimeProvider::now_seconds()
}
#[cfg(target_arch = "wasm32")]
{
WasmTimeProvider::now_seconds()
}
}

View file

@ -1,65 +0,0 @@
//! Core abstractions for platform-agnostic cryptography and storage
use crate::domain::entities::*;
use crate::domain::error::DomainError;
/// Mnemonic generation trait
pub trait MnemonicGenerator {
type Error: Into<DomainError>;
fn generate(&self) -> Result<RecoveryPhrase, Self::Error>;
fn validate(&self, words: &[String]) -> Result<(), Self::Error>;
}
/// Key derivation trait
pub trait KeyDeriver {
type Error: Into<DomainError>;
fn derive_from_seed(&self, seed: &Seed) -> Result<(PublicKey, PrivateKey), Self::Error>;
fn derive_from_mnemonic(&self, mnemonic: &RecoveryPhrase, univ_id: &str) -> Result<Seed, Self::Error>;
}
/// File encryption trait
pub trait FileEncryptor {
type Error: Into<DomainError>;
fn encrypt(
&self,
seed: &Seed,
password: &str,
public_key: &PublicKey,
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
) -> Result<PassportFile, Self::Error>;
fn decrypt(
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error>;
}
/// Storage trait for passport files
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait)]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait(?Send))]
pub trait FileStorage {
type Error: Into<DomainError>;
async fn save(&self, file: &PassportFile, path: &str) -> Result<(), Self::Error>;
async fn load(&self, path: &str) -> Result<PassportFile, Self::Error>;
}
/// Random number generation trait
pub trait RngCore {
type Error: Into<DomainError>;
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error>;
}
/// Time provider trait
pub trait TimeProvider {
type Error: Into<DomainError>;
fn now_seconds(&self) -> Result<u64, Self::Error>;
}

View file

@ -21,10 +21,9 @@ pub use application::use_cases::{
};
pub use application::error::ApplicationError;
// Re-export infrastructure implementations (automatically selected by target)
pub use infrastructure::{
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
FileSystemStorage
};

View file

@ -1,8 +1,7 @@
use sharenet_passport::{
application::use_cases::*,
Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor, FileSystemStorage,
FileStorage,
};
use sharenet_passport::application::use_cases::*;
use sharenet_passport::domain::traits::*;
use sharenet_passport::infrastructure::crypto::*;
use sharenet_passport::infrastructure::storage::*;
use std::fs;
fn main() {

View file

@ -1,8 +1,9 @@
use sharenet_passport::{
application::use_cases::*,
domain::entities::{UserIdentity, UserPreferences},
Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor, FileSystemStorage,
ApplicationError, FileStorage,
infrastructure::*,
ApplicationError,
FileStorage,
};
use rpassword::prompt_password;
use hex;