Metadata hash fields

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?

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.

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.

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.

Thanks! Let me give that a try.

@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?

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.

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)

Any help would be greatly appreciated.

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

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