diff --git a/src/proto_alpha/lib_delegate/baking_actions.ml b/src/proto_alpha/lib_delegate/baking_actions.ml index d28e84214c1cc636d516c6fd5b25405a904a62e9..7f7cc97609fb5b6ff63b901ce85429dcbdc125f2 100644 --- a/src/proto_alpha/lib_delegate/baking_actions.ml +++ b/src/proto_alpha/lib_delegate/baking_actions.ml @@ -744,24 +744,45 @@ let forge_and_sign_consensus_vote global_state ~branch unsigned_consensus_vote : sk_companion_uri unsigned_operation_bytes in - match (consensus_sig, companion_sig) with + match + ( consensus_sig, + companion_sig, + delegate.consensus_key.public_key, + companion_key.public_key ) + with (* This if-else branch is for BLS mode so both signatures - should be BLS signatures *) - | Signature.Bls consensus_sig, Signature.Bls companion_sig -> ( + should be BLS signatures, and both public keys should be + BLS public keys. *) + | ( Signature.Bls consensus_sig, + Signature.Bls companion_sig, + Signature.Bls consensus_pk, + Signature.Bls companion_pk ) -> ( let dal_dependent_bls_sig_opt = Alpha_context.Dal.Attestation.Dal_dependent_signing.aggregate_sig ~subgroup_check:false + ~consensus_pk + ~companion_pk ~consensus_sig ~companion_sig + ~op:unsigned_operation_bytes dal_attestation in match dal_dependent_bls_sig_opt with | None -> tzfail Baking_errors.Signature_aggregation_failure | Some dal_dependent_bls_sig -> return (Signature.Bls dal_dependent_bls_sig : Signature.t)) - | Signature.Bls _, _ -> + | _, Signature.Bls _, Signature.Bls _, Signature.Bls _ -> + tzfail (Baking_errors.Unexpected_signature_type consensus_sig) + | _, _, Signature.Bls _, Signature.Bls _ -> tzfail (Baking_errors.Unexpected_signature_type companion_sig) - | _ -> tzfail (Baking_errors.Unexpected_signature_type consensus_sig)) + | _, _, _, Signature.Bls _ -> + tzfail + (Baking_errors.Unexpected_public_key_type + delegate.consensus_key.public_key) + | _, _, _, _ -> + tzfail + (Baking_errors.Unexpected_public_key_type companion_key.public_key) + ) in let protocol_data = Operation_data {contents; signature = Some signature} in let signed_operation : Operation.packed = {shell; protocol_data} in diff --git a/src/proto_alpha/lib_delegate/baking_errors.ml b/src/proto_alpha/lib_delegate/baking_errors.ml index b55184dc5c5b66ba06078f0781a9fbcfb88b416c..a83d8ee3c31513f3b9fe40c3dacea844eb15b7c3 100644 --- a/src/proto_alpha/lib_delegate/baking_errors.ml +++ b/src/proto_alpha/lib_delegate/baking_errors.ml @@ -440,6 +440,7 @@ let () = type error += | Signature_aggregation_failure | Unexpected_signature_type of Signature.t + | Unexpected_public_key_type of Signature.public_key let () = register_error_kind @@ -464,4 +465,18 @@ let () = s) Data_encoding.(obj1 (req "signature" Signature.encoding)) (function Unexpected_signature_type s -> Some s | _ -> None) - (fun s -> Unexpected_signature_type s) + (fun s -> Unexpected_signature_type s) ; + register_error_kind + `Permanent + ~id:"Baking_actions.unexpected_public_key_type" + ~title:"Unexpected public key type" + ~description:"Public key should be a BLS public key." + ~pp:(fun ppf s -> + Format.fprintf + ppf + "Public key should be a BLS public key %a." + Signature.Public_key.pp + s) + Data_encoding.(obj1 (req "pk" Signature.Public_key.encoding)) + (function Unexpected_public_key_type s -> Some s | _ -> None) + (fun s -> Unexpected_public_key_type s) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 71ac86c95628468834dd6232592002bf64b54c53..230b0fc3c056094640e6d7d565cd769f9b5db8e1 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2883,21 +2883,30 @@ module Dal : sig module Dal_dependent_signing : sig (** See {!Dal_attestation_repr.Dal_dependent_signing.weight}. *) - val weight : t -> Z.t + val weight : + consensus_pk:Bls.Public_key.t -> + companion_pk:Bls.Public_key.t -> + op:bytes -> + t -> + Z.t (** 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 -> + op:bytes -> t -> Bls.Public_key.t option (** See {!Dal_attestation_repr.Dal_dependent_signing.aggregate_sig}. *) val aggregate_sig : subgroup_check:bool -> + consensus_pk:Bls.Public_key.t -> + companion_pk:Bls.Public_key.t -> consensus_sig:Bls.t -> companion_sig:Bls.t -> + op:bytes -> t -> Bls.t option end @@ -4968,6 +4977,11 @@ module Operation : sig val bls_mode_unsigned_encoding : (Operation.shell_header * packed_contents_list) Data_encoding.t + val serialize_unsigned_operation : + (Operation.shell_header * packed_contents_list) Data_encoding.t -> + 'a operation -> + bytes + type raw = Operation.t = {shell : Operation.shell_header; proto : bytes} val raw_encoding : raw Data_encoding.t diff --git a/src/proto_alpha/lib_protocol/dal_attestation_repr.ml b/src/proto_alpha/lib_protocol/dal_attestation_repr.ml index e14313eceb5c04abd04d9b27d4f7276f7049fcd7..1e151b0a47d90bcd3f72da044fd3d56a3d911e0d 100644 --- a/src/proto_alpha/lib_protocol/dal_attestation_repr.ml +++ b/src/proto_alpha/lib_protocol/dal_attestation_repr.ml @@ -179,46 +179,72 @@ module Accountability = struct end module Dal_dependent_signing = struct - let weight t = Z.succ (to_z t) + module HashModule = + Blake2B.Make + (Base58) + (struct + let name = "Dal attestation hash" - (* Computes [((to_z t) + 1) * companion + consensus]. + let title = "Dal attestation hash" - 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). + let b58check_prefix = "\056\012\165" (* dba(53) *) + + let size = Some 32 + end) + + (* Computes the hash of the public keys, the message [op] and the DAL attestation slot. + This hash is used as a weight for the companion key and companion signature + in the aggregated linear combinations, in [aggregate_pk] and [aggregate_sig]. *) + let weight ~consensus_pk ~companion_pk ~op t = + let consensus_bytes = + Bls.Public_key.hash consensus_pk |> Bls.Public_key_hash.to_bytes + in + let companion_bytes = + Bls.Public_key.hash companion_pk |> Bls.Public_key_hash.to_bytes + in + let bitset_bytes = Z.to_bits (to_z t) |> Bytes.of_string in + HashModule.( + hash_bytes [consensus_bytes; companion_bytes; op; bitset_bytes] + |> to_bytes) + |> Bytes.to_string |> Z.of_bits + + (* Computes [(weight t) * companion_agg + consensus_agg]. Produces the same result but is faster than calling [aggregate_weighted_opt - [((to_z t) + 1, companion); (Z.one, consensus)]]. *) + [(weight t, companion_agg); (Z.one, consensus_agg)]]. *) let aggregate ~subgroup_check ~aggregate_opt ~aggregate_weighted_opt - ~consensus ~companion t = + ~consensus_pk ~companion_pk ~consensus_agg ~companion_agg ~op t = let subgroup_check = Some subgroup_check in - let z = weight t in + let z = weight ~consensus_pk ~companion_pk ~op 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)] + aggregate_weighted_opt ?subgroup_check [(z, companion_agg)] in Option.bind weighted_companion_opt (fun weighted_companion -> - aggregate_opt ?subgroup_check [weighted_companion; consensus]) + aggregate_opt ?subgroup_check [weighted_companion; consensus_agg]) - let aggregate_pk ~subgroup_check ~consensus_pk ~companion_pk t = + let aggregate_pk ~subgroup_check ~consensus_pk ~companion_pk ~op 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 + ~consensus_pk + ~companion_pk + ~consensus_agg:consensus_pk + ~companion_agg:companion_pk + ~op t - let aggregate_sig ~subgroup_check ~consensus_sig ~companion_sig t = + let aggregate_sig ~subgroup_check ~consensus_pk ~companion_pk ~consensus_sig + ~companion_sig ~op t = aggregate ~subgroup_check ~aggregate_opt:Bls.aggregate_signature_opt ~aggregate_weighted_opt:Bls.aggregate_signature_weighted_opt - ~consensus:consensus_sig - ~companion:companion_sig + ~consensus_pk + ~companion_pk + ~consensus_agg:consensus_sig + ~companion_agg:companion_sig + ~op 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 0751c30e7522b48f460c3983078bae67aa9311d8..3a4e1f4969e335e825b0934eb38fa92d88a02a2d 100644 --- a/src/proto_alpha/lib_protocol/dal_attestation_repr.mli +++ b/src/proto_alpha/lib_protocol/dal_attestation_repr.mli @@ -141,10 +141,13 @@ end (** {!type-t}-dependent combination of public keys or signatures. *) module Dal_dependent_signing : sig (** Encodes a {!type-t} into an integer that can be used as a weight - for the companion key when signing an attestation with DAL. - - Post-condition: the output is strictly positive. *) - val weight : t -> Z.t + for the companion key when signing an attestation with DAL. *) + val weight : + consensus_pk:Bls.Public_key.t -> + companion_pk:Bls.Public_key.t -> + op:Bytes.t -> + t -> + Z.t (** Efficiently computes [consensus_pk + (weight t) * companion_pk]. @@ -153,14 +156,12 @@ module Dal_dependent_signing : sig 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]. *) + keys are valid BLS keys. *) val aggregate_pk : subgroup_check:bool -> consensus_pk:Bls.Public_key.t -> companion_pk:Bls.Public_key.t -> + op:Bytes.t -> t -> Bls.Public_key.t option @@ -168,8 +169,11 @@ module Dal_dependent_signing : sig {!aggregate_pk}, but with signatures instead of public keys. *) val aggregate_sig : subgroup_check:bool -> + consensus_pk:Bls.Public_key.t -> + companion_pk:Bls.Public_key.t -> consensus_sig:Bls.t -> companion_sig:Bls.t -> + op:Bytes.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 2adb9d2178c502cabbcbb3156a1fe2ea78a7165f..154dadb0ae78bd814512833898c9710102604066 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -889,6 +889,12 @@ module Consensus = struct (Missing_companion_key_for_bls_dal (Consensus_key.pkh consensus_key)) | Some companion_pk -> ( + let serialized_op = + Operation.( + serialize_unsigned_operation + bls_mode_unsigned_encoding + operation) + in let dal_dependent_bls_pk_opt = Dal.Attestation.Dal_dependent_signing.aggregate_pk ~subgroup_check:false @@ -898,6 +904,7 @@ module Consensus = struct keys. *) ~consensus_pk:consensus_bls_pk ~companion_pk + ~op:serialized_op dal_attestation in match dal_dependent_bls_pk_opt with @@ -1383,7 +1390,7 @@ module Consensus = struct let validate_attestations_aggregate ~check_signature info operation_state block_state oph - ({protocol_data = {contents = Single content; _}; _} as op : + ({protocol_data = {contents = Single content; _}; shell} as op : Kind.attestations_aggregate operation) = let open Lwt_result_syntax in match info.mode with @@ -1415,6 +1422,22 @@ module Consensus = struct consensus_info {level; round; block_payload_hash; slot = Slot.zero} in + let attestation : Kind.attestation operation = + (* Reconstructing an attestation to match the content signed by + each delegate. Fields 'slot' and 'dal_content' are filled with + dummy values as they are not part of the signed payload *) + let consensus_content = + {slot = Slot.zero; level; round; block_payload_hash} + in + let contents = + Single (Attestation {consensus_content; dal_content = None}) + in + {shell; protocol_data = {contents; signature = None}} + in + let serialized_op = + Operation.( + serialize_unsigned_operation bls_mode_unsigned_encoding attestation) + in (* Retrieve public keys that will later be aggregated (keys with weight 1 are put in [pks]; keys with a different weight are put in [weighted_pks]) and compute total attesting @@ -1442,6 +1465,9 @@ module Consensus = struct | Some {attestation = dal_attestation}, Some companion_pk -> let weight = Dal.Attestation.Dal_dependent_signing.weight + ~consensus_pk + ~companion_pk + ~op:serialized_op dal_attestation in if Z.(equal weight one) then diff --git a/tezt/lib_tezos/operation_core.ml b/tezt/lib_tezos/operation_core.ml index bbee44d35219cb7db28a9501d9bf55ee0c23f1fc..e0126d7d67f1b4841da828ef7952216e525aba8e 100644 --- a/tezt/lib_tezos/operation_core.ml +++ b/tezt/lib_tezos/operation_core.ml @@ -114,6 +114,20 @@ let bls_mode_raw t client : Hex.t Lwt.t = t.raw <- Some (`Hex inject_bytes) ; return (`Hex sign_bytes) +(* Same hash module as [src/proto_alpha/lib_protocol/dal_attestation_repr.ml] *) +module HashModule = + Tezos_crypto.Blake2B.Make + (Tezos_crypto.Base58) + (struct + let name = "Dal attestation hash" + + let title = "Dal attestation hash" + + let b58check_prefix = "\056\012\165" (* dba(53) *) + + let size = Some 32 + end) + let sign_tz4_attestation_with_dal ~watermark consensus_key companion_key contents bytes = let dal_attestation = @@ -135,10 +149,41 @@ let sign_tz4_attestation_with_dal ~watermark consensus_key companion_key in match (consensus_signature, companion_signature) with | Bls consensus, Bls companion -> ( + let consensus_pkh = + Tezos_crypto.Signature.Public_key_hash.( + of_b58check_exn consensus_key.public_key_hash) + in + let companion_pkh = + Tezos_crypto.Signature.Public_key_hash.( + of_b58check_exn companion_key.public_key_hash) + in + let consensus_pkh_bytes, companion_pkh_bytes = + match (consensus_pkh, companion_pkh) with + | Bls consensus_pkh, Bls companion_pkh -> + ( Tezos_crypto.Signature.Bls.Public_key_hash.to_bytes consensus_pkh, + Tezos_crypto.Signature.Bls.Public_key_hash.to_bytes companion_pkh + ) + | _ -> assert false + in + let dal_attestation_bytes = + Z.to_bits dal_attestation |> Bytes.of_string + in + let weight = + HashModule.( + hash_bytes + [ + consensus_pkh_bytes; + companion_pkh_bytes; + bytes; + dal_attestation_bytes; + ] + |> to_bytes) + |> Bytes.to_string |> Z.of_bits + in let aggregate = Tezos_crypto.Signature.Bls.aggregate_signature_weighted_opt ~subgroup_check:false - [(Z.one, consensus); (Z.succ dal_attestation, companion)] + [(Z.one, consensus); (weight, companion)] in match aggregate with | Some aggregate -> return (Tezos_crypto.Signature.of_bls aggregate)