diff --git a/docs-main/appdev/deep-dives/external-signing-hashing-algorithm.mdx b/docs-main/appdev/deep-dives/external-signing-hashing-algorithm.mdx index 812b5fb73..e5b7682a9 100644 --- a/docs-main/appdev/deep-dives/external-signing-hashing-algorithm.mdx +++ b/docs-main/appdev/deep-dives/external-signing-hashing-algorithm.mdx @@ -37,6 +37,7 @@ The hashing algorithm is tied to the protocol version of the synchronizer used t |------------------|---------------------------| | v34 | V2 | | v35 | V2, V3 | +| dev | V2, V3, V4 | ### Transaction Nodes @@ -60,11 +61,21 @@ message Node { } ``` +## V4 + +### General approach + +V4 follows the same general hashing approach as V3. The V3 section below defines the common encoding used by V4 unless a V4-specific difference is stated. + +### Changes from V3 + +- Exercise nodes with version dev include `external_call_results` in the prepared transaction hash. This binds the recorded external-call result payloads shown in prepared transaction review to the external party's signature. + ## V3 ### General approach -The hash of the `PreparedTransaction` is computed by encoding every protobuf field of the messages to byte arrays, and feeding those encoded values into a `SHA-256` hash builder. The rest of this section details how to deterministically encode every proto message into a byte array. Sometimes during the process, partially encoded results are hashed with SHA-256, and the resulting hash value serves as the encoding in messages further up. This is explicit when necessary. +The hash of the `PreparedTransaction` is computed by encoding the fields specified by this section to byte arrays, and feeding those encoded values into a `SHA-256` hash builder. The rest of this section details how to deterministically encode every proto message into a byte array. Sometimes during the process, partially encoded results are hashed with SHA-256, and the resulting hash value serves as the encoding in messages further up. This is explicit when necessary. Big Endian notation is used for numeric values. Furthermore, protobuf numeric values are encoded according to their Java type representation. Refer to the official protobuf documentation for more information about protobuf to Java type mappings: [https://protobuf.dev/programming-guides/proto3/#scalar](https://protobuf.dev/programming-guides/proto3/#scalar) In particular: @@ -89,7 +100,7 @@ Additionally, this is the java library used under the hood in Canton to serializ > - These effectively replace a fixed ledger time with time bounds, allowing Daml Models to make assertions based on time without restricting the signing window as was required with a fixed set ledger time. -V3 introduces support for contract keys. Usage of contract keys in externally signed transactions requires usage of V3. Contract keys will not work on V2. Also note that V3 is only supported on protocol version 35. +V3 introduces support for contract keys. Usage of contract keys in externally signed transactions requires usage of V3. Contract keys will not work on V2. Also note that V3 is supported from protocol version 35 onwards. ### Notation and Utility Functions @@ -566,34 +577,76 @@ fn encode_node(create): #### Exercise -``` -fn encode_node(exercise): - 0x01 || # Node encoding version - encode(exercise.lf_version) || # Node LF version - 0x01 || # Exercise node tag - encode_seed(find_seed(node.node_id).get) || - encode(exercise.contract_id) || - encode(exercise.package_name) || - encode(exercise.template_id) || - encode(exercise.signatories) || - encode(exercise.stakeholders) || - encode(exercise.acting_parties) || - encode(exercise.interface_id) || - encode(exercise.choice_id) || - encode(exercise.chosen_value) || - encode(exercise.consuming) || - encode(exercise.exercise_result) || - encode(exercise.choice_observers) || - encode(exercise.children) -``` + + + + ``` + fn encode_node(exercise): + encode(exercise.lf_version) || # Node LF version + 0x01 || # Exercise node tag + encode_seed(find_seed(node.node_id).get) || + encode(exercise.contract_id) || + encode(exercise.package_name) || + encode(exercise.template_id) || + encode(exercise.signatories) || + encode(exercise.stakeholders) || + encode(exercise.acting_parties) || + encode(exercise.interface_id) || + encode(exercise.choice_id) || + encode(exercise.chosen_value) || + encode(exercise.consuming) || + encode(exercise.exercise_result) || + encode(exercise.choice_observers) || + encode(exercise.by_key) || + encode(exercise.key) || + encode(exercise.external_call_results) || # new in V4 + encode(exercise.children) + ``` + + + + + ``` + fn encode_node(exercise): + encode(exercise.lf_version) || # Node LF version + 0x01 || # Exercise node tag + encode_seed(find_seed(node.node_id).get) || + encode(exercise.contract_id) || + encode(exercise.package_name) || + encode(exercise.template_id) || + encode(exercise.signatories) || + encode(exercise.stakeholders) || + encode(exercise.acting_parties) || + encode(exercise.interface_id) || + encode(exercise.choice_id) || + encode(exercise.chosen_value) || + encode(exercise.consuming) || + encode(exercise.exercise_result) || + encode(exercise.choice_observers) || + encode(exercise.by_key) || + encode(exercise.key) || + encode(exercise.children) + ``` + + + For Exercise nodes, the node seed **MUST** be defined. Therefore it is encoded as a **non** optional field, as noted via the `.get` in `find_seed(node.node_id).get`. If the seed of an exercise node cannot be found in the list of `node_seeds`, encoding must be stopped and it should be reported as a bug. - -The last encoded value of the exercise node is its `children` field. This recursively traverses the transaction tree. - +#### External call result + +When V4 encodes an `external_call_results` element, the element is encoded as follows: + +``` +fn encode_external_call_result(result): + return encode(result.extension_id) || + encode(result.function_id) || + encode(result.config) || + encode(result.input) || + encode(result.output) +``` #### Fetch @@ -682,7 +735,7 @@ Finally, compute the hash that needs to be signed to commit to the ledger change ``` fn encode(prepared_transaction): 0x00000030 || # Hash purpose - 0x03 || # Hashing Scheme Version + hashing_scheme_version_byte || # e.g. 0x03 for V3, 0x04 for V4 hash(transaction) || hash(metadata) ```