2

How is the “Ledger Blake2b hash” calculated by the tezos-client and the Ledger? I can calculate the other hashes, but unable to figure out the “Ledger Blake2b hash”:

Raw Script-expression-ID-Hash: 0xfe0f19b14a46b7e60df6329444f2e248be9f52e72a0dc2cdc558725cad345689    
Ledger Blake2b hash: J6junU7NtmNS9S71Z2W2Q7PuqCyuAX7YZADqB7hP1Poe                                   
Raw Sha256 hash: 0xd63ad933f6cfc82a6084811e8cc737ffad709fd744e05ec0c1fef8622155a1a2                                                          
Raw Sha512 hash: 0xcd393557d0cb6104869dadf16d77ab24956b1d147fd53388705b3a6805b7eaf2c6df68b9e302e6cb237c40a4fc222d1b9249b57745d1fb73c35fc2b82dbb9535

Tried to look it up in the code, but I’m not able to understand what it exactly does:

https://gitlab.com/tezos/tezos/-/blob/master/src/proto_007_PsDELPH1/lib_client_commands/client_proto_programs_commands.ml#L374

Thanks for your help.

2

tl;dr: we serialize the michelson value to binary, we then hash those bytes it with Blake2b-256. We base58 encode the result.

Long version

Let's consider the case of the Ledger Blake2b has of the integer 1, as given by:

$ tezos-client hash data 1 of type int
Raw packed data: 0x050001
Script-expression-ID-Hash: expru2dKqDfZG8hu4wNGkiyunvq2hdSKuVYtcKta7BWP6Q18oNxKjS
Raw Script-expression-ID-Hash: 0x438c52065d4605460b12d1b9446876a1c922b416103a20d44e994a9fd2b8ed07
Ledger Blake2b hash: 5YgR7rjfSbSbzGEYhhBG9ENRHhdVSUu2TJ6RyNLawjiv
Raw Sha256 hash: 0x57072915640d052f4e2843e1498b10c4f71b62df565525d33c4a66a724e3e20a
Raw Sha512 hash: 0x112e6b61a60ecf001d501f39284ff8a575d818f2f79295b90b24f045d165a490c19cac2add9149dbdd23a8f2cf956dbee0efe17449111e6326e97ab21532f445

How do we go from 1 to 5YgR7rjfSbSbzGEYhhBG9ENRHhdVSUu2TJ6RyNLawjiv?

In protocol 007, the result of this operation is given by src/proto_007_PsDELPH1/lib_client_commands/client_proto_programs_commands.ml on line 374:

  1. First, on line 369, the data is packed(*), that is serialized, to a binary representation. This is handled by the function Script_expr_hash.hash_bytes. This result of this operation is 0x050001. That is the three bytes [5, 0, 1].

    We can replicate this using the pytezos python library, and more specifically using the pytezos.michelson.pack function:

    >>> import pytezos
    >>> pytezos.michelson.pack.pack({'int': 1}, {'prim': 'int'})
    b'\x05\x00\x01'
    

    Aside: {'int': 1} and {'prim': 'int'} are the micheline representation, in JSON, of the michelson value 1 respectively the michelson type int.

  2. Then, on line 390, In the source code, this byte sequence is applied as follows:

    Base58.raw_encode Blake2B.(hash_bytes [bytes] |> to_string)
    

    this is equivalent to

    Base58.raw_encode (Blake2B.to_string (Blake2B.hash_bytes [bytes]))
    

    In other words we:

    1. Hash the byte sequence using the Blake2B module. To be precise, this module implements Blake2b-256 with a digest size of 32 bytes.

      We can try this out using the OCaml REPL utop (this requires that you have utop installed and are in the root of the Tezos source tree):

      $ dune utop src/lib_crypto/
      utop # let hash_in_bytes = Blake2B.hash_bytes [Bytes.of_string "\x05\x00\x01"] ;;
      val hash_in_bytes : Blake2B.t = <abstr>
      

      We can inspect the hash's bytes in hex:

      utop # Blake2B.to_hex @@ hash_in_bytes ;;
      - : Hex.t = 438c52065d4605460b12d1b9446876a1c922b416103a20d44e994a9fd2b8ed07
      

      We can do the same thing in pytezos:

      >>> import pytezos
      >>> hash = pytezos.crypto.blake2b(b'\x05\x00\x01', digest_size=32)
      >>> hash.hexdigest()
      '438c52065d4605460b12d1b9446876a1c922b416103a20d44e994a9fd2b8ed07'
      
    2. We then convert the byte sequence to a string and base58-encode it.

      Again, In OCaml:

      utop # Base58.raw_encode ( Blake2B.to_string hash_in_bytes ) ;;
      - : string = "5YgR7rjfSbSbzGEYhhBG9ENRHhdVSUu2TJ6RyNLawjiv"
      

      In python, we can use the base58 library to encode the hash. We first retrieve the underlying byte sequence of the hash using hash.digest, and then pass it to base58:

      >>> import base58
      >>> base58.b58encode(hash.digest())
      b'5YgR7rjfSbSbzGEYhhBG9ENRHhdVSUu2TJ6RyNLawjiv'
      

(*) I will not go into the details of serialization, as it has been covered extensively in other resources, please ask if you want more information about this.

1
  • Awesome! Many thanks for your fast and detailed answer! For console: echo '050001' | xxd -r -p | b2sum -l 256 |xxd -r -p |base58
    – xinuix
    Dec 18 '20 at 10:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.