// Gate: our (un)announce signable == hyperdht's, byte for byte.
//
// The vector in unannounce_vector.json is produced by gen_unannounce_vector.js,
// which calls hyperdht's OWN lib/messages.js + lib/constants.js and the nested
// compact-encoding hyperdht actually depends on. So this compares against their
// bytes, not against our reading of their code — the trap hyper-games #72 named
// as costing this repo five times ("a faithful port -- of its own comments").
//
// build: see build_tests.sh
// red: before the ann_signable fix, the UNANNOUNCE case differed (ours was
// ns || topic || peer, variable length, instead of ns || hash, 64B)
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <sodium.h>
#include "hyperdht.h"
static int failures = 0;
static int checks = 0;
static std::vector<uint8_t> unhex(const std::string &h) {
std::vector<uint8_t> out(h.size() / 2);
for (size_t i = 0; i < out.size(); i++)
out[i] = (uint8_t)strtoul(h.substr(i * 2, 2).c_str(), nullptr, 16);
return out;
}
static std::string hex(const uint8_t *p, size_t n) {
static const char *d = "0123456789abcdef";
std::string s;
for (size_t i = 0; i < n; i++) { s += d[p[i] >> 4]; s += d[p[i] & 15]; }
return s;
}
static void eq(const char *what, const std::string &got, const std::string &want) {
checks++;
if (got == want) { printf(" ok %s\n", what); return; }
failures++;
printf(" FAIL %s\n got %s\n want %s\n", what, got.c_str(), want.c_str());
}
int main() {
if (sodium_init() < 0) { printf("sodium_init failed\n"); return 1; }
// Vector inputs (must match gen_unannounce_vector.js).
auto target = unhex("a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1");
auto token = unhex("b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2");
auto id = unhex("c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3");
auto peer = unhex("ed3234b276d4ceda57d59bad14fbaf5a773c0f318c999de3a60d53c5a5b34c0500");
const char *WANT_NS_ANN = "36386adddf9f6fd60db83a6f42fc159d1146aa8644037664230aaa1f0179d497";
const char *WANT_NS_UNANN = "ded293cd93fb395e756ecf5fff426529e72c36eacc22e5ed944d9099a2561e32";
const char *WANT_SIG_ANN =
"36386adddf9f6fd60db83a6f42fc159d1146aa8644037664230aaa1f0179d497"
"c099df785db855b9019b52db6bc4eff54f7d562b1a4f5849188a7403bfee7ff4";
const char *WANT_SIG_UNANN =
"ded293cd93fb395e756ecf5fff426529e72c36eacc22e5ed944d9099a2561e32"
"c099df785db855b9019b52db6bc4eff54f7d562b1a4f5849188a7403bfee7ff4";
printf("ann_signable vs hyperdht 6.34.0\n");
// The namespace hashes themselves, first: if these are wrong every signature
// below is wrong for a reason that has nothing to do with the signable.
auto ns = unhex(std::string(WANT_NS_ANN) + WANT_NS_UNANN);
uint8_t sig[64];
HyperDht::ann_signable(sig, ns.data(), target.data(), id.data(), token.data(),
peer.data(), peer.size());
eq("ANNOUNCE signable", hex(sig, 64), WANT_SIG_ANN);
HyperDht::ann_signable(sig, ns.data() + 32, target.data(), id.data(), token.data(),
peer.data(), peer.size());
eq("UNANNOUNCE signable", hex(sig, 64), WANT_SIG_UNANN);
// The old, wrong form, asserted to be DIFFERENT. Without this the gate would
// still pass if someone reintroduced ns || topic || peer for one namespace,
// because nothing here would notice a second code path existing.
{
std::vector<uint8_t> old(32 + 32 + peer.size());
memcpy(old.data(), ns.data() + 32, 32);
memcpy(old.data() + 32, target.data(), 32);
memcpy(old.data() + 64, peer.data(), peer.size());
checks++;
if (old.size() == 64 || hex(old.data(), 64) == WANT_SIG_UNANN) {
failures++;
printf(" FAIL the pre-fix form is indistinguishable from the correct one\n");
} else {
printf(" ok pre-fix form (%zuB, ns||topic||peer) differs as expected\n", old.size());
}
}
printf("\n%d/%d passed\n", checks - failures, checks);
return failures ? 1 : 0;
}