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:
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());
+ }
+}