aggregateNonces method

Uint8List aggregateNonces()

BIP-327 NonceAgg: sum each participant's R1 and R2, then b = H("MuSig/noncecoef", R1_agg || R2_agg || Q_x || msg) mod n, R = R1_agg + b*R2_agg (or G if R = infinity). Returns 33-byte compressed R.

Per BIP-327 sec. GetSessionValues the noncecoef hash input is aggnonce (R1_agg || R2_agg) || x-only aggregate key || message - the message IS included (its omission was the cause of the non-empty-message vector failures). infinity is serialised as 33 zero bytes in the hash input and, if the final R is infinity, it is replaced by the generator G.

Implementation

Uint8List aggregateNonces() {
  if (nonces.isEmpty) {
    throw StateError('No nonces have been collected');
  }

  EcPoint aggR1 = EcPoint.infinity();
  EcPoint aggR2 = EcPoint.infinity();

  for (final nonce in nonces) {
    aggR1 = ecPointAdd(aggR1, ecBytesToPoint(nonce.r1));
    aggR2 = ecPointAdd(aggR2, ecBytesToPoint(nonce.r2));
  }

  // Serialise infinity as 33 zero bytes per BIP-327.
  final aggR1Bytes = aggR1.isInfinity
      ? Uint8List(33)
      : ecPointToBytes(aggR1, compressed: true);
  final aggR2Bytes = aggR2.isInfinity
      ? Uint8List(33)
      : ecPointToBytes(aggR2, compressed: true);

  final bInput =
      concatBytes([aggR1Bytes, aggR2Bytes, aggKey.xOnly, message]);
  final bHash = taggedHash('MuSig/noncecoef', bInput);
  final b = bytesToBigInt(bHash) % secp256k1N;

  // R = R1 + b*R2; if R = infinity use G.
  EcPoint aggR;
  if (aggR1.isInfinity && aggR2.isInfinity) {
    aggR = secp256k1G;
  } else if (aggR2.isInfinity) {
    aggR = aggR1.isInfinity ? secp256k1G : aggR1;
  } else {
    final bR2 = ecScalarMult(b, aggR2);
    final candidate = aggR1.isInfinity ? bR2 : ecPointAdd(aggR1, bR2);
    aggR = candidate.isInfinity ? secp256k1G : candidate;
  }

  return ecPointToBytes(aggR, compressed: true);
}