5

As the title says, I want to to reimplement the hashing part of a policy script in Python 3. I have already made a post over at forums.cardano.org, and someone has pointed me to the right direction of serializing to CBOR according to the CDDL specs.

Here's the relevant part of the CDDL specifications:

native_script =
  [ script_pubkey
  // script_all
  // script_any
  // script_n_of_k
  // invalid_before
     ; Timelock validity intervals are half-open intervals [a, b).
     ; This field specifies the left (included) endpoint a.
  // invalid_hereafter
     ; Timelock validity intervals are half-open intervals [a, b).
     ; This field specifies the right (excluded) endpoint b.
  ]

script_pubkey = (0, addr_keyhash)
script_all = (1, [ * native_script ])
script_any = (2, [ * native_script ])
script_n_of_k = (3, n: uint, [ * native_script ])
invalid_before = (4, uint)
invalid_hereafter = (5, uint)

CBOR and CDDL are both new to me, so I'm not sure if I am doing it right.

According to the above specification, my script

{
   "type":"all",
   "scripts":[
      {
         "keyHash":"37c19eca0d623d804bfeb8951bf1eb0f7fe193deeb71793985dbd25e",
         "type":"sig"
      },
      {
         "type":"before",
         "slot":32241869
      }
   ]
}

would evaluate to: native_script = [1, [[0, $hash28], [5, uint]]]

Is this correct? Do I need to use any cbor tags for serializing?

Here is my attempt so far (I've tried many variations already, but this feels like the most correct one):

import hashlib
from cbor2 import dumps, loads, shareable_encoder, CBORTag

class Sig:
        def __init__(self, hash, slot):
                self.hash=hash
                self.slot=slot


def default_encoder(encoder,value):
        encoder.encode([1, [[0, value.hash.encode()], [5, value.slot]]])

obj=Sig("37c19eca0d623d804bfeb8951bf1eb0f7fe193deeb71793985dbd25e", 32241869)

serialized = dumps(obj, default=default_encoder, value_sharing=False)
print(serialized.hex())
m=hashlib.blake2b(digest_size=28)
m.update(serialized)
print(m.digest().hex())

result = loads(serialized)
print(result)

Needless to say, the code does not calculate me the same policy id as cardano-cli transaction policyid --script-file test.script does.

I am using the library cbor2, which seems the best maintained one for python. Also, I use blake2b with digest_size set to 28 bytes since the ledger specs say blake2b_224 is used for multisig scripts. I am however also not sure if the other parameters in the hash function need to be set or not. My haskell skills are also not the best, so trying to look up in the original source code didn't get me very far, and I've tried so now for several hours :-)

If anyone could help me, it'd be very appreciated.

1 Answer 1

2

It seems I was close, but the str.encode() function was not the correct one to use. Also a zero byte needs to be prepended to the data to be hashed. Thanks to user bwbush from the cardano forum for helping me out, couldn't have done this without his help!

Here's the final solution:

import hashlib
from cbor2 import dumps, loads, shareable_encoder, CBORTag

class Sig:
        def __init__(self, hash, slot):
                self.hash=hash
                self.slot=slot


def default_encoder(encoder,value):
        encoder.encode([1, [[0, bytes.fromhex(value.hash)], [5, value.slot]]])


obj=Sig("37c19eca0d623d804bfeb8951bf1eb0f7fe193deeb71793985dbd25e", 32241869 + i)

serialized = bytes.fromhex("00") + dumps(obj, default=default_encoder, value_sharing=False)

m=hashlib.blake2b(digest_size=28)
m.update(serialized)

print(m.hexdigest())

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