Summary
All BLS operations take raw byte arrays (types.rs#L17-L21), so every call re-deserializes (verify does a G1 decompression per call) and re-runs subgroup checks — verify passes pk_validate = true and sig_groupcheck = true on every invocation. For a DV node the public shares are cluster-constant; nothing is memoized.
There is also no batch verification: blst's verify_multiple_aggregate_signatures is unused, and the hot paths verify one signature at a time — every incoming peer partial signature (parsigex → verify_eth2_signed_data → signing.rs#L148), a nested validators×partials loop in dkg/src/aggregate.rs#L146, and one builder registration per validator in cluster/src/lock.rs#L427.
Additionally, aggregate_public_keys hand-rolls point addition through unsafe FFI and skips per-key subgroup validation — blst's safe AggregatePublicKey::aggregate(..., validate) covers it. A second independent blst wrapper with safe constructors already exists in pluto-frost (crates/frost/src/curve.rs); the ad-hoc scalar helpers in blst_impl.rs duplicate it.
There are no benchmarks: the only criterion setup in the workspace is crates/k1util/benches, while Charon benchmarks its BLS backend (tbls_test.go BenchmarkHerumiImplementation).
Proposed change
- Introduce parsed key/signature types (wrapping
blst types) validated once at construction; keep raw bytes only at serialization boundaries.
- Add a batch-verification entry point and use it on the partial-signature paths.
- Replace the unsafe aggregation with the blst safe API; consolidate scalar helpers with
pluto-frost's wrappers.
- Hoist the O(n²)
scalar_from_u64 recomputation out of the interpolation loops — evaluate_polynomial rebuilds the same scalar every iteration and compute_lagrange_coefficients reconstructs per-index scalars inside the inner loop; build them once per index.
- Add criterion benches (sign/verify/aggregate/threshold_aggregate) using the k1util bench as a template, so wins are measurable.
Summary
All BLS operations take raw byte arrays (
types.rs#L17-L21), so every call re-deserializes (verifydoes a G1 decompression per call) and re-runs subgroup checks —verifypassespk_validate = trueandsig_groupcheck = trueon every invocation. For a DV node the public shares are cluster-constant; nothing is memoized.There is also no batch verification: blst's
verify_multiple_aggregate_signaturesis unused, and the hot paths verify one signature at a time — every incoming peer partial signature (parsigex→verify_eth2_signed_data→signing.rs#L148), a nested validators×partials loop indkg/src/aggregate.rs#L146, and one builder registration per validator incluster/src/lock.rs#L427.Additionally,
aggregate_public_keyshand-rolls point addition through unsafe FFI and skips per-key subgroup validation — blst's safeAggregatePublicKey::aggregate(..., validate)covers it. A second independent blst wrapper with safe constructors already exists inpluto-frost(crates/frost/src/curve.rs); the ad-hoc scalar helpers inblst_impl.rsduplicate it.There are no benchmarks: the only criterion setup in the workspace is
crates/k1util/benches, while Charon benchmarks its BLS backend (tbls_test.goBenchmarkHerumiImplementation).Proposed change
blsttypes) validated once at construction; keep raw bytes only at serialization boundaries.pluto-frost's wrappers.scalar_from_u64recomputation out of the interpolation loops —evaluate_polynomialrebuilds the same scalar every iteration andcompute_lagrange_coefficientsreconstructs per-index scalars inside the inner loop; build them once per index.