Logic Signature
Description
Section titled “Description”This example demonstrates how to use a logic signature (lsig) to authorize transactions. Key concepts:
- Compiling a TEAL program using algod.tealCompile()
- Creating a LogicSig from compiled program bytes
- Understanding the logic signature address (derived from program hash)
- Funding and using a logic signature as a standalone account
- Creating a delegated logic signature where an account delegates signing to a program Logic signatures allow transactions to be authorized by a program instead of (or in addition to) a cryptographic signature. This enables smart contracts that can hold and send funds based purely on program logic.
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 transact/11-logic-sig.ts/** * Example: Logic Signature * * This example demonstrates how to use a logic signature (lsig) to authorize transactions. * * Key concepts: * - Compiling a TEAL program using algod.tealCompile() * - Creating a LogicSig from compiled program bytes * - Understanding the logic signature address (derived from program hash) * - Funding and using a logic signature as a standalone account * - Creating a delegated logic signature where an account delegates signing to a program * * Logic signatures allow transactions to be authorized by a program instead of (or in addition to) * a cryptographic signature. This enables smart contracts that can hold and send funds based * purely on program logic. * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { AlgorandClient } from '@algorandfoundation/algokit-utils';import { assignFee, LogicSig, LogicSigAccount, Transaction, TransactionType, type PaymentTransactionFields,} from '@algorandfoundation/algokit-utils/transact';import { createAlgodClient, formatAlgo, getAccountBalance, loadTealSource, printHeader, printInfo, printStep, printSuccess, shortenAddress, waitForConfirmation,} from '../shared/utils.js';
/** * Gets a funded account from LocalNet's KMD wallet */async function getLocalNetFundedAccount(algorand: AlgorandClient) { return await algorand.account.kmd.getLocalNetDispenserAccount();}
async function main() { printHeader('Logic Signature Example');
// Step 1: Initialize clients printStep(1, 'Initialize Algod Client'); const algod = createAlgodClient(); const algorand = AlgorandClient.defaultLocalNet(); printInfo('Connected to LocalNet Algod');
// Step 2: Compile a simple TEAL program using algod.tealCompile() printStep(2, 'Compile TEAL Program');
// Load the "always approve" TEAL program from shared artifacts // In real-world use cases, you would have logic that validates: // - Who the receiver is // - Maximum amount that can be sent // - Time-based restrictions // - etc. const tealSource = loadTealSource('always-approve.teal');
printInfo('TEAL source code:'); printInfo(' #pragma version 10'); printInfo(' int 1'); printInfo(' return'); printInfo(''); printInfo('This program always returns 1 (true), meaning it approves all transactions.'); printInfo('WARNING: Real logic sigs should have proper validation logic!'); printInfo('');
// Compile the TEAL program using algod const compileResult = await algod.tealCompile(tealSource); const programBytes = new Uint8Array(Buffer.from(compileResult.result, 'base64'));
printInfo(`Compiled program size: ${programBytes.length} bytes`); printInfo(`Program hash (base32): ${compileResult.hash}`);
// Step 3: Create LogicSig from the compiled program bytes printStep(3, 'Create LogicSig from Program Bytes');
// The LogicSig wraps the compiled program // Optionally, you can pass arguments to the program const logicSig = new LogicSig(programBytes);
printInfo('LogicSig created from compiled program bytes'); printInfo(''); printInfo('How LogicSig address is derived:'); printInfo(' 1. Prefix "Program" is concatenated with program bytes'); printInfo(' 2. SHA512/256 hash is computed'); printInfo(' 3. Hash becomes the 32-byte public key equivalent'); printInfo(' 4. Address is derived same as for ed25519 keys');
// Step 4: Show the logic signature address printStep(4, 'Show Logic Signature Address');
const lsigAddress = logicSig.addr; printInfo(`Logic signature address: ${lsigAddress.toString()}`); printInfo(''); printInfo('This address is deterministically derived from the program.'); printInfo('Anyone with the same program can compute this address.'); printInfo('Funds sent to this address can only be spent by providing the program.');
// Step 5: Fund the logic signature address printStep(5, 'Fund the Logic Signature Address');
const dispenser = await getLocalNetFundedAccount(algorand); const fundingAmount = 5_000_000n; // 5 ALGO
const suggestedParams = await algod.suggestedParams();
const fundTx = new Transaction({ type: TransactionType.Payment, sender: dispenser.addr, firstValid: suggestedParams.firstValid, lastValid: suggestedParams.lastValid, genesisHash: suggestedParams.genesisHash, genesisId: suggestedParams.genesisId, payment: { receiver: lsigAddress, amount: fundingAmount, }, });
const fundTxWithFee = assignFee(fundTx, { feePerByte: suggestedParams.fee, minFee: suggestedParams.minFee, });
const signedFundTx = await dispenser.signer([fundTxWithFee], [0]); await algod.sendRawTransaction(signedFundTx); await waitForConfirmation(algod, fundTxWithFee.txId());
const lsigBalance = await getAccountBalance(algorand, lsigAddress.toString()); printInfo(`Funded logic signature with ${formatAlgo(fundingAmount)}`); printInfo(`Logic signature balance: ${formatAlgo(lsigBalance.microAlgo)}`);
// Step 6: Create LogicSigAccount and use its signer to authorize a payment printStep(6, 'Create LogicSigAccount and Send Payment');
// LogicSigAccount wraps the LogicSig and provides a signer function // For a non-delegated lsig, the sender is the lsig address itself const lsigAccount = new LogicSigAccount(programBytes);
const receiver = algorand.account.random(); const paymentAmount = 1_000_000n; // 1 ALGO
const payParams = await algod.suggestedParams();
const paymentFields: PaymentTransactionFields = { receiver: receiver.addr, amount: paymentAmount, };
const paymentTx = new Transaction({ type: TransactionType.Payment, sender: lsigAddress, // The logic signature is the sender firstValid: payParams.firstValid, lastValid: payParams.lastValid, genesisHash: payParams.genesisHash, genesisId: payParams.genesisId, payment: paymentFields, });
const paymentTxWithFee = assignFee(paymentTx, { feePerByte: payParams.fee, minFee: payParams.minFee, });
printInfo(`Payment amount: ${formatAlgo(paymentAmount)}`); printInfo(`Sender (lsig): ${shortenAddress(lsigAddress.toString())}`); printInfo(`Receiver: ${shortenAddress(receiver.addr.toString())}`); printInfo(''); printInfo('How logic signature authorization works:'); printInfo(' 1. Transaction is created with lsig address as sender'); printInfo(' 2. Instead of a signature, the program bytes are attached'); printInfo(' 3. Network executes the program to validate the transaction'); printInfo(' 4. If program returns non-zero, transaction is authorized');
// Step 7: Submit transaction authorized by the logic signature printStep(7, 'Submit Logic Signature Transaction');
// The LogicSigAccount.signer attaches the program instead of a signature const signedTxns = await lsigAccount.signer([paymentTxWithFee], [0]);
printInfo(`Signed transaction size: ${signedTxns[0].length} bytes`); printInfo('(Contains program bytes instead of ed25519 signature)');
await algod.sendRawTransaction(signedTxns); printInfo('Transaction submitted to network...');
const pendingInfo = await waitForConfirmation(algod, paymentTxWithFee.txId()); printInfo(`Transaction confirmed in round: ${pendingInfo.confirmedRound}`);
// Verify balances const lsigBalanceAfter = await getAccountBalance(algorand, lsigAddress.toString()); let receiverBalance: bigint; try { const info = await getAccountBalance(algorand, receiver.addr.toString()); receiverBalance = info.microAlgo; } catch { receiverBalance = 0n; }
printInfo(`Logic signature balance after: ${formatAlgo(lsigBalanceAfter.microAlgo)}`); printInfo(`Receiver balance: ${formatAlgo(receiverBalance)}`);
if (receiverBalance === paymentAmount) { printSuccess('Receiver received the payment from logic signature!'); }
// Step 8: Demonstrate delegated logic signature printStep(8, 'Demonstrate Delegated Logic Signature');
printInfo('A delegated logic signature allows an account to delegate'); printInfo('transaction authorization to a program. The account signs'); printInfo('the program once, and then transactions from that account'); printInfo('can be authorized by the program without further signatures.'); printInfo('');
// Create an account that will delegate to the lsig using AlgorandClient helper const delegator = algorand.account.random();
// Fund the delegator account const fundDelegatorParams = await algod.suggestedParams(); const fundDelegatorTx = new Transaction({ type: TransactionType.Payment, sender: dispenser.addr, firstValid: fundDelegatorParams.firstValid, lastValid: fundDelegatorParams.lastValid, genesisHash: fundDelegatorParams.genesisHash, genesisId: fundDelegatorParams.genesisId, payment: { receiver: delegator.addr, amount: 3_000_000n, // 3 ALGO }, });
const fundDelegatorTxWithFee = assignFee(fundDelegatorTx, { feePerByte: fundDelegatorParams.fee, minFee: fundDelegatorParams.minFee, });
const signedFundDelegatorTx = await dispenser.signer([fundDelegatorTxWithFee], [0]); await algod.sendRawTransaction(signedFundDelegatorTx); await waitForConfirmation(algod, fundDelegatorTxWithFee.txId());
const delegatorBalance = await getAccountBalance(algorand, delegator.addr.toString()); printInfo(`Delegator account: ${shortenAddress(delegator.addr.toString())}`); printInfo(`Delegator balance: ${formatAlgo(delegatorBalance.microAlgo)}`); printInfo('');
// Create a delegated logic signature // The delegator signs the program, allowing it to authorize transactions on their behalf printInfo('Creating delegated logic signature...'); printInfo('The delegator signs the program bytes to create a delegation.'); printInfo('');
// Create a LogicSigAccount with the delegator's address const delegatedLsig = new LogicSigAccount(programBytes, null, delegator.addr);
// Sign the lsig for delegation using the delegator's lsigSigner await delegatedLsig.signForDelegation(delegator);
printInfo('Delegator has signed the program for delegation.'); printInfo( `Delegated lsig will authorize transactions FROM: ${shortenAddress(delegator.addr.toString())}`, ); printInfo(''); printInfo('How delegation works:'); printInfo(' 1. Delegator signs: Hash("Program" || program_bytes) with their key'); printInfo(' 2. This signature is stored in the LogicSigAccount'); printInfo(' 3. Transactions include: program + delegator signature'); printInfo(' 4. Network verifies signature matches delegator public key'); printInfo(' 5. Then executes program to authorize the transaction');
// Create a payment from the delegator, authorized by the delegated lsig const delegatedReceiver = algorand.account.random(); const delegatedPaymentAmount = 500_000n; // 0.5 ALGO
const delegatedPayParams = await algod.suggestedParams();
const delegatedPaymentTx = new Transaction({ type: TransactionType.Payment, sender: delegator.addr, // Sender is the delegator's address, NOT the lsig address firstValid: delegatedPayParams.firstValid, lastValid: delegatedPayParams.lastValid, genesisHash: delegatedPayParams.genesisHash, genesisId: delegatedPayParams.genesisId, payment: { receiver: delegatedReceiver.addr, amount: delegatedPaymentAmount, }, });
const delegatedPaymentTxWithFee = assignFee(delegatedPaymentTx, { feePerByte: delegatedPayParams.fee, minFee: delegatedPayParams.minFee, });
printInfo(`Delegated payment amount: ${formatAlgo(delegatedPaymentAmount)}`); printInfo(`Sender (delegator account): ${shortenAddress(delegator.addr.toString())}`); printInfo(`Receiver: ${shortenAddress(delegatedReceiver.addr.toString())}`);
// Sign with the delegated lsig - this uses the program + stored delegation signature const delegatedSignedTxns = await delegatedLsig.signer([delegatedPaymentTxWithFee], [0]);
await algod.sendRawTransaction(delegatedSignedTxns); printInfo('Delegated transaction submitted to network...');
const delegatedPendingInfo = await waitForConfirmation(algod, delegatedPaymentTxWithFee.txId()); printInfo(`Transaction confirmed in round: ${delegatedPendingInfo.confirmedRound}`);
// Verify balances const delegatorBalanceAfter = await getAccountBalance(algorand, delegator.addr.toString()); let delegatedReceiverBalance: bigint; try { const info = await getAccountBalance(algorand, delegatedReceiver.addr.toString()); delegatedReceiverBalance = info.microAlgo; } catch { delegatedReceiverBalance = 0n; }
printInfo(`Delegator balance after: ${formatAlgo(delegatorBalanceAfter.microAlgo)}`); printInfo(`Receiver balance: ${formatAlgo(delegatedReceiverBalance)}`);
if (delegatedReceiverBalance === delegatedPaymentAmount) { printSuccess('Delegated logic signature successfully authorized the transaction!'); }
// Summary printInfo(''); printInfo('Summary - Logic Signature Key Points:'); printInfo(' - LogicSig wraps a compiled TEAL program'); printInfo(' - The lsig address is derived from the program hash'); printInfo(' - Non-delegated: lsig acts as its own account'); printInfo(' - Delegated: an account signs the program to delegate auth'); printInfo(' - Program is executed to validate each transaction'); printInfo(' - Real programs should have strict validation logic!'); printInfo(''); printInfo('Common use cases for logic signatures:'); printInfo(' - Escrow accounts with release conditions'); printInfo(' - Hash time-locked contracts (HTLC)'); printInfo(' - Recurring payment authorizations'); printInfo(' - Multi-condition authorization logic');
printSuccess('Logic signature example completed!');}
main().catch(error => { console.error('Error:', error); process.exit(1);});