processMessage method

Uint8List processMessage(
  1. Uint8List theirMessage, {
  2. bool verbose = false,
})

Processes the peer's SPAKE2 message and derives the shared key.

theirMessage is the 32-byte message received from the peer. verbose enables detailed transcript logging. Returns a 64-byte shared key.

Implementation

Uint8List processMessage(Uint8List theirMessage, {bool verbose = false}) {
  if (!_messageGenerated) throw StateError('Must call generateMessage first');
  if (_keyDerived) throw StateError('Key already derived');
  _keyDerived = true;

  // Decode their point
  final theirPoint = Ed25519.decodePoint(theirMessage);

  // Choose the opposite blind point (peer used the other one)
  final theirBlindPoint = _role == Spake2Role.alice ? Ed25519.pointN : Ed25519.pointM;

  // Unblind: remove w * theirBlindPoint from their message
  // unblinded = theirPoint - w * theirBlindPoint
  final wTheirBlind = Ed25519.scalarMult(_passwordScalar, theirBlindPoint);
  final unblinded = Ed25519.pointSub(theirPoint, wTheirBlind);

  // Compute shared secret: K = private_key * unblinded
  final sharedPoint = Ed25519.scalarMult(_privateKey, unblinded);
  _sharedEncoded = Ed25519.encodePoint(sharedPoint);
  final sharedEncoded = _sharedEncoded!;

  // Build the transcript for key derivation
  // transcript = len(myName) || myName || len(theirName) || theirName ||
  //              len(myMessage) || myMessage || len(theirMessage) || theirMessage ||
  //              len(sharedEncoded) || sharedEncoded || len(passwordScalar) || passwordScalar
  final transcript = BytesBuilder();

  // Order depends on role: Alice's message first, then Bob's
  final Uint8List firstMsg;
  final Uint8List secondMsg;
  final Uint8List firstName;
  final Uint8List secondName;

  if (_role == Spake2Role.alice) {
    firstMsg = _myMessage;
    secondMsg = theirMessage;
    firstName = _myName;
    secondName = _theirName;
  } else {
    firstMsg = theirMessage;
    secondMsg = _myMessage;
    firstName = _theirName;
    secondName = _myName;
  }

  void append(String label, Uint8List data) {
    if (verbose) print('Transcript $label (${data.length} bytes): ${_toHex(data)}');
    _appendLengthPrefixed(transcript, data);
  }

  if (verbose) print('--- SPAKE2 Transcript Debug ---');
  append('A', firstName);
  append('B', secondName);
  append('X', firstMsg);
  append('Y', secondMsg);
  append('K', sharedEncoded);
  append('w', _passwordHash);
  if (verbose) print('--- End SPAKE2 Transcript Debug ---');

  final hash = SHA512Digest();
  final transcriptBytes = transcript.toBytes();
  final key = Uint8List(64);
  hash.update(transcriptBytes, 0, transcriptBytes.length);
  hash.doFinal(key, 0);

  return key;
}