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.