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 ikm, required String info, List 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 = []; var previous = []; var counter = 1; while (out.length < length) { final input = [...previous, ...infoBytes, counter]; previous = Hmac(sha256, prk).convert(input).bytes; out.addAll(previous); counter++; } return Uint8List.fromList(out.sublist(0, length)); } /// HKDF-Extract (RFC 5869 §2.2): `PRK = HMAC(salt, ikm)`. NIP-44 uses this /// alone to turn the ECDH secret into the conversation key. Uint8List hkdfExtract({required List salt, required List ikm}) => Uint8List.fromList(Hmac(sha256, salt).convert(ikm).bytes); /// HKDF-Expand (RFC 5869 §2.3) with a *byte* info (NIP-44 uses the nonce as /// info), producing [length] bytes. Uint8List hkdfExpandBytes({ required List prk, required List info, required int length, }) { final out = []; var previous = []; var counter = 1; while (out.length < length) { previous = Hmac(sha256, prk).convert([...previous, ...info, counter]).bytes; out.addAll(previous); counter++; } return Uint8List.fromList(out.sublist(0, length)); }