Skip to content

Logic Signature

← Back to Transactions

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:
  • LocalNet running (via algokit localnet start)

From the repository’s examples directory:

Terminal window
cd examples
uv run python transact/11_logic_sig.py

View source on GitHub

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