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