input-output-hk / offchain-metadata-tools

Tools for creating, submitting, and managing off-chain metadata such as multi-asset token metadata
Apache License 2.0
46 stars 33 forks source link

Please provide algorithm about how token-metadata-creator hashes metadata or provide a hash commend on this app #43

Closed ghost closed 2 years ago

ghost commented 2 years ago

I'm working on a native token on cardano. Instead of storing private key in files such as key.skey, we build a sign server and store private key in encrypt database and it's not allowed to be exported. So if I want to sign metadata, I have to hash metadata myself, and then send to sign server. But I'm not familiar with haskell. so I can't find out how token-metadata-creator hashes metadata. So can you provide some help about how to hash items in metadata. or provide a hash commend on this app. Thanks.

sevanspowell commented 2 years ago

Hi @somereason,

This is a basic overview of the algorithm used. I'm not sure it's 100% correct, but it is as far as I can tell, let me know if you have any issues.

Most everything is encoded as CBOR, then hashed with Blake2b_256.

  1. The first step is to grab a series of hashes

    1. hash subject

      • encode string as CBOR
      • hash with Blake2b_256 algorithm
    2. hash well-known property name

      • encode string as CBOR
      • hash with Blake2b_256 algorithm
    3. hash well-known property value (different properties have different representations before hashing)

      • policy:
        • encode string as CBOR
        • hash with Blake2b_256 algorithm
      • decimals
        • encode int as CBOR
        • hash with Blake2b_256 algorithm
      • name
        • encode string as CBOR
        • hash with Blake2b_256 algorithm
      • description
        • encode string as CBOR
        • hash with Blake2b_256 algorithm
      • logo
        • encode bytes as CBOR
        • hash with Blake2b_256 algorithm
      • url
        • encode string as CBOR
        • hash with Blake2b_256 algorithm
      • ticker
        • encode string as CBOR
        • hash with Blake2b_256 algorithm
    4. hash sequence number

      • encode word (unsigned int) as CBOR
      • hash with Blake2b_256 algorithm
  2. Form an attestation digest:

    • concatenate, in order:
      • [ bytes of subject hash (i) , bytes of property name hash (ii) , bytes of property value hash (iii) , bytes of sequence number hash (iv) ]
  3. Sign the attestation digest with your private key.

  4. Create JSON object:

  { "publicKey" = public key of private key used in last step, encoded as base16
  , "signature" = signed attestation digest created in last step, encoded as base16
  }
  1. Add this JSON object to the list of signatures (e.g. https://github.com/cardano-foundation/cardano-token-registry/blob/master/mappings/01332334e96294ad39016bc8fa1e40ec05d601e9e27a83942739fe78474843.json#L26).
ghost commented 2 years ago

Hi @sevanspowell

I'm afraid it doesn't work, I wrote a sample here: https://github.com/somereason/token-metadata-creater-helper/blob/master/index.js It seems hash is a little bit more complex as described here

attestationDigest
    :: HashesForAttestation
    -> Hash Blake2b_256 HashesForAttestation
attestationDigest hashes = castHash $ hashWith id $ mconcat
    [ hashToBytes $ _hashesForAttestation_subject hashes
    , hashToBytes $ _hashesForAttestation_property hashes
    , hashToBytes $ _hashesForAttestation_value hashes
    , hashToBytes $ _hashesForAttestation_sequence_number hashes
    ]

I don't know what does "hashWith id" mean, but I think after concatenate, It should be hash again. But It still doesn't work

gitmachtl commented 2 years ago

I think your line

const siged = sign(content2Hash);

should be

const siged = sign(finalHash);

id could be the policyID, but i also tried many combinations. didn't work for me too.

don't know if this solves your problem. but if you find the solution, please repost your testscript. would need something similar too.

sevanspowell commented 2 years ago

Thank you for your test script @somereason, I will try and have a look tomorrow.

ghost commented 2 years ago

I think your line

const siged = sign(content2Hash);

should be

const siged = sign(finalHash);

id could be the policyID, but i also tried many combinations. didn't work for me too.

don't know if this solves your problem. but if you find the solution, please repost your testscript. would need something similar too.

I tried, and didn't get any luck. I think maybe they should simply add a hash command for this tool. I tried to forked the code and edit myself, but I know nothing about haskell.

gitmachtl commented 2 years ago

I wanna know how the signature is generated to prepare it for hw-wallet integration. Should be enough that the hw-wallet sign the final hash for each property.

ghost commented 2 years ago

Hi @gitmachtl @sevanspowell My demo finally worked, https://github.com/somereason/token-metadata-creater-helper/blob/master/index.js

turn out I neglect data type when I read hex bytes. After I fix this issue, signed hex are matched So algorithm provided by @sevanspowell is correct. I'll run more test before I close this case.

PS: I really don't want to read Haskell code ever again.

gitmachtl commented 2 years ago

awesome, thx! :-) have tested it with various jsons and its matching up. not tested with the base64 encoded logo entries, but should be straight forward too.

and, i feel you with haskell 😄

gitmachtl commented 2 years ago

Hi @gitmachtl @sevanspowell My demo finally worked, https://github.com/somereason/token-metadata-creater-helper/blob/master/index.js

turn out I neglect data type when I read hex bytes. After I fix this issue, signed hex are matched So algorithm provided by @sevanspowell is correct. I'll run more test before I close this case.

PS: I really don't want to read Haskell code ever again.

also tested if with a base64 encoded logo by inserting a simple line like:

if (itemKey != "logo") { var propertyValueHash = cborAndHash(item.value); } //entry is not a logo entry
                  else { var propertyValueHash = cborAndHash(Buffer.from(item.value,'base64'));} //entry is a base64 encoded logo entry

in your for loop, and it also worked.

gitmachtl commented 2 years ago

Thx again, i copied your testscript with added comments and my demofiles into a subdir of my repo for having it handy as a reference. https://github.com/gitmachtl/cardano-related-stuff/tree/master/token_metadata_sign_demo

sevanspowell commented 2 years ago

Great work! I'm glad you've figured it out! Thanks @somereason & @gitmachtl.