From 51d617e70f90a972185917ea0096c1ecef2bfa8b Mon Sep 17 00:00:00 2001 From: Bitflash-sh Date: Fri, 24 Jul 2026 11:18:11 -0300 Subject: Bitflash: fair-launch release Satoshi's bitcoin-0.1.0 reborn with RandomX CPU proof of work, anonymous .btf addressing over Nostr with CGNAT rendezvous, automatic peer discovery, a headless Linux node, and Windows GUI wallet. Fair launch, no premine, 21M cap. --- src/btfaddr.cpp | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/btfaddr.cpp (limited to 'src/btfaddr.cpp') diff --git a/src/btfaddr.cpp b/src/btfaddr.cpp new file mode 100644 index 0000000..5f983bc --- /dev/null +++ b/src/btfaddr.cpp @@ -0,0 +1,248 @@ +// Copyright (c) 2026 Bitflash developers +// Distributed under the MIT/X11 software license. +// +// Bitflash `.btf` self-certifying addresses + service descriptors. +// Ported from the Itzal hidden.rs design. See btfaddr.h. + +#include "btfaddr.h" + +#include +#define SECP256K1_STATIC +#include +#include +#include +#include + +#include +#include +#include + +using json = nlohmann::json; + +namespace btf +{ + +const char* TLD = ".btf"; + +static const char* B32 = "abcdefghijklmnopqrstuvwxyz234567"; + +std::string Base32Encode(const unsigned char* data, size_t n) +{ + std::string out; + out.reserve(n * 8 / 5 + 1); + uint64_t buf = 0; + uint32_t bits = 0; + for (size_t i = 0; i < n; i++) + { + buf = (buf << 8) | data[i]; + bits += 8; + while (bits >= 5) + { + bits -= 5; + out.push_back(B32[(buf >> bits) & 0x1f]); + } + buf &= (bits == 0) ? 0 : ((1ULL << bits) - 1); + } + if (bits > 0) + out.push_back(B32[(buf << (5 - bits)) & 0x1f]); + return out; +} + +bool Base32Decode(const std::string& s, std::vector& out) +{ + out.clear(); + out.reserve(s.size() * 5 / 8); + uint64_t buf = 0; + uint32_t bits = 0; + for (unsigned char c : s) + { + uint64_t val; + if (c >= 'a' && c <= 'z') val = c - 'a'; + else if (c >= 'A' && c <= 'Z') val = c - 'A'; + else if (c >= '2' && c <= '7') val = c - '2' + 26; + else return false; + buf = (buf << 5) | val; + bits += 5; + if (bits >= 8) + { + bits -= 8; + out.push_back((unsigned char)((buf >> bits) & 0xff)); + } + buf &= (bits == 0) ? 0 : ((1ULL << bits) - 1); + } + return true; +} + +static std::string ToLowerTrim(const std::string& in) +{ + std::string s = in; + // trim whitespace + size_t a = s.find_first_not_of(" \t\r\n"); + size_t b = s.find_last_not_of(" \t\r\n"); + if (a == std::string::npos) return std::string(); + s = s.substr(a, b - a + 1); + // strip trailing dots (fully-qualified hostname form) + while (!s.empty() && s.back() == '.') s.pop_back(); + for (char& c : s) c = (char)std::tolower((unsigned char)c); + return s; +} + +// Two-byte checksum, domain-separated so it can't collide with any other hash use. +static void Checksum(const unsigned char pubkey[32], unsigned char out[2]) +{ + static const char* DS = ".btf-checksum-v1"; + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, (const unsigned char*)DS, strlen(DS)); + SHA256_Update(&ctx, pubkey, 32); + unsigned char d[32]; + SHA256_Final(d, &ctx); + out[0] = d[0]; + out[1] = d[1]; +} + +std::string Address(const unsigned char pubkey[32]) +{ + unsigned char raw[34]; + memcpy(raw, pubkey, 32); + Checksum(pubkey, raw + 32); + return Base32Encode(raw, 34) + TLD; +} + +bool IsBtf(const std::string& host) +{ + std::string s = ToLowerTrim(host); + std::string tld = TLD; + if (s.size() < tld.size()) return false; + return s.compare(s.size() - tld.size(), tld.size(), tld) == 0; +} + +bool ParseAddress(const std::string& addr, unsigned char pubkeyOut[32]) +{ + std::string s = ToLowerTrim(addr); + std::string tld = TLD; + if (s.size() < tld.size()) return false; + if (s.compare(s.size() - tld.size(), tld.size(), tld) != 0) return false; + std::string label = s.substr(0, s.size() - tld.size()); + + std::vector raw; + if (!Base32Decode(label, raw)) return false; + if (raw.size() < 34) return false; + + unsigned char pk[32]; + memcpy(pk, &raw[0], 32); + unsigned char sum[2]; + Checksum(pk, sum); + if (raw[32] != sum[0] || raw[33] != sum[1]) return false; + + memcpy(pubkeyOut, pk, 32); + return true; +} + + +// ---- descriptor ------------------------------------------------------------ + +static std::string HexEncode(const unsigned char* p, size_t n) +{ + static const char* h = "0123456789abcdef"; + std::string s; + s.reserve(n * 2); + for (size_t i = 0; i < n; i++) { s += h[p[i] >> 4]; s += h[p[i] & 0xf]; } + return s; +} + +static bool HexDecode(const std::string& s, unsigned char* out, size_t n) +{ + if (s.size() != n * 2) return false; + for (size_t i = 0; i < n; i++) + { + unsigned int b; + if (sscanf(s.c_str() + i * 2, "%2x", &b) != 1) return false; + out[i] = (unsigned char)b; + } + return true; +} + +// Digest signed over the descriptor fields (domain-separated). +static void DescriptorDigest(const unsigned char pubkey[32], const std::string& enc, + const std::string& meeting_node, uint64_t created, + unsigned char out[32]) +{ + static const char* DS = ".btf-descriptor-v2"; + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, (const unsigned char*)DS, strlen(DS)); + SHA256_Update(&ctx, pubkey, 32); + SHA256_Update(&ctx, (const unsigned char*)enc.data(), enc.size()); + SHA256_Update(&ctx, (const unsigned char*)meeting_node.data(), meeting_node.size()); + unsigned char be[8]; + for (int i = 0; i < 8; i++) be[i] = (unsigned char)((created >> (8 * (7 - i))) & 0xff); + SHA256_Update(&ctx, be, 8); + SHA256_Final(out, &ctx); +} + +std::string SignDescriptor(void* ctxv, const unsigned char seckey[32], + const std::string& enc, const std::string& meeting_node, + uint64_t created) +{ + secp256k1_context* ctx = (secp256k1_context*)ctxv; + secp256k1_keypair kp; + if (!secp256k1_keypair_create(ctx, &kp, seckey)) return std::string(); + secp256k1_xonly_pubkey xpub; + if (!secp256k1_keypair_xonly_pub(ctx, &xpub, NULL, &kp)) return std::string(); + unsigned char pubkey[32]; + secp256k1_xonly_pubkey_serialize(ctx, pubkey, &xpub); + + unsigned char digest[32]; + DescriptorDigest(pubkey, enc, meeting_node, created, digest); + + unsigned char sig[64]; + // Deterministic signature (NULL aux_rand), matching Itzal's no-aux-rand signing. + if (!secp256k1_schnorrsig_sign32(ctx, sig, digest, &kp, NULL)) return std::string(); + + json v = json::object(); + v["v"] = 2; + v["pubkey"] = HexEncode(pubkey, 32); + v["enc"] = enc; + v["meeting_node"] = meeting_node; + v["created"] = created; + v["sig"] = HexEncode(sig, 64); + return v.dump(); +} + +bool VerifyDescriptor(void* ctxv, const std::string& jsonStr, + const unsigned char expect_pubkey[32], Descriptor& out) +{ + secp256k1_context* ctx = (secp256k1_context*)ctxv; + json v; + try { v = json::parse(jsonStr); } catch (...) { return false; } + if (!v.is_object()) return false; + if (!v.contains("pubkey") || !v.contains("enc") || !v.contains("meeting_node") + || !v.contains("created") || !v.contains("sig")) return false; + + unsigned char pubkey[32]; + if (!HexDecode(v["pubkey"].get(), pubkey, 32)) return false; + if (memcmp(pubkey, expect_pubkey, 32) != 0) return false; // descriptor is for a different key + + std::string enc = v["enc"].get(); + std::string meeting_node = v["meeting_node"].get(); + uint64_t created = v["created"].get(); + + unsigned char sig[64]; + if (!HexDecode(v["sig"].get(), sig, 64)) return false; + + unsigned char digest[32]; + DescriptorDigest(expect_pubkey, enc, meeting_node, created, digest); + + secp256k1_xonly_pubkey xpub; + if (!secp256k1_xonly_pubkey_parse(ctx, &xpub, expect_pubkey)) return false; + if (!secp256k1_schnorrsig_verify(ctx, sig, digest, 32, &xpub)) return false; + + memcpy(out.pubkey, expect_pubkey, 32); + out.enc = enc; + out.meeting_node = meeting_node; + out.created = created; + return true; +} + +} // namespace btf -- cgit v1.2.3