Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Time Lock (TimeLock / nLockTime)

Time locks are an important Bitcoin feature that prevent a transaction from being confirmed before a specific time or block height.

Overview

What is a Time Lock?

A time lock allows a transaction to be used only at some future point in time, implementing a “deferred payment” feature.

Example:

Alice creates a transaction: 1 BTC → Bob
Time lock: January 1, 2025

Before January 1, 2025:
  ❌ Transaction cannot be confirmed
  ❌ Miners refuse to pack it

After January 1, 2025:
  ✓ Transaction can be confirmed
  ✓ Miners can pack it

Two Types

1. Unix Timestamp-Based

#![allow(unused)]
fn main() {
// locktime >= 500,000,000
let unlock_time = 1735689600;  // 2025-01-01 00:00:00
let timelock = TimeLock::new_time_based(unlock_time);
}

Characteristics:

  • Unit: seconds
  • Suitable for precise time control
  • Affected by system time

Use cases:

  • Salary payment (1st of each month)
  • Bond maturity (fixed date)
  • Term deposit (3/6/12 months)

2. Block Height-Based

#![allow(unused)]
fn main() {
// locktime < 500,000,000
let unlock_height = 800000;  // Block #800,000
let timelock = TimeLock::new_block_based(unlock_height);
}

Characteristics:

  • Unit: blocks
  • More precise (approximately 10 minutes per block)
  • Not affected by system time

Use cases:

  • More precise time control
  • Avoid timestamp manipulation
  • Smart contract triggers

Time estimation:

1 block  ≈ 10 minutes
6 blocks ≈ 1 hour
144 blocks ≈ 1 day
1008 blocks ≈ 1 week
4032 blocks ≈ 1 month

TimeLock Implementation

Data Structure

#![allow(unused)]
fn main() {
pub struct TimeLock {
    pub locktime: u64,         // Lock time / height
    pub is_block_height: bool, // true: block height, false: timestamp
}
}

Methods

new_time_based

#![allow(unused)]
fn main() {
pub fn new_time_based(timestamp: u64) -> Self
}

Creates a time-based time lock.

Parameters:

  • timestamp - Unix timestamp (seconds)

Example:

#![allow(unused)]
fn main() {
use bitcoin_simulation::advanced_tx::TimeLock;
use std::time::{SystemTime, UNIX_EPOCH};

let current_time = SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap()
    .as_secs();

// Unlock after 3 months
let three_months = 90 * 24 * 3600;
let unlock_time = current_time + three_months;
let timelock = TimeLock::new_time_based(unlock_time);

println!("Locked until: {}", format_timestamp(unlock_time));
}

new_block_based

#![allow(unused)]
fn main() {
pub fn new_block_based(block_height: u64) -> Self
}

Creates a block height-based time lock.

Parameters:

  • block_height - Target block height

Example:

#![allow(unused)]
fn main() {
let current_height = blockchain.chain.len() as u64;

// Unlock after 1000 blocks (approximately 1 week)
let unlock_height = current_height + 1000;
let timelock = TimeLock::new_block_based(unlock_height);

println!("Locked until block #{}", unlock_height);
}

is_mature

#![allow(unused)]
fn main() {
pub fn is_mature(&self, current_time: u64, current_height: u64) -> bool
}

Checks whether the time lock has expired.

Parameters:

  • current_time - Current Unix timestamp
  • current_height - Current block height

Return value:

  • true - Expired; can be used
  • false - Not yet expired; still locked

Example:

#![allow(unused)]
fn main() {
let timelock = TimeLock::new_time_based(unlock_time);

if timelock.is_mature(current_time, 0) {
    println!("✓ Expired; can be spent");
} else {
    let remaining = unlock_time - current_time;
    println!("🔒 Still locked; {} seconds remaining", remaining);
}
}

Use Cases

Use Case 1: Term Deposit

#![allow(unused)]
fn main() {
fn savings_account_demo() -> Result<(), String> {
    println!("=== Term Deposit Demo ===\n");

    let alice = Wallet::new();
    let mut blockchain = Blockchain::new();

    // Initialize balance
    setup_balance(&mut blockchain, &alice, 100000)?;

    let current_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();

    // Product 1: 3-month term deposit
    println!("--- Product A: 3-Month Term Deposit ---");
    let three_months = 90 * 24 * 3600;
    let maturity_3m = current_time + three_months;
    let deposit_3m = TimeLock::new_time_based(maturity_3m);

    println!("Deposit amount: 30,000 sat");
    println!("Term: 3 months");
    println!("Maturity date: {}", format_date(maturity_3m));
    println!("Annual interest rate: 3%\n");

    // Product 2: 1-year term deposit
    println!("--- Product B: 1-Year Term Deposit ---");
    let one_year = 365 * 24 * 3600;
    let maturity_1y = current_time + one_year;
    let deposit_1y = TimeLock::new_time_based(maturity_1y);

    println!("Deposit amount: 50,000 sat");
    println!("Term: 1 year");
    println!("Maturity date: {}", format_date(maturity_1y));
    println!("Annual interest rate: 5%\n");

    // Check maturity status
    println!("--- Current Status Check ---");
    println!("Current time: {}", format_date(current_time));

    if deposit_3m.is_mature(current_time, 0) {
        println!("✓ 3-month deposit has matured; can be withdrawn");
        let interest = 30000 * 3 / 100 / 4;  // Quarterly interest
        println!("  Principal + interest: {} sat", 30000 + interest);
    } else {
        let days_left = (maturity_3m - current_time) / 86400;
        println!("🔒 3-month deposit is locked");
        println!("  Remaining: {} days", days_left);
    }

    if deposit_1y.is_mature(current_time, 0) {
        println!("✓ 1-year deposit has matured; can be withdrawn");
        let interest = 50000 * 5 / 100;  // Annual interest
        println!("  Principal + interest: {} sat", 50000 + interest);
    } else {
        let days_left = (maturity_1y - current_time) / 86400;
        println!("🔒 1-year deposit is locked");
        println!("  Remaining: {} days", days_left);
    }

    Ok(())
}
}

Output:

=== Term Deposit Demo ===

--- Product A: 3-Month Term Deposit ---
Deposit amount: 30,000 sat
Term: 3 months
Maturity date: 2025-03-15 00:00:00
Annual interest rate: 3%

--- Product B: 1-Year Term Deposit ---
Deposit amount: 50,000 sat
Term: 1 year
Maturity date: 2025-12-15 00:00:00
Annual interest rate: 5%

--- Current Status Check ---
Current time: 2024-12-15 00:00:00
🔒 3-month deposit is locked
  Remaining: 90 days
🔒 1-year deposit is locked
  Remaining: 365 days

Use Case 2: Inheritance Planning

#![allow(unused)]
fn main() {
fn inheritance_planning() -> Result<(), String> {
    println!("=== Inheritance Plan ===\n");

    let owner = Wallet::new();
    let heir = Wallet::new();
    let lawyer = Wallet::new();

    println!("Participants:");
    println!("  Owner: {}", &owner.address[..16]);
    println!("  Heir: {}", &heir.address[..16]);
    println!("  Lawyer: {}\n", &lawyer.address[..16]);

    let current_time = current_timestamp();

    // Plan: after 1 year of inactivity, assets automatically transfer to the heir
    println!("--- Plan Design ---");
    println!("Normal situation:");
    println!("  Requires: owner + heir (2-of-2)");
    println!("  Protects privacy; prevents unilateral transfer\n");

    println!("Emergency situation (after 1 year):");
    println!("  Owner is unreachable or deceased");
    println!("  Time lock has expired");
    println!("  Heir can operate independently\n");

    // Create time lock transaction
    let one_year = 365 * 24 * 3600;
    let inheritance_time = current_time + one_year;
    let timelock = TimeLock::new_time_based(inheritance_time);

    println!("--- Time Lock Configuration ---");
    println!("Trigger time: {}", format_date(inheritance_time));
    println!("Trigger condition: no transaction signed by owner within 1 year\n");

    // Periodic check (performed by lawyer)
    println!("--- Periodic Check ---");
    let last_activity = current_time;
    let inactive_period = current_time - last_activity;

    if inactive_period > one_year {
        if timelock.is_mature(current_time, 0) {
            println!("✓ Time lock triggered");
            println!("✓ Inheritance process started");
            println!("✓ Assets can be transferred to heir");
        }
    } else {
        let days_remaining = (one_year - inactive_period) / 86400;
        println!("🔒 Normal status");
        println!("{} days until inheritance trigger", days_remaining);
    }

    Ok(())
}
}

Use Case 3: Salary Payment

#![allow(unused)]
fn main() {
fn salary_payment_system() -> Result<(), String> {
    println!("=== Salary Payment System ===\n");

    let company = Wallet::new();
    let employees: Vec<_> = (0..5)
        .map(|i| (format!("Employee {}", i+1), Wallet::new()))
        .collect();

    let current_time = current_timestamp();

    println!("Company address: {}", &company.address[..16]);
    println!("Number of employees: {}\n", employees.len());

    // Pay salary on the 1st of each month
    println!("--- Salary Payment Schedule ---");

    for month in 1..=3 {
        // Calculate the timestamp for the 1st of the next month
        let payment_date = calculate_first_day_of_month(current_time, month);
        let timelock = TimeLock::new_time_based(payment_date);

        println!("Month {} salary:", month);
        println!("  Payment date: {}", format_date(payment_date));

        if timelock.is_mature(current_time, 0) {
            println!("  Status: ✓ Ready to pay");

            for (name, wallet) in &employees {
                println!("    {} → {} sat", name, 10000);
            }
        } else {
            let days_until = (payment_date - current_time) / 86400;
            println!("  Status: 🔒 Locked");
            println!("  Countdown: {} days", days_until);
        }
        println!();
    }

    println!("--- Advantages ---");
    println!("✓ Automated payment");
    println!("✓ Cannot be diverted early");
    println!("✓ Employees have predictable income");
    println!("✓ Reduced administrative costs");

    Ok(())
}
}

Use Case 4: Crowdfunding Refund

#![allow(unused)]
fn main() {
fn crowdfunding_refund() -> Result<(), String> {
    println!("=== Crowdfunding Refund Mechanism ===\n");

    let project_owner = Wallet::new();
    let backers: Vec<_> = (0..10).map(|_| Wallet::new()).collect();

    let current_time = current_timestamp();

    println!("--- Crowdfunding Project ---");
    println!("Target amount: 1,000,000 sat");
    println!("Currently raised: 500,000 sat");
    println!("Deadline: 30 days from now\n");

    // If the target is not met after 30 days, automatically refund
    let deadline = current_time + 30 * 86400;
    let refund_timelock = TimeLock::new_time_based(deadline);

    println!("--- Refund Time Lock ---");
    println!("Trigger condition: target not met after 30 days");
    println!("Trigger time: {}", format_date(deadline));
    println!("Refund method: automatically returned to backers\n");

    // Check status
    if refund_timelock.is_mature(current_time, 0) {
        println!("--- Project failed; executing refund ---");
        for (i, backer) in backers.iter().enumerate() {
            println!("✓ Refund to backer #{}: {} sat", i+1, 50000);
        }
    } else {
        let days_left = (deadline - current_time) / 86400;
        println!("--- Crowdfunding in progress ---");
        println!("Time remaining: {} days", days_left);
        println!("Still needed: 500,000 sat");
    }

    Ok(())
}
}

Advanced Usage

TimeLock + MultiSig

Combining multi-sig for more complex logic:

#![allow(unused)]
fn main() {
fn timelock_multisig_combination() -> Result<(), String> {
    let owner = Wallet::new();
    let heir = Wallet::new();
    let lawyer = Wallet::new();

    // Normal: 2-of-2 (owner + heir)
    let normal_multisig = MultiSigAddress::new(
        2,
        vec![owner.public_key.clone(), heir.public_key.clone()]
    )?;

    // After 1 year: 2-of-3 (any two parties)
    let emergency_multisig = MultiSigAddress::new(
        2,
        vec![owner.public_key, heir.public_key, lawyer.public_key]
    )?;

    let one_year = 365 * 24 * 3600;
    let timelock = TimeLock::new_time_based(current_timestamp() + one_year);

    println!("=== TimeLock + MultiSig Combination ===");
    println!("\nNormal period (first year):");
    println!("  Multi-sig address: {}", &normal_multisig.address[..16]);
    println!("  Requirement: owner + heir (2-of-2)");

    println!("\nEmergency period (after one year):");
    println!("  Multi-sig address: {}", &emergency_multisig.address[..16]);
    println!("  Requirement: any two parties (2-of-3)");
    println!("  Possible combinations:");
    println!("    - owner + heir");
    println!("    - owner + lawyer");
    println!("    - heir + lawyer");

    Ok(())
}
}

Technical Details

nLockTime Field

In actual Bitcoin transactions:

#![allow(unused)]
fn main() {
struct Transaction {
    version: u32,
    inputs: Vec<TxInput>,
    outputs: Vec<TxOutput>,
    locktime: u32,  // Time lock field
}
}

Rules:

if locktime < 500,000,000:
    # Block height mode
    if current_block_height >= locktime:
        can confirm
    else:
        reject

else:
    # Timestamp mode
    if current_timestamp >= locktime:
        can confirm
    else:
        reject

nSequence and Time Locks

For a time lock to be enabled, nSequence must be < 0xFFFFFFFF:

#![allow(unused)]
fn main() {
// Enable time lock
input.sequence = 0xFFFFFFFD;

// Disable time lock (final transaction)
input.sequence = 0xFFFFFFFF;
}

Security Considerations

1. Timestamp Manipulation

Problem: Miners may manipulate block timestamps

Constraints:

  • Timestamp cannot be earlier than the median of the previous 11 blocks
  • Cannot be more than 2 hours later than the current time

Recommendation: Using block height is more reliable

2. Emergencies

Problem: A time lock cannot be cancelled

Solutions:

#![allow(unused)]
fn main() {
// Solution 1: Use RBF to replace before expiry
if !timelock.is_mature(...) && need_cancel {
    replace_with_non_locked_tx();
}

// Solution 2: Double spend (before expiry)
create_alternative_tx_without_timelock();
}

3. Key Loss

Problem: Key lost before expiry

Recommendation:

  • Use multi-sig to reduce risk
  • Back up keys
  • Set up recovery mechanisms

Relationship to CLTV/CSV

CheckLockTimeVerify (CLTV)

Introduced in BIP65:

OP_CLTV opcode
Locks a single UTXO
More flexible

nLockTime vs CLTV:

nLockTime: locks the entire transaction
CLTV: locks a single output (more flexible)

CheckSequenceVerify (CSV)

Introduced in BIP112:

OP_CSV opcode
Relative time lock
Calculated from the time the UTXO was created

Best Practices

1. Choose the Right Type

#![allow(unused)]
fn main() {
// Exact date: use timestamp
let birthday = to_timestamp("2025-01-01");
let timelock = TimeLock::new_time_based(birthday);

// Relative delay: use block height
let blocks_1week = 1008;  // About 1 week
let timelock = TimeLock::new_block_based(current_height + blocks_1week);
}

2. User-Friendly Time Display

#![allow(unused)]
fn main() {
fn display_timelock_status(timelock: &TimeLock, current_time: u64, current_height: u64) {
    if timelock.is_mature(current_time, current_height) {
        println!("✓ Unlocked");
    } else {
        if timelock.is_block_height {
            let blocks_left = timelock.locktime - current_height;
            let hours = blocks_left * 10 / 60;  // Approximately 10 minutes per block
            println!("🔒 Locked; {} blocks remaining (approximately {} hours)", blocks_left, hours);
        } else {
            let seconds_left = timelock.locktime - current_time;
            let days = seconds_left / 86400;
            println!("🔒 Locked; {} days remaining", days);
        }
    }
}
}

3. Testing Time Locks

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[test]
    fn test_timelock() {
        let current = 1000000;
        let future = 2000000;

        let timelock = TimeLock::new_time_based(future);

        // Not yet expired
        assert!(!timelock.is_mature(current, 0));

        // Expired
        assert!(timelock.is_mature(future + 1, 0));
    }
}
}

References


Summary: Time locks are a key technology for implementing deferred payments and smart contracts. Used appropriately, they enable a wide range of applications such as term deposits, inheritance planning, and salary payments.

Back to Advanced Features