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.teal_compile()
- 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:
Prerequisites
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start)
Run This Example
Section titled “Run This Example”From the repository’s examples directory:
cd examplesuv run python transact/11_logic_sig.py# ruff: noqa: N999, C901, PLR0912, PLR0915, PLR2004"""Example: Logic Signature
This example demonstrates how to use a logic signature (lsig) to authorize transactions.
Key concepts:- Compiling a TEAL program using algod.teal_compile()- 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 basedpurely on program logic.
Prerequisites:- LocalNet running (via `algokit localnet start`)"""
import base64
from shared import ( create_algod_client, format_algo, get_account_balance, load_teal_source, print_error, print_header, print_info, print_step, print_success, shorten_address, wait_for_confirmation,)
from algokit_transact import ( LogicSigAccount, PaymentTransactionFields, Transaction, TransactionType, assign_fee,)from algokit_utils import AlgorandClient
def main() -> None: print_header("Logic Signature Example")
# Step 1: Initialize clients print_step(1, "Initialize Algod Client") algod = create_algod_client() algorand = AlgorandClient.default_localnet()
try: algod.status() print_info("Connected to LocalNet Algod") except Exception as e: print_error(f"Failed to connect to LocalNet: {e}") print_info("Make sure LocalNet is running (e.g., algokit localnet start)") return
# Step 2: Compile a simple TEAL program using algod.teal_compile() print_step(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. teal_source = load_teal_source("always-approve.teal")
print_info("TEAL source code:") print_info(" #pragma version 10") print_info(" int 1") print_info(" return") print_info("") print_info("This program always returns 1 (true), meaning it approves all transactions.") print_info("WARNING: Real logic sigs should have proper validation logic!") print_info("")
# Compile the TEAL program using algod compile_result = algod.teal_compile(teal_source) program_bytes = base64.b64decode(compile_result.result)
print_info(f"Compiled program size: {len(program_bytes)} bytes") print_info(f"Program hash (base32): {compile_result.hash_}")
# Step 3: Create LogicSig from the compiled program bytes print_step(3, "Create LogicSig from Program Bytes")
# The LogicSigAccount wraps the compiled program and provides a signer # Optionally, you can pass arguments to the program logic_sig = LogicSigAccount(logic=program_bytes)
print_info("LogicSig created from compiled program bytes") print_info("") print_info("How LogicSig address is derived:") print_info(' 1. Prefix "Program" is concatenated with program bytes') print_info(" 2. SHA512/256 hash is computed") print_info(" 3. Hash becomes the 32-byte public key equivalent") print_info(" 4. Address is derived same as for ed25519 keys")
# Step 4: Show the logic signature address print_step(4, "Show Logic Signature Address")
lsig_address = logic_sig.addr print_info(f"Logic signature address: {lsig_address}") print_info("") print_info("This address is deterministically derived from the program.") print_info("Anyone with the same program can compute this address.") print_info("Funds sent to this address can only be spent by providing the program.")
# Step 5: Fund the logic signature address print_step(5, "Fund the Logic Signature Address")
dispenser = algorand.account.localnet_dispenser() funding_amount = 5_000_000 # 5 ALGO
suggested_params = algod.suggested_params()
fund_tx_without_fee = Transaction( transaction_type=TransactionType.Payment, sender=dispenser.addr, first_valid=suggested_params.first_valid, last_valid=suggested_params.last_valid, genesis_hash=suggested_params.genesis_hash, genesis_id=suggested_params.genesis_id, payment=PaymentTransactionFields( receiver=lsig_address, amount=funding_amount, ), )
fund_tx = assign_fee( fund_tx_without_fee, fee_per_byte=suggested_params.fee, min_fee=suggested_params.min_fee, )
signed_fund_tx = dispenser.signer([fund_tx], [0]) algod.send_raw_transaction(signed_fund_tx[0]) wait_for_confirmation(algod, fund_tx.tx_id())
lsig_balance = get_account_balance(algorand, lsig_address) print_info(f"Funded logic signature with {format_algo(funding_amount)}") print_info(f"Logic signature balance: {format_algo(lsig_balance)}")
# Step 6: Create LogicSigAccount and use its signer to authorize a payment print_step(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 lsig_account = LogicSigAccount(logic=program_bytes)
receiver = algorand.account.random() payment_amount = 1_000_000 # 1 ALGO
pay_params = algod.suggested_params()
payment_tx_without_fee = Transaction( transaction_type=TransactionType.Payment, sender=lsig_address, # The logic signature is the sender first_valid=pay_params.first_valid, last_valid=pay_params.last_valid, genesis_hash=pay_params.genesis_hash, genesis_id=pay_params.genesis_id, payment=PaymentTransactionFields( receiver=receiver.addr, amount=payment_amount, ), )
payment_tx = assign_fee( payment_tx_without_fee, fee_per_byte=pay_params.fee, min_fee=pay_params.min_fee, )
print_info(f"Payment amount: {format_algo(payment_amount)}") print_info(f"Sender (lsig): {shorten_address(lsig_address)}") print_info(f"Receiver: {shorten_address(receiver.addr)}") print_info("") print_info("How logic signature authorization works:") print_info(" 1. Transaction is created with lsig address as sender") print_info(" 2. Instead of a signature, the program bytes are attached") print_info(" 3. Network executes the program to validate the transaction") print_info(" 4. If program returns non-zero, transaction is authorized")
# Step 7: Submit transaction authorized by the logic signature print_step(7, "Submit Logic Signature Transaction")
# The LogicSigAccount.signer attaches the program instead of a signature signed_txns = lsig_account.signer([payment_tx], [0])
print_info(f"Signed transaction size: {len(signed_txns[0])} bytes") print_info("(Contains program bytes instead of ed25519 signature)")
algod.send_raw_transaction(signed_txns[0]) print_info("Transaction submitted to network...")
pending_info = wait_for_confirmation(algod, payment_tx.tx_id()) print_info(f"Transaction confirmed in round: {pending_info.confirmed_round}")
# Verify balances lsig_balance_after = get_account_balance(algorand, lsig_address) try: receiver_info = algorand.account.get_information(receiver.addr) receiver_balance = receiver_info.amount.micro_algo except Exception: receiver_balance = 0
print_info(f"Logic signature balance after: {format_algo(lsig_balance_after)}") print_info(f"Receiver balance: {format_algo(receiver_balance)}")
if receiver_balance == payment_amount: print_success("Receiver received the payment from logic signature!")
# Step 8: Demonstrate delegated logic signature print_step(8, "Demonstrate Delegated Logic Signature")
print_info("A delegated logic signature allows an account to delegate") print_info("transaction authorization to a program. The account signs") print_info("the program once, and then transactions from that account") print_info("can be authorized by the program without further signatures.") print_info("")
# Create an account that will delegate to the lsig delegator = algorand.account.random()
# Fund the delegator account fund_delegator_params = algod.suggested_params() fund_delegator_tx_without_fee = Transaction( transaction_type=TransactionType.Payment, sender=dispenser.addr, first_valid=fund_delegator_params.first_valid, last_valid=fund_delegator_params.last_valid, genesis_hash=fund_delegator_params.genesis_hash, genesis_id=fund_delegator_params.genesis_id, payment=PaymentTransactionFields( receiver=delegator.addr, amount=3_000_000, # 3 ALGO ), )
fund_delegator_tx = assign_fee( fund_delegator_tx_without_fee, fee_per_byte=fund_delegator_params.fee, min_fee=fund_delegator_params.min_fee, )
signed_fund_delegator_tx = dispenser.signer([fund_delegator_tx], [0]) algod.send_raw_transaction(signed_fund_delegator_tx[0]) wait_for_confirmation(algod, fund_delegator_tx.tx_id())
delegator_balance = get_account_balance(algorand, delegator.addr) print_info(f"Delegator account: {shorten_address(delegator.addr)}") print_info(f"Delegator balance: {format_algo(delegator_balance)}") print_info("")
# Create a delegated logic signature # The delegator signs the program, allowing it to authorize transactions on their behalf print_info("Creating delegated logic signature...") print_info("The delegator signs the program bytes to create a delegation.") print_info("")
# Create a LogicSigAccount with the delegator's address delegated_lsig = LogicSigAccount(logic=program_bytes, _address=delegator.addr)
# Sign the lsig for delegation using the delegator's signer delegated_lsig.sign_for_delegation(delegator)
print_info("Delegator has signed the program for delegation.") delegator_addr = shorten_address(delegator.addr) print_info(f"Delegated lsig will authorize transactions FROM: {delegator_addr}") print_info("") print_info("How delegation works:") print_info(' 1. Delegator signs: Hash("Program" || program_bytes) with their key') print_info(" 2. This signature is stored in the LogicSigAccount") print_info(" 3. Transactions include: program + delegator signature") print_info(" 4. Network verifies signature matches delegator public key") print_info(" 5. Then executes program to authorize the transaction")
# Create a payment from the delegator, authorized by the delegated lsig delegated_receiver = algorand.account.random() delegated_payment_amount = 500_000 # 0.5 ALGO
delegated_pay_params = algod.suggested_params()
delegated_payment_tx_without_fee = Transaction( transaction_type=TransactionType.Payment, sender=delegator.addr, # Sender is the delegator's address, NOT the lsig address first_valid=delegated_pay_params.first_valid, last_valid=delegated_pay_params.last_valid, genesis_hash=delegated_pay_params.genesis_hash, genesis_id=delegated_pay_params.genesis_id, payment=PaymentTransactionFields( receiver=delegated_receiver.addr, amount=delegated_payment_amount, ), )
delegated_payment_tx = assign_fee( delegated_payment_tx_without_fee, fee_per_byte=delegated_pay_params.fee, min_fee=delegated_pay_params.min_fee, )
print_info(f"Delegated payment amount: {format_algo(delegated_payment_amount)}") print_info(f"Sender (delegator account): {shorten_address(delegator.addr)}") print_info(f"Receiver: {shorten_address(delegated_receiver.addr)}")
# Sign with the delegated lsig - this uses the program + stored delegation signature delegated_signed_txns = delegated_lsig.signer([delegated_payment_tx], [0])
algod.send_raw_transaction(delegated_signed_txns[0]) print_info("Delegated transaction submitted to network...")
delegated_pending_info = wait_for_confirmation(algod, delegated_payment_tx.tx_id()) delegated_confirmed_round = delegated_pending_info.confirmed_round print_info(f"Transaction confirmed in round: {delegated_confirmed_round}")
# Verify balances delegator_balance_after = get_account_balance(algorand, delegator.addr) try: delegated_receiver_info = algorand.account.get_information(delegated_receiver.addr) delegated_receiver_balance = delegated_receiver_info.amount.micro_algo except Exception: delegated_receiver_balance = 0
print_info(f"Delegator balance after: {format_algo(delegator_balance_after)}") print_info(f"Receiver balance: {format_algo(delegated_receiver_balance)}")
if delegated_receiver_balance == delegated_payment_amount: print_success("Delegated logic signature successfully authorized the transaction!")
# Summary print_info("") print_info("Summary - Logic Signature Key Points:") print_info(" - LogicSig wraps a compiled TEAL program") print_info(" - The lsig address is derived from the program hash") print_info(" - Non-delegated: lsig acts as its own account") print_info(" - Delegated: an account signs the program to delegate auth") print_info(" - Program is executed to validate each transaction") print_info(" - Real programs should have strict validation logic!") print_info("") print_info("Common use cases for logic signatures:") print_info(" - Escrow accounts with release conditions") print_info(" - Hash time-locked contracts (HTLC)") print_info(" - Recurring payment authorizations") print_info(" - Multi-condition authorization logic")
print_success("Logic signature example completed!")
if __name__ == "__main__": main()