generateMessage method
Generates the SPAKE2 message to send to the peer.
password is the raw pairing code bytes (UTF-8 encoded).
Returns a 32-byte compressed Ed25519 point.
Implementation
Uint8List generateMessage(Uint8List password) {
if (_messageGenerated) throw StateError('Message already generated');
_messageGenerated = true;
// Generate random private scalar
final randomScalarBytes = _generatePrivateScalar();
final privateScalar = Ed25519.leBytesToBigInt(randomScalarBytes);
// Multiply by cofactor (8)
final cofactorPrivateScalar = (privateScalar << 3) % (BigInt.one << 256);
_privateKey = Ed25519.bigIntToLeBytes(cofactorPrivateScalar, 32);
// Compute password scalar (w) by hashing password and reducing mod L
// BoringSSL uses the raw hash for the transcript but the reduced scalar for points.
_passwordHash = _passwordToHash(password);
var w = Ed25519.leBytesToBigInt(_passwordHash) % Ed25519.L;
// The BoringSSL "password scalar hack": ensure bottom 3 bits are zero.
// We add multiples of L (the group order) until the bottom 3 bits are zero.
// L is odd, so adding L flips the LSB.
if (w.isOdd) w += Ed25519.L;
if ((w >> 1).isOdd) w += (Ed25519.L << 1);
if ((w >> 2).isOdd) w += (Ed25519.L << 2);
_passwordScalar = Ed25519.bigIntToLeBytes(w, 32);
// Compute T = private_key * B (base point multiplication)
final tPoint = Ed25519.scalarMultBase(_privateKey);
// Choose M or N based on role
final blindPoint = _role == Spake2Role.alice ? Ed25519.pointM : Ed25519.pointN;
// Compute message = T + w * blind_point
final wBlind = Ed25519.scalarMult(_passwordScalar, blindPoint);
final messagePoint = Ed25519.pointAdd(tPoint, wBlind);
_myMessage = Ed25519.encodePoint(messagePoint);
return Uint8List.fromList(_myMessage);
}