upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/btfaddr.cpp40
-rw-r--r--src/test_btfaddr.cpp19
2 files changed, 41 insertions, 18 deletions
diff --git a/src/btfaddr.cpp b/src/btfaddr.cpp
index ce2ac86..502220e 100644
--- a/src/btfaddr.cpp
+++ b/src/btfaddr.cpp
@@ -319,20 +319,32 @@ bool VerifyDescriptor(void* ctxv, const std::string& jsonStr,
319 std::string onion; 319 std::string onion;
320 if (v.contains("onion")) 320 if (v.contains("onion"))
321 { 321 {
322 if (v["onion"].is_string() && v.contains("sig3") && v["sig3"].is_string()) 322 // A descriptor that carries an onion endpoint must prove it. The
323 { 323 // endpoint is covered by sig3 and only sig3, so any failure here --
324 std::string onionNorm; 324 // missing sig3, bad hex, unnormalised endpoint, bad signature --
325 std::string onionRaw = v["onion"].get<std::string>(); 325 // means the descriptor was tampered with, and a tampered
326 if (NormalizeOnionEndpoint(onionRaw, onionNorm) && onionNorm == onionRaw) 326 // descriptor is rejected outright. Upstream dropped just the onion
327 { 327 // and accepted the rest, which let any relay convert an onion
328 unsigned char sig3[64]; 328 // descriptor into a rendezvous-only one by deleting a field.
329 unsigned char digest3[32]; 329 //
330 DescriptorDigestV3(expect_pubkey, enc, meeting_node, onionNorm, created, digest3); 330 // Residual, by design: removing BOTH "onion" and "sig3" still
331 if (HexDecode(v["sig3"].get<std::string>(), sig3, 64) && 331 // verifies, because the result is indistinguishable from a peer
332 secp256k1_schnorrsig_verify(ctx, sig3, digest3, 32, &xpub)) 332 // that never had an onion endpoint. Closing that requires binding
333 onion = onionNorm; 333 // the onion into the primary sig (the V2 digest), which every
334 } 334 // upstream 1.2.18/1.2.19 verifier would reject -- an upstream
335 } 335 // protocol change, not something this fork can do unilaterally.
336 if (!v["onion"].is_string()) return false;
337 if (!v.contains("sig3") || !v["sig3"].is_string()) return false;
338 std::string onionNorm;
339 std::string onionRaw = v["onion"].get<std::string>();
340 if (!NormalizeOnionEndpoint(onionRaw, onionNorm) || onionNorm != onionRaw)
341 return false;
342 unsigned char sig3[64];
343 unsigned char digest3[32];
344 DescriptorDigestV3(expect_pubkey, enc, meeting_node, onionNorm, created, digest3);
345 if (!HexDecode(v["sig3"].get<std::string>(), sig3, 64)) return false;
346 if (!secp256k1_schnorrsig_verify(ctx, sig3, digest3, 32, &xpub)) return false;
347 onion = onionNorm;
336 } 348 }
337 349
338 memcpy(out.pubkey, expect_pubkey, 32); 350 memcpy(out.pubkey, expect_pubkey, 32);
diff --git a/src/test_btfaddr.cpp b/src/test_btfaddr.cpp
index 8de426e..9d33d63 100644
--- a/src/test_btfaddr.cpp
+++ b/src/test_btfaddr.cpp
@@ -133,12 +133,23 @@ int main()
133 std::string d3 = btf::SignDescriptor(ctx, sk, ENC_HEX, "node-1", 5, ONION); 133 std::string d3 = btf::SignDescriptor(ctx, sk, ENC_HEX, "node-1", 5, ONION);
134 json j3 = json::parse(d3); 134 json j3 = json::parse(d3);
135 j3["onion"] = "bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde.onion:8433"; 135 j3["onion"] = "bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde.onion:8433";
136 CHECK(btf::VerifyDescriptor(ctx, j3.dump(), pk, o) && o.onion.empty(), 136 CHECK(!btf::VerifyDescriptor(ctx, j3.dump(), pk, o),
137 "tampered onion ignored rather than trusted"); 137 "tampered onion rejects the descriptor");
138 json j4 = json::parse(d3); 138 json j4 = json::parse(d3);
139 j4["sig3"] = std::string(128, '0'); 139 j4["sig3"] = std::string(128, '0');
140 CHECK(btf::VerifyDescriptor(ctx, j4.dump(), pk, o) && o.onion.empty(), 140 CHECK(!btf::VerifyDescriptor(ctx, j4.dump(), pk, o),
141 "bad onion signature ignored rather than trusted"); 141 "bad onion signature rejects the descriptor");
142 json j5 = json::parse(d3);
143 j5.erase("sig3");
144 CHECK(!btf::VerifyDescriptor(ctx, j5.dump(), pk, o),
145 "unsigned onion rejects the descriptor");
146 // Removing both fields still verifies: the result is indistinguishable
147 // from a peer that never had an onion endpoint. That residual strip
148 // needs the onion bound into the primary sig, an upstream change.
149 json j6 = json::parse(d3);
150 j6.erase("onion"); j6.erase("sig3");
151 CHECK(btf::VerifyDescriptor(ctx, j6.dump(), pk, o) && o.onion.empty(),
152 "full strip indistinguishable from no-onion (documented residual)");
142 } 153 }
143 154
144 printf("full_resolve_chain_from_address\n"); 155 printf("full_resolve_chain_from_address\n");