Hash script data

I wanted to understand a bit better the hash-script-data functionality from cardano-cli. I checked the source code but had no luck in understanding how this hash is made.

From the examples, if we run:

cardano-cli transaction hash-script-data --script-data-value 42

We get the following hash:

9e1199a988ba72ffd6e9c269cadb3b53b5f360ff99f112d9b2ee30c4d74ad88b

However if we try to do a simple SHA256 hash of a 42 value we get:

73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049

Also tried in the Plutus object format:

{
  "constructor": 0,
  "fields": [{
    "int": 42
  }]
}

but none of those match the hash generated by cardano-cli.

Also tried innumerous other alternatives but obviously I’m missing something, anyone knows what’s the process for generating this hash?

Thank you!

1 Like

It’s not a SHA256 hash.

Related topic: Cardano-cli hash-script-data
ScriptData code: cardano-node/ScriptData.hs at 2124712b360b37b5d590279a4674d504dd32eda5 · input-output-hk/cardano-node · GitHub
ScriptData doc: Cardano.Api.ScriptData
CBOR: rfc7049
Hero: @bwbush

3 Likes

Thanks for the references! I was able to reproduce the hash from cardano-cli using libsodium generic-hash and converting the 42 number into CBOR (182a) prior to hashing.

I was trying to do the same for objects though, without luck:

For example, the object mentioned in the other topic:

{
   "constructor":0,
   "fields":[
      {
         "int": 4
      }
   ]
}

I convert it to CBOR A26B636F6E7374727563746F7200666669656C647381A163696E7404 and then try to hash it and I get 9aa23c572ebf5d39113f516d4bbf69d1b9650e55ea63cdd7fb4ee77975def5b0, while if I put that data in a json file and use cardano-cli transaction hash-script-data --script-data-file ./tests/sample.json I get 8bb54ceaee2f57731094a2e96b8ad87bcc8988b1fa838e8c833eb3b72eae29a1

Also tried using bytes instead of UTF string in the CBOR encoding: A24B636F6E7374727563746F7200466669656C647381A143696E7404
which gives me 5677bf1048dad6dce36483d2bec0fafc71f5dc9b01a2b1326c6443c2cd264b50

Even when reading the source code I’m not sure what I may be missing here :frowning:

Anyone has any idea?

I haven’t tracked down the complete answer, which I believe involves finding the schema for how PlutusData is serialized—the information will be in the ledger spec or its implementation.

A partial answer is that one shouldn’t serialize text like "construction". That is too verbose and it is very likely that there is a custom, succinct serialization that is closely related to the CBOR you generated. I’ll poke around in the specs or source code and post here when I find the answer.

2 Likes

That makes sense and is already very helpful! Made me find this which may contain the answer about how PlutusData is encoded into CBOR :slight_smile: definitely looks like my encoding is not correct.

I’ll check that in more detail tomorrow and post here my findings,

Thank you all again for the help :slight_smile:

Alright, looking at the source code it seems the constructor is converted into a tag, based on its index. The values are then converted into a list, which makes the CBOR encoding to be much smaller, makes much more sense.

What I get from the object I mentioned is: D8798104 (Equivalent to 121([4])) - Tag 121, since constructor value is 0 (121 + 0) and a list with values.

Hash turns it into 034eb48ee254d7e58ff9fda1398f8086b9a8c392e63322960137e6c30bbd7bcf, which is still different than cardano-cli output 8bb54ceaee2f57731094a2e96b8ad87bcc8988b1fa838e8c833eb3b72eae29a1 :slightly_frowning_face:

Still seems like a step forward though, feels like we’re getting closer :joy:

If anyone has any idea what I may be missing here let me know

2 Likes

Found the answer for this case :slight_smile: Turns out the list of fields property should be an indefinite length array.

The correct CBOR is: d8799f04ff

d879 - Equivalent to the tag 121
9f - indefinite-length array
04 - The value 4
ff - The list break

Thanks everyone for the inputs, they were really helpful!

2 Likes

This is probably too late but I just found this repo which could be helpful in the future.

Basically this is Intel’s take on CBOR serialization (probably inspired by TinyXML if anyone else remembers old-school libraries)

1 Like

Can you shed some light because I’m missing something.

Using follwoing code

CborWriter w = new CborWriter();
            w.WriteUInt32(42);
            var encoded = w.Encode();
            var data =  Sodium.Utilities.BinaryToHex(encoded);
            Console.WriteLine(data);
            var res = Sodium.GenericHash.Hash(encoded, null, 32);
            Console.WriteLine(Sodium.Utilities.BinaryToHex(res));

output:
182a
9e1199a988ba72ffd6e9c269cadb3b53b5f360ff99f112d9b2ee30c4d74ad88b

I’m able to get same hash as for running:
cardano-cli transaction hash-script-data --script-data-value 42
9e1199a988ba72ffd6e9c269cadb3b53b5f360ff99f112d9b2ee30c4d74ad88b

Now I have a json file with simple data:
{“fields”:[],“constructor”:0}

if I execute cli
cardano-cli transaction hash-script-data --script-data-file cos.json
923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec

if I execute:
cardano-cli transaction hash-script-data --script-data-value {“constructor”:0,“fields”:}
e91e9c37cad5a5215f210776134143165a4c438a46cc0d82e5111611805c3864

However if I execute it in c#:

CborWriter cw = new CborWriter();
            cw.WriteStartMap(null);
            cw.WriteTextString("constructor");
            cw.WriteUInt32(0);
            cw.WriteTextString("fields");
            cw.WriteStartArray(null);
            cw.WriteEndArray();
            cw.WriteEndMap();
            var complexEncoded = cw.Encode();
            
            var dataComplex = Sodium.Utilities.BinaryToHex(complexEncoded);
            Console.WriteLine(dataComplex);
            var complexRes = Sodium.GenericHash.Hash(complexEncoded, null, 32);
            Console.WriteLine(Sodium.Utilities.BinaryToHex(complexRes));

bf6b636f6e7374727563746f7200666669656c64739fffff
2b50f0300204565e13018c878fffdfc646f7c8d78fc140e89a3084b6c35c5a90 - hash

  1. Why cardano-cli that takes an file input and the one that takes string value input return different value?
  2. Any idea how to make c# code to return same hash?

I have just checked the new beta version of the
npm i @emurgo/cardano-serialization-lib-nodejs@10.0.0-beta.8

and it works same way as the cardano-cli

import { Buffer } from "buffer";
import * as pp from "@emurgo/cardano-serialization-lib-nodejs"
export const fromHex = (hex) => Buffer.from(hex, "hex");
export const toHex = (bytes) => Buffer.from(bytes).toString("hex");
const datum = pp.PlutusData.new_constr_plutus_data(
  pp.ConstrPlutusData.new(
    pp.BigNum.from_str("0"),
    pp.PlutusList.new()
  )
);
var res = pp.hash_plutus_data(datum);
console.log(res);
console.log(toHex(res.to_bytes()));

Does it? I wasn’t able to reproduce it with 10.0.0-beta.8. What argument did you provide to cardano-cli transaction hash-script-data to get the same hash as with the serialization lib snippet you posted?

Let me paste the whole code

import { Buffer } from "buffer";
import * as pp from "@emurgo/cardano-serialization-lib-nodejs"
import * as fs from "fs";
import * as uuid from "uuid";
import { execSync } from "child_process";
export const fromHex = (hex) => Buffer.from(hex, "hex");
export const toHex = (bytes) => Buffer.from(bytes).toString("hex");
export const toByteArray = (name) => Buffer.from(name, "utf8");

async function checkCliOutput(content: string) {
  var fileName = `${uuid.v1()}.json`;
  await fs.promises.writeFile(fileName, content);
  var proc = execSync(`cardano-cli transaction hash-script-data --script-data-file ${fileName}`);
  await fs.promises.unlink(fileName);
  return proc.toString("utf8");
}

function hashDatum(datum: pp.PlutusData) {
  var res = pp.hash_plutus_data(datum);
  return toHex(res.to_bytes());
}

export async function run() {

  let address = pp.Address.from_bech32("addr_test1vp79fjgpkfwhrkk0gavz4m8pg6wq4tl0gggj0x5ksscjs7gguxkrx");
  console.log(address);

  console.log('run for contructor0')
  var cli0Res = await checkCliOutput('{"constructor":0,"fields":[]}');
  const datumConstructor0 = pp.PlutusData.new_constr_plutus_data(
    pp.ConstrPlutusData.new(
      pp.BigNum.from_str("0"),
      pp.PlutusList.new()
    )
  );
  var emurgo0Res = hashDatum(datumConstructor0);
  var same0 = emurgo0Res.trim() == cli0Res.trim()
  console.log(`emurgo hashed datum: ${emurgo0Res.trim()} cli: ${cli0Res.trim()} equal ${same0}`)


  console.log('run for contructor1')
  var cli1Res = await checkCliOutput('{"constructor":1,"fields":[]}');
  const datumConstructor1 = pp.PlutusData.new_constr_plutus_data(
    pp.ConstrPlutusData.new(
      pp.BigNum.from_str("1"),
      pp.PlutusList.new()
    )
  );
  var emurgo1Res = hashDatum(datumConstructor1);
  var same1 = emurgo1Res.trim() == cli1Res.trim()
  console.log(`emurgo hashed datum: ${emurgo1Res.trim()} cli: ${cli1Res.trim()} equal ${same1}`)

  console.log('run for marketPlace')
  //AdamNFT == 4164616d4e4654
  var marketPlaceCliRes = await checkCliOutput('{"constructor":0,"fields":[{"bytes":"e8d475a410f15d38c72f65adb29b3224547a5d883343b1bc447f961d"},{"int":6000000},{"bytes":"f9160cfb676909cbd6a6a9bdd7868abd1e3b988fd32ceee05b95246d"},{"bytes":"4164616d4e4654"}]}');
  var list = pp.PlutusList.new();
  list.add(pp.PlutusData.new_bytes(fromHex("e8d475a410f15d38c72f65adb29b3224547a5d883343b1bc447f961d")));
  list.add(pp.PlutusData.new_integer(pp.BigInt.from_str("6000000")));
  list.add(pp.PlutusData.new_bytes(fromHex("f9160cfb676909cbd6a6a9bdd7868abd1e3b988fd32ceee05b95246d")));
  //fromHex("4164616d4e4654") use toByteArray
  list.add(pp.PlutusData.new_bytes(toByteArray("AdamNFT")));
  const marketPlaceDatum = pp.PlutusData.new_constr_plutus_data(
    pp.ConstrPlutusData.new(
      pp.BigNum.from_str("0"),
      list
    )
  );
  var marketPlaceHashRes = hashDatum(marketPlaceDatum);
  var marketPlaceAndCliAreSame = marketPlaceCliRes.trim() == marketPlaceHashRes.trim()
  console.log(`emurgo hashed datum: ${marketPlaceHashRes.trim()} cli: ${marketPlaceCliRes.trim()} equal ${marketPlaceAndCliAreSame}`)
}

and package.json

{
  "dependencies": {
    "@emurgo/cardano-serialization-lib-nodejs": "^10.0.0-beta.8",
    "buffer": "^6.0.3",
    "child_process": "^1.0.2",
    "typescript": "^4.5.2",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@types/node": "^16.11.12"
  }
}