diff --git a/src/proto_alpha/lib_delegate/baking_actions.ml b/src/proto_alpha/lib_delegate/baking_actions.ml index 48ca71b4086b17fe653b85db7d9dd30d4b79acd8..614603b688a6ae1476880cf0349bc937f97b4138 100644 --- a/src/proto_alpha/lib_delegate/baking_actions.ml +++ b/src/proto_alpha/lib_delegate/baking_actions.ml @@ -30,6 +30,42 @@ module Events = Baking_events.Actions module Profiler = (val Profiler.wrap Baking_profiler.baker_profiler) +type error += + | Unexpected_signature_type of Signature.t + | Missing_bls_companion_key_for_dal of Baking_state.Key.t + +let () = + register_error_kind + `Permanent + ~id:"Baking_actions.unexpected_signature_type" + ~title:"Unexpected signature type" + ~description:"Signature should be a BLS signature." + ~pp:(fun ppf s -> + Format.fprintf + ppf + "Signature should be a BLS signature %a." + Signature.pp + s) + Data_encoding.(obj1 (req "signature" Signature.encoding)) + (function Unexpected_signature_type s -> Some s | _ -> None) + (fun s -> Unexpected_signature_type s) ; + register_error_kind + `Permanent + ~id:"Baking_actions.Missing_bls_companion_key_for_dal" + ~title:"Missing a BLS companion key for DAL attestations" + ~description: + "The consensus key is a BLS key but is missing a companion key, so it \ + cannot sign a DAL attestation." + ~pp:(fun ppf s -> + Format.fprintf + ppf + "Expected a BLS companion key for the consensus key %a." + Baking_state.Key.pp + s) + Data_encoding.(obj1 (req "consensus_key" Baking_state.Key.encoding)) + (function Missing_bls_companion_key_for_dal ck -> Some ck | _ -> None) + (fun ck -> Missing_bls_companion_key_for_dal ck) + module Operations_source = struct type error += | Failed_operations_fetch of { @@ -690,16 +726,60 @@ let forge_and_sign_consensus_vote global_state ~branch unsigned_consensus_vote : let unsigned_operation_bytes = Data_encoding.Binary.to_bytes_exn encoding unsigned_operation in - let sk_uri = delegate.consensus_key.secret_key_uri in - let* signature = + let sk_consensus_uri = delegate.consensus_key.secret_key_uri in + let* consensus_sig = sign ?timeout:global_state.config.remote_calls_timeout ~signing_request cctxt ~watermark - sk_uri + sk_consensus_uri unsigned_operation_bytes in + let* signature = + if not bls_mode then return consensus_sig + else + match dal_content with + | None -> return consensus_sig + | Some {attestation = dal_attestation} -> ( + let* companion_key = + match delegate.companion_key with + | None -> + tzfail + (Missing_bls_companion_key_for_dal delegate.consensus_key) + | Some companion_key -> return companion_key + in + let sk_companion_uri = companion_key.secret_key_uri in + let* companion_sig = + sign + ?timeout:global_state.config.remote_calls_timeout + ~signing_request + cctxt + ~watermark + sk_companion_uri + unsigned_operation_bytes + in + match (consensus_sig, companion_sig) with + (* This if-else branch is for BLS mode so both signatures + should be BLS signatures *) + | Signature.Bls consensus_sig, Signature.Bls companion_sig -> ( + let dal_dependent_bls_sig_opt = + Alpha_context.Dal.Attestation.Dal_dependent_signing + .aggregate_sig + ~subgroup_check:false + ~consensus_sig + ~companion_sig + dal_attestation + in + match dal_dependent_bls_sig_opt with + | None -> tzfail Baking_errors.Signature_aggregation_failure + | Some dal_dependent_bls_sig_opt -> + return (Signature.Bls dal_dependent_bls_sig_opt : Signature.t) + ) + | Signature.Bls _, _ -> + tzfail (Unexpected_signature_type companion_sig) + | _ -> tzfail (Unexpected_signature_type consensus_sig)) + in let protocol_data = Operation_data {contents; signature = Some signature} in let signed_operation : Operation.packed = {shell; protocol_data} in return {unsigned_consensus_vote; signed_operation} diff --git a/src/proto_alpha/lib_delegate/baking_errors.ml b/src/proto_alpha/lib_delegate/baking_errors.ml index 1ebc51e5a8f6743474c10e105c95bf60c5ffebea..ff0dccdc812cc7a3a3b6796e477b69165bfdd61b 100644 --- a/src/proto_alpha/lib_delegate/baking_errors.ml +++ b/src/proto_alpha/lib_delegate/baking_errors.ml @@ -435,3 +435,17 @@ let () = Data_encoding.unit (function Incompatible_dal_options -> Some () | _ -> None) (fun () -> Incompatible_dal_options) + +(* BLS related errors *) +type error += Signature_aggregation_failure + +let () = + register_error_kind + `Permanent + ~id:"baker.signature_aggregation_failure" + ~title:"Signature aggregation failure" + ~description:"Signature aggregation failed." + ~pp:(fun ppf () -> Format.fprintf ppf "Signature aggregation failed.") + Data_encoding.unit + (function Signature_aggregation_failure -> Some () | _ -> None) + (fun () -> Signature_aggregation_failure) diff --git a/src/proto_alpha/lib_delegate/block_forge.ml b/src/proto_alpha/lib_delegate/block_forge.ml index 102fd897253bb90d6020639e8064e6bd325d0c0b..7e3531ce1c87eeeebd7a34f0b51f5a0470abfa30 100644 --- a/src/proto_alpha/lib_delegate/block_forge.ml +++ b/src/proto_alpha/lib_delegate/block_forge.ml @@ -26,19 +26,6 @@ open Protocol open Alpha_context -type error += Signature_aggregation_failure - -let () = - register_error_kind - `Permanent - ~id:"Block_forge.signature_aggregation_failure" - ~title:"Signature aggregation failure" - ~description:"Signature aggregation failed." - ~pp:(fun ppf () -> Format.fprintf ppf "Signature aggregation failed.") - Data_encoding.unit - (function Signature_aggregation_failure -> Some () | _ -> None) - (fun () -> Signature_aggregation_failure) - type unsigned_block = { unsigned_block_header : Block_header.t; operations : Tezos_base.Operation.t list list; @@ -449,7 +436,7 @@ let aggregate_attestations eligible_attestations = in let protocol_data = {contents; signature = Some (Bls signature)} in return (Some {shell; protocol_data = Operation_data protocol_data}) - | None -> tzfail Signature_aggregation_failure) + | None -> tzfail Baking_errors.Signature_aggregation_failure) (* [partition_consensus_operations_on_proposal operations] partitions consensus operations as follows : @@ -578,7 +565,7 @@ let aggregate_attestations_on_reproposal consensus_operations = {shell; protocol_data = Operation_data protocol_data} in return (attestations_aggregate :: other_operations) - | None -> tzfail Signature_aggregation_failure) + | None -> tzfail Baking_errors.Signature_aggregation_failure) (* [forge] a new [unsigned_block] in accordance with [simulation_kind] and [simulation_mode] *) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 4a43e54b22afd463467dd2e30ad6b25c4a1015f4..89880e76062638462c6bb0617bdace4ee321abe4 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2878,6 +2878,24 @@ module Dal : sig val record_attestation : context -> tb_slot:Slot.t -> t -> context val attestations : context -> t Slot.Map.t + + module Dal_dependent_signing : sig + (** See {!Dal_attestation_repr.Dal_dependent_signing.aggregate_pk}. *) + val aggregate_pk : + subgroup_check:bool -> + consensus_pk:Bls.Public_key.t -> + companion_pk:Bls.Public_key.t -> + t -> + Bls.Public_key.t option + + (** See {!Dal_attestation_repr.Dal_dependent_signing.aggregate_sig}. *) + val aggregate_sig : + subgroup_check:bool -> + consensus_sig:Bls.t -> + companion_sig:Bls.t -> + t -> + Bls.t option + end end type slot_id = {published_level : Raw_level.t; index : Slot_index.t} diff --git a/src/proto_alpha/lib_protocol/dal_attestation_repr.ml b/src/proto_alpha/lib_protocol/dal_attestation_repr.ml index 0daa88ff0fd3ff2a69825673b9a87ac963c5d749..5c9375180b3c745155b80cb9222a238a57054f64 100644 --- a/src/proto_alpha/lib_protocol/dal_attestation_repr.ml +++ b/src/proto_alpha/lib_protocol/dal_attestation_repr.ml @@ -47,6 +47,8 @@ let encoding = Bitset.encoding let empty = Bitset.empty +let to_z = Bitset.to_z + let is_attested t index = let open Dal_slot_index_repr in match Bitset.mem t (to_int index) with @@ -175,3 +177,46 @@ module Accountability = struct total_shards = number_of_shards; } end + +module Dal_dependent_signing = struct + (* Computes [((to_z t) + 1) * companion + consensus]. + + The purpose of the [+ 1] is to guarantee that the result is + different from [consensus], even when [t] is {!empty} (because + dal is optional in attestations, and attestations without dal are + signed with just the consensus key). + + Produces the same result but is faster than calling + [aggregate_weighted_opt + [((to_z t) + 1, companion); (Z.one, consensus)]]. *) + let aggregate ~subgroup_check ~aggregate_opt ~aggregate_weighted_opt + ~consensus ~companion t = + let subgroup_check = Some subgroup_check in + let z = Z.succ (to_z t) in + let weighted_companion_opt = + if Z.equal z Z.one then + (* Small optimization for the [t = empty] case. *) + Some companion + else aggregate_weighted_opt ?subgroup_check [(z, companion)] + in + Option.bind weighted_companion_opt (fun weighted_companion -> + aggregate_opt ?subgroup_check [weighted_companion; consensus]) + + let aggregate_pk ~subgroup_check ~consensus_pk ~companion_pk t = + aggregate + ~subgroup_check + ~aggregate_opt:Bls.aggregate_public_key_opt + ~aggregate_weighted_opt:Bls.aggregate_public_key_weighted_opt + ~consensus:consensus_pk + ~companion:companion_pk + t + + let aggregate_sig ~subgroup_check ~consensus_sig ~companion_sig t = + aggregate + ~subgroup_check + ~aggregate_opt:Bls.aggregate_signature_opt + ~aggregate_weighted_opt:Bls.aggregate_signature_weighted_opt + ~consensus:consensus_sig + ~companion:companion_sig + t +end diff --git a/src/proto_alpha/lib_protocol/dal_attestation_repr.mli b/src/proto_alpha/lib_protocol/dal_attestation_repr.mli index 06d59da6dd1a29d88cd01248bbd115c18e451272..6ef9b54fad05db5f9328f357c1333db3214986af 100644 --- a/src/proto_alpha/lib_protocol/dal_attestation_repr.mli +++ b/src/proto_alpha/lib_protocol/dal_attestation_repr.mli @@ -137,3 +137,34 @@ module Accountability : sig Dal_slot_index_repr.t -> attestation_status end + +(** {!type-t}-dependent combination of public keys or signatures. *) +module Dal_dependent_signing : sig + (** Encodes the {!type-t} argument into a specific combination of + [consensus_pk] and [companion_pk]. + + If [subgroup_check] is set, also checks whether the points are + in the appropriate subgroup. + + Returns [None] if the deserialization of points fails or the + subgroup check fails -- this cannot happen when the provided + keys are valid BLS keys. + + Guarantees that the resulting key is different from both + [consensus_pk] and [companion_pk]. *) + val aggregate_pk : + subgroup_check:bool -> + consensus_pk:Bls.Public_key.t -> + companion_pk:Bls.Public_key.t -> + t -> + Bls.Public_key.t option + + (** Computes the same {!type-t}-dependent combination as + {!aggregate_pk}, but with signatures instead of public keys. *) + val aggregate_sig : + subgroup_check:bool -> + consensus_sig:Bls.t -> + companion_sig:Bls.t -> + t -> + Bls.t option +end diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 0c2f8be7e326d9069d90e83f064345df52a03003..137079c75d4d3c9d0d1e32b64fe81ab97c691d5c 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -826,12 +826,52 @@ module Consensus = struct dal_content in let*? () = + let open Result_syntax in if check_signature then - Operation.check_signature - vi.ctxt - consensus_key.consensus_pk - vi.chain_id - operation + let* dal_dependent_pk = + match (consensus_key.Consensus_key.consensus_pk, dal_content) with + | Bls consensus_bls_pk, Some {attestation = dal_attestation} + when Constants.aggregate_attestation vi.ctxt -> ( + match consensus_key.companion_pk with + | None -> + tzfail + (Missing_companion_key_for_bls_dal + (Consensus_key.pkh consensus_key)) + | Some companion_pk -> ( + let dal_dependent_bls_pk_opt = + Dal.Attestation.Dal_dependent_signing.aggregate_pk + ~subgroup_check:false + (* We disable subgroup check (for better + performances) because the context only + contains valid consensus and companion + keys. *) + ~consensus_pk:consensus_bls_pk + ~companion_pk + dal_attestation + in + match dal_dependent_bls_pk_opt with + | None -> + tzfail + Validate_errors.Consensus.Public_key_aggregation_failure + | Some dal_dependent_bls_pk -> + return + (Signature.Bls dal_dependent_bls_pk + : Signature.Public_key.t))) + | _ -> + (* When the feature flag is not set or the consensus key + is non-BLS, we use the old behavior: the signed + content (which includes the dal_content if any) is + signed by the consensus key alone. + + When the dal_content is None (even with enabled + feature flag and BLS consensus key), it is + represented by [dal_dependent_pk = + consensus_pk]. Notably, this allows a BLS consensus + key without any associated companion key to still + issue an attestation without DAL. *) + return consensus_key.consensus_pk + in + Operation.check_signature vi.ctxt dal_dependent_pk vi.chain_id operation else ok_unit in return voting_power diff --git a/src/proto_alpha/lib_protocol/validate_errors.ml b/src/proto_alpha/lib_protocol/validate_errors.ml index e01f026e71c4effb67c5b7995418d7f9f8e2b867..64555a25cd91c50ed1334f869f31dfd060568874 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.ml +++ b/src/proto_alpha/lib_protocol/validate_errors.ml @@ -129,6 +129,7 @@ module Consensus = struct conflict : operation_conflict; } | Consensus_operation_not_allowed + | Missing_companion_key_for_bls_dal of Consensus_key.t | Aggregate_disabled | Aggregate_in_mempool | Aggregate_not_implemented @@ -362,6 +363,16 @@ module Consensus = struct Data_encoding.empty (function Consensus_operation_not_allowed -> Some () | _ -> None) (fun () -> Consensus_operation_not_allowed) ; + register_error_kind + `Permanent + ~id:"validate.missing_companion_key_for_bls_dal" + ~title:"Missing companion key for DAL attestation with BLS" + ~description: + "The consensus key is a BLS key but is missing a companion key, so it \ + cannot issue a DAL attestation" + Data_encoding.(obj1 (req "consensus_key" Consensus_key.encoding)) + (function Missing_companion_key_for_bls_dal x -> Some x | _ -> None) + (fun x -> Missing_companion_key_for_bls_dal x) ; register_error_kind `Permanent ~id:"validate.aggregate_operation_not_allowed_in_mempool" diff --git a/src/proto_alpha/lib_protocol/validate_errors.mli b/src/proto_alpha/lib_protocol/validate_errors.mli index b95ca58bf12f658face616373b135ea634a00d31..f3d24a8d0f89a2c479a9d7af9b27a4cdff24f478 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.mli +++ b/src/proto_alpha/lib_protocol/validate_errors.mli @@ -82,6 +82,7 @@ module Consensus : sig kind : consensus_operation_kind; conflict : operation_conflict; } + | Missing_companion_key_for_bls_dal of Consensus_key.t | Aggregate_disabled | Aggregate_in_mempool | Aggregate_not_implemented