Skip to content

Crypto Hash (SHA-512/256)

← Back to Common Utilities

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
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example common/05-crypto-hash.ts

View source on GitHub

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()