1

I have a contract where I'd like to convert a hash (sha256 in this case, but I'm open to any of the other hashing algorithms available) into an Int (or Nat) between 1 and 100.

I have the code that generates a hash, but it doesn't seem possible to convert the hash bytes into a Nat (or Int) in order to divide or modulo to constrain it to a range.

Trying to modulo the hash directly throws a type error. Here's what I have so far:

import smartpy as sp
        
class HashToNat(sp.Contract):
    def __init__(self):
        self.init(
            rnd = sp.none,
            rnd_nat = 0
        )

    @sp.entry_point
    def do_it(self):
        # Do some stuff
        
        self.data.rnd = sp.some(sp.sha256(sp.pack(sp.now)))
        
        # Error: Type Error
        # sp.TBytes is not sp.TIntOrNat
        self.data.rnd_nat = self.data.rnd.open_some() % 100
    
@sp.add_test(name = "Amount recording")
def test():
    scenario = sp.test_scenario()
    scenario.h1("hash to nat?")
    c1 = HashToNat()
    scenario += c1
    scenario += c1.do_it().run()

EDIT: Based on Raphaël Cauderlier's advice below, I've crafted a working example that appears to work. I welcome any feedback/improvements in the code or any caveats to watch out for.

import smartpy as sp
        
class HashToNat(sp.Contract):
    def __init__(self):
        self.init(
            hash = sp.bytes('0x'),
            nat_value = 0,

            bytes_to_nat = {
                sp.bytes('0x00'): 0, sp.bytes('0x01'): 1,
                sp.bytes('0x02'): 2, sp.bytes('0x03'): 3,
                sp.bytes('0x04'): 4, sp.bytes('0x05'): 5,
                sp.bytes('0x06'): 6, sp.bytes('0x07'): 7,
                sp.bytes('0x08'): 8, sp.bytes('0x09'): 9,
                sp.bytes('0x0a'): 10, sp.bytes('0x0b'): 11,
                sp.bytes('0x0c'): 12, sp.bytes('0x0d'): 13,
                sp.bytes('0x0e'): 14, sp.bytes('0x0f'): 15,
                sp.bytes('0x10'): 16, sp.bytes('0x11'): 17,
                sp.bytes('0x12'): 18, sp.bytes('0x13'): 19,
                sp.bytes('0x14'): 20, sp.bytes('0x15'): 21,
                sp.bytes('0x16'): 22, sp.bytes('0x17'): 23,
                sp.bytes('0x18'): 24, sp.bytes('0x19'): 25,
                sp.bytes('0x1a'): 26, sp.bytes('0x1b'): 27,
                sp.bytes('0x1c'): 28, sp.bytes('0x1d'): 29,
                sp.bytes('0x1e'): 30, sp.bytes('0x1f'): 31,
                sp.bytes('0x20'): 32, sp.bytes('0x21'): 33,
                sp.bytes('0x22'): 34, sp.bytes('0x23'): 35,
                sp.bytes('0x24'): 36, sp.bytes('0x25'): 37,
                sp.bytes('0x26'): 38, sp.bytes('0x27'): 39,
                sp.bytes('0x28'): 40, sp.bytes('0x29'): 41,
                sp.bytes('0x2a'): 42, sp.bytes('0x2b'): 43,
                sp.bytes('0x2c'): 44, sp.bytes('0x2d'): 45,
                sp.bytes('0x2e'): 46, sp.bytes('0x2f'): 47,
                sp.bytes('0x30'): 48, sp.bytes('0x31'): 49,
                sp.bytes('0x32'): 50, sp.bytes('0x33'): 51,
                sp.bytes('0x34'): 52, sp.bytes('0x35'): 53,
                sp.bytes('0x36'): 54, sp.bytes('0x37'): 55,
                sp.bytes('0x38'): 56, sp.bytes('0x39'): 57,
                sp.bytes('0x3a'): 58, sp.bytes('0x3b'): 59,
                sp.bytes('0x3c'): 60, sp.bytes('0x3d'): 61,
                sp.bytes('0x3e'): 62, sp.bytes('0x3f'): 63,
                sp.bytes('0x40'): 64, sp.bytes('0x41'): 65,
                sp.bytes('0x42'): 66, sp.bytes('0x43'): 67,
                sp.bytes('0x44'): 68, sp.bytes('0x45'): 69,
                sp.bytes('0x46'): 70, sp.bytes('0x47'): 71,
                sp.bytes('0x48'): 72, sp.bytes('0x49'): 73,
                sp.bytes('0x4a'): 74, sp.bytes('0x4b'): 75,
                sp.bytes('0x4c'): 76, sp.bytes('0x4d'): 77,
                sp.bytes('0x4e'): 78, sp.bytes('0x4f'): 79,
                sp.bytes('0x50'): 80, sp.bytes('0x51'): 81,
                sp.bytes('0x52'): 82, sp.bytes('0x53'): 83,
                sp.bytes('0x54'): 84, sp.bytes('0x55'): 85,
                sp.bytes('0x56'): 86, sp.bytes('0x57'): 87,
                sp.bytes('0x58'): 88, sp.bytes('0x59'): 89,
                sp.bytes('0x5a'): 90, sp.bytes('0x5b'): 91,
                sp.bytes('0x5c'): 92, sp.bytes('0x5d'): 93,
                sp.bytes('0x5e'): 94, sp.bytes('0x5f'): 95,
                sp.bytes('0x60'): 96, sp.bytes('0x61'): 97,
                sp.bytes('0x62'): 98, sp.bytes('0x63'): 99,
                sp.bytes('0x64'): 100, sp.bytes('0x65'): 101,
                sp.bytes('0x66'): 102, sp.bytes('0x67'): 103,
                sp.bytes('0x68'): 104, sp.bytes('0x69'): 105,
                sp.bytes('0x6a'): 106, sp.bytes('0x6b'): 107,
                sp.bytes('0x6c'): 108, sp.bytes('0x6d'): 109,
                sp.bytes('0x6e'): 110, sp.bytes('0x6f'): 111,
                sp.bytes('0x70'): 112, sp.bytes('0x71'): 113,
                sp.bytes('0x72'): 114, sp.bytes('0x73'): 115,
                sp.bytes('0x74'): 116, sp.bytes('0x75'): 117,
                sp.bytes('0x76'): 118, sp.bytes('0x77'): 119,
                sp.bytes('0x78'): 120, sp.bytes('0x79'): 121,
                sp.bytes('0x7a'): 122, sp.bytes('0x7b'): 123,
                sp.bytes('0x7c'): 124, sp.bytes('0x7d'): 125,
                sp.bytes('0x7e'): 126, sp.bytes('0x7f'): 127,
                sp.bytes('0x80'): 128, sp.bytes('0x81'): 129,
                sp.bytes('0x82'): 130, sp.bytes('0x83'): 131,
                sp.bytes('0x84'): 132, sp.bytes('0x85'): 133,
                sp.bytes('0x86'): 134, sp.bytes('0x87'): 135,
                sp.bytes('0x88'): 136, sp.bytes('0x89'): 137,
                sp.bytes('0x8a'): 138, sp.bytes('0x8b'): 139,
                sp.bytes('0x8c'): 140, sp.bytes('0x8d'): 141,
                sp.bytes('0x8e'): 142, sp.bytes('0x8f'): 143,
                sp.bytes('0x90'): 144, sp.bytes('0x91'): 145,
                sp.bytes('0x92'): 146, sp.bytes('0x93'): 147,
                sp.bytes('0x94'): 148, sp.bytes('0x95'): 149,
                sp.bytes('0x96'): 150, sp.bytes('0x97'): 151,
                sp.bytes('0x98'): 152, sp.bytes('0x99'): 153,
                sp.bytes('0x9a'): 154, sp.bytes('0x9b'): 155,
                sp.bytes('0x9c'): 156, sp.bytes('0x9d'): 157,
                sp.bytes('0x9e'): 158, sp.bytes('0x9f'): 159,
                sp.bytes('0xa0'): 160, sp.bytes('0xa1'): 161,
                sp.bytes('0xa2'): 162, sp.bytes('0xa3'): 163,
                sp.bytes('0xa4'): 164, sp.bytes('0xa5'): 165,
                sp.bytes('0xa6'): 166, sp.bytes('0xa7'): 167,
                sp.bytes('0xa8'): 168, sp.bytes('0xa9'): 169,
                sp.bytes('0xaa'): 170, sp.bytes('0xab'): 171,
                sp.bytes('0xac'): 172, sp.bytes('0xad'): 173,
                sp.bytes('0xae'): 174, sp.bytes('0xaf'): 175,
                sp.bytes('0xb0'): 176, sp.bytes('0xb1'): 177,
                sp.bytes('0xb2'): 178, sp.bytes('0xb3'): 179,
                sp.bytes('0xb4'): 180, sp.bytes('0xb5'): 181,
                sp.bytes('0xb6'): 182, sp.bytes('0xb7'): 183,
                sp.bytes('0xb8'): 184, sp.bytes('0xb9'): 185,
                sp.bytes('0xba'): 186, sp.bytes('0xbb'): 187,
                sp.bytes('0xbc'): 188, sp.bytes('0xbd'): 189,
                sp.bytes('0xbe'): 190, sp.bytes('0xbf'): 191,
                sp.bytes('0xc0'): 192, sp.bytes('0xc1'): 193,
                sp.bytes('0xc2'): 194, sp.bytes('0xc3'): 195,
                sp.bytes('0xc4'): 196, sp.bytes('0xc5'): 197,
                sp.bytes('0xc6'): 198, sp.bytes('0xc7'): 199,
                sp.bytes('0xc8'): 200, sp.bytes('0xc9'): 201,
                sp.bytes('0xca'): 202, sp.bytes('0xcb'): 203,
                sp.bytes('0xcc'): 204, sp.bytes('0xcd'): 205,
                sp.bytes('0xce'): 206, sp.bytes('0xcf'): 207,
                sp.bytes('0xd0'): 208, sp.bytes('0xd1'): 209,
                sp.bytes('0xd2'): 210, sp.bytes('0xd3'): 211,
                sp.bytes('0xd4'): 212, sp.bytes('0xd5'): 213,
                sp.bytes('0xd6'): 214, sp.bytes('0xd7'): 215,
                sp.bytes('0xd8'): 216, sp.bytes('0xd9'): 217,
                sp.bytes('0xda'): 218, sp.bytes('0xdb'): 219,
                sp.bytes('0xdc'): 220, sp.bytes('0xdd'): 221,
                sp.bytes('0xde'): 222, sp.bytes('0xdf'): 223,
                sp.bytes('0xe0'): 224, sp.bytes('0xe1'): 225,
                sp.bytes('0xe2'): 226, sp.bytes('0xe3'): 227,
                sp.bytes('0xe4'): 228, sp.bytes('0xe5'): 229,
                sp.bytes('0xe6'): 230, sp.bytes('0xe7'): 231,
                sp.bytes('0xe8'): 232, sp.bytes('0xe9'): 233,
                sp.bytes('0xea'): 234, sp.bytes('0xeb'): 235,
                sp.bytes('0xec'): 236, sp.bytes('0xed'): 237,
                sp.bytes('0xee'): 238, sp.bytes('0xef'): 239,
                sp.bytes('0xf0'): 240, sp.bytes('0xf1'): 241,
                sp.bytes('0xf2'): 242, sp.bytes('0xf3'): 243,
                sp.bytes('0xf4'): 244, sp.bytes('0xf5'): 245,
                sp.bytes('0xf6'): 246, sp.bytes('0xf7'): 247,
                sp.bytes('0xf8'): 248, sp.bytes('0xf9'): 249,
                sp.bytes('0xfa'): 250, sp.bytes('0xfb'): 251,
                sp.bytes('0xfc'): 252, sp.bytes('0xfd'): 253,
                sp.bytes('0xfe'): 254, sp.bytes('0xff'): 255
            }
        )

    @sp.entry_point
    def hash_to_nat(self, hash_bytes):
        sp.set_type(hash_bytes, sp.TBytes)
        
        # Convert given string (as bytes) to sha256 hash
        self.data.hash = sp.sha256(hash_bytes)

        # Iterate over each byte of the hash and look up the corresponding int value from our big table above
        x = sp.local('x', 0)
        # Store the 'total' Nat value of the bytes
        total = sp.local('total', 0)
        hash_len = sp.local("hash_len", sp.len(self.data.hash))
        sp.while x.value < hash_len.value:
            # Add to previous total and then multiple the new value by 256
            total.value = (total.value + self.data.bytes_to_nat[sp.slice(self.data.hash, x.value, 1).open_some()]) * 256
            x.value = x.value + 1
        
        # Save this here to allow the test to verify
        self.data.nat_value = total.value
        sp.trace(self.data.nat_value)
    
@sp.add_test(name = "sha256 to nat")
def test():
    scenario = sp.test_scenario()

    c1 = HashToNat()
    scenario += c1
    c1.hash_to_nat(sp.utils.bytes_of_string('abc'))
    scenario.verify(c1.data.nat_value == 21591646332695244893830101741604419496986850225924227680630524372407505175031040)
    
    c1.hash_to_nat(sp.utils.bytes_of_string(''))
    scenario.verify(c1.data.nat_value == 26364758079885848839561014354580818634188774565938696958305125632285610262156544)

1 Answer 1

3

There is no instruction in Michelson to interpret arbitrary bytes as numbers. The simplest way to achieve what you want is to use a constant table (as a Michelson map) from bytes of length 1 to their values (between 0 and 255).

Then, depending on how uniform you need your distribution to be, you can either take a modulo on the value of the last byte of your hash (which would be biased in favor of the [0, 55] interval) or iterate over the complete hash to compute a big number before taking the modulo.

Another solution is to use UNPACK nat but this is only convenient in the [0..63] interval.

5
  • Thanks! To make sure I understand your first sentence: To convert from bytes using a map, it would be a map with 256 keys, and each key is one of 16 bytes in the hash (0x01 to 0xff, or 0-255)? But then adding those up would only give you a max of ~8192 which seems quite smaller than expected. What am I missing, or is this what you are suggesting?
    – Geoff
    Jul 5, 2021 at 18:13
  • 1
    > it would be a map with 256 keys, and each key is one of 16 bytes in the hash (0x01 to 0xff, or 0-255)? Yes > adding those up would only give you a max of ~8192 which seems quite smaller than expected. You don't only add them, you also multiply by 256 each time you add a byte. Jul 6, 2021 at 9:18
  • 1
    Ok thanks, I've updated my q above to include a working example using your method. It seems to work (although I have not tested how the distribution of the random values looks after using a modulo on the resulting nat value). Any further comments are very welcome!
    – Geoff
    Jul 8, 2021 at 1:03
  • 2
    The way you currently do it you willl always get a multiple of 256. If you want to avoid that you shoud replace total.value = (total.value + self.data.bytes_to_nat[sp.slice(self.data.hash, x.value, 1).open_some()]) * 256 by total.value = total.value * 256 + self.data.bytes_to_nat[sp.slice(self.data.hash, x.value, 1).open_some()]. Jul 12, 2021 at 12:44
  • If anyone else comes along, I turned this into a "random number generator" contract 😊 github.com/asbjornenge/tezos-randomizer Thanks a bunch for great inspiration both of you 💖 Nov 9, 2021 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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