add nip11

This commit is contained in:
DanConwayDev
2025-11-21 04:44:40 +00:00
parent 2e799fa7ec
commit 7dda553918
7 changed files with 929 additions and 57 deletions
Generated
+510 -17
View File
@@ -163,6 +163,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
@@ -225,6 +231,12 @@ dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
@@ -380,6 +392,16 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[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"
@@ -446,12 +468,37 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
@@ -464,6 +511,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -618,6 +680,8 @@ dependencies = [
"clap",
"futures",
"nostr-sdk 0.43.0",
"regex",
"reqwest",
"serde",
"serde_json",
"thiserror 1.0.69",
@@ -628,6 +692,25 @@ dependencies = [
"uuid",
]
[[package]]
name = "h2"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "h2"
version = "0.4.12"
@@ -639,7 +722,7 @@ dependencies = [
"fnv",
"futures-core",
"futures-sink",
"http",
"http 1.3.1",
"indexmap",
"slab",
"tokio",
@@ -689,6 +772,17 @@ dependencies = [
"digest",
]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.3.1"
@@ -700,6 +794,17 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]]
name = "http-body"
version = "1.0.1"
@@ -707,7 +812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
"http 1.3.1",
]
[[package]]
@@ -718,8 +823,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"http 1.3.1",
"http-body 1.0.1",
"pin-project-lite",
]
@@ -735,6 +840,30 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.27",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper"
version = "1.8.1"
@@ -745,9 +874,9 @@ dependencies = [
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"h2 0.4.12",
"http 1.3.1",
"http-body 1.0.1",
"httparse",
"httpdate",
"itoa",
@@ -758,6 +887,19 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper 0.14.32",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "hyper-util"
version = "0.1.18"
@@ -766,9 +908,9 @@ checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"hyper",
"http 1.3.1",
"http-body 1.0.1",
"hyper 1.8.1",
"pin-project-lite",
"tokio",
]
@@ -931,6 +1073,12 @@ dependencies = [
"web-sys",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -965,6 +1113,12 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.8.1"
@@ -1007,6 +1161,12 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mio"
version = "1.1.0"
@@ -1018,6 +1178,23 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "negentropy"
version = "0.5.0"
@@ -1029,12 +1206,12 @@ name = "ngit-grasp"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"base64 0.22.1",
"dotenvy",
"futures-util",
"grasp-audit",
"http-body-util",
"hyper",
"hyper 1.8.1",
"hyper-util",
"nostr-relay-builder",
"nostr-sdk 0.44.1",
@@ -1054,7 +1231,7 @@ version = "0.43.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62a97d745f1bd8d5e05a978632bbb87b0614567d5142906fe7c86fb2440faac6"
dependencies = [
"base64",
"base64 0.22.1",
"bech32",
"bip39",
"bitcoin_hashes 0.14.0",
@@ -1077,7 +1254,7 @@ version = "0.44.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3595fecf0e0aaacb69a0dc0101a4453f3c76eda333d6bbc49f68f64390b3d85"
dependencies = [
"base64",
"base64 0.22.1",
"bech32",
"bip39",
"bitcoin_hashes 0.14.0",
@@ -1242,6 +1419,50 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [
"bitflags 2.10.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -1304,6 +1525,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "poly1305"
version = "0.8.0"
@@ -1422,7 +1649,19 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.10.0",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
@@ -1442,6 +1681,46 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.3.27",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.32",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]]
name = "ring"
version = "0.17.14"
@@ -1456,6 +1735,19 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.34"
@@ -1470,6 +1762,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pki-types"
version = "1.13.0"
@@ -1511,6 +1812,15 @@ dependencies = [
"cipher",
]
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1549,6 +1859,29 @@ dependencies = [
"cc",
]
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.10.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.228"
@@ -1592,6 +1925,18 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha1"
version = "0.10.6"
@@ -1650,6 +1995,16 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.1"
@@ -1689,6 +2044,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "synstructure"
version = "0.13.2"
@@ -1700,6 +2061,40 @@ dependencies = [
"syn",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tempfile"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -1786,7 +2181,7 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"socket2 0.6.1",
"tokio-macros",
"windows-sys 0.61.2",
]
@@ -1802,6 +2197,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
@@ -1877,6 +2282,12 @@ dependencies = [
"tokio",
]
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
@@ -1952,7 +2363,7 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
"bytes",
"data-encoding",
"http",
"http 1.3.1",
"httparse",
"log",
"rand 0.9.2",
@@ -2047,6 +2458,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -2222,6 +2639,15 @@ dependencies = [
"windows-link",
]
[[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"
@@ -2249,6 +2675,21 @@ dependencies = [
"windows-link",
]
[[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"
@@ -2282,6 +2723,12 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[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"
@@ -2294,6 +2741,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[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"
@@ -2306,6 +2759,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[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"
@@ -2330,6 +2789,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[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"
@@ -2342,6 +2807,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[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"
@@ -2354,6 +2825,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[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"
@@ -2366,6 +2843,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[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"
@@ -2378,6 +2861,16 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
+4
View File
@@ -33,6 +33,10 @@ clap = { version = "4", features = ["derive"] }
uuid = { version = "1", features = ["v4"] }
chrono = "0.4"
# HTTP Client
reqwest = { version = "0.11", features = ["json"] }
regex = "1"
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+19
View File
@@ -84,6 +84,25 @@ impl AuditClient {
self.keys.public_key()
}
/// Get the relay URL
pub async fn relay_url(&self) -> Result<String> {
let relays = self.client.relays().await;
let relay = relays.values().next()
.ok_or_else(|| anyhow!("No relays configured"))?;
Ok(relay.url().to_string())
}
/// Convert WebSocket URL to HTTP(S) URL for NIP-11 requests
pub fn ws_to_http_url(ws_url: &str) -> Result<String> {
if ws_url.starts_with("ws://") {
Ok(ws_url.replace("ws://", "http://"))
} else if ws_url.starts_with("wss://") {
Ok(ws_url.replace("wss://", "https://"))
} else {
Err(anyhow!("Invalid WebSocket URL: {}", ws_url))
}
}
/// Check if connected to relay
pub async fn is_connected(&self) -> bool {
// Check if we have any connected relays
+161 -40
View File
@@ -34,25 +34,53 @@ impl Nip11DocumentTests {
///
/// Spec: Line 11 of ../grasp/01.md
/// Requirement: MUST serve NIP-11 document
async fn test_nip11_document_exists(_client: &AuditClient) -> TestResult {
pub async fn test_nip11_document_exists(client: &AuditClient) -> TestResult {
TestResult::new(
"nip11_document_exists",
"GRASP-01:nostr-relay:11",
"Serve NIP-11 relay information document",
)
.run(|| async {
// TODO: Implementation
// 1. Extract HTTP(S) URL from client's WebSocket URL
// - ws://localhost:8081 -> http://localhost:8081
// - wss://relay.example.com -> https://relay.example.com
// 2. HTTP GET to base URL with header:
// - Accept: application/nostr+json
// 3. Verify 200 OK response
// 4. Verify response is valid JSON
// 5. Parse as NIP-11 document
// 6. Verify has required fields (name, description, etc.)
let ws_url = client.relay_url().await
.map_err(|e| format!("Failed to get relay URL: {}", e))?;
let http_url = AuditClient::ws_to_http_url(&ws_url)
.map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
Err("Not implemented yet".to_string())
// 2. HTTP GET to base URL with Accept: application/nostr+json header
let http_client = reqwest::Client::new();
let response = http_client
.get(&http_url)
.header("Accept", "application/nostr+json")
.send()
.await
.map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
// 3. Verify 200 OK response
if !response.status().is_success() {
return Err(format!(
"Expected 200 OK, got {} {}",
response.status().as_u16(),
response.status().canonical_reason().unwrap_or("Unknown")
));
}
// 4. Verify response is valid JSON
let json_text = response.text().await
.map_err(|e| format!("Failed to read response body: {}", e))?;
let doc: serde_json::Value = serde_json::from_str(&json_text)
.map_err(|e| format!("Response is not valid JSON: {}", e))?;
// 5. Verify has required NIP-11 fields
let required_fields = ["name", "description", "software", "version"];
for field in &required_fields {
if !doc.get(field).is_some() {
return Err(format!("Missing required NIP-11 field: {}", field));
}
}
Ok(())
})
.await
}
@@ -61,22 +89,68 @@ impl Nip11DocumentTests {
///
/// Spec: Line 12 of ../grasp/01.md
/// Requirement: MUST list supported GRASPs as string array
async fn test_nip11_supported_grasps_field(_client: &AuditClient) -> TestResult {
pub async fn test_nip11_supported_grasps_field(client: &AuditClient) -> TestResult {
TestResult::new(
"nip11_supported_grasps_field",
"GRASP-01:nostr-relay:12",
"NIP-11 document includes supported_grasps field with GRASP-01",
)
.run(|| async {
// TODO: Implementation
// 1. Fetch NIP-11 document (same as above)
// 2. Verify `supported_grasps` field exists
// 3. Verify it's a JSON array of strings
// 4. Verify array includes "GRASP-01"
// 5. Verify format: each entry matches pattern "GRASP-\d{2}"
// 6. Document other GRASPs found (for info)
// 1. Fetch NIP-11 document
let ws_url = client.relay_url().await
.map_err(|e| format!("Failed to get relay URL: {}", e))?;
let http_url = AuditClient::ws_to_http_url(&ws_url)
.map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
Err("Not implemented yet".to_string())
let http_client = reqwest::Client::new();
let response = http_client
.get(&http_url)
.header("Accept", "application/nostr+json")
.send()
.await
.map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
let json_text = response.text().await
.map_err(|e| format!("Failed to read response body: {}", e))?;
let doc: serde_json::Value = serde_json::from_str(&json_text)
.map_err(|e| format!("Response is not valid JSON: {}", e))?;
// 2. Verify `supported_grasps` field exists
let supported_grasps = doc.get("supported_grasps")
.ok_or_else(|| "Missing required field: supported_grasps".to_string())?;
// 3. Verify it's a JSON array
let grasps_array = supported_grasps.as_array()
.ok_or_else(|| "supported_grasps must be an array".to_string())?;
// 4. Verify array includes "GRASP-01"
let grasp_strings: Vec<String> = grasps_array
.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect();
if !grasp_strings.contains(&"GRASP-01".to_string()) {
return Err(format!(
"supported_grasps must include 'GRASP-01', found: {:?}",
grasp_strings
));
}
// 5. Verify format: each entry should match pattern "GRASP-\d{2}"
let grasp_pattern = regex::Regex::new(r"^GRASP-\d{2}$")
.map_err(|e| format!("Failed to compile regex: {}", e))?;
for grasp in &grasp_strings {
if !grasp_pattern.is_match(grasp) {
return Err(format!(
"Invalid GRASP format: '{}' (expected GRASP-XX where XX is two digits)",
grasp
));
}
}
Ok(())
})
.await
}
@@ -85,23 +159,47 @@ impl Nip11DocumentTests {
///
/// Spec: Line 13 of ../grasp/01.md
/// Requirement: MUST list repository acceptance criteria
async fn test_nip11_repo_acceptance_criteria_field(_client: &AuditClient) -> TestResult {
pub async fn test_nip11_repo_acceptance_criteria_field(client: &AuditClient) -> TestResult {
TestResult::new(
"nip11_repo_acceptance_criteria_field",
"GRASP-01:nostr-relay:13",
"NIP-11 document includes repo_acceptance_criteria field",
)
.run(|| async {
// TODO: Implementation
// 1. Fetch NIP-11 document
// 2. Verify `repo_acceptance_criteria` field exists
// 3. Verify it's a string (human-readable)
// 4. Verify non-empty
// 5. Document the criteria (for info)
// Examples: "Must list this relay in clone and relays tags"
// "Pre-payment required via Lightning invoice"
let ws_url = client.relay_url().await
.map_err(|e| format!("Failed to get relay URL: {}", e))?;
let http_url = AuditClient::ws_to_http_url(&ws_url)
.map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
Err("Not implemented yet".to_string())
let http_client = reqwest::Client::new();
let response = http_client
.get(&http_url)
.header("Accept", "application/nostr+json")
.send()
.await
.map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
let json_text = response.text().await
.map_err(|e| format!("Failed to read response body: {}", e))?;
let doc: serde_json::Value = serde_json::from_str(&json_text)
.map_err(|e| format!("Response is not valid JSON: {}", e))?;
// 2. Verify `repo_acceptance_criteria` field exists
let criteria = doc.get("repo_acceptance_criteria")
.ok_or_else(|| "Missing required field: repo_acceptance_criteria".to_string())?;
// 3. Verify it's a string
let criteria_str = criteria.as_str()
.ok_or_else(|| "repo_acceptance_criteria must be a string".to_string())?;
// 4. Verify non-empty
if criteria_str.trim().is_empty() {
return Err("repo_acceptance_criteria must not be empty".to_string());
}
Ok(())
})
.await
}
@@ -110,24 +208,47 @@ impl Nip11DocumentTests {
///
/// Spec: Line 14 of ../grasp/01.md
/// Requirement: MUST include curation if curated, omit otherwise
async fn test_nip11_curation_field(_client: &AuditClient) -> TestResult {
pub async fn test_nip11_curation_field(client: &AuditClient) -> TestResult {
TestResult::new(
"nip11_curation_field",
"GRASP-01:nostr-relay:14",
"NIP-11 curation field present if curated, absent otherwise",
)
.run(|| async {
// TODO: Implementation
// 1. Fetch NIP-11 document
// 2. Check if `curation` field exists
// 3. If present:
// - Verify it's a non-empty string
// - Document the curation policy
// 4. If absent:
// - Document that no curation beyond SPAM prevention
// 5. Both cases are valid per spec
let ws_url = client.relay_url().await
.map_err(|e| format!("Failed to get relay URL: {}", e))?;
let http_url = AuditClient::ws_to_http_url(&ws_url)
.map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
Err("Not implemented yet".to_string())
let http_client = reqwest::Client::new();
let response = http_client
.get(&http_url)
.header("Accept", "application/nostr+json")
.send()
.await
.map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
let json_text = response.text().await
.map_err(|e| format!("Failed to read response body: {}", e))?;
let doc: serde_json::Value = serde_json::from_str(&json_text)
.map_err(|e| format!("Response is not valid JSON: {}", e))?;
// 2. Check if `curation` field exists
if let Some(curation) = doc.get("curation") {
// 3. If present: verify it's a non-empty string
let curation_str = curation.as_str()
.ok_or_else(|| "curation field must be a string when present".to_string())?;
if curation_str.trim().is_empty() {
return Err("curation field must not be empty when present".to_string());
}
}
// 4. If absent: both cases are valid per spec
// 5. Both cases are valid - test passes
Ok(())
})
.await
}
@@ -163,4 +284,4 @@ mod tests {
// Don't assert all passed yet - tests not implemented
// assert!(results.all_passed(), "Some GRASP-01 NIP-11 document tests failed");
}
}
}
+27
View File
@@ -2,6 +2,7 @@
///
/// Provides hyper HTTP server with WebSocket upgrade support for the Nostr relay.
pub mod landing;
pub mod nip11;
use std::future::Future;
use std::net::SocketAddr;
@@ -46,6 +47,32 @@ impl Service<Request<Incoming>> for HttpService {
fn call(&self, req: Request<Incoming>) -> Self::Future {
let base = Response::builder().header("server", "ngit-grasp");
// Check for NIP-11 relay information request (Accept: application/nostr+json)
if let Some(accept) = req.headers().get("accept") {
if accept
.to_str()
.map(|s| s.contains("application/nostr+json"))
.unwrap_or(false)
{
let doc = nip11::RelayInformationDocument::from_config(&self.config);
let json = doc.to_json().unwrap_or_else(|e| {
tracing::error!("Failed to serialize NIP-11 document: {}", e);
"{}".to_string()
});
tracing::debug!("Serving NIP-11 relay information document to {}", self.remote);
return Box::pin(async move {
Ok(base
.status(200)
.header("content-type", "application/nostr+json")
.header("access-control-allow-origin", "*")
.body(json)
.unwrap())
});
}
}
// Check if this is a WebSocket upgrade request
if let (Some(c), Some(w)) = (
req.headers().get("connection"),
+146
View File
@@ -0,0 +1,146 @@
/// NIP-11 Relay Information Document
///
/// Implements NIP-11 relay information endpoint with GRASP-01 extensions.
/// See: https://github.com/nostr-protocol/nips/blob/master/11.md
use serde::{Deserialize, Serialize};
use crate::config::Config;
/// NIP-11 Relay Information Document
///
/// This structure represents the relay metadata served at the HTTP(S) endpoint
/// when the client sends `Accept: application/nostr+json` header.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayInformationDocument {
/// Relay name
pub name: String,
/// Relay description
pub description: String,
/// Relay owner's public key (hex format)
#[serde(skip_serializing_if = "Option::is_none")]
pub pubkey: Option<String>,
/// Contact information for relay admin
#[serde(skip_serializing_if = "Option::is_none")]
pub contact: Option<String>,
/// List of NIPs supported by this relay
pub supported_nips: Vec<u16>,
/// Relay software identifier
pub software: String,
/// Software version
pub version: String,
// GRASP-01 Extensions (lines 11-14 of GRASP-01 spec)
/// List of supported GRASPs (e.g., ["GRASP-01"])
/// Required by GRASP-01 specification line 12
pub supported_grasps: Vec<String>,
/// Repository acceptance criteria description
/// Required by GRASP-01 specification line 13
pub repo_acceptance_criteria: String,
/// Curation policy (present if curated, absent otherwise)
/// Optional per GRASP-01 specification line 14
#[serde(skip_serializing_if = "Option::is_none")]
pub curation: Option<String>,
}
impl RelayInformationDocument {
/// Create NIP-11 relay information document from configuration
pub fn from_config(config: &Config) -> Self {
Self {
name: config.relay_name.clone(),
description: config.relay_description.clone(),
pubkey: Some(config.owner_npub.clone()),
contact: None, // Could be added to config if needed
supported_nips: vec![
1, // NIP-01: Basic protocol flow
11, // NIP-11: Relay information document (this!)
34, // NIP-34: Git repository announcements
],
software: env!("CARGO_PKG_NAME").to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
// GRASP-01 Extensions
supported_grasps: vec!["GRASP-01".to_string()],
repo_acceptance_criteria: format!(
"Repositories must list this relay ({}) in both 'clone' and 'relays' tags of kind 30617 announcements. \
All other events must reference accepted repositories or accepted events.",
config.domain
),
curation: None, // Not a curated relay - only SPAM prevention via GRASP-01 policy
}
}
/// Serialize to JSON string
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relay_information_document_structure() {
let config = Config {
domain: "relay.example.com".to_string(),
owner_npub: "npub1test".to_string(),
relay_name: "Test Relay".to_string(),
relay_description: "A test relay".to_string(),
git_data_path: "./data/git".to_string(),
relay_data_path: "./data/relay".to_string(),
bind_address: "127.0.0.1:8080".to_string(),
database_backend: crate::config::DatabaseBackend::Memory,
};
let doc = RelayInformationDocument::from_config(&config);
assert_eq!(doc.name, "Test Relay");
assert_eq!(doc.description, "A test relay");
assert_eq!(doc.pubkey, Some("npub1test".to_string()));
assert!(doc.supported_nips.contains(&1));
assert!(doc.supported_nips.contains(&11));
assert!(doc.supported_nips.contains(&34));
assert_eq!(doc.supported_grasps, vec!["GRASP-01"]);
assert!(doc.repo_acceptance_criteria.contains("relay.example.com"));
assert!(doc.curation.is_none());
}
#[test]
fn test_relay_information_document_json() {
let config = Config {
domain: "relay.example.com".to_string(),
owner_npub: "npub1test".to_string(),
relay_name: "Test Relay".to_string(),
relay_description: "A test relay".to_string(),
git_data_path: "./data/git".to_string(),
relay_data_path: "./data/relay".to_string(),
bind_address: "127.0.0.1:8080".to_string(),
database_backend: crate::config::DatabaseBackend::Memory,
};
let doc = RelayInformationDocument::from_config(&config);
let json = doc.to_json().expect("Failed to serialize to JSON");
// Verify JSON contains expected fields
assert!(json.contains("\"name\""));
assert!(json.contains("\"description\""));
assert!(json.contains("\"supported_nips\""));
assert!(json.contains("\"supported_grasps\""));
assert!(json.contains("\"repo_acceptance_criteria\""));
assert!(json.contains("GRASP-01"));
// Verify it's valid JSON by parsing
let parsed: serde_json::Value = serde_json::from_str(&json).expect("Invalid JSON");
assert_eq!(parsed["name"], "Test Relay");
assert_eq!(parsed["supported_grasps"][0], "GRASP-01");
}
}
+62
View File
@@ -0,0 +1,62 @@
//! GRASP-01 NIP-11 Document Integration Tests
//!
//! Tests ngit-grasp relay's implementation of GRASP-01 NIP-11 relay information requirements.
//! Uses grasp-audit library to avoid code duplication.
//!
//! # Test Strategy
//!
//! - Each test runs in complete isolation with its own fresh relay instance
//! - Uses macro to eliminate boilerplate while maintaining test isolation
//! - Calls individual test methods from grasp-audit for minimal duplication
//!
//! # Running Tests
//!
//! ```bash
//! # Run all GRASP-01 NIP-11 tests
//! cargo test --test nip11_document
//!
//! # Run specific test
//! cargo test --test nip11_document test_nip11_document_exists
//!
//! # With output
//! cargo test --test nip11_document -- --nocapture
//! ```
mod common;
use common::TestRelay;
use grasp_audit::*;
/// Macro to generate isolated integration tests
///
/// Each test runs with its own fresh relay instance to ensure complete isolation.
/// This eliminates rate-limiting issues and ensures tests don't interfere with each other.
macro_rules! isolated_test {
($test_name:ident) => {
#[tokio::test]
async fn $test_name() {
let relay = TestRelay::start().await;
let config = AuditConfig::ci();
let client = AuditClient::new(relay.url(), config)
.await
.expect("Failed to create audit client");
let result = specs::Nip11DocumentTests::$test_name(&client).await;
relay.stop().await;
assert!(
result.passed,
"{} failed: {}",
stringify!($test_name),
result.error.as_deref().unwrap_or("unknown error")
);
}
};
}
// Generate isolated tests for all GRASP-01 NIP-11 document tests
isolated_test!(test_nip11_document_exists);
isolated_test!(test_nip11_supported_grasps_field);
isolated_test!(test_nip11_repo_acceptance_criteria_field);
isolated_test!(test_nip11_curation_field);