deriveKeyPair static method

PqKeyPair deriveKeyPair(
  1. Uint8List seed, {
  2. int account = 0,
})

Derives the ML-KEM-1024 keypair for account from a BIP-39 seed.

The keys are siblings of the secp256k1 key derived from the same mnemonic, never children of it — an adversary who recovers the Nostr private key cannot repeat the derivation to reach these.

seed must be the 64-byte BIP-39 seed, not a private key. Passing a 32-byte secp256k1 private key is rejected rather than silently producing unrelated keys.

A 12-word mnemonic expands to a valid 64-byte seed carrying only 128 bits of entropy, which would make the seed — not the lattice — the weakest link. Callers advertising keys as seed-derived should require 24 words; this function cannot detect mnemonic length, because PBKDF2 stretches any mnemonic to 64 bytes.

Implementation

static PqKeyPair deriveKeyPair(Uint8List seed, {int account = 0}) {
  if (seed.length != seedBytes) {
    throw ArgumentError.value(
      seed.length,
      'seed',
      'expected a $seedBytes-byte BIP-39 seed',
    );
  }
  final seedPtr = calloc<Uint8>(seed.length);
  final pk = calloc<rust_lib.QsBuffer>();
  final sk = calloc<rust_lib.QsBuffer>();
  try {
    seedPtr.asTypedList(seed.length).setAll(0, seed);
    final ok = rust_lib.pqDeriveKemKeypair(
      seedPtr,
      seed.length,
      account,
      pk,
      sk,
    );
    if (ok != 1) throw StateError('Post-quantum key derivation failed');
    return PqKeyPair(publicKey: _copyOut(pk), secretKey: _copyOut(sk));
  } finally {
    // Wipe our copy of the seed before releasing it.
    seedPtr.asTypedList(seed.length).fillRange(0, seed.length, 0);
    calloc.free(seedPtr);
    calloc.free(pk);
    calloc.free(sk);
  }
}