AggregatedKey.fromKeys constructor

AggregatedKey.fromKeys(
  1. List<PublicKey> keys
)

Keys are used in the order provided, per BIP-327 sec. Key Aggregation. The aggregated key depends on the ordering - callers are responsible for providing a consistent, agreed-upon ordering across all signers.

Implementation

factory AggregatedKey.fromKeys(List<PublicKey> keys) {
  if (keys.isEmpty) {
    throw ArgumentError('At least one public key is required');
  }

  // L = H("KeyAgg list", pk1 || pk2 || ... || pkn)
  final allKeyBytes = <int>[];
  for (final key in keys) {
    allKeyBytes.addAll(key.bytes);
  }
  final keyAggList = taggedHash(
    'KeyAgg list',
    Uint8List.fromList(allKeyBytes),
  );

  // Second unique key for the coefficient optimization (none if all identical).
  // Per BIP-327: u is the first key in the list that differs from keys[0].
  Uint8List? secondKey;
  for (int i = 1; i < keys.length; i++) {
    if (!bytesEqual(keys[i].bytes, keys[0].bytes)) {
      secondKey = keys[i].bytes;
      break;
    }
  }

  // Q = sum(a_i * P_i)
  EcPoint aggPoint = EcPoint.infinity();
  for (final key in keys) {
    final coeff = _computeCoefficient(keyAggList, key.bytes, secondKey);
    final point = ecBytesToPoint(key.bytes);
    final tweaked = ecScalarMult(coeff, point);
    aggPoint = ecPointAdd(aggPoint, tweaked);
  }

  if (aggPoint.isInfinity) {
    throw StateError('Aggregated key is the point at infinity');
  }

  final aggKeyBytes = ecPointToBytes(aggPoint, compressed: true);

  return AggregatedKey._(
    publicKeys: List.unmodifiable(keys),
    aggregatedKey: PublicKey(aggKeyBytes),
    secondKey: secondKey,
    keyAggList: keyAggList,
  );
}