Error with metadata hash length/encoding for ASA

Hi all,

I’m new here and am working on creating an ASA using the Javascript SDK. I’ve read through the ARC3 standards and I’d like to include sha256 digest as the metadata hash for the ASA. I’ve never dealt with hashes before and am getting very confused about the encoding/length. I’m pretty sure the answer is dead-simple but I’m just not understanding things.

As an example suppose I just want to hash a URL “https://developer.algorand.org/”. I can do the hashing using the crypto module in node, but the output is apparently not in the right format for the SDK, as I get an error saying that the hash must be “32 byte Uint8Array or string”.

Here’s how I’m doing the hashing:

const hash = crypto.createHash('sha256');
const data = hash.update('https://developer.algorand.org/', 'utf8');
const metadataHash = (data.digest('hex'))

I can change the digest encoding from ‘hex’ to ‘base64’ and also still get something reasonable (in the sense that it matches what I get from doing this on various websites). However, when doing either of these and passing the metadataHash to the SDK function makeAssetCreateTxnWithSuggestedParams, I get the error. My guess is that it’s because my metadataHash string has too many characters (e.g., 64 with hex or 44 with base64). I’m just not understanding exactly what I need to do in order to get it into the correct 32-byte string representation.

Any help would be greatly appreciated!

UPDATE:

After doing a lot of reading and experimenting, I think I’ve sorted out what the real issue is, but have yet to figure out how to actually fix things. In the js-sdk documention for makeAssetCreateTxnWithSuggestedParams, the metadata hash field must be a “Uint8Array or UTF-8 string representation of a hash commitment with respect to the asset. Must be exactly 32 bytes long.” So, my goal is to create a sha256 hash of some metadata (just a string for testing right now) so that it will be exactly 32 bytes (256 bits) long.

Here’s how I generate my hash (using node.js):

const metadata = 'metadata json goes here';
const metadataBuffer = crypto.createHash('sha256').update(metadata, 'utf8').digest();
const metadataHash = new Uint8Array(metadataBuffer);

I’m able to pass this Uint8Array version of the hash into makeAssetCreateTxnWithSuggestedParams and the transaction is generated without error since it’s a 32-byte Uint8Array.

However, I’m unable to create the transaction if I instead try to pass in a string. I understand that the string needs to be in utf8 representation, but the issue is that when I convert the buffer into a utf8 encoded string (using metadataBuffer.toString('utf8')), I end up with non-printable/invalid/non-ascii (unsure of the exact term) characters. This seems to cause the sdk to throw an error. My understanding is that the only way to avoid these characters would be to use a binary-to-text encoding like hex or base64. The problem with this is that doing so gives a string that’s longer than 32 characters (64 for hex and 44 for base64). So, it seems like I can’t avoid these kinds of characters when creating the 32 byte utf8 string, which suggests I should just use the Uint8Array and move on from the issue.

I’m totally fine just using the Uint8Array from above (it works after all), but the second issue is that the hash I generate doesn’t seem to match up the hash found in algoexplorer when I look at the created asset.

My code to create the hash

const metadata = 'metadata json goes here';
const metadataBuffer = crypto.createHash('sha256').update(metadata, 'utf8').digest();
const metadataHash = new Uint8Array(metadataBuffer);
console.log(metadataBuffer)
console.log(metadataHash)
console.log(metadataBuffer.toString('utf8'))
console.log(metadataBuffer.toString('base64'))
console.log(metadataBuffer.toString('hex'))

gives the following

<Buffer d6 f0 a1 ea 70 9e 24 3f cd 30 72 ef 87 a9 b2 71 0e b0 02 15 f1 fa c9 08 ee 66 f7 6e 2b 6e 30 03>
Uint8Array(32) [
  214, 240, 161, 234, 112, 158,  36,  63,
  205,  48, 114, 239, 135, 169, 178, 113,
   14, 176,   2,  21, 241, 250, 201,   8,
  238, 102, 247, 110,  43, 110,  48,   3
]
���p�$?�0r�q����f�n+n0
1vCh6nCeJD/NMHLvh6mycQ6wAhXx+skI7mb3bituMAM=
d6f0a1ea709e243fcd3072ef87a9b2710eb00215f1fac908ee66f76e2b6e3003

When I create the asset, the transaction assetMetadataHash field matches the Uint8Array of my hash. I submit the transaction and everything goes through. However, when I look on algoexplorer at my asset (Asset ID: 44524145), the metadata hash field doesn’t match any of the console.log() outputs from my code—I see: MXZDaDZuQ2VKRC9OTUhMdmg2bXljUTZ3QWhYeCtza0k3bWIzYml0dU1BTT0=. It’s also particularly strange that the hash on algoexplorer is 60 characters. I’ve created this asset twice and the incorrect hash is at least the same both times.

I tried to be clever and also made the note field equal to the same Uint8Array as the metadata hash. Interestingly, this seems to not be corrupted??? On algoexplorer, I see 1vCh6nCeJD/NMHLvh6mycQ6wAhXx+skI7mb3bituMAM=, which matches the base64 encoding of the has from my console.log() output above.

One other thing to note is that for the purposes of getting up and running quickly, I’m using the algoexplorer API to interact with the blockchain instead of using an algod client for now. I’m not sure if somehow the API is mucking up the hash somehow. To test this, I just did the exact same thing, but instead of using a real sha256 hash, I just used 32 characters (this is a total of 32 characters) as my “hash.” In doing so, I don’t have any issues when I create the string and pass the string to makeAssetCreateTxnWithSuggestedParams since all the characters are printable. When I look on algoexplorer at my new asset (Asset ID: 44526747), I again see that the metadata hash doesn’t match. This seems to suggest that either the API is mucking up something with the metadata hash when I submit it or that the hash displayed on algoexplorer is encoded in some way that isn’t clear to me (it looks like base64?).

1 Like

You found the issue in your original code: you need to include just the 32 bytes of the hash/digest as Uint8array and should not convert them base64 or hex.

Now, the reason why you see the wrong hash on algoexplorer.io seems to be a bug of algoexplorer.io.
If you look on goalseeker, it works properly: GoalSeeker by PureStake | Algorand Block Explorer

It looks like algoexplorer is applying twice base64 instead of once. I’ve contacted Randlabs to fix this issue.

1 Like

Hi @illusieve , took note of the issue. Will fix. Thanks for the report.

1 Like