lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 3da25581e8d1675ef68106aec1f5fe92feab1893
parent 3ff5f6ad0a06c45b3849a0220d3b23702a888657
Author: triesap <tyson@radroots.org>
Date:   Fri, 20 Feb 2026 17:25:11 +0000

nostr-accounts: add account manager store vault and ndb bridge

Diffstat:
MCargo.lock | 917+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcrates/nostr-accounts/Cargo.toml | 15+++++++++++++--
Mcrates/nostr-accounts/src/error.rs | 36++++++++++++++++++++++++++++++++++--
Mcrates/nostr-accounts/src/lib.rs | 27+++++++++++++++++++++++++++
Acrates/nostr-accounts/src/manager.rs | 386+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/nostr-accounts/src/model.rs | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/nostr-accounts/src/ndb_bridge.rs | 41+++++++++++++++++++++++++++++++++++++++++
Acrates/nostr-accounts/src/store.rs | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/nostr-accounts/src/vault.rs | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 1689 insertions(+), 42 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -181,7 +181,7 @@ dependencies = [ "rustc-hash 2.1.1", "serde", "serde_derive", - "syn", + "syn 2.0.106", ] [[package]] @@ -193,10 +193,168 @@ dependencies = [ "memchr", "serde", "serde_derive", - "winnow", + "winnow 0.7.13", ] [[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.0.8", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io 2.6.0", + "async-lock 3.4.2", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.0.8", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] name = "async-trait" version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -204,7 +362,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -323,7 +481,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn", + "syn 2.0.106", "which", ] @@ -405,12 +563,31 @@ dependencies = [ ] [[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -580,7 +757,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -596,6 +773,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] name = "config" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -644,6 +830,16 @@ dependencies = [ ] [[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -730,6 +926,17 @@ dependencies = [ ] [[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -758,7 +965,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -769,7 +976,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -797,6 +1004,27 @@ dependencies = [ ] [[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -813,6 +1041,44 @@ dependencies = [ ] [[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -826,6 +1092,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" @@ -946,6 +1221,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -953,7 +1256,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -1110,6 +1413,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1131,6 +1446,15 @@ dependencies = [ ] [[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1414,6 +1738,17 @@ dependencies = [ ] [[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1483,6 +1818,20 @@ dependencies = [ ] [[package]] +name = "keyring" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363387f0019d714aa60cc30ab4fe501a747f4c08fc58f069dd14be971bd495a0" +dependencies = [ + "byteorder", + "lazy_static", + "linux-keyutils", + "secret-service", + "security-framework", + "windows-sys 0.52.0", +] + +[[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1574,6 +1923,22 @@ dependencies = [ ] [[package]] +name = "linux-keyutils" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" +dependencies = [ + "bitflags 2.9.3", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1625,6 +1990,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] name = "minicov" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1674,6 +2057,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" [[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + +[[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1789,10 +2184,74 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] [[package]] name = "num-traits" @@ -1847,12 +2306,28 @@ dependencies = [ ] [[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1916,7 +2391,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -1942,6 +2417,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1954,6 +2440,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.0.8", + "windows-sys 0.61.2", +] + +[[package]] name = "poly1305" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1995,7 +2511,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.106", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", ] [[package]] @@ -2172,12 +2698,14 @@ dependencies = [ "nostr", "radroots-events", "radroots-runtime", + "secrecy", "serde", "serde_json", "tempfile", "thiserror 1.0.69", "tracing", "ts-rs", + "zeroize", ] [[package]] @@ -2207,6 +2735,7 @@ dependencies = [ "radroots-events", "radroots-log", "radroots-nostr", + "radroots-nostr-accounts", "secrecy", "serde", "serde_json", @@ -2236,6 +2765,13 @@ dependencies = [ name = "radroots-nostr-accounts" version = "0.1.0" dependencies = [ + "keyring", + "radroots-identity", + "radroots-nostr-ndb", + "radroots-runtime", + "serde", + "serde_json", + "tempfile", "thiserror 1.0.69", ] @@ -2652,7 +3188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6268b74858287e1a062271b988a0c534bf85bbeb567fe09331bf40ed78113d5" dependencies = [ "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -2684,6 +3220,20 @@ dependencies = [ [[package]] name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" @@ -2790,7 +3340,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -2835,6 +3385,48 @@ dependencies = [ ] [[package]] +name = "secret-service" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5204d39df37f06d1944935232fd2dfe05008def7ca599bf28c0800366c8a8f9" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand 0.8.5", + "serde", + "sha2", + "zbus", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.3", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2882,7 +3474,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -2898,6 +3490,17 @@ dependencies = [ ] [[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] name = "serde_spanned" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2996,6 +3599,16 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" @@ -3040,6 +3653,17 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" @@ -3066,7 +3690,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -3086,7 +3710,7 @@ version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ - "fastrand", + "fastrand 2.3.0", "getrandom 0.3.3", "once_cell", "rustix 1.0.8", @@ -3137,7 +3761,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -3148,7 +3772,7 @@ checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -3252,7 +3876,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -3311,7 +3935,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -3325,6 +3949,17 @@ dependencies = [ [[package]] name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" @@ -3334,7 +3969,7 @@ dependencies = [ "serde_spanned", "toml_datetime", "toml_write", - "winnow", + "winnow 0.7.13", ] [[package]] @@ -3419,7 +4054,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -3485,7 +4120,7 @@ checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "termcolor", ] @@ -3539,7 +4174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f" dependencies = [ "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -3549,6 +4184,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3644,7 +4290,7 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -3659,7 +4305,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.106", "toml 0.5.11", "uniffi_meta", ] @@ -3803,6 +4449,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3858,7 +4510,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -3893,7 +4545,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3928,7 +4580,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -4012,7 +4664,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4042,7 +4694,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -4053,7 +4705,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -4088,6 +4740,15 @@ dependencies = [ [[package]] name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" @@ -4124,6 +4785,21 @@ dependencies = [ [[package]] name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" @@ -4157,6 +4833,12 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" @@ -4169,6 +4851,12 @@ checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" @@ -4181,6 +4869,12 @@ checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" @@ -4205,6 +4899,12 @@ checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" @@ -4217,6 +4917,12 @@ checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" @@ -4229,6 +4935,12 @@ checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" @@ -4241,6 +4953,12 @@ checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" @@ -4253,6 +4971,15 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" @@ -4286,6 +5013,16 @@ dependencies = [ ] [[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] name = "yaml-rust2" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4316,11 +5053,77 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "synstructure", ] [[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] name = "zerocopy" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4337,7 +5140,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -4357,7 +5160,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "synstructure", ] @@ -4397,7 +5200,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -4431,3 +5234,41 @@ dependencies = [ "log", "simd-adler32", ] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/crates/nostr-accounts/Cargo.toml b/crates/nostr-accounts/Cargo.toml @@ -7,10 +7,21 @@ rust-version.workspace = true license.workspace = true [features] -default = ["std"] -std = [] +default = ["std", "file-store", "memory-vault"] +std = ["dep:serde", "dep:serde_json", "dep:radroots-identity", "dep:radroots-runtime"] +file-store = ["std"] +memory-vault = ["std"] +os-keyring = ["std", "dep:keyring"] +ndb-bridge = ["std", "dep:radroots-nostr-ndb"] [dependencies] +keyring = { version = "2.3.3", optional = true } +radroots-identity = { workspace = true, optional = true, default-features = false, features = ["std", "profile", "json-file"] } +radroots-nostr-ndb = { workspace = true, optional = true, default-features = false, features = ["ndb", "giftwrap", "rt"] } +radroots-runtime = { workspace = true, optional = true } +serde = { workspace = true, optional = true, features = ["derive"] } +serde_json = { workspace = true, optional = true } thiserror = { workspace = true } [dev-dependencies] +tempfile = { workspace = true } diff --git a/crates/nostr-accounts/src/error.rs b/crates/nostr-accounts/src/error.rs @@ -2,6 +2,38 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum RadrootsNostrAccountsError { - #[error("not implemented")] - NotImplemented, + #[error("identity error: {0}")] + Identity(String), + + #[error("store error: {0}")] + Store(String), + + #[error("vault error: {0}")] + Vault(String), + + #[error("account not found: {0}")] + AccountNotFound(String), + + #[error("account already exists: {0}")] + AccountAlreadyExists(String), + + #[error("invalid account state: {0}")] + InvalidState(String), + + #[error("public key does not match secret key")] + PublicKeyMismatch, +} + +#[cfg(feature = "std")] +impl From<radroots_identity::IdentityError> for RadrootsNostrAccountsError { + fn from(value: radroots_identity::IdentityError) -> Self { + Self::Identity(value.to_string()) + } +} + +#[cfg(feature = "std")] +impl From<radroots_runtime::RuntimeJsonError> for RadrootsNostrAccountsError { + fn from(value: radroots_runtime::RuntimeJsonError) -> Self { + Self::Store(value.to_string()) + } } diff --git a/crates/nostr-accounts/src/lib.rs b/crates/nostr-accounts/src/lib.rs @@ -2,7 +2,34 @@ #![forbid(unsafe_code)] pub mod error; +#[cfg(feature = "std")] +pub mod manager; +#[cfg(feature = "std")] +pub mod model; +#[cfg(feature = "ndb-bridge")] +pub mod ndb_bridge; +#[cfg(feature = "std")] +pub mod store; +#[cfg(feature = "std")] +pub mod vault; pub mod prelude { pub use crate::error::RadrootsNostrAccountsError; + #[cfg(feature = "std")] + pub use crate::manager::RadrootsNostrAccountsManager; + #[cfg(feature = "std")] + pub use crate::model::{ + RADROOTS_NOSTR_ACCOUNTS_STORE_VERSION, RadrootsNostrAccountRecord, + RadrootsNostrAccountStoreState, + }; + #[cfg(feature = "ndb-bridge")] + pub use crate::ndb_bridge::radroots_nostr_accounts_register_selected_secret_with_ndb; + #[cfg(feature = "std")] + pub use crate::store::{ + RadrootsNostrAccountStore, RadrootsNostrFileAccountStore, RadrootsNostrMemoryAccountStore, + }; + #[cfg(feature = "std")] + pub use crate::vault::{RadrootsNostrSecretVault, RadrootsNostrSecretVaultMemory}; + #[cfg(feature = "os-keyring")] + pub use crate::vault::RadrootsNostrSecretVaultOsKeyring; } diff --git a/crates/nostr-accounts/src/manager.rs b/crates/nostr-accounts/src/manager.rs @@ -0,0 +1,386 @@ +use crate::error::RadrootsNostrAccountsError; +use crate::model::{RadrootsNostrAccountRecord, RadrootsNostrAccountStoreState}; +use crate::store::{RadrootsNostrAccountStore, RadrootsNostrMemoryAccountStore}; +use crate::vault::{RadrootsNostrSecretVault, RadrootsNostrSecretVaultMemory}; +use radroots_identity::{RadrootsIdentity, RadrootsIdentityId, RadrootsIdentityPublic}; +use std::path::Path; +use std::sync::{Arc, RwLock}; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Clone)] +pub struct RadrootsNostrAccountsManager { + store: Arc<dyn RadrootsNostrAccountStore>, + vault: Arc<dyn RadrootsNostrSecretVault>, + state: Arc<RwLock<RadrootsNostrAccountStoreState>>, +} + +impl RadrootsNostrAccountsManager { + pub fn new_in_memory() -> Self { + Self { + store: Arc::new(RadrootsNostrMemoryAccountStore::new()), + vault: Arc::new(RadrootsNostrSecretVaultMemory::new()), + state: Arc::new(RwLock::new(RadrootsNostrAccountStoreState::default())), + } + } + + pub fn new( + store: Arc<dyn RadrootsNostrAccountStore>, + vault: Arc<dyn RadrootsNostrSecretVault>, + ) -> Result<Self, RadrootsNostrAccountsError> { + let mut state = store.load()?; + if state.version != crate::model::RADROOTS_NOSTR_ACCOUNTS_STORE_VERSION { + return Err(RadrootsNostrAccountsError::InvalidState(format!( + "unsupported accounts schema version {}", + state.version + ))); + } + + if let Some(selected) = state.selected_account_id.clone() { + let exists = state.accounts.iter().any(|record| record.account_id == selected); + if !exists { + state.selected_account_id = None; + } + } + + Ok(Self { + store, + vault, + state: Arc::new(RwLock::new(state)), + }) + } + + pub fn list_accounts(&self) -> Result<Vec<RadrootsNostrAccountRecord>, RadrootsNostrAccountsError> { + let guard = self + .state + .read() + .map_err(|_| RadrootsNostrAccountsError::Store("accounts state lock poisoned".into()))?; + Ok(guard.accounts.clone()) + } + + pub fn selected_account_id(&self) -> Result<Option<RadrootsIdentityId>, RadrootsNostrAccountsError> { + let guard = self + .state + .read() + .map_err(|_| RadrootsNostrAccountsError::Store("accounts state lock poisoned".into()))?; + Ok(guard.selected_account_id.clone()) + } + + pub fn selected_account( + &self, + ) -> Result<Option<RadrootsNostrAccountRecord>, RadrootsNostrAccountsError> { + let guard = self + .state + .read() + .map_err(|_| RadrootsNostrAccountsError::Store("accounts state lock poisoned".into()))?; + let Some(selected) = guard.selected_account_id.as_ref() else { + return Ok(None); + }; + Ok(guard + .accounts + .iter() + .find(|record| &record.account_id == selected) + .cloned()) + } + + pub fn selected_public_identity( + &self, + ) -> Result<Option<RadrootsIdentityPublic>, RadrootsNostrAccountsError> { + Ok(self + .selected_account()? + .map(|record| record.public_identity.clone())) + } + + pub fn selected_signing_identity( + &self, + ) -> Result<Option<RadrootsIdentity>, RadrootsNostrAccountsError> { + let Some(record) = self.selected_account()? else { + return Ok(None); + }; + self.resolve_signing_identity(record) + } + + pub fn get_signing_identity( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<Option<RadrootsIdentity>, RadrootsNostrAccountsError> { + let guard = self + .state + .read() + .map_err(|_| RadrootsNostrAccountsError::Store("accounts state lock poisoned".into()))?; + let Some(record) = guard + .accounts + .iter() + .find(|record| &record.account_id == account_id) + .cloned() + else { + return Ok(None); + }; + drop(guard); + self.resolve_signing_identity(record) + } + + pub fn upsert_identity( + &self, + identity: &RadrootsIdentity, + label: Option<String>, + make_selected: bool, + ) -> Result<RadrootsIdentityId, RadrootsNostrAccountsError> { + let account_id = identity.id(); + self.vault + .store_secret_hex(&account_id, identity.secret_key_hex().as_str())?; + + let public_identity = identity.to_public(); + self.upsert_public_identity(public_identity, label, make_selected) + } + + pub fn upsert_public_identity( + &self, + public_identity: RadrootsIdentityPublic, + label: Option<String>, + make_selected: bool, + ) -> Result<RadrootsIdentityId, RadrootsNostrAccountsError> { + let updated_at_unix = now_unix_secs(); + let account_id = public_identity.id.clone(); + self.update_state(|state| { + if let Some(existing) = state + .accounts + .iter_mut() + .find(|record| record.account_id == account_id) + { + existing.public_identity = public_identity.clone(); + if let Some(next_label) = label.clone() { + existing.label = Some(next_label); + } + existing.touch_updated(updated_at_unix); + } else { + state.accounts.push(RadrootsNostrAccountRecord::new( + public_identity.clone(), + label.clone(), + updated_at_unix, + )); + } + + if state.selected_account_id.is_none() || make_selected { + state.selected_account_id = Some(account_id.clone()); + } + Ok(()) + })?; + Ok(account_id) + } + + pub fn generate_identity( + &self, + label: Option<String>, + make_selected: bool, + ) -> Result<RadrootsIdentityId, RadrootsNostrAccountsError> { + let identity = RadrootsIdentity::generate(); + self.upsert_identity(&identity, label, make_selected) + } + + pub fn select_account( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<(), RadrootsNostrAccountsError> { + let account_id = account_id.clone(); + self.update_state(|state| { + let exists = state + .accounts + .iter() + .any(|record| record.account_id == account_id); + if !exists { + return Err(RadrootsNostrAccountsError::AccountNotFound( + account_id.to_string(), + )); + } + state.selected_account_id = Some(account_id); + Ok(()) + }) + } + + pub fn remove_account( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<(), RadrootsNostrAccountsError> { + let account_id = account_id.clone(); + self.update_state(|state| { + let before = state.accounts.len(); + state.accounts.retain(|record| record.account_id != account_id); + if state.accounts.len() == before { + return Err(RadrootsNostrAccountsError::AccountNotFound( + account_id.to_string(), + )); + } + + if state.selected_account_id.as_ref() == Some(&account_id) { + state.selected_account_id = state.accounts.first().map(|record| record.account_id.clone()); + } + Ok(()) + })?; + self.vault.remove_secret(&account_id)?; + Ok(()) + } + + pub fn export_secret_hex( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<Option<String>, RadrootsNostrAccountsError> { + self.vault.load_secret_hex(account_id) + } + + pub fn migrate_legacy_identity_file( + &self, + path: impl AsRef<Path>, + label: Option<String>, + make_selected: bool, + ) -> Result<RadrootsIdentityId, RadrootsNostrAccountsError> { + let identity = RadrootsIdentity::load_from_path_auto(path)?; + self.upsert_identity(&identity, label, make_selected) + } + + fn resolve_signing_identity( + &self, + record: RadrootsNostrAccountRecord, + ) -> Result<Option<RadrootsIdentity>, RadrootsNostrAccountsError> { + let Some(secret_key_hex) = self.vault.load_secret_hex(&record.account_id)? else { + return Ok(None); + }; + let mut identity = RadrootsIdentity::from_secret_key_str(secret_key_hex.as_str())?; + if identity.public_key_hex() != record.public_identity.public_key_hex { + return Err(RadrootsNostrAccountsError::PublicKeyMismatch); + } + if let Some(profile) = record.public_identity.profile { + identity.set_profile(profile); + } + Ok(Some(identity)) + } + + fn update_state( + &self, + update: impl FnOnce(&mut RadrootsNostrAccountStoreState) -> Result<(), RadrootsNostrAccountsError>, + ) -> Result<(), RadrootsNostrAccountsError> { + let mut guard = self + .state + .write() + .map_err(|_| RadrootsNostrAccountsError::Store("accounts state lock poisoned".into()))?; + let mut next = guard.clone(); + update(&mut next)?; + self.store.save(&next)?; + *guard = next; + Ok(()) + } +} + +fn now_unix_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_secs()) + .unwrap_or(0) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::store::RadrootsNostrFileAccountStore; + use crate::vault::RadrootsNostrSecretVaultMemory; + use std::sync::Arc; + + #[test] + fn manager_persists_selection_and_restores_signing_identity() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RadrootsNostrFileAccountStore::new( + temp.path().join("accounts.json"), + )); + let vault = Arc::new(RadrootsNostrSecretVaultMemory::new()); + let manager = RadrootsNostrAccountsManager::new(store.clone(), vault.clone()).expect("manager"); + let created_id = manager + .generate_identity(Some("primary".into()), true) + .expect("create identity"); + + let selected = manager + .selected_account_id() + .expect("selected") + .expect("selected id"); + assert_eq!(selected, created_id); + + let manager2 = RadrootsNostrAccountsManager::new(store, vault).expect("manager2"); + let selected2 = manager2 + .selected_account_id() + .expect("selected2") + .expect("selected2 id"); + assert_eq!(selected2, created_id); + assert!(manager2 + .selected_signing_identity() + .expect("signing") + .is_some()); + } + + #[test] + fn watch_only_account_has_no_signing_identity() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RadrootsNostrFileAccountStore::new( + temp.path().join("accounts.json"), + )); + let vault = Arc::new(RadrootsNostrSecretVaultMemory::new()); + let manager = RadrootsNostrAccountsManager::new(store, vault).expect("manager"); + + let identity = RadrootsIdentity::generate(); + let public = identity.to_public(); + manager + .upsert_public_identity(public, Some("watch".into()), true) + .expect("watch"); + + assert!(manager + .selected_signing_identity() + .expect("signing") + .is_none()); + } + + #[test] + fn migrate_legacy_identity_file_imports_identity() { + let temp = tempfile::tempdir().expect("tempdir"); + let legacy_path = temp.path().join("legacy_identity.json"); + let legacy_identity = RadrootsIdentity::generate(); + legacy_identity + .save_json(&legacy_path) + .expect("legacy save"); + + let store = Arc::new(RadrootsNostrFileAccountStore::new( + temp.path().join("accounts.json"), + )); + let vault = Arc::new(RadrootsNostrSecretVaultMemory::new()); + let manager = RadrootsNostrAccountsManager::new(store, vault).expect("manager"); + let id = manager + .migrate_legacy_identity_file(&legacy_path, Some("legacy".into()), true) + .expect("migrate"); + assert_eq!( + manager + .selected_account_id() + .expect("selected") + .expect("selected id"), + id + ); + } + + #[test] + fn upsert_public_identity_without_label_preserves_existing_label() { + let manager = RadrootsNostrAccountsManager::new_in_memory(); + let account_id = manager + .generate_identity(Some("primary".into()), true) + .expect("generate"); + + let existing = manager + .selected_public_identity() + .expect("selected public") + .expect("public identity"); + manager + .upsert_public_identity(existing, None, false) + .expect("upsert"); + + let records = manager.list_accounts().expect("list"); + let record = records + .into_iter() + .find(|record| record.account_id == account_id) + .expect("account"); + assert_eq!(record.label.as_deref(), Some("primary")); + } +} diff --git a/crates/nostr-accounts/src/model.rs b/crates/nostr-accounts/src/model.rs @@ -0,0 +1,52 @@ +use radroots_identity::{RadrootsIdentityId, RadrootsIdentityPublic}; +use serde::{Deserialize, Serialize}; + +pub const RADROOTS_NOSTR_ACCOUNTS_STORE_VERSION: u32 = 1; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RadrootsNostrAccountRecord { + pub account_id: RadrootsIdentityId, + pub public_identity: RadrootsIdentityPublic, + #[serde(skip_serializing_if = "Option::is_none")] + pub label: Option<String>, + pub created_at_unix: u64, + pub updated_at_unix: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RadrootsNostrAccountStoreState { + pub version: u32, + pub selected_account_id: Option<RadrootsIdentityId>, + pub accounts: Vec<RadrootsNostrAccountRecord>, +} + +impl Default for RadrootsNostrAccountStoreState { + fn default() -> Self { + Self { + version: RADROOTS_NOSTR_ACCOUNTS_STORE_VERSION, + selected_account_id: None, + accounts: Vec::new(), + } + } +} + +impl RadrootsNostrAccountRecord { + pub fn new( + public_identity: RadrootsIdentityPublic, + label: Option<String>, + created_at_unix: u64, + ) -> Self { + let account_id = public_identity.id.clone(); + Self { + account_id, + public_identity, + label, + created_at_unix, + updated_at_unix: created_at_unix, + } + } + + pub fn touch_updated(&mut self, updated_at_unix: u64) { + self.updated_at_unix = updated_at_unix; + } +} diff --git a/crates/nostr-accounts/src/ndb_bridge.rs b/crates/nostr-accounts/src/ndb_bridge.rs @@ -0,0 +1,41 @@ +use crate::error::RadrootsNostrAccountsError; +use crate::manager::RadrootsNostrAccountsManager; +use radroots_nostr_ndb::prelude::RadrootsNostrNdb; + +pub fn radroots_nostr_accounts_register_selected_secret_with_ndb( + manager: &RadrootsNostrAccountsManager, + ndb: &RadrootsNostrNdb, +) -> Result<bool, RadrootsNostrAccountsError> { + let Some(identity) = manager.selected_signing_identity()? else { + return Ok(false); + }; + Ok(ndb.add_giftwrap_secret_key(identity.secret_key_bytes())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::store::RadrootsNostrFileAccountStore; + use crate::vault::RadrootsNostrSecretVaultMemory; + use radroots_nostr_ndb::prelude::RadrootsNostrNdbConfig; + use std::sync::Arc; + + #[test] + fn register_selected_secret_returns_true_for_signing_account() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RadrootsNostrFileAccountStore::new( + temp.path().join("accounts.json"), + )); + let vault = Arc::new(RadrootsNostrSecretVaultMemory::new()); + let manager = RadrootsNostrAccountsManager::new(store, vault).expect("manager"); + manager + .generate_identity(Some("primary".into()), true) + .expect("generate"); + + let ndb = RadrootsNostrNdb::open(RadrootsNostrNdbConfig::new(temp.path().join("ndb"))) + .expect("ndb"); + let added = radroots_nostr_accounts_register_selected_secret_with_ndb(&manager, &ndb) + .expect("register"); + assert!(added); + } +} diff --git a/crates/nostr-accounts/src/store.rs b/crates/nostr-accounts/src/store.rs @@ -0,0 +1,105 @@ +use crate::error::RadrootsNostrAccountsError; +use crate::model::RadrootsNostrAccountStoreState; +use radroots_runtime::json::{JsonFile, JsonWriteOptions}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; + +pub trait RadrootsNostrAccountStore: Send + Sync { + fn load(&self) -> Result<RadrootsNostrAccountStoreState, RadrootsNostrAccountsError>; + fn save( + &self, + state: &RadrootsNostrAccountStoreState, + ) -> Result<(), RadrootsNostrAccountsError>; +} + +#[derive(Debug, Clone)] +pub struct RadrootsNostrFileAccountStore { + path: PathBuf, +} + +impl RadrootsNostrFileAccountStore { + pub fn new(path: impl AsRef<Path>) -> Self { + Self { + path: path.as_ref().to_path_buf(), + } + } + + pub fn path(&self) -> &Path { + self.path.as_path() + } +} + +#[derive(Debug, Clone, Default)] +pub struct RadrootsNostrMemoryAccountStore { + state: Arc<RwLock<RadrootsNostrAccountStoreState>>, +} + +impl RadrootsNostrMemoryAccountStore { + pub fn new() -> Self { + Self::default() + } +} + +impl RadrootsNostrAccountStore for RadrootsNostrFileAccountStore { + fn load(&self) -> Result<RadrootsNostrAccountStoreState, RadrootsNostrAccountsError> { + if !self.path.exists() { + return Ok(RadrootsNostrAccountStoreState::default()); + } + let file = JsonFile::<RadrootsNostrAccountStoreState>::load(self.path.as_path())?; + Ok(file.value) + } + + fn save( + &self, + state: &RadrootsNostrAccountStoreState, + ) -> Result<(), RadrootsNostrAccountsError> { + let mut file = JsonFile::load_or_create_with(self.path.as_path(), || state.clone())?; + file.set_options(JsonWriteOptions { + pretty: true, + mode_unix: Some(0o600), + }); + file.value = state.clone(); + file.save()?; + Ok(()) + } +} + +impl RadrootsNostrAccountStore for RadrootsNostrMemoryAccountStore { + fn load(&self) -> Result<RadrootsNostrAccountStoreState, RadrootsNostrAccountsError> { + let guard = self + .state + .read() + .map_err(|_| RadrootsNostrAccountsError::Store("memory store lock poisoned".into()))?; + Ok(guard.clone()) + } + + fn save( + &self, + state: &RadrootsNostrAccountStoreState, + ) -> Result<(), RadrootsNostrAccountsError> { + let mut guard = self + .state + .write() + .map_err(|_| RadrootsNostrAccountsError::Store("memory store lock poisoned".into()))?; + *guard = state.clone(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn file_store_round_trip() { + let temp = tempfile::tempdir().expect("tempdir"); + let path = temp.path().join("accounts.json"); + let store = RadrootsNostrFileAccountStore::new(path.as_path()); + + let state = RadrootsNostrAccountStoreState::default(); + store.save(&state).expect("save"); + let loaded = store.load().expect("load"); + assert_eq!(loaded.version, state.version); + assert!(loaded.accounts.is_empty()); + } +} diff --git a/crates/nostr-accounts/src/vault.rs b/crates/nostr-accounts/src/vault.rs @@ -0,0 +1,152 @@ +use crate::error::RadrootsNostrAccountsError; +use radroots_identity::RadrootsIdentityId; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +pub trait RadrootsNostrSecretVault: Send + Sync { + fn store_secret_hex( + &self, + account_id: &RadrootsIdentityId, + secret_key_hex: &str, + ) -> Result<(), RadrootsNostrAccountsError>; + fn load_secret_hex( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<Option<String>, RadrootsNostrAccountsError>; + fn remove_secret(&self, account_id: &RadrootsIdentityId) + -> Result<(), RadrootsNostrAccountsError>; +} + +#[derive(Debug, Clone, Default)] +pub struct RadrootsNostrSecretVaultMemory { + entries: Arc<RwLock<HashMap<String, String>>>, +} + +impl RadrootsNostrSecretVaultMemory { + pub fn new() -> Self { + Self::default() + } +} + +impl RadrootsNostrSecretVault for RadrootsNostrSecretVaultMemory { + fn store_secret_hex( + &self, + account_id: &RadrootsIdentityId, + secret_key_hex: &str, + ) -> Result<(), RadrootsNostrAccountsError> { + let mut guard = self + .entries + .write() + .map_err(|_| RadrootsNostrAccountsError::Vault("memory vault poisoned".into()))?; + guard.insert(account_id.to_string(), secret_key_hex.to_owned()); + Ok(()) + } + + fn load_secret_hex( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<Option<String>, RadrootsNostrAccountsError> { + let guard = self + .entries + .read() + .map_err(|_| RadrootsNostrAccountsError::Vault("memory vault poisoned".into()))?; + Ok(guard.get(account_id.as_str()).cloned()) + } + + fn remove_secret( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<(), RadrootsNostrAccountsError> { + let mut guard = self + .entries + .write() + .map_err(|_| RadrootsNostrAccountsError::Vault("memory vault poisoned".into()))?; + guard.remove(account_id.as_str()); + Ok(()) + } +} + +#[cfg(feature = "os-keyring")] +#[derive(Debug, Clone)] +pub struct RadrootsNostrSecretVaultOsKeyring { + service_name: String, +} + +#[cfg(feature = "os-keyring")] +impl RadrootsNostrSecretVaultOsKeyring { + pub fn new(service_name: impl Into<String>) -> Self { + Self { + service_name: service_name.into(), + } + } +} + +#[cfg(feature = "os-keyring")] +impl Default for RadrootsNostrSecretVaultOsKeyring { + fn default() -> Self { + Self::new("org.radroots.nostr.accounts") + } +} + +#[cfg(feature = "os-keyring")] +impl RadrootsNostrSecretVault for RadrootsNostrSecretVaultOsKeyring { + fn store_secret_hex( + &self, + account_id: &RadrootsIdentityId, + secret_key_hex: &str, + ) -> Result<(), RadrootsNostrAccountsError> { + let entry = keyring::Entry::new(self.service_name.as_str(), account_id.as_str()) + .map_err(|source| RadrootsNostrAccountsError::Vault(source.to_string()))?; + entry + .set_password(secret_key_hex) + .map_err(|source| RadrootsNostrAccountsError::Vault(source.to_string())) + } + + fn load_secret_hex( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<Option<String>, RadrootsNostrAccountsError> { + let entry = keyring::Entry::new(self.service_name.as_str(), account_id.as_str()) + .map_err(|source| RadrootsNostrAccountsError::Vault(source.to_string()))?; + match entry.get_password() { + Ok(secret) => Ok(Some(secret)), + Err(keyring::Error::NoEntry) => Ok(None), + Err(source) => Err(RadrootsNostrAccountsError::Vault(source.to_string())), + } + } + + fn remove_secret( + &self, + account_id: &RadrootsIdentityId, + ) -> Result<(), RadrootsNostrAccountsError> { + let entry = keyring::Entry::new(self.service_name.as_str(), account_id.as_str()) + .map_err(|source| RadrootsNostrAccountsError::Vault(source.to_string()))?; + match entry.delete_password() { + Ok(_) | Err(keyring::Error::NoEntry) => Ok(()), + Err(source) => Err(RadrootsNostrAccountsError::Vault(source.to_string())), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use radroots_identity::RadrootsIdentityId; + + #[test] + fn memory_vault_round_trip() { + let vault = RadrootsNostrSecretVaultMemory::new(); + let account_id = RadrootsIdentityId::parse( + "3bf0c63f0f4478a288f6b67f0429dbf7f5119d4fa7218a4c40ef1378f80f7606", + ) + .expect("account id"); + vault + .store_secret_hex(&account_id, "abc123") + .expect("store"); + let loaded = vault.load_secret_hex(&account_id).expect("load"); + assert_eq!(loaded.as_deref(), Some("abc123")); + vault.remove_secret(&account_id).expect("remove"); + let loaded = vault.load_secret_hex(&account_id).expect("load"); + assert!(loaded.is_none()); + } +}