Transaction Proof
Description
Section titled “Description”This example demonstrates how to get transaction proofs using:
- transactionProof(round, txId) - Get the Merkle proof for a transaction Transaction proofs are cryptographic proofs that a transaction is included in a specific block. They are used for light client verification, allowing clients to verify transaction inclusion without downloading the entire blockchain. The proof uses a Merkle tree structure where:
- Each transaction in a block is a leaf in the tree
- The root of the tree is committed in the block header
- The proof provides the sibling hashes needed to reconstruct the root
Prerequisites
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start)
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example algod_client/15-transaction-proof.ts/** * Example: Transaction Proof * * This example demonstrates how to get transaction proofs using: * - transactionProof(round, txId) - Get the Merkle proof for a transaction * * Transaction proofs are cryptographic proofs that a transaction is included in a specific * block. They are used for light client verification, allowing clients to verify transaction * inclusion without downloading the entire blockchain. * * The proof uses a Merkle tree structure where: * - Each transaction in a block is a leaf in the tree * - The root of the tree is committed in the block header * - The proof provides the sibling hashes needed to reconstruct the root * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { algo } from '@algorandfoundation/algokit-utils'import type { TransactionProof } from '@algorandfoundation/algokit-utils/algod-client'import { createAlgodClient, createAlgorandClient, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js'
async function main() { printHeader('Transaction Proof Example')
// Create clients const algod = createAlgodClient() const algorand = createAlgorandClient()
// ========================================================================= // Step 1: Submit a transaction and wait for confirmation // ========================================================================= printStep(1, 'Submitting a transaction and waiting for confirmation')
// Get a funded account from LocalNet (the dispenser) const sender = await algorand.account.dispenserFromEnvironment() printInfo(`Sender address: ${shortenAddress(sender.addr.toString())}`)
// Create a new random account as receiver const receiver = algorand.account.random() printInfo(`Receiver address: ${shortenAddress(receiver.addr.toString())}`)
// Submit a payment transaction const paymentAmount = algo(1) printInfo(`Sending ${paymentAmount.algo} ALGO to receiver...`)
const result = await algorand.send.payment({ sender: sender.addr, receiver: receiver.addr, amount: paymentAmount, })
const txId = result.txIds[0] const confirmedRound = result.confirmation.confirmedRound!
printSuccess(`Transaction confirmed!`) printInfo(`Transaction ID: ${txId}`) printInfo(`Confirmed in round: ${confirmedRound.toLocaleString('en-US')}`) printInfo('')
// ========================================================================= // Step 2: Get transaction proof using transactionProof(round, txId) // ========================================================================= printStep(2, 'Getting transaction proof using transactionProof(round, txId)')
printInfo('transactionProof(round, txId) returns a Merkle proof that the transaction') printInfo('is included in the specified block. This is used for light client verification.') printInfo('')
try { const proof = await algod.transactionProof(confirmedRound, txId) printSuccess('Successfully retrieved transaction proof!') printInfo('')
displayTransactionProof(proof) } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) printError(`Error getting transaction proof: ${errorMessage}`) printInfo('') }
// ========================================================================= // Step 3: Demonstrate proof with different hash type (sha256) // ========================================================================= printStep(3, 'Getting transaction proof with SHA-256 hash type')
printInfo('The hashtype parameter specifies the hash function used to create the proof.') printInfo('Supported values: "sha512_256" (default) and "sha256"') printInfo('')
try { const proofSha256 = await algod.transactionProof(confirmedRound, txId, { hashtype: 'sha256' }) printSuccess('Successfully retrieved transaction proof with SHA-256!') printInfo('')
displayTransactionProof(proofSha256) } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) if (errorMessage.includes('not supported') || errorMessage.includes('400')) { printError('SHA-256 hash type may not be supported on this node configuration.') printInfo('The default SHA-512/256 is the native Algorand hash function.') } else { printError(`Error getting transaction proof with SHA-256: ${errorMessage}`) } printInfo('') }
// ========================================================================= // Step 4: Demonstrate structure of Merkle proof data // ========================================================================= printStep(4, 'Understanding the Merkle proof structure')
printInfo('The transaction proof contains data needed to verify transaction inclusion:') printInfo('')
try { const proof = await algod.transactionProof(confirmedRound, txId)
printInfo(' Merkle Proof Structure:') printInfo('') printInfo(' 1. idx (index): Position of the transaction in the block\'s payset') printInfo(` Value: ${proof.idx}`) printInfo(' This tells you which leaf in the Merkle tree corresponds to this transaction.') printInfo('')
printInfo(' 2. treedepth: Number of levels in the Merkle tree') printInfo(` Value: ${proof.treedepth}`) printInfo(` A tree with depth ${proof.treedepth} can hold up to ${Math.pow(2, proof.treedepth)} transactions.`) printInfo('')
printInfo(' 3. proof: Sibling hashes needed to reconstruct the Merkle root') printInfo(` Length: ${proof.proof.length} bytes`) if (proof.treedepth > 0) { printInfo(` Number of hashes: ${proof.treedepth} (one for each level)`) printInfo(` Hash size: ${proof.proof.length / proof.treedepth} bytes per hash`) } else { printInfo(' (Empty - single transaction in block, stibhash IS the Merkle root)') } printInfo('')
printInfo(' 4. stibhash: Hash of SignedTxnInBlock') printInfo(` Length: ${proof.stibhash.length} bytes`) printInfo(' This is the leaf value - the hash of the transaction as stored in the block.') printInfo('')
printInfo(' 5. hashtype: Hash function used') printInfo(` Value: "${proof.hashtype}"`) printInfo(' SHA-512/256 is Algorand\'s native hash function (first 256 bits of SHA-512).') printInfo('') } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) printError(`Error demonstrating proof structure: ${errorMessage}`) printInfo('') }
// ========================================================================= // Step 5: Handle errors - invalid round or transaction ID // ========================================================================= printStep(5, 'Handling errors when proof is not available')
printInfo('Transaction proofs may not be available if:') printInfo(' - The round number is invalid or not yet committed') printInfo(' - The transaction ID does not exist in the specified round') printInfo(' - The node does not have the block data') printInfo('')
// Try getting proof for a non-existent transaction ID const fakeTxId = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' try { await algod.transactionProof(confirmedRound, fakeTxId) printInfo('Unexpectedly succeeded with fake transaction ID') } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) printSuccess(`Correctly rejected invalid transaction ID`) printInfo(`Error: ${errorMessage.substring(0, 100)}...`) } printInfo('')
// Try getting proof for a future round const status = await algod.status() const futureRound = status.lastRound + 1000n try { await algod.transactionProof(futureRound, txId) printInfo('Unexpectedly succeeded with future round') } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) printSuccess(`Correctly rejected future round`) printInfo(`Error: ${errorMessage.substring(0, 100)}...`) } printInfo('')
// ========================================================================= // Step 6: Submit multiple transactions and compare proofs // ========================================================================= printStep(6, 'Comparing proofs for multiple transactions in the same block')
printInfo('Each transaction in a block has a unique position (idx) in the Merkle tree.') printInfo('Submitting multiple transactions to observe different proof indices...') printInfo('')
// Submit multiple transactions in sequence (they may end up in different blocks on LocalNet dev mode) const txIds: string[] = [] const confirmedRounds: bigint[] = []
for (let i = 0; i < 3; i++) { const newReceiver = algorand.account.random() const txResult = await algorand.send.payment({ sender: sender.addr, receiver: newReceiver.addr, amount: algo(0.1), }) txIds.push(txResult.txIds[0]) confirmedRounds.push(txResult.confirmation.confirmedRound!) }
printInfo(`Submitted ${txIds.length} transactions`) printInfo('')
// Get proofs for each transaction for (let i = 0; i < txIds.length; i++) { try { const proof = await algod.transactionProof(confirmedRounds[i], txIds[i]) printInfo(` Transaction ${i + 1}:`) printInfo(` Round: ${confirmedRounds[i].toLocaleString('en-US')}`) printInfo(` TX ID: ${txIds[i].substring(0, 20)}...`) printInfo(` Index in block (idx): ${proof.idx}`) printInfo(` Tree depth: ${proof.treedepth}`) printInfo('') } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) printError(`Error getting proof for transaction ${i + 1}: ${errorMessage}`) } }
// ========================================================================= // Summary // ========================================================================= printHeader('Summary')
printInfo('Transaction Proof Use Cases:') printInfo('') printInfo('1. Light Client Verification:') printInfo(' - Verify a transaction is included in the blockchain without downloading all blocks') printInfo(' - Only need the block header (with Merkle root) and the proof') printInfo(' - Reduces bandwidth and storage requirements significantly') printInfo('') printInfo('2. Cross-Chain Bridges:') printInfo(' - Prove to another blockchain that a transaction occurred on Algorand') printInfo(' - The proof can be verified by a smart contract on the target chain') printInfo('') printInfo('3. Auditing and Compliance:') printInfo(' - Provide cryptographic proof of transaction inclusion') printInfo(' - Third parties can verify without trusting the provider') printInfo('') printInfo('TransactionProof Type Structure:') printInfo(' proof: Uint8Array - Merkle proof (sibling hashes concatenated)') printInfo(' stibhash: Uint8Array - Hash of SignedTxnInBlock (leaf value)') printInfo(' treedepth: number - Depth of the Merkle tree') printInfo(' idx: number - Transaction index in the block\'s payset') printInfo(' hashtype: string - Hash function used ("sha512_256" or "sha256")') printInfo('') printInfo('API Method:') printInfo(' transactionProof(round, txId, params?)') printInfo(' round: number | bigint - The round (block) containing the transaction') printInfo(' txId: string - The transaction ID') printInfo(' params?: { hashtype?: "sha512_256" | "sha256" }') printInfo('') printInfo('Verification Process:') printInfo(' 1. Get the stibhash (leaf value) from the proof') printInfo(' 2. Use idx to determine if leaf is left or right child at each level') printInfo(' 3. Combine with sibling hashes from proof, hashing up the tree') printInfo(' 4. Compare computed root with the txnCommitments in the block header') printInfo(' 5. If they match, the transaction is verified as included in the block')}
/** * Display details from a TransactionProof */function displayTransactionProof(proof: TransactionProof): void { printInfo(' TransactionProof fields:') printInfo(` idx: ${proof.idx}`) printInfo(` Index of the transaction in the block's payset`) printInfo('') if (proof.proof.length > 0) { printInfo(` proof: ${Buffer.from(proof.proof).toString('hex').substring(0, 64)}...`) printInfo(` (${proof.proof.length} bytes total - Merkle proof data)`) } else { printInfo(' proof: (empty - single transaction in block)') printInfo(' When treedepth=0, stibhash IS the Merkle root') } printInfo('') printInfo(` stibhash: ${Buffer.from(proof.stibhash).toString('hex')}`) printInfo(` (${proof.stibhash.length} bytes - Hash of SignedTxnInBlock)`) printInfo('') printInfo(` treedepth: ${proof.treedepth}`) printInfo(` Number of edges from leaf to root in the Merkle tree`) printInfo('') printInfo(` hashtype: "${proof.hashtype}"`) printInfo(` Hash function used to create the proof`) printInfo('')}
main().catch((error) => { console.error('Fatal error:', error) process.exit(1)})Other examples in Algod Client
Section titled “Other examples in Algod Client”- Node Health and Status
- Version and Genesis Information
- Ledger Supply Information
- Account Information
- Transaction Parameters
- Send and Confirm Transaction
- Pending Transactions
- Block Data
- Asset Information
- Application Information
- Application Boxes
- TEAL Compile and Disassemble
- Transaction Simulation
- Ledger State Deltas
- Transaction Proof
- Light Block Header Proof
- State Proof
- DevMode Timestamp Offset
- Sync Round Management