AggregatedKey.fromKeys constructor
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,
);
}