github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/docs/rfc/rfc-007-deterministic-proto-bytes.md (about)

     1  # RFC 007 : Deterministic Proto Byte Serialization
     2  
     3  ## Changelog
     4  
     5  - 09-Dec-2021: Initial draft (@williambanfield).
     6  
     7  ## Abstract
     8  
     9  This document discusses the issue of stable byte-representation of serialized messages
    10  within Tendermint and describes a few possible routes that could be taken to address it.
    11  
    12  ## Background
    13  
    14  We use the byte representations of wire-format proto messages to produce
    15  and verify hashes of data within the Tendermint codebase as well as for
    16  producing and verifying cryptographic signatures over these signed bytes.
    17  
    18  The protocol buffer [encoding spec][proto-spec-encoding] does not guarantee that the byte representation
    19  of a protocol buffer message will be the same between two calls to an encoder.
    20  While there is a mode to force the encoder to produce the same byte representation
    21  of messages within a single binary, these guarantees are not good enough for our
    22  use case in Tendermint. We require multiple different versions of a binary running
    23  Tendermint to be able to inter-operate. Additionally, we require that multiple different
    24  systems written in _different languages_ be able to participate in different aspects
    25  of the protocols of Tendermint and be able to verify the integrity of the messages
    26  they each produce.
    27  
    28  While this has not yet created a problem that we know of in a running network, we should
    29  make sure to provide stronger guarantees around the serialized representation of the messages
    30  used within the Tendermint consensus algorithm to prevent any issue from occurring.
    31  
    32  
    33  ## Discussion
    34  
    35  Proto has the following points of variability that can produce non-deterministic byte representation:
    36  
    37  1. Encoding order of fields within a message.
    38  
    39  Proto allows fields to be encoded in any order and even be repeated.
    40  
    41  2. Encoding order of elements of a repeated field.
    42  
    43  `repeated` fields in a proto message can be serialized in any order.
    44  
    45  3. Presence or absence of default values.
    46  
    47  Types in proto have defined default values similar to Go's zero values. 
    48  Writing or omitting a default value are both legal ways of encoding a wire message.
    49  
    50  4. Serialization of 'unknown' fields. 
    51  
    52  Unknown fields can be present when a message is created by a binary with a newer 
    53  version of the proto that contains fields that the deserializer in a different 
    54  binary does not yet know about. Deserializers in binaries that do not know about the field
    55  will maintain the bytes of the unknown field but not place them into the deserialized structure.
    56  
    57  We have a few options to consider when producing this stable representation.
    58  
    59  ### Options for deterministic byte representation
    60  
    61  #### Use only compliant serializers and constrain field usage
    62  
    63  According to [Cosmos-SDK ADR-27][cosmos-sdk-adr-27], when message types obey a simple 
    64  set of rules, gogoproto produces a consistent byte representation of serialized messages.
    65  This seems promising, although more research is needed to guarantee gogoproto always
    66  produces a consistent set of bytes on serialized messages. This would solve the problem 
    67  within Tendermint as written in Go, but would require ensuring that there are similar
    68  serializers written in other languages that produce the same output as gogoproto.
    69  
    70  #### Reorder serialized bytes to ensure determinism.
    71  
    72  The serialized form of a proto message can be transformed into a canonical representation
    73  by applying simple rules to the serialized bytes. Re-ordering the serialized bytes
    74  would allow Tendermint to produce a canonical byte representation without having to
    75  simultaneously maintain a custom proto marshaller.
    76  
    77  This could be implemented as a function in many languages that performed the following 
    78  producing bytes to sign or hashing:
    79  
    80  1. Does not add any of the data from unknown fields into the type to hash.
    81  
    82  Tendermint should not run into a case where it needs to verify the integrity of 
    83  data with unknown fields for the following reasons:
    84  
    85  The purpose of checking hash equality within Tendermint is to ensure that
    86  its local copy of data matches the data that the network agreed on. There should
    87  therefore not be a case where a process is checking hash equality using data that it did not expect
    88  to receive. What the data represent may be opaque to the process, such as when checking the
    89  transactions in a block, _but the process will still have expected to receive this data_,
    90  despite not understanding what their internal structure is. It's not clear what it would
    91  mean to verify that a block contains data that a process does not know about.
    92  
    93  The same reasoning applies for signature verification within Tendermint. Processes
    94  verify that a digital signature signed over a set of bytes by locally reconstructing the 
    95  data structure that the digital signature signed using the process's local data.
    96  
    97  2. Reordered all message fields to be in tag-sorted order.
    98  
    99  Tag-sorting top-level fields will place all fields of the same tag in a adjacent
   100  to eachother within the serialized representation.
   101  
   102  3. Reordered the contents of all `repeated` fields to be in lexicographically sorted order.
   103  
   104  `repeated` fields will appear in a message as having the same tag but will contain different
   105  contents. Therefore, lexicographical sorting will produce a stable ordering of
   106  fields with the same tag.
   107  
   108  4. Deleted all default values from the byte representation.
   109  
   110  Encoders can include default values or omit them. Most encoders appear to omit them
   111  but we may wish to delete them just to be safe.
   112  
   113  5. Recursively performed these operations on any length-delimited subfields.
   114  
   115  Length delimited fields may contain messages, strings, or just bytes. However,
   116  it's not possible to know what data is being represented by such a field.
   117  A 'string' may happen to have the same structure as an embedded message and we cannot
   118  disambiguate. For this reason, we must apply these same rules to all subfields that
   119  may contain messages. Because we cannot know if we have totally mangled the interior 'string'
   120  or not, this data should never be deserialized or used for anything beyond hashing.
   121  
   122  A **prototype** implementation by @creachadair of this can be found in [the wirepb repo][wire-pb].
   123  This could be implemented in multiple languages more simply than ensuring that there are
   124  canonical proto serializers that match in each language.
   125  
   126  ### Future work
   127  
   128  We should add clear documentation to the Tendermint codebase every time we
   129  compare hashes of proto messages or use proto serialized bytes to produces a
   130  digital signatures that we have been careful to ensure that the hashes are performed
   131  properly.
   132  
   133  ### References
   134  
   135  [proto-spec-encoding]: https://developers.google.com/protocol-buffers/docs/encoding
   136  [spec-issue]: https://github.com/tendermint/tendermint/issues/5005
   137  [cosmos-sdk-adr-27]: https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-027-deterministic-protobuf-serialization.md
   138  [cer-proto-3]: https://github.com/regen-network/canonical-proto3
   139  [wire-pb]: https://github.com/creachadair/wirepb
   140