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.
80 lines
3 KiB
Dart
80 lines
3 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:block2_spike/block2_spike.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
/// Q2 — the `OfferTransport` seam must not leak inventory or an exact location.
|
|
/// The offer carries only a chosen summary; the wire event is checked byte-wise.
|
|
void main() {
|
|
final key = NostrKey.deriveFromSeed(
|
|
Uint8List.fromList(List.generate(32, (i) => i)),
|
|
);
|
|
final codec = Nip99Codec();
|
|
|
|
// A realistic private-ish context: the caller ACCIDENTALLY passes a very
|
|
// precise geohash and there is secret inventory the offer must never expose.
|
|
const secretInventoryMarker = 'SECRET-LOT-12-KG-IN-SHED';
|
|
const preciseGeohash = 'u09tunqmabc'; // ~sub-metre precision
|
|
|
|
Offer sampleOffer() => Offer(
|
|
id: 'offer-1',
|
|
authorPubkeyHex: key.publicKeyHex,
|
|
summary: 'Tomate rosa de Barbastro',
|
|
type: OfferType.gift,
|
|
category: 'Solanaceae',
|
|
approxGeohash: preciseGeohash,
|
|
);
|
|
|
|
test('exact location is coarsened on the wire (never full precision)', () {
|
|
final event = codec.encode(sampleOffer(), createdAt: 1000);
|
|
final gTags = event.tags.where((t) => t[0] == 'g').map((t) => t[1]);
|
|
// No geohash tag exceeds the coarse cap...
|
|
expect(gTags.every((g) => g.length <= Nip99Codec.maxGeohashChars), isTrue);
|
|
// ...and the precise fix never appears anywhere in the serialised event.
|
|
final wire = jsonEncode(event.toJson());
|
|
expect(wire, isNot(contains(preciseGeohash)));
|
|
expect(wire, contains('u09t')); // coarse prefix is present
|
|
});
|
|
|
|
test('no inventory / address field can ride along (seam has none)', () {
|
|
final event = codec.encode(sampleOffer(), createdAt: 1000);
|
|
final wire = jsonEncode(event.toJson());
|
|
expect(wire, isNot(contains(secretInventoryMarker)));
|
|
// The event has no "location", "address" or "geo-exact" tag by construction.
|
|
final tagNames = event.tags.map((t) => t[0]).toSet();
|
|
expect(tagNames, isNot(contains('location')));
|
|
expect(tagNames, isNot(contains('address')));
|
|
});
|
|
|
|
test('geohash ladder lets an area query match by exact tag', () {
|
|
final event = codec.encode(sampleOffer(), createdAt: 1000);
|
|
final gTags = event.tags.where((t) => t[0] == 'g').map((t) => t[1]).toList();
|
|
expect(gTags, containsAll(['u', 'u0', 'u09', 'u09t']));
|
|
});
|
|
|
|
test('round-trips Offer ↔ NIP-99 event, only chosen fields survive', () {
|
|
final original = Offer(
|
|
id: 'offer-2',
|
|
authorPubkeyHex: key.publicKeyHex,
|
|
summary: 'Judía del ganxet',
|
|
type: OfferType.sale,
|
|
priceAmount: 3,
|
|
priceCurrency: 'G1',
|
|
category: 'Fabaceae',
|
|
approxGeohash: 'sp3e9',
|
|
);
|
|
final event = codec.encode(original, createdAt: 1000)
|
|
..signWith(key.privateKeyHex);
|
|
expect(event.verify(), isTrue);
|
|
|
|
final decoded = codec.decode(event);
|
|
expect(decoded.id, original.id);
|
|
expect(decoded.summary, original.summary);
|
|
expect(decoded.type, OfferType.sale);
|
|
expect(decoded.priceAmount, 3);
|
|
expect(decoded.priceCurrency, 'G1');
|
|
expect(decoded.category, 'Fabaceae');
|
|
expect(decoded.approxGeohash, 'sp3e9');
|
|
});
|
|
}
|