spike(block2): de-risk social layer — key derivation, OfferTransport, NIP-99 round-trip
Throwaway research spike (spike/ branch, outside pub workspace, no prod deps touched). Validates the open Block 2 decisions before committing a funded round: 1. Deterministic one-way secp256k1 (Nostr) key derived from the Ğ1 root seed via HKDF — user still backs up ONE thing. Reproducible + signs (BIP340). 2. OfferTransport abstraction over Nostr NIP-99 (kind 30402); Offer stays inventory/location-agnostic, geohash coarsened on the wire (tested). 3. publish->discover-by-geohash proven end-to-end against an in-process hermetic mini-relay (~34ms round-trip). Findings + risks + recommendation in docs/design/spike-block2-findings.md. Block 1 suite untouched. 14 tests green, analyzer clean.
This commit is contained in:
parent
6eb1517ffb
commit
49b872b405
16 changed files with 1284 additions and 0 deletions
54
spike/block2_spike/lib/src/bech32.dart
Normal file
54
spike/block2_spike/lib/src/bech32.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/// Self-contained bech32 encode (BIP-173 checksum), enough for NIP-19
|
||||
/// `npub`/`nsec`. Written inline to avoid a dependency's version quirks; NIP-19
|
||||
/// is plain bech32 (checksum constant 1), not bech32m.
|
||||
const _charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
||||
|
||||
int _polymod(List<int> values) {
|
||||
const gen = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
|
||||
var chk = 1;
|
||||
for (final v in values) {
|
||||
final top = chk >> 25;
|
||||
chk = ((chk & 0x1ffffff) << 5) ^ v;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
if ((top >> i) & 1 == 1) chk ^= gen[i];
|
||||
}
|
||||
}
|
||||
return chk;
|
||||
}
|
||||
|
||||
List<int> _hrpExpand(String hrp) => [
|
||||
...hrp.codeUnits.map((c) => c >> 5),
|
||||
0,
|
||||
...hrp.codeUnits.map((c) => c & 31),
|
||||
];
|
||||
|
||||
List<int> _createChecksum(String hrp, List<int> data) {
|
||||
final values = [..._hrpExpand(hrp), ...data, 0, 0, 0, 0, 0, 0];
|
||||
final mod = _polymod(values) ^ 1;
|
||||
return List.generate(6, (i) => (mod >> (5 * (5 - i))) & 31);
|
||||
}
|
||||
|
||||
/// Converts 8-bit bytes to 5-bit groups (bech32 payload), padding with zeros.
|
||||
List<int> _convertBits8to5(List<int> data) {
|
||||
var acc = 0;
|
||||
var bits = 0;
|
||||
final out = <int>[];
|
||||
for (final value in data) {
|
||||
acc = (acc << 8) | value;
|
||||
bits += 8;
|
||||
while (bits >= 5) {
|
||||
bits -= 5;
|
||||
out.add((acc >> bits) & 31);
|
||||
}
|
||||
}
|
||||
if (bits > 0) out.add((acc << (5 - bits)) & 31);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// bech32-encodes [data] (raw bytes) under [hrp] (e.g. `npub`).
|
||||
String bech32Encode(String hrp, List<int> data) {
|
||||
final five = _convertBits8to5(data);
|
||||
final checksum = _createChecksum(hrp, five);
|
||||
final combined = [...five, ...checksum];
|
||||
return '${hrp}1${combined.map((v) => _charset[v]).join()}';
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue