tane/spike/block2_spike/lib/src/hkdf.dart
vjrj 49b872b405 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.
2026-07-10 01:57:25 +02:00

32 lines
1,011 B
Dart

import 'dart:typed_data';
import 'package:crypto/crypto.dart';
/// Minimal, self-contained HKDF-SHA256 (RFC 5869), synchronous.
///
/// Spike-local so the derivation stays pure Dart and easy to reason about. In
/// production this would reuse `commons_core`'s `Hkdf` (the `cryptography`
/// package already ships one — see backup_box.dart), keyed by a domain string.
Uint8List hkdfSha256({
required List<int> ikm,
required String info,
List<int> salt = const [],
int length = 32,
}) {
// Extract.
final actualSalt = salt.isEmpty ? Uint8List(32) : salt; // HashLen zeros
final prk = Hmac(sha256, actualSalt).convert(ikm).bytes;
// Expand.
final infoBytes = info.codeUnits;
final out = <int>[];
var previous = <int>[];
var counter = 1;
while (out.length < length) {
final input = <int>[...previous, ...infoBytes, counter];
previous = Hmac(sha256, prk).convert(input).bytes;
out.addAll(previous);
counter++;
}
return Uint8List.fromList(out.sublist(0, length));
}