Crypto Hash (SHA-512/256)
Description
Section titled “Description”This example demonstrates the hash() function for computing Algorand-compatible SHA-512/256 hashes. This hash algorithm is used throughout Algorand for:
- Transaction IDs
- Address checksums
- Application escrow addresses
- State proof verification
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example common/05-crypto-hash.ts/** * Example: Crypto Hash (SHA-512/256) * * This example demonstrates the hash() function for computing Algorand-compatible * SHA-512/256 hashes. This hash algorithm is used throughout Algorand for: * - Transaction IDs * - Address checksums * - Application escrow addresses * - State proof verification * * Prerequisites: * - No LocalNet required */
import { arrayEqual, concatArrays, HASH_BYTES_LENGTH, TRANSACTION_DOMAIN_SEPARATOR } from '@algorandfoundation/algokit-utils/common'import { hash } from '@algorandfoundation/algokit-utils/crypto'import { formatBytes, formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
function main() { printHeader('Crypto Hash (SHA-512/256) Example')
// Step 1: Hash a simple message printStep(1, 'Hash a Simple Message')
const message = 'Hello, Algorand!' const messageBytes = new TextEncoder().encode(message) const messageHash = hash(messageBytes)
printInfo(`Input message: "${message}"`) printInfo(`Input bytes: ${formatBytes(messageBytes)}`) printInfo(`Hash output: ${formatBytes(messageHash)}`) printInfo(`Hash as hex: ${formatHex(messageHash)}`)
// Step 2: Hash empty bytes printStep(2, 'Hash Empty Bytes')
const emptyBytes = new Uint8Array(0) const emptyHash = hash(emptyBytes)
printInfo(`Input: empty byte array (0 bytes)`) printInfo(`Hash output: ${formatBytes(emptyHash)}`) printInfo(`Hash as hex: ${formatHex(emptyHash)}`) printInfo(`Note: Even empty input produces a 32-byte hash`)
// Step 3: Verify hash always returns exactly 32 bytes (HASH_BYTES_LENGTH) printStep(3, 'Verify Hash Always Returns 32 Bytes')
printInfo(`HASH_BYTES_LENGTH constant: ${HASH_BYTES_LENGTH} bytes`)
// Test with various input sizes const testInputs = [ { name: 'empty', data: new Uint8Array(0) }, { name: '1 byte', data: new Uint8Array([0x42]) }, { name: '32 bytes', data: new Uint8Array(32).fill(0xaa) }, { name: '100 bytes', data: new Uint8Array(100).fill(0xbb) }, { name: '1000 bytes', data: new Uint8Array(1000).fill(0xcc) }, ]
for (const input of testInputs) { const inputHash = hash(input.data) const lengthMatch = inputHash.length === HASH_BYTES_LENGTH printInfo(`Input: ${input.name.padEnd(12)} -> Output: ${inputHash.length} bytes ${lengthMatch ? '✓' : '✗'}`) }
printSuccess(`All hash outputs are exactly ${HASH_BYTES_LENGTH} bytes`)
// Step 4: Hash a transaction-like payload printStep(4, 'Hash a Transaction-Like Payload')
// Algorand transaction hashing uses a domain separator prefix printInfo(`Transaction domain separator: "${TRANSACTION_DOMAIN_SEPARATOR}"`)
// Simulate a minimal transaction-like structure const fakeTxPayload = new Uint8Array([ 0x01, // version 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // first valid round (8 bytes) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, // last valid round (8 bytes) // ... simplified for demonstration ])
// In Algorand, transaction ID = hash(domain_separator + msgpack(transaction)) const domainSeparatorBytes = new TextEncoder().encode(TRANSACTION_DOMAIN_SEPARATOR) const prefixedPayload = concatArrays(domainSeparatorBytes, fakeTxPayload)
const txLikeHash = hash(prefixedPayload)
printInfo(`Domain separator bytes: ${formatBytes(domainSeparatorBytes)}`) printInfo(`Payload bytes: ${formatBytes(fakeTxPayload)}`) printInfo(`Combined (prefixed) bytes: ${formatBytes(prefixedPayload)}`) printInfo(`Transaction-like hash: ${formatHex(txLikeHash)}`)
// Step 5: Demonstrate hex representation printStep(5, 'Hex Representation of Hash Output')
// Hash a known value for demonstration const knownInput = new Uint8Array([0x00, 0x01, 0x02, 0x03]) const knownHash = hash(knownInput)
printInfo(`Input bytes: ${formatBytes(knownInput, 4)}`) printInfo(`Hash (raw bytes): ${formatBytes(knownHash, 16)}`) printInfo(`Hash (full hex): ${formatHex(knownHash)}`)
// Show the hex format breakdown const hexString = Array.from(knownHash as Uint8Array) .map((b) => b.toString(16).padStart(2, '0')) .join('') printInfo(`Hash length: ${hexString.length} hex characters (${hexString.length / 2} bytes)`)
// Step 6: Verify determinism - same input always produces same hash printStep(6, 'Verify Determinism')
const deterministicInput = new TextEncoder().encode('Algorand is great!')
// Hash the same input multiple times const hash1 = hash(deterministicInput) const hash2 = hash(deterministicInput) const hash3 = hash(deterministicInput)
printInfo(`Input: "Algorand is great!"`) printInfo(`Hash #1: ${formatHex(hash1)}`) printInfo(`Hash #2: ${formatHex(hash2)}`) printInfo(`Hash #3: ${formatHex(hash3)}`)
const allEqual = arrayEqual(hash1, hash2) && arrayEqual(hash2, hash3) printInfo(`All hashes equal: ${allEqual ? 'Yes ✓' : 'No ✗'}`)
if (allEqual) { printSuccess('Determinism verified: same input always produces same hash') }
// Step 7: Show that different inputs produce different hashes printStep(7, 'Different Inputs Produce Different Hashes')
const inputA = new TextEncoder().encode('input A') const inputB = new TextEncoder().encode('input B') const inputC = new TextEncoder().encode('input a') // lowercase 'a' vs uppercase 'A'
const hashA = hash(inputA) const hashB = hash(inputB) const hashC = hash(inputC)
printInfo(`"input A" -> ${formatHex(hashA).slice(0, 22)}...`) printInfo(`"input B" -> ${formatHex(hashB).slice(0, 22)}...`) printInfo(`"input a" -> ${formatHex(hashC).slice(0, 22)}...`)
const abDifferent = !arrayEqual(hashA, hashB) const acDifferent = !arrayEqual(hashA, hashC)
printInfo(`"input A" vs "input B": ${abDifferent ? 'Different ✓' : 'Same ✗'}`) printInfo(`"input A" vs "input a": ${acDifferent ? 'Different ✓' : 'Same ✗'}`) printInfo(`Note: Even a single-bit change produces a completely different hash`)
// Summary printStep(8, 'Summary')
printInfo('SHA-512/256 in Algorand:') printInfo(` - Always produces exactly ${HASH_BYTES_LENGTH} bytes (256 bits)`) printInfo(' - Deterministic: same input always yields same output') printInfo(' - Collision-resistant: different inputs produce different outputs') printInfo(' - Used for: transaction IDs, address checksums, app addresses') printInfo(` - Domain separator "${TRANSACTION_DOMAIN_SEPARATOR}" prevents cross-protocol attacks`)
printSuccess('Crypto Hash example completed successfully!')}
main()