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:
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
.
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:
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'
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.