Security: upgrade nostr stack 0.40->0.45 clearing 11 RustSec advisories
- nostr 0.40.0 -> 0.45.3, nostr-sdk 0.40.0 -> 0.45.2 (secp256k1 0.30) - fixes RUSTSEC-2026-0216/0219/0224/0225/0226/0227/0228/0229/0230/0231/0232 incl. forged-event signature-bypass and NIP-46 credential Debug leak - NIP-42 auth now explicit: SignerAuthenticator on every client path - EventBuilder::sign -> finalize_async; nip44 encrypt supplies random nonce - feed/signer receive loops moved to stream_events (receiver opens before the signer announces, preserving the ordering fix) - relay-pool replaced by in-SDK relay/gossip; try_connect().timeout() - tests: malformed NIP-44 payload rejection + secret-leak regression - suite: 115 backend tests green (113 preserved + 2 new); frontend untouched
This commit is contained in:
parent
bf10ae383a
commit
fa5ba08578
8 changed files with 344 additions and 232 deletions
353
Cargo.lock
generated
353
Cargo.lock
generated
|
|
@ -37,15 +37,6 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argon2"
|
name = "argon2"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -78,29 +69,24 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-wsocket"
|
name = "async-wsocket"
|
||||||
version = "0.13.2"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c92385c7c8b3eb2de1b78aeca225212e4c9a69a78b802832759b108681a5069"
|
checksum = "2c713e1f14c7b82e32ea159af1c6e2f070cfadbdf23fb2512acce9af0a26f1a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-happy-eyeballs",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-socks",
|
"tokio-socks",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic-destructor"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
|
|
@ -115,9 +101,9 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bech32"
|
name = "bech32"
|
||||||
version = "0.11.1"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
|
checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bip39"
|
name = "bip39"
|
||||||
|
|
@ -125,7 +111,7 @@ version = "2.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc"
|
checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin_hashes",
|
"bitcoin_hashes 0.14.101",
|
||||||
"serde",
|
"serde",
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
@ -164,6 +150,17 @@ checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin-io",
|
"bitcoin-io",
|
||||||
"hex-conservative 0.2.2",
|
"hex-conservative 0.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitcoin_hashes"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5304e53726dbe5f93141535e102ed97b5bf4714fbecefdda8f9fb98d7fdaff0e"
|
||||||
|
dependencies = [
|
||||||
|
"bitcoin-consensus-encoding",
|
||||||
|
"bitcoin-internals",
|
||||||
|
"hex-conservative 1.2.0",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -206,6 +203,12 @@ version = "3.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.12.1"
|
version = "1.12.1"
|
||||||
|
|
@ -345,6 +348,16 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "faster-hex"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73"
|
||||||
|
dependencies = [
|
||||||
|
"heapless",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
|
|
@ -368,6 +381,7 @@ checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
|
@ -390,12 +404,34 @@ version = "0.3.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
|
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
|
|
@ -417,6 +453,7 @@ dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -441,10 +478,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -468,6 +503,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi 6.0.0",
|
"r-efi 6.0.0",
|
||||||
|
"rand_core 0.10.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -492,6 +528,25 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hash32"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heapless"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||||
|
dependencies = [
|
||||||
|
"hash32",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
@ -516,15 +571,6 @@ dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hmac"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
|
||||||
dependencies = [
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -655,18 +701,6 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "instant"
|
|
||||||
version = "0.1.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -732,9 +766,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
version = "0.13.0"
|
version = "0.18.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
|
checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
|
|
@ -753,12 +787,6 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "negentropy"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "negentropy"
|
name = "negentropy"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -767,70 +795,70 @@ checksum = "81c353b400a5503efdcf398f11a83fb7aa84f59f5d76fc4bf5bbc1e4f5366caa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.40.0"
|
version = "0.45.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f900ddcdc28395759fcd44b18a03255e7deee8858551bfe5d5d5a07311d82ea"
|
checksum = "b0ba32ce43631188586469ba1a4c40bcfa63641f1ad5de89ef77f74d801cc17a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
|
||||||
"base64",
|
"base64",
|
||||||
"bech32",
|
"bech32",
|
||||||
"bip39",
|
"bip39",
|
||||||
"bitcoin_hashes",
|
"bitcoin_hashes 1.2.0",
|
||||||
"cbc",
|
"cbc",
|
||||||
"chacha20",
|
"chacha20",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"getrandom 0.2.17",
|
"faster-hex",
|
||||||
"instant",
|
"opaquerr",
|
||||||
"regex",
|
"rand 0.10.2",
|
||||||
"scrypt",
|
|
||||||
"secp256k1",
|
"secp256k1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
|
"universal-time",
|
||||||
"url",
|
"url",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.40.0"
|
version = "0.45.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "714512e4653f4e7c4f4abb50a0ac82257541b22087dee780b9e3d787276e88d4"
|
checksum = "4b1fdb9fcba732e32719662afad1b267e50322dbe89e506017ec13f24361bddf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lru",
|
|
||||||
"nostr",
|
"nostr",
|
||||||
"tokio",
|
"opaquerr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-gossip"
|
||||||
version = "0.40.1"
|
version = "0.45.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5bde07a729e0a1b306c9a07da81a0d1d55d0487316017090906f3b6660741b8d"
|
checksum = "fa07539e52a71cb91fe0d693facaa298f03fcf9edcd66a521094e18e286e2336"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
|
||||||
"async-wsocket",
|
|
||||||
"atomic-destructor",
|
|
||||||
"lru",
|
|
||||||
"negentropy 0.3.1",
|
|
||||||
"negentropy 0.5.1",
|
|
||||||
"nostr",
|
"nostr",
|
||||||
"nostr-database",
|
"opaquerr",
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.40.0"
|
version = "0.45.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26238eee805d7dc3abcc8d570068c81cb4285b08e9db4d7999e54e20748c472e"
|
checksum = "245f8642b10feecb0a40739a886ca1850e3be4e35dc6592b806ec437403ab9c9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
|
"async-wsocket",
|
||||||
|
"faster-hex",
|
||||||
|
"futures",
|
||||||
|
"lru",
|
||||||
|
"negentropy",
|
||||||
"nostr",
|
"nostr",
|
||||||
"nostr-database",
|
"nostr-database",
|
||||||
"nostr-relay-pool",
|
"nostr-gossip",
|
||||||
|
"opaquerr",
|
||||||
|
"rand 0.10.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"universal-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -845,6 +873,12 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaquerr"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f933a4265d5cdad61d19bbdfc972ea5726d56cd8d3d57b8f2d3c365dd42bee9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -879,16 +913,6 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pbkdf2"
|
|
||||||
version = "0.12.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
|
||||||
dependencies = [
|
|
||||||
"digest",
|
|
||||||
"hmac",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -974,9 +998,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.7"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha 0.3.1",
|
||||||
|
|
@ -993,6 +1017,16 @@ dependencies = [
|
||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.4.3",
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
@ -1031,6 +1065,12 @@ dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
|
|
@ -1040,35 +1080,6 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
|
|
@ -1144,42 +1155,21 @@ version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "salsa20"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
|
||||||
dependencies = [
|
|
||||||
"cipher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scrypt"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
|
|
||||||
dependencies = [
|
|
||||||
"password-hash",
|
|
||||||
"pbkdf2",
|
|
||||||
"salsa20",
|
|
||||||
"sha2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "secp256k1"
|
name = "secp256k1"
|
||||||
version = "0.29.1"
|
version = "0.30.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.8.7",
|
"bitcoin_hashes 0.14.101",
|
||||||
|
"rand 0.8.8",
|
||||||
"secp256k1-sys",
|
"secp256k1-sys",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1245,17 +1235,6 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha2"
|
|
||||||
version = "0.10.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures",
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
|
@ -1421,6 +1400,15 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-happy-eyeballs"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8564c32dfb6f4257f8bc6edfc178a34af97520e0b7b9815500c55eb3d092f29f"
|
||||||
|
dependencies = [
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.7.2"
|
version = "2.7.2"
|
||||||
|
|
@ -1455,10 +1443,22 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-tungstenite"
|
name = "tokio-stream"
|
||||||
version = "0.26.2"
|
version = "0.1.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -1470,6 +1470,19 @@ dependencies = [
|
||||||
"webpki-roots 0.26.11",
|
"webpki-roots 0.26.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.7.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
|
@ -1477,21 +1490,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-attributes"
|
|
||||||
version = "0.1.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.119",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.36"
|
version = "0.1.36"
|
||||||
|
|
@ -1503,9 +1504,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.26.2"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
|
|
@ -1551,6 +1552,12 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-time"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47a939edecc3c5a7b83c02e5f6b3c31d2bc69eabcc9a87ab12c6d37ee6dbc856"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nostr = { version = "0.40", features = ["nip44", "nip46"] }
|
nostr = { version = "0.45", features = ["nip44", "nip98"] }
|
||||||
nostr-sdk = { version = "0.40", features = ["nip44", "nip98"] }
|
nostr-sdk = "0.45"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
|
||||||
53
src/feed.rs
53
src/feed.rs
|
|
@ -102,32 +102,30 @@ async fn contact_pubkeys(settings: &Settings, owner_hex: &str) -> Result<Vec<Pub
|
||||||
crate::relays::open_pool(Keys::generate(), &relay_urls, Some(CONNECT_TIMEOUT)).await?;
|
crate::relays::open_pool(Keys::generate(), &relay_urls, Some(CONNECT_TIMEOUT)).await?;
|
||||||
|
|
||||||
let filter = Filter::new().kind(Kind::ContactList).author(owner);
|
let filter = Filter::new().kind(Kind::ContactList).author(owner);
|
||||||
client
|
let mut events = client
|
||||||
.subscribe(filter, None)
|
.stream_events(filter)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::network(format!("Could not subscribe for contacts: {e}")))?;
|
.map_err(|e| AppError::network(format!("Could not subscribe for contacts: {e}")))?;
|
||||||
|
|
||||||
let mut contacts: HashSet<PublicKey> = HashSet::new();
|
let mut contacts: HashSet<PublicKey> = HashSet::new();
|
||||||
let mut notifications = client.notifications();
|
|
||||||
let deadline = tokio::time::Instant::now() + QUERY_TIMEOUT;
|
let deadline = tokio::time::Instant::now() + QUERY_TIMEOUT;
|
||||||
loop {
|
loop {
|
||||||
match tokio::time::timeout_at(deadline, notifications.recv()).await {
|
match tokio::time::timeout_at(deadline, events.next()).await {
|
||||||
Ok(Ok(RelayPoolNotification::Event { event, .. })) => {
|
Ok(Some((_, Ok(event)))) => {
|
||||||
if event.kind == Kind::ContactList {
|
if event.kind == Kind::ContactList {
|
||||||
for pubkey in event
|
for tag in event.tags.iter() {
|
||||||
.tags
|
if tag.single_letter_tag().map(|s| s.as_char()) == Some('p') {
|
||||||
.iter()
|
if let Some(content) = tag.content() {
|
||||||
.filter_map(|tag| match tag.as_standardized() {
|
if let Ok(pubkey) = PublicKey::parse(content) {
|
||||||
Some(TagStandard::PublicKey { public_key, .. }) => Some(*public_key),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
contacts.insert(pubkey);
|
contacts.insert(pubkey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Ok(_)) => continue,
|
}
|
||||||
Ok(Err(_)) | Err(_) => break,
|
}
|
||||||
|
}
|
||||||
|
Ok(Some((_, Err(_)))) => continue,
|
||||||
|
Ok(None) | Err(_) => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,25 +161,22 @@ async fn aggregate_for(
|
||||||
Some(authors) => filter.authors(authors.iter().copied()),
|
Some(authors) => filter.authors(authors.iter().copied()),
|
||||||
None => filter,
|
None => filter,
|
||||||
};
|
};
|
||||||
client
|
let mut events = client
|
||||||
.subscribe(filter, None)
|
.stream_events(filter)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::network(format!("Could not subscribe for the feed: {e}")))?;
|
.map_err(|e| AppError::network(format!("Could not subscribe for the feed: {e}")))?;
|
||||||
|
|
||||||
let mut feed = FeedBuilder::new(effective_limit, authors.as_deref());
|
let mut feed = FeedBuilder::new(effective_limit, authors.as_deref());
|
||||||
let mut notifications = client.notifications();
|
|
||||||
let deadline = tokio::time::Instant::now() + QUERY_TIMEOUT;
|
let deadline = tokio::time::Instant::now() + QUERY_TIMEOUT;
|
||||||
loop {
|
loop {
|
||||||
match tokio::time::timeout_at(deadline, notifications.recv()).await {
|
match tokio::time::timeout_at(deadline, events.next()).await {
|
||||||
Ok(Ok(RelayPoolNotification::Event {
|
Ok(Some((relay_url, Ok(event)))) => {
|
||||||
event, relay_url, ..
|
|
||||||
})) => {
|
|
||||||
if !feed.add(&event, Some(relay_url)) {
|
if !feed.add(&event, Some(relay_url)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Ok(_)) => continue,
|
Ok(Some((_, Err(_)))) => continue,
|
||||||
Ok(Err(_)) | Err(_) => break,
|
Ok(None) | Err(_) => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +257,7 @@ impl FeedItem {
|
||||||
author: event.pubkey.to_hex(),
|
author: event.pubkey.to_hex(),
|
||||||
author_npub,
|
author_npub,
|
||||||
content: event.content.trim().to_string(),
|
content: event.content.trim().to_string(),
|
||||||
created_at: event.created_at.as_u64(),
|
created_at: event.created_at.as_secs(),
|
||||||
relays: relay.map(|url| vec![url.to_string()]).unwrap_or_default(),
|
relays: relay.map(|url| vec![url.to_string()]).unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +271,7 @@ mod tests {
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
EventBuilder::new(Kind::TextNote, content.to_string())
|
EventBuilder::new(Kind::TextNote, content.to_string())
|
||||||
.custom_created_at(Timestamp::from(created_at))
|
.custom_created_at(Timestamp::from(created_at))
|
||||||
.sign(&keys)
|
.finalize_async(&keys)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
@ -333,7 +328,7 @@ mod tests {
|
||||||
let mut builder = FeedBuilder::new(10, None);
|
let mut builder = FeedBuilder::new(10, None);
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let other = EventBuilder::new(Kind::Metadata, "{}")
|
let other = EventBuilder::new(Kind::Metadata, "{}")
|
||||||
.sign(&keys)
|
.finalize_async(&keys)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder.add(&other, None);
|
builder.add(&other, None);
|
||||||
|
|
@ -363,12 +358,12 @@ mod tests {
|
||||||
|
|
||||||
let from_followed = EventBuilder::new(Kind::TextNote, "from a contact".to_string())
|
let from_followed = EventBuilder::new(Kind::TextNote, "from a contact".to_string())
|
||||||
.custom_created_at(Timestamp::from(5))
|
.custom_created_at(Timestamp::from(5))
|
||||||
.sign(&followed)
|
.finalize_async(&followed)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let from_stranger = EventBuilder::new(Kind::TextNote, "from a stranger".to_string())
|
let from_stranger = EventBuilder::new(Kind::TextNote, "from a stranger".to_string())
|
||||||
.custom_created_at(Timestamp::from(6))
|
.custom_created_at(Timestamp::from(6))
|
||||||
.sign(&stranger)
|
.finalize_async(&stranger)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -431,7 +431,7 @@ async fn publish_metadata_async(
|
||||||
metadata = metadata.nip05(nip05);
|
metadata = metadata.nip05(nip05);
|
||||||
}
|
}
|
||||||
let event = match EventBuilder::new(Kind::Metadata, metadata.as_json())
|
let event = match EventBuilder::new(Kind::Metadata, metadata.as_json())
|
||||||
.sign(keys)
|
.finalize_async(keys)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(event) => event,
|
Ok(event) => event,
|
||||||
|
|
@ -450,7 +450,12 @@ async fn publish_metadata_async(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = Client::new(keys.clone());
|
// This path deliberately builds its own client instead of
|
||||||
|
// `relays::open_pool`: adding a broken relay must not abort the whole
|
||||||
|
// metadata publish, so add errors are ignored here.
|
||||||
|
let client = Client::builder()
|
||||||
|
.authenticator(SignerAuthenticator::new(keys.clone()))
|
||||||
|
.build();
|
||||||
for url in &relay_urls {
|
for url in &relay_urls {
|
||||||
let _ = client.add_relay(url.as_str()).await;
|
let _ = client.add_relay(url.as_str()).await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ async fn publish_with_keys(
|
||||||
// reported as such rather than as a network error.
|
// reported as such rather than as a network error.
|
||||||
let builder = EventBuilder::new(Kind::TextNote, content.to_string()).tags(image_tags(content));
|
let builder = EventBuilder::new(Kind::TextNote, content.to_string()).tags(image_tags(content));
|
||||||
let event = builder
|
let event = builder
|
||||||
.sign(keys)
|
.finalize_async(keys)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::sign_failed(format!("{e}")))?;
|
.map_err(|e| AppError::sign_failed(format!("{e}")))?;
|
||||||
|
|
||||||
|
|
@ -220,7 +220,7 @@ pub(crate) async fn send_to_all_relays(
|
||||||
let noun = noun.clone();
|
let noun = noun.clone();
|
||||||
sends.spawn(async move {
|
sends.spawn(async move {
|
||||||
match client.relay(url.as_str()).await {
|
match client.relay(url.as_str()).await {
|
||||||
Ok(relay) => {
|
Ok(Some(relay)) => {
|
||||||
match tokio::time::timeout(RELAY_SEND_TIMEOUT, relay.send_event(&event)).await {
|
match tokio::time::timeout(RELAY_SEND_TIMEOUT, relay.send_event(&event)).await {
|
||||||
Ok(Ok(_)) => (index, Ok(url)),
|
Ok(Ok(_)) => (index, Ok(url)),
|
||||||
Ok(Err(err)) => {
|
Ok(Err(err)) => {
|
||||||
|
|
@ -246,6 +246,14 @@ pub(crate) async fn send_to_all_relays(
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(None) => (
|
||||||
|
index,
|
||||||
|
Err(RelayFailure {
|
||||||
|
url,
|
||||||
|
error: "Relay is not in the active pool.".to_string(),
|
||||||
|
details: Some("The client dropped this relay before sending.".to_string()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let (message, details) = relay_error_message(&err);
|
let (message, details) = relay_error_message(&err);
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -79,16 +79,21 @@ pub(crate) async fn open_pool(
|
||||||
relay_urls: &[String],
|
relay_urls: &[String],
|
||||||
wait: Option<Duration>,
|
wait: Option<Duration>,
|
||||||
) -> Result<Client, AppError> {
|
) -> Result<Client, AppError> {
|
||||||
let client = Client::new(keys);
|
// The authenticator answers NIP-42 AUTH challenges automatically on every
|
||||||
|
// path that opens a client (nostr-sdk >= 0.45 has no implicit signer).
|
||||||
|
let client = Client::builder()
|
||||||
|
.authenticator(SignerAuthenticator::new(keys))
|
||||||
|
.build();
|
||||||
for url in relay_urls {
|
for url in relay_urls {
|
||||||
client
|
client
|
||||||
.add_relay(url.as_str())
|
.add_relay(url.as_str())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::network(format!("Could not add relay {url}: {e}")))?;
|
.map_err(|e| AppError::network(format!("Could not add relay {url}: {e}")))?;
|
||||||
}
|
}
|
||||||
client.connect().await;
|
// Deprecated `wait_for_connection`; the builder form is the 0.45 way.
|
||||||
if let Some(timeout) = wait {
|
match wait {
|
||||||
client.wait_for_connection(timeout).await;
|
Some(timeout) => client.connect().and_wait(timeout).await,
|
||||||
|
None => client.connect().await,
|
||||||
}
|
}
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +112,9 @@ pub struct RelayTestResult {
|
||||||
/// during a connection test.
|
/// during a connection test.
|
||||||
pub async fn test_connection(url: &str, timeout: Duration) -> Result<RelayTestResult, AppError> {
|
pub async fn test_connection(url: &str, timeout: Duration) -> Result<RelayTestResult, AppError> {
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let client = Client::new(keys);
|
let client = Client::builder()
|
||||||
|
.authenticator(SignerAuthenticator::new(keys))
|
||||||
|
.build();
|
||||||
|
|
||||||
client
|
client
|
||||||
.add_relay(url)
|
.add_relay(url)
|
||||||
|
|
@ -117,10 +124,12 @@ pub async fn test_connection(url: &str, timeout: Duration) -> Result<RelayTestRe
|
||||||
let relay = client
|
let relay = client
|
||||||
.relay(url)
|
.relay(url)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::network(format!("Could not look up relay {url}: {e}")))?;
|
.map_err(|e| AppError::network(format!("Could not look up relay {url}: {e}")))?
|
||||||
|
.ok_or_else(|| AppError::network(format!("Relay {url} is not in the pool")))?;
|
||||||
|
|
||||||
relay
|
relay
|
||||||
.try_connect(timeout)
|
.try_connect()
|
||||||
|
.timeout(timeout)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::network(format!("Connection failed: {e}")))?;
|
.map_err(|e| AppError::network(format!("Connection failed: {e}")))?;
|
||||||
|
|
||||||
|
|
|
||||||
117
src/signer.rs
117
src/signer.rs
|
|
@ -17,9 +17,9 @@ use std::time::Duration;
|
||||||
|
|
||||||
use base64::engine::general_purpose::STANDARD as B64;
|
use base64::engine::general_purpose::STANDARD as B64;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
use getrandom::getrandom;
|
||||||
use nostr::nips::nip44::v2;
|
use nostr::nips::nip44::v2;
|
||||||
use nostr::nips::nip44::v2::ConversationKey;
|
use nostr::nips::nip44::v2::ConversationKey;
|
||||||
use nostr::JsonUtil;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
@ -363,7 +363,11 @@ fn percent_decode(raw: &str) -> Option<String> {
|
||||||
|
|
||||||
/// NIP-44 encrypt with the conversation key, returned base64-encoded.
|
/// NIP-44 encrypt with the conversation key, returned base64-encoded.
|
||||||
fn nip44_encrypt(conversation: &ConversationKey, plaintext: &str) -> Result<String, AppError> {
|
fn nip44_encrypt(conversation: &ConversationKey, plaintext: &str) -> Result<String, AppError> {
|
||||||
let payload = v2::encrypt_to_bytes(conversation, plaintext.as_bytes())
|
// nostr-sdk 0.45 removed the convenience wrapper that generated the nonce
|
||||||
|
// internally; the caller now supplies fresh randomness per message.
|
||||||
|
let mut nonce = [0u8; 32];
|
||||||
|
getrandom(&mut nonce).map_err(|e| AppError::internal(format!("Could not get entropy: {e}")))?;
|
||||||
|
let payload = v2::encrypt_to_bytes_with_nonce(conversation, plaintext.as_bytes(), nonce)
|
||||||
.map_err(|e| AppError::internal(format!("Could not encrypt a message: {e}")))?;
|
.map_err(|e| AppError::internal(format!("Could not encrypt a message: {e}")))?;
|
||||||
Ok(B64.encode(payload))
|
Ok(B64.encode(payload))
|
||||||
}
|
}
|
||||||
|
|
@ -570,8 +574,8 @@ fn sign_event(keys: &Keys, request: &RawRequest) -> Result<String, String> {
|
||||||
|
|
||||||
let unsigned: UnsignedEvent =
|
let unsigned: UnsignedEvent =
|
||||||
serde_json::from_value(value).map_err(|e| format!("Invalid event: {e}"))?;
|
serde_json::from_value(value).map_err(|e| format!("Invalid event: {e}"))?;
|
||||||
let event = unsigned
|
let event = keys
|
||||||
.sign_with_keys(keys)
|
.sign_event(unsigned)
|
||||||
.map_err(|e| format!("The event could not be signed: {e}"))?;
|
.map_err(|e| format!("The event could not be signed: {e}"))?;
|
||||||
Ok(event.as_json())
|
Ok(event.as_json())
|
||||||
}
|
}
|
||||||
|
|
@ -631,23 +635,30 @@ async fn run_sign_task(signer: Signer, app: Arc<tokio::sync::Mutex<App>>, uri: C
|
||||||
// announcement within milliseconds, and a receiver created afterwards
|
// announcement within milliseconds, and a receiver created afterwards
|
||||||
// would miss those early messages (tokio broadcast semantics), leaving
|
// would miss those early messages (tokio broadcast semantics), leaving
|
||||||
// the client waiting forever for a reply.
|
// the client waiting forever for a reply.
|
||||||
let client = Client::new(keys.clone());
|
let client = Client::builder()
|
||||||
|
.authenticator(SignerAuthenticator::new(keys.clone()))
|
||||||
|
.build();
|
||||||
for url in &uri.relays {
|
for url in &uri.relays {
|
||||||
if let Err(err) = client.add_relay(url.to_string()).await {
|
if let Err(err) = client.add_relay(url.to_string()).await {
|
||||||
signer.fail(format!("Could not add relay {url}: {err}"));
|
signer.fail(format!("Could not add relay {url}: {err}"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut notifications = client.notifications();
|
client.connect().and_wait(CONNECT_TIMEOUT).await;
|
||||||
client.connect().await;
|
|
||||||
client.wait_for_connection(CONNECT_TIMEOUT).await;
|
|
||||||
|
|
||||||
// 4. Subscribe to the client's kind 24133 events so we hear its requests.
|
// 4. Subscribe to the client's kind 24133 events so we hear its requests.
|
||||||
|
// `stream_events` opens its internal notification receiver as part of
|
||||||
|
// subscribing, which must happen BEFORE we announce (step 5): the client
|
||||||
|
// acknowledges within milliseconds and a receiver created afterwards would
|
||||||
|
// miss those early messages (tokio broadcast semantics).
|
||||||
let filter = Filter::new().kind(Kind::NostrConnect).author(uri.peer);
|
let filter = Filter::new().kind(Kind::NostrConnect).author(uri.peer);
|
||||||
if let Err(err) = client.subscribe(filter, None).await {
|
let mut events = match client.stream_events(filter).await {
|
||||||
|
Ok(events) => events,
|
||||||
|
Err(err) => {
|
||||||
signer.fail(format!("Could not subscribe for messages: {err}"));
|
signer.fail(format!("Could not subscribe for messages: {err}"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 5. Announce ourselves: send the connect request with the optional secret.
|
// 5. Announce ourselves: send the connect request with the optional secret.
|
||||||
if let Err(err) = send_connect(&client, &keys, &conversation, &uri).await {
|
if let Err(err) = send_connect(&client, &keys, &conversation, &uri).await {
|
||||||
|
|
@ -657,16 +668,18 @@ async fn run_sign_task(signer: Signer, app: Arc<tokio::sync::Mutex<App>>, uri: C
|
||||||
|
|
||||||
// 6. Answer requests until the connection goes away or we are stopped.
|
// 6. Answer requests until the connection goes away or we are stopped.
|
||||||
loop {
|
loop {
|
||||||
let notification = match notifications.recv().await {
|
let (relay_url, incoming) = match events.next().await {
|
||||||
Ok(notification) => notification,
|
Some(next) => next,
|
||||||
Err(_) => {
|
None => {
|
||||||
signer.fail("The signer connection was closed.");
|
signer.fail("The signer connection was closed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let RelayPoolNotification::Event { event, .. } = notification else {
|
let event = match incoming {
|
||||||
continue;
|
Ok(event) => event,
|
||||||
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
let _ = relay_url;
|
||||||
if event.kind != Kind::NostrConnect || event.pubkey != uri.peer {
|
if event.kind != Kind::NostrConnect || event.pubkey != uri.peer {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -728,7 +741,7 @@ async fn publish_payload(
|
||||||
let tag = Tag::parse(["p", peer.to_hex().as_str()]).map_err(|e| format!("{e}"))?;
|
let tag = Tag::parse(["p", peer.to_hex().as_str()]).map_err(|e| format!("{e}"))?;
|
||||||
let event = EventBuilder::new(Kind::NostrConnect, content)
|
let event = EventBuilder::new(Kind::NostrConnect, content)
|
||||||
.tags([tag])
|
.tags([tag])
|
||||||
.sign(keys)
|
.finalize_async(keys)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Could not sign a message: {e}"))?;
|
.map_err(|e| format!("Could not sign a message: {e}"))?;
|
||||||
client
|
client
|
||||||
|
|
@ -742,6 +755,80 @@ async fn publish_payload(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// Malformed NIP-44 payloads (RUSTSEC-2026-0216/0227 classes) must come
|
||||||
|
/// back as clean errors, never a panic or a hang. A payload that fails to
|
||||||
|
/// decrypt against the conversation key is also exactly how forged signer
|
||||||
|
/// messages from a non-peer key are rejected.
|
||||||
|
#[test]
|
||||||
|
fn nip44_decrypt_rejects_malformed_payloads() {
|
||||||
|
let signer = Keys::generate();
|
||||||
|
let peer = Keys::generate();
|
||||||
|
let conversation =
|
||||||
|
ConversationKey::derive(signer.secret_key(), &peer.public_key()).unwrap();
|
||||||
|
|
||||||
|
let not_base64 = "this is !! not base64 !!";
|
||||||
|
let empty = "";
|
||||||
|
let too_short = B64.encode([0x02u8, 0x00]);
|
||||||
|
let bad_version = B64.encode([0xFFu8, 0x00, 0x11]);
|
||||||
|
let truncated = {
|
||||||
|
// encrypt returns raw bytes; nip44_decrypt takes their base64 form.
|
||||||
|
let mut bytes = v2::encrypt_to_bytes_with_nonce(
|
||||||
|
&conversation,
|
||||||
|
"a message long enough to be truncated meaningfully".as_bytes(),
|
||||||
|
[7u8; 32],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
bytes.truncate(bytes.len() / 2);
|
||||||
|
B64.encode(&bytes)
|
||||||
|
};
|
||||||
|
|
||||||
|
for payload in [
|
||||||
|
not_base64,
|
||||||
|
empty,
|
||||||
|
&too_short,
|
||||||
|
&bad_version,
|
||||||
|
truncated.as_str(),
|
||||||
|
] {
|
||||||
|
let result = nip44_decrypt(&conversation, payload);
|
||||||
|
assert!(result.is_err(), "payload {payload:?} must be rejected");
|
||||||
|
if let Err(err) = result {
|
||||||
|
assert!(
|
||||||
|
!err.message().is_empty(),
|
||||||
|
"rejection of {payload:?} must carry a concise reason"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NIP-46 credential-leakage regression (RUSTSEC-2026-0225 class): error
|
||||||
|
/// strings surfaced to callers or logs must never contain secret-key hex.
|
||||||
|
#[test]
|
||||||
|
fn error_paths_never_leak_secret_key_material() {
|
||||||
|
let signer = Keys::generate();
|
||||||
|
let sk_hex = hex::encode(signer.secret_key().secret_bytes());
|
||||||
|
assert_eq!(sk_hex.len(), 64);
|
||||||
|
let peer = Keys::generate();
|
||||||
|
let conversation =
|
||||||
|
ConversationKey::derive(signer.secret_key(), &peer.public_key()).unwrap();
|
||||||
|
|
||||||
|
// Collect the human-readable reasons our failure paths produce.
|
||||||
|
let mut failures = Vec::new();
|
||||||
|
if let Err(err) = nip44_decrypt(&conversation, "not-base64 !!!") {
|
||||||
|
failures.push(err.message().to_string());
|
||||||
|
}
|
||||||
|
if let Err(err) = nip44_decrypt(&conversation, &B64.encode([0xFFu8, 0x00, 0x11])) {
|
||||||
|
failures.push(err.message().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(!failures.is_empty(), "expected at least one failure path");
|
||||||
|
for message in failures {
|
||||||
|
assert!(
|
||||||
|
!message.to_lowercase().contains(&sk_hex),
|
||||||
|
"error text leaked secret-key material: {message}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_uri_with_relays_and_secret() {
|
fn parse_uri_with_relays_and_secret() {
|
||||||
let uri = parse_connect_uri(
|
let uri = parse_connect_uri(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use nostr::nips::nip98::{HttpData, HttpMethod};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
use crate::crypto::VaultKey;
|
use crate::crypto::VaultKey;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue