Metadata hash fields - Algorand

Metadata hash fields

post by toad on May 19, 2021

is the metadata hashfield refer to the hash we created on asset creation? I’m not seeing it match to what I sent, is there a particular hash we should use?


post by ryanRfox on May 19, 2021

The MetaDataHash field is optional and immutable at creation of the asset. It allows 32-bytes of arbitrary data storage.

A common use is for an NFT creator to hash the source digital asset and include the resulting value into MetaDataHash field. This makes it very easy for someone else to validate authenticity of a given digital asset by comparing both the CreatorAddress and MetaDataHash fields to the known creator’s address and results of hashing the digital file.


post by toad on May 19, 2021

Thanks Ryan, is there a particular hash algorithm to use for the 32byte? I’m just not seeing the values matching what I created to the info on explorer.


post by fabrice on May 19, 2021

Currently there is no specification on what to write in this hash field.

I guess most people would use SHA-256 or SHA-512/256 or Keccak-256.


post by toad on May 19, 2021

Thanks! Let me give that a try.


post by _erik on Jul 8, 2021

[@toad] were you able to solve this?

I’m adding a simple 32 byte hash in the Metadata, but upon creation it doesn’t match.

I found this:

Which states "A 32-byte hash specifying a commitment to asset-specific metadata, encoded with msgpack field am"

Does anyone know if there is a way to avoid the msgpack encoding of the field?


post by fabrice on Jul 9, 2021

Can you create the asset on TestNet (on a fake account), show the code you used, and show the creation transaction in a block explorer, so we can help you debug?

The msgpack encoding is how the algod node process the metadata. You can completely ignore it.

What you need to provide is exactly 32 bytes.


post by _erik on Jul 9, 2021

Hi [@fabrice],

The metadata was generated with hashlib.sha256()

‘7f3a1942999ccdcd0123e76c3b1a63d50cbf92e975f454ddc703e01fe4bd0213’ in bytes

b’\x7f:\x19B\x99\x9c\xcd\xcd\x01#\xe7l;\x1ac\xd5\x0c\xbf\x92\xe9u\xf4T\xdd\xc7\x03\xe0\x1f\xe4\xbd\x02\x13’

Here is the asset created which is showing: “fzoZQpmczc0BI+dsOxpj1Qy/kul19FTdxwPgH+S9AhM=”:
https://testnet.algoexplorer.io/asset/18919028

example of hash:

import hashlib

data = {
    'filed1': 'value',
    'field2': 'value'
}
data_as_text = str(data)
data_hash = hashlib.sha256(data_as_text.encode())

metadata_hash = data_hash.digest()
print('metadata_hash')
print(metadata_hash)
print('size')
print(data_hash.digest_size)

script to create asset:

import hashlib
import json
from algosdk.v2client import algod
from algosdk import account, mnemonic
from algosdk.future.transaction import AssetConfigTxn, AssetTransferTxn, \
    AssetFreezeTxn

from tokens import algod_address, algod_token

mnemonic1 = "mnemonic"

accounts = {}
counter = 1
for m in [mnemonic1]:
    accounts[counter] = {}
    accounts[counter]['pk'] = mnemonic.to_public_key(m)
    accounts[counter]['sk'] = mnemonic.to_private_key(m)
    counter += 1

# Initialize an algod client
algod_client = algod.AlgodClient(algod_token=algod_token,
                                 algod_address=algod_address)

def wait_for_confirmation(client, txid):
    """Utility function to wait until the transaction is
    confirmed before proceeding."""
    last_round = client.status().get('last-round')
    txinfo = client.pending_transaction_info(txid)
    while not (txinfo.get('confirmed-round') and txinfo.get(
            'confirmed-round') > 0):
        print("Waiting for confirmation")
        last_round += 1
        client.status_after_block(last_round)
        txinfo = client.pending_transaction_info(txid)
    print("Transaction {} confirmed in round {}.".format(txid, txinfo.get(
        'confirmed-round')))
    return txinfo

#   Utility function used to print created asset for account and assetid
def print_created_asset(algodclient, account, assetid):
    # note: if you have an indexer instance available it is easier to just use this
    # response = myindexer.accounts(asset_id = assetid)
    # then use 'account_info['created-assets'][0] to get info on the created asset
    account_info = algodclient.account_info(account)
    idx = 0;
    for my_account_info in account_info['created-assets']:
        scrutinized_asset = account_info['created-assets'][idx]
        idx = idx + 1
        if (scrutinized_asset['index'] == assetid):
            print("Asset ID: {}".format(scrutinized_asset['index']))
            print(json.dumps(my_account_info['params'], indent=4))
            break

#   Utility function used to print asset holding for account and assetid
def print_asset_holding(algodclient, account, assetid):
    # note: if you have an indexer instance available it is easier to just use this
    # response = myindexer.accounts(asset_id = assetid)
    # then loop thru the accounts returned and match the account you are looking for
    account_info = algodclient.account_info(account)
    idx = 0
    for my_account_info in account_info['assets']:
        scrutinized_asset = account_info['assets'][idx]
        idx = idx + 1
        if (scrutinized_asset['asset-id'] == assetid):
            print("Asset ID: {}".format(scrutinized_asset['asset-id']))
            print(json.dumps(scrutinized_asset, indent=4))
            break

print("Account 1 address: {}".format(accounts[1]['pk']))

# Get network params for transactions before every transaction.
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# Account 1 creates an asset called latinum and
# sets Account 2 as the manager, reserve, freeze, and clawback address.
# Asset Creation transaction

data = {
    'filed1': 'value',
    'field2': 'value'
}
data_as_text = str(data)
data_hash = hashlib.sha256(data_as_text.encode())

metadata_hash = data_hash.digest()
print('metadata_hash')
print(metadata_hash)
print('size')
print(data_hash.digest_size)

txn = AssetConfigTxn(
    sender=accounts[1]['pk'],
    sp=params,
    total=1000,
    default_frozen=False,
    unit_name="LATINUM",
    asset_name="latinum",
    manager=accounts[1]['pk'],
    reserve=accounts[1]['pk'],
    freeze=accounts[1]['pk'],
    clawback=accounts[1]['pk'],
    url="https://path/to/my/asset/details",
    metadata_hash=metadata_hash,
    decimals=0)
# Sign with secret key of creator
stxn = txn.sign(accounts[1]['sk'])

# Send the transaction to the network and retrieve the txid.
txid = algod_client.send_transaction(stxn)
print(txid)

# Retrieve the asset ID of the newly created asset by first
# ensuring that the creation transaction was confirmed,
# then grabbing the asset id from the transaction.

# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)

try:
    # Pull account info for the creator
    # account_info = algod_client.account_info(accounts[1]['pk'])
    # get asset_id from tx
    # Get the new asset's information from the creator account
    ptx = algod_client.pending_transaction_info(txid)
    asset_id = ptx["asset-index"]
    print_created_asset(algod_client, accounts[1]['pk'], asset_id)
    print_asset_holding(algod_client, accounts[1]['pk'], asset_id)
except Exception as e:
    print(e)

... (additional code omitted for brevity)


post by fabrice on Jul 11, 2021

So it’s working properly.

The hash fzoZQpmczc0BI+dsOxpj1Qy/kul19FTdxwPgH+S9AhM= is in base64.

When you convert it back to hexadecimal, you get your hash in hexadecimal.

On macOS:

$ echo -n "fzoZQpmczc0BI+dsOxpj1Qy/kul19FTdxwPgH+S9AhM=" | base64 -D | xxd -p -c 64
7f3a1942999ccdcd0123e76c3b1a63d50cbf92e975f454ddc703e01fe4bd0213

post by _erik on Jul 12, 2021

[@fabrice] Thank you very much for clarifying and for including the example.