app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 47835176e939ecea38116ae5f45cff5b29dc4cbe
parent c06ae168d15c5197cba2eff0bec33e3b99499aaa
Author: triesap <triesap@radroots.dev>
Date:   Mon, 19 Jan 2026 02:29:02 +0000

app-core: add web nostr keystore

- implement web nostr keystore with key parsing, generation, and storage
- export nostr keystore module and add config/invalid key tests
- align keystore legacy cipher store for nostr config
- exclude refs/crates workspace and add radroots-nostr dependency

Diffstat:
MCargo.lock | 413+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCargo.toml | 4++++
Mcrates/core/Cargo.toml | 1+
Mcrates/core/src/keystore/mod.rs | 2++
Mcrates/core/src/keystore/web.rs | 14++++++++++++--
Acrates/core/src/keystore/web_nostr.rs | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 529 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -3,6 +3,27 @@ version = 4 [[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "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", +] + +[[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -29,6 +50,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] name = "async-lock" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -99,6 +126,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] +name = "base64ct" +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.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -114,6 +181,15 @@ dependencies = [ ] [[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 = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -132,12 +208,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" [[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.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[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", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] name = "codee" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -257,6 +387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -293,6 +424,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -362,6 +494,12 @@ dependencies = [ ] [[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -477,8 +615,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -554,6 +694,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] +name = "hex" +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 = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] name = "html-escape" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -699,6 +863,28 @@ dependencies = [ ] [[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 = "interpolator" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -927,6 +1113,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" [[package]] +name = "nostr" +version = "0.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1" +dependencies = [ + "aes", + "base64", + "bech32", + "bip39", + "bitcoin_hashes", + "cbc", + "chacha20", + "chacha20poly1305", + "getrandom 0.2.17", + "hex", + "instant", + "scrypt", + "secp256k1", + "serde", + "serde_json", + "unicode-normalization", + "url", +] + +[[package]] name = "num_cpus" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -953,6 +1164,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] name = "or_poisoned" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -988,6 +1205,17 @@ dependencies = [ ] [[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1000,6 +1228,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1038,6 +1276,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] name = "potential_utf" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1047,6 +1296,15 @@ dependencies = [ ] [[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1165,6 +1423,7 @@ dependencies = [ "futures", "getrandom 0.2.17", "js-sys", + "radroots-nostr", "serde", "serde-wasm-bindgen", "serde_json", @@ -1174,6 +1433,46 @@ dependencies = [ ] [[package]] +name = "radroots-nostr" +version = "0.1.0" +dependencies = [ + "nostr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] name = "reactive_graph" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1302,6 +1601,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[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" @@ -1317,6 +1625,38 @@ 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", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand", + "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 = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1475,6 +1815,12 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1502,6 +1848,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] name = "syn" version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1629,6 +1981,21 @@ dependencies = [ ] [[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] name = "toml" version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1712,6 +2079,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1724,6 +2100,16 @@ 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", + "subtle", +] + +[[package]] name = "url" version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1733,6 +2119,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -1986,6 +2373,26 @@ dependencies = [ ] [[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2007,6 +2414,12 @@ dependencies = [ ] [[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] name = "zerotrie" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -3,6 +3,9 @@ members = [ "app", "crates/core" ] +exclude = [ + "refs/crates" +] resolver = "2" @@ -38,6 +41,7 @@ web-sys = { version = "0.3.77", features = [ wasm-bindgen-futures = "0.4" base64 = "0.22" serde-wasm-bindgen = "0.6" +radroots-nostr = { path = "refs/crates/nostr" } [profile.release] codegen-units = 1 diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml @@ -15,6 +15,7 @@ serde = { workspace = true } serde_json = { workspace = true } getrandom = { workspace = true } base64 = { workspace = true } +radroots-nostr = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = { workspace = true } diff --git a/crates/core/src/keystore/mod.rs b/crates/core/src/keystore/mod.rs @@ -1,6 +1,7 @@ pub mod error; pub mod types; pub mod web; +pub mod web_nostr; pub use error::{RadrootsClientKeystoreError, RadrootsClientKeystoreErrorMessage}; pub use types::{ @@ -10,3 +11,4 @@ pub use types::{ RadrootsClientKeystoreValue, }; pub use web::RadrootsClientWebKeystore; +pub use web_nostr::RadrootsClientWebKeystoreNostr; diff --git a/crates/core/src/keystore/web.rs b/crates/core/src/keystore/web.rs @@ -4,7 +4,13 @@ use crate::backup::RadrootsClientBackupKeystorePayload; #[cfg(target_arch = "wasm32")] use crate::crypto::RadrootsClientCryptoError; use crate::crypto::RadrootsClientLegacyKeyConfig; -use crate::idb::{IDB_CONFIG_KEYSTORE, IDB_STORE_KEYSTORE_CIPHER, RadrootsClientIdbConfig}; +use crate::idb::{ + IDB_CONFIG_KEYSTORE, + IDB_STORE_KEYSTORE_CIPHER, + IDB_STORE_KEYSTORE_NOSTR, + IDB_STORE_KEYSTORE_NOSTR_CIPHER, + RadrootsClientIdbConfig, +}; #[cfg(target_arch = "wasm32")] use crate::idb::RadrootsClientIdbStoreError; use crate::idb::{RadrootsClientWebEncryptedStore, RadrootsClientWebEncryptedStoreConfig}; @@ -28,7 +34,11 @@ impl RadrootsClientWebKeystore { pub fn new(config: Option<RadrootsClientIdbConfig>) -> Self { let config = config.unwrap_or(IDB_CONFIG_KEYSTORE); let store_id = format!("keystore:{}:{}", config.database, config.store); - let legacy_store = IDB_STORE_KEYSTORE_CIPHER; + let legacy_store = if config.store == IDB_STORE_KEYSTORE_NOSTR { + IDB_STORE_KEYSTORE_NOSTR_CIPHER + } else { + IDB_STORE_KEYSTORE_CIPHER + }; let legacy_key_config = RadrootsClientLegacyKeyConfig { idb_config: RadrootsClientIdbConfig::new(config.database, legacy_store), key_name: format!("radroots.keystore.{}.aes-gcm.key", config.store), diff --git a/crates/core/src/keystore/web_nostr.rs b/crates/core/src/keystore/web_nostr.rs @@ -0,0 +1,97 @@ +use async_trait::async_trait; + +use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrSecretKey}; + +use crate::idb::{IDB_CONFIG_KEYSTORE_NOSTR, RadrootsClientIdbConfig}; + +use super::{ + RadrootsClientKeystore, + RadrootsClientKeystoreError, + RadrootsClientKeystoreNostr, + RadrootsClientKeystoreResult, + RadrootsClientWebKeystore, +}; + +pub struct RadrootsClientWebKeystoreNostr { + keystore: RadrootsClientWebKeystore, +} + +impl RadrootsClientWebKeystoreNostr { + pub fn new(config: Option<RadrootsClientIdbConfig>) -> Self { + let config = config.unwrap_or(IDB_CONFIG_KEYSTORE_NOSTR); + let keystore = RadrootsClientWebKeystore::new(Some(config)); + Self { keystore } + } + + pub fn get_config(&self) -> RadrootsClientIdbConfig { + self.keystore.get_config() + } + + async fn add_secret_key( + &self, + secret_key: RadrootsNostrSecretKey, + ) -> RadrootsClientKeystoreResult<String> { + let secret_hex = secret_key.to_secret_hex(); + let keys = RadrootsNostrKeys::new(secret_key); + let public_key = keys.public_key.to_hex(); + let _ = self.keystore.add(&public_key, &secret_hex).await?; + Ok(public_key) + } +} + +#[async_trait(?Send)] +impl RadrootsClientKeystoreNostr for RadrootsClientWebKeystoreNostr { + async fn generate(&self) -> RadrootsClientKeystoreResult<String> { + let secret_key = RadrootsNostrSecretKey::generate(); + self.add_secret_key(secret_key).await + } + + async fn add(&self, secret_key: &str) -> RadrootsClientKeystoreResult<String> { + let secret_key = RadrootsNostrSecretKey::parse(secret_key) + .map_err(|_| RadrootsClientKeystoreError::NostrInvalidSecretKey)?; + self.add_secret_key(secret_key).await + } + + async fn read(&self, public_key: &str) -> RadrootsClientKeystoreResult<String> { + let value = self.keystore.read(Some(public_key)).await?; + value.ok_or(RadrootsClientKeystoreError::MissingKey) + } + + async fn keys(&self) -> RadrootsClientKeystoreResult<Vec<String>> { + let keys = self.keystore.keys().await?; + if keys.is_empty() { + return Err(RadrootsClientKeystoreError::NostrNoResults); + } + Ok(keys) + } + + async fn remove(&self, public_key: &str) -> RadrootsClientKeystoreResult<String> { + let _ = self.keystore.remove(public_key).await?; + Ok(public_key.to_string()) + } + + async fn reset(&self) -> RadrootsClientKeystoreResult<()> { + self.keystore.reset().await + } +} + +#[cfg(test)] +mod tests { + use super::RadrootsClientWebKeystoreNostr; + use crate::idb::IDB_CONFIG_KEYSTORE_NOSTR; + use crate::keystore::{RadrootsClientKeystoreError, RadrootsClientKeystoreNostr}; + + #[test] + fn default_config_is_nostr_store() { + let keystore = RadrootsClientWebKeystoreNostr::new(None); + assert_eq!(keystore.get_config(), IDB_CONFIG_KEYSTORE_NOSTR); + } + + #[test] + fn invalid_secret_key_errors() { + let keystore = RadrootsClientWebKeystoreNostr::new(None); + let err = futures::executor::block_on(keystore.add("not-a-key")) + .expect_err("invalid secret key"); + assert_eq!(err, RadrootsClientKeystoreError::NostrInvalidSecretKey); + } +}