OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_ann_signable.cpp

97 lines · cpp ref:
// 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;
}