TSP-0007 โ€” APO

Proposal Number: TSP-0007

Proposal Number: TSP-0007
Proposal Name: ANYPREVOUT Support for Eltoo-based Payment Channels (Tondi Flash)"
Category: Consensus (C) โ€” pre-Oct 2025 numbering retained as 000x
Status: Review
Author: Tondi Foundation Development Team & Avato Labs
Created: 2025-09-05
Target: Tondi Frontier (v2026a)
Scope: On-chain payload schema, validation semantics, DAG ordering, data availability, zk/bridge extensions, governance mechanism

This document uses the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" as described in RFC 2119.

Introduction

Abstract

This proposal describes a new type of public key and associated signature opcodes for tapscript transactions in the Tondi blockchain. It allows signatures for these public keys to not commit to the exact UTXO being spent, enabling dynamic binding of transactions to different UTXOs provided they have compatible scripts. This enables the implementation of Eltoo-based payment channels, branded as Tondi Flash, for high-frequency micropayments.

Motivation

Off-chain protocols utilize unbroadcast transactions to renegotiate final states for on-chain settlement. In scenarios requiring predetermined responses to on-chain transactions, the same reaction MAY apply to multiple possible transactions. However, standard input signatures commit to the exact transaction, necessitating new signatures for each variant.

This proposal introduces a public key type that alters the transaction digest algorithm, excluding commitments to the previous output (and optionally the witness script and value). This permits dynamic rebinding of signed transactions to compatible previous outputs. The opt-in nature uses a distinct public key type, with rebinding scope restricted by keys, script commitments, amounts, nSequence values, or codeseparator opcodes.

Tondi, a high-performance DAG-based UTXO platform derived from Kaspa's GHOSTDAG/PHANTOM protocols, achieves sub-second confirmations and high TPS (>10,000). Optimized for micropayments (e.g., IoT, gaming, AI settlements), introducing ANYPREVOUT is essential for Tondi Flashโ€”a simplified Eltoo payment network supporting millisecond updates and cross-chain bridging.

Bitcoin discussions on similar mechanisms highlighted concerns like replay risks, consensus splits, and upgrade timing. Tondi addresses these through enhanced bindings, contextual restrictions, and agile governance, transforming the feature from optional to core in its performance-oriented ecosystem.

Specification

This proposal modifies the behavior of signature opcodes for public keys of 33 bytes starting with 0x01 or the single byte 0x01. These are termed TSP-0007 public keys. ANYPREVOUT support is restricted to tapscript (witness version 1 or higher) contexts to ensure a soft fork. New opcodes (OP_CHECKSIG_APO, OP_CHECKSIGVERIFY_APO, OP_CHECKSIGADD_APO) are introduced via OP_SUCCESSx encoding: unupgraded nodes treat them as unconditional success, while upgraded nodes enforce Schnorr validation with the modified digest, constituting a soft fork by tightening rules.

Non-tapscript paths (e.g., legacy P2PKH/P2SH) do not support ANYPREVOUT in this proposal; future extensions MAY introduce new witness versions or opcodes if needed.

Rules for Signature Opcodes

The tapscript signature opcode rules are modified by defining new opcodes encoded as OP_SUCCESSx (e.g., opcode values in the 0xB2-0xBF range reserved for future soft forks). Specific OP_SUCCESSx encoding values will be specified in the deployment parameter table (e.g., OP_SUCCESS178/OP_SUCCESS179/OP_SUCCESS180 corresponding to OP_CHECKSIG_APO/OP_CHECKSIGVERIFY_APO/OP_CHECKSIGADD_APO respectively). Implementations MUST use these fixed numbers to avoid implementation divergence. Unupgraded nodes MUST treat these as success (no further execution), while upgraded nodes MUST perform the following:

  • If the public key is the single byte 0x01, or 33 bytes with first byte 0x01, it is a TSP-0007 public key:
    • If the signature is not empty, validate it using Schnorr validation rules with the public key, allowable hash_type values, and modified transaction digest as defined below.
    • Failure to validate MUST cause the script to fail.

This tightening (from unconditional success to validated success) is a soft fork.

Public Key and Signature Algorithm

TSP-0007 public keys are x-only 32-byte Schnorr public keys. In scripts, identified as 0x01 || pubkey32 (33 bytes) for TSP-0007 public key type. This proposal is effective only in tapscript script-path, not applicable to key-path. Signature algorithm: secp256k1-Schnorr (BIP-340 style). ECDSA-APO is not defined.

Terms and Constants

Core Terminology Definitions

ANYPREVOUT (APO): A signature mechanism that allows signatures to be valid for multiple UTXOs with compatible spending conditions, without committing to the specific previous output being spent. This enables dynamic rebinding of signed transactions to different UTXOs.

TSP-0007 Public Key: A special public key type identified by:

  • Single byte 0x01 (references Taproot internal key)
  • 33-byte format 0x01 || pubkey32 (explicit 32-byte Schnorr public key)

SIGHASH Types: Signature hash types that determine which parts of the transaction are committed to in the signature:

  • BASE SIGHASH: BASE = hash_type & 0x03
    • 0x01 = SIGHASH_ALL: Commits to all inputs and outputs
    • 0x02 = SIGHASH_NONE: Commits to all inputs, no outputs
    • 0x03 = SIGHASH_SINGLE: Commits to all inputs and corresponding output

Extended SIGHASH Flags:

  • APO_BIT: 0x40 - Enables ANYPREVOUT behavior
  • ANYSCRIPT_BIT: 0x80 - Allows signature to be valid for any scriptPubKey

Valid Hash Type Combinations

The complete set of valid hash_type values is:

{0x01, 0x02, 0x03, 0x41, 0x42, 0x43, 0xC1, 0xC2, 0xC3}

Detailed Breakdown:

  • 0x01 = SIGHASH_ALL (standard)
  • 0x02 = SIGHASH_NONE (standard)
  • 0x03 = SIGHASH_SINGLE (standard)
  • 0x41 = SIGHASH_ALL | APO_BIT (ANYPREVOUT with ALL)
  • 0x42 = SIGHASH_NONE | APO_BIT (ANYPREVOUT with NONE)
  • 0x43 = SIGHASH_SINGLE | APO_BIT (ANYPREVOUT with SINGLE)
  • 0xC1 = SIGHASH_ALL | APO_BIT | ANYSCRIPT_BIT (ANYPREVOUTANYSCRIPT with ALL)
  • 0xC2 = SIGHASH_NONE | APO_BIT | ANYSCRIPT_BIT (ANYPREVOUTANYSCRIPT with NONE)
  • 0xC3 = SIGHASH_SINGLE | APO_BIT | ANYSCRIPT_BIT (ANYPREVOUTANYSCRIPT with SINGLE)

Constraint Rules

Legacy Compatibility: When hash_type & APO_BIT != 0, the legacy ANYONECANPAY meaning (bits 0x80-0x83) is reserved and MUST NOT be used. Values 0x81, 0x82, 0x83 MUST fail validation.

Script Context: ANYPREVOUT signatures are ONLY valid in tapscript contexts (witness version 1 or higher). Legacy P2PKH/P2SH scripts do not support ANYPREVOUT.

Binding Mechanisms

Amount Binding: When hash_type & ANYSCRIPT_BIT == 0, signatures commit to the specific input amount, preventing reuse across UTXOs with different values.

Script Binding: When hash_type & ANYSCRIPT_BIT == 0, signatures commit to the specific scriptPubKey, restricting reuse to UTXOs with identical spending conditions.

Sequence Binding: All ANYPREVOUT signatures commit to the nSequence value, enabling temporal ordering and preventing replay attacks.

Tapleaf Binding: When hash_type & ANYSCRIPT_BIT == 0, signatures commit to the specific tapleaf hash, ensuring script version consistency.

Security Parameters

Signature Length: MUST be exactly 64 or 65 bytes:

  • 64 bytes: Default hash_type = 0x01 (SIGHASH_ALL)
  • 65 bytes: Last byte specifies the hash_type

Domain Separation: Uses tagged hash with label "TSP-0007/APSighash" to prevent cross-protocol signature reuse.

Key Version: Fixed at 0x01 to ensure signatures for standard Taproot keys are not valid for TSP-0007 keys and vice versa.

Usage Examples and Code Patterns

Example 1: Basic ANYPREVOUT Signature Creation

// TSP-0007 public key format
let tsp_pubkey = [0x01, 0x02, 0x03, /* ... 32 bytes ... */];

// Create ANYPREVOUT signature with SIGHASH_ALL
let hash_type = 0x41; // SIGHASH_ALL | APO_BIT
let signature = create_anyprevout_signature(
    private_key,
    transaction,
    input_index,
    hash_type,
    None, // no annex
    None, // no codeseparator
);

// Signature can be reused for compatible UTXOs
let compatible_utxos = find_compatible_utxos(&script_pubkey, amount);
for utxo in compatible_utxos {
    let tx = create_transaction_with_utxo(utxo, outputs);
    // Same signature works without modification
    validate_signature(&signature, &tx, input_index);
}

Example 2: Eltoo Channel Update Pattern

// Channel setup with ANYPREVOUT trigger transaction
let funding_script = Script::new(vec![
    Opcode::OP_2,
    Opcode::OP_PUSH32, agg_pubkey1,
    Opcode::OP_PUSH32, agg_pubkey2,
    Opcode::OP_2,
    Opcode::OP_CHECKMULTISIG,
]);

// Create trigger transaction with ANYPREVOUT
let trigger_tx = Transaction {
    inputs: vec![Input {
        previous_output: funding_outpoint,
        script_sig: Script::new(vec![]),
        sequence: 0,
    }],
    outputs: vec![Output {
        value: channel_amount,
        script_pubkey: update_script.clone(),
    }],
    lock_time: 0,
};

// Sign with ANYPREVOUT (hash_type = 0x41)
let trigger_sig = sign_anyprevout(
    &channel_key,
    &trigger_tx,
    0, // input index
    0x41, // SIGHASH_ALL | APO_BIT
);

// Update transaction reuses the same signature
let update_tx = Transaction {
    inputs: vec![Input {
        previous_output: previous_update_outpoint,
        script_sig: Script::new(vec![]),
        sequence: sequence_number + 1, // Incremented sequence
    }],
    outputs: vec![Output {
        value: new_balance,
        script_pubkey: update_script,
    }],
    lock_time: 0,
};

// Same signature validates for the update transaction
assert!(verify_anyprevout_signature(
    &trigger_sig,
    &update_tx,
    0,
    &channel_pubkey,
));

Example 3: Hash Type Combinations

// Different hash type combinations and their use cases
let hash_types = [
    (0x01, "SIGHASH_ALL - Standard signature"),
    (0x41, "SIGHASH_ALL | APO_BIT - ANYPREVOUT with full commitment"),
    (0x42, "SIGHASH_NONE | APO_BIT - ANYPREVOUT without output commitment"),
    (0x43, "SIGHASH_SINGLE | APO_BIT - ANYPREVOUT with single output"),
    (0xC1, "SIGHASH_ALL | APO_BIT | ANYSCRIPT_BIT - Most flexible"),
    (0xC2, "SIGHASH_NONE | APO_BIT | ANYSCRIPT_BIT - Flexible without outputs"),
    (0xC3, "SIGHASH_SINGLE | APO_BIT | ANYSCRIPT_BIT - Flexible with single output"),
];

for (hash_type, description) in hash_types {
    println!("0x{:02X}: {}", hash_type, description);
    
    // Validate hash type
    assert!(is_valid_hash_type(hash_type));
    
    // Create signature with this hash type
    let sig = create_signature_with_hash_type(private_key, tx, input_idx, hash_type);
    
    // Verify signature
    assert!(verify_signature(&sig, tx, input_idx, public_key));
}

Example 4: Binding Mechanism Demonstration

// Demonstrate different binding mechanisms
fn demonstrate_bindings() {
    let utxo1 = UTXO { amount: 1000, script_pubkey: script_a.clone() };
    let utxo2 = UTXO { amount: 1000, script_pubkey: script_a.clone() }; // Same script
    let utxo3 = UTXO { amount: 2000, script_pubkey: script_a.clone() }; // Different amount
    let utxo4 = UTXO { amount: 1000, script_pubkey: script_b.clone() }; // Different script
    
    // Create signature with amount and script binding (hash_type = 0x41)
    let sig_bound = create_anyprevout_signature(key, tx, 0, 0x41);
    
    // This signature works with utxo1 and utxo2 (same amount and script)
    assert!(verify_with_utxo(&sig_bound, &utxo1));
    assert!(verify_with_utxo(&sig_bound, &utxo2));
    
    // But fails with utxo3 (different amount) and utxo4 (different script)
    assert!(!verify_with_utxo(&sig_bound, &utxo3));
    assert!(!verify_with_utxo(&sig_bound, &utxo4));
    
    // Create signature without bindings (hash_type = 0xC1)
    let sig_unbound = create_anyprevout_signature(key, tx, 0, 0xC1);
    
    // This signature works with all UTXOs
    assert!(verify_with_utxo(&sig_unbound, &utxo1));
    assert!(verify_with_utxo(&sig_unbound, &utxo2));
    assert!(verify_with_utxo(&sig_unbound, &utxo3));
    assert!(verify_with_utxo(&sig_unbound, &utxo4));
}

Example 5: Sequence-Based Ordering

// Demonstrate sequence-based ordering for Eltoo channels
fn channel_update_sequence() {
    let mut sequence = 0;
    let mut signatures = Vec::new();
    
    // Create a series of update transactions with increasing sequence numbers
    for i in 0..5 {
        let update_tx = create_update_transaction(
            previous_outpoint,
            new_balance,
            sequence + i,
        );
        
        // Sign with ANYPREVOUT
        let sig = sign_anyprevout(&channel_key, &update_tx, 0, 0x41);
        signatures.push(sig);
        
        // Later signatures can spend earlier ones due to sequence ordering
        if i > 0 {
            let earlier_tx = create_update_transaction(
                previous_outpoint,
                old_balance,
                sequence + i - 1,
            );
            
            // Earlier signature cannot spend later transaction
            assert!(!verify_anyprevout_signature(
                &signatures[i-1],
                &update_tx,
                0,
                &channel_pubkey,
            ));
        }
    }
}

Technical Implementation Details

Algorithm Specifications

1. Signature Message Construction Algorithm

fn construct_signature_message(
    tx: &Transaction,
    input_index: usize,
    hash_type: u8,
    annex: Option<&[u8]>,
    leaf_hash: Option<&[u8; 32]>,
    codesep_pos: u32,
) -> [u8; 32] {
    let mut msg = Vec::new();
    
    // 1. Append hash_type
    msg.push(hash_type);
    
    // 2. Append transaction version
    msg.extend_from_slice(&tx.version.to_le_bytes());
    
    // 3. Append lock time
    msg.extend_from_slice(&tx.lock_time.to_le_bytes());
    
    // 4. Append outputs hash if needed
    let base = hash_type & 0x03;
    if base != 0x02 && base != 0x03 { // Not NONE or SINGLE
        let outputs_hash = hash_outputs(&tx.outputs);
        msg.extend_from_slice(&outputs_hash);
    }
    
    // 5. Append spend type
    let spend_type = if annex.is_some() { 3 } else { 2 };
    msg.push(spend_type);
    
    // 6. Append amount and script binding if not ANYSCRIPT
    if hash_type & 0x80 == 0 { // Not ANYSCRIPT_BIT
        let input = &tx.inputs[input_index];
        msg.extend_from_slice(&input.previous_output.value.to_le_bytes());
        msg.extend_from_slice(&compact_size_len(input.previous_output.script_pubkey.len()));
        msg.extend_from_slice(&input.previous_output.script_pubkey);
    }
    
    // 7. Append sequence
    msg.extend_from_slice(&tx.inputs[input_index].sequence.to_le_bytes());
    
    // 8. Append annex hash if present
    if let Some(annex_data) = annex {
        msg.extend_from_slice(&compact_size_len(annex_data.len()));
        msg.extend_from_slice(annex_data);
    }
    
    // 9. Append single output hash if SINGLE
    if base == 0x03 { // SINGLE
        if input_index < tx.outputs.len() {
            let output_hash = hash_single_output(&tx.outputs[input_index]);
            msg.extend_from_slice(&output_hash);
        } else {
            return [0u8; 32]; // Invalid SINGLE
        }
    }
    
    // 10. Append extension data
    let mut ext = Vec::new();
    if hash_type & 0x80 == 0 { // Not ANYSCRIPT_BIT
        if let Some(leaf) = leaf_hash {
            ext.extend_from_slice(leaf);
        }
    }
    ext.push(0x01); // key_version
    ext.extend_from_slice(&codesep_pos.to_le_bytes());
    
    // 11. Create tagged hash
    tagged_hash("TSP-0007/APSighash", &[&msg, &ext])
}

2. Signature Validation Algorithm

fn validate_anyprevout_signature(
    signature: &[u8],
    public_key: &[u8],
    tx: &Transaction,
    input_index: usize,
    annex: Option<&[u8]>,
    leaf_hash: Option<&[u8; 32]>,
    codesep_pos: u32,
) -> bool {
    // 1. Validate signature length
    if signature.len() != 64 && signature.len() != 65 {
        return false;
    }
    
    // 2. Extract hash_type
    let hash_type = if signature.len() == 65 {
        signature[64]
    } else {
        0x01 // Default SIGHASH_ALL
    };
    
    // 3. Validate hash_type
    if !is_valid_hash_type(hash_type) {
        return false;
    }
    
    // 4. Check SINGLE bounds
    let base = hash_type & 0x03;
    if base == 0x03 && input_index >= tx.outputs.len() {
        return false;
    }
    
    // 5. Construct signature message
    let msg = construct_signature_message(
        tx, input_index, hash_type, annex, leaf_hash, codesep_pos
    );
    
    // 6. Perform Schnorr validation
    let sig_bytes = &signature[..64];
    schnorr_verify(sig_bytes, &msg, public_key)
}

3. UTXO Compatibility Check Algorithm

fn check_utxo_compatibility(
    signature: &[u8],
    target_utxo: &UTXO,
    original_utxo: &UTXO,
    hash_type: u8,
) -> bool {
    // Extract hash_type from signature
    let sig_hash_type = if signature.len() == 65 {
        signature[64]
    } else {
        0x01
    };
    
    // Check amount binding
    if sig_hash_type & 0x80 == 0 { // Not ANYSCRIPT_BIT
        if target_utxo.value != original_utxo.value {
            return false;
        }
    }
    
    // Check script binding
    if sig_hash_type & 0x80 == 0 { // Not ANYSCRIPT_BIT
        if target_utxo.script_pubkey != original_utxo.script_pubkey {
            return false;
        }
    }
    
    // Check tapleaf binding (if applicable)
    if sig_hash_type & 0x80 == 0 { // Not ANYSCRIPT_BIT
        if let (Some(target_leaf), Some(original_leaf)) = 
            (target_utxo.tapleaf_hash, original_utxo.tapleaf_hash) {
            if target_leaf != original_leaf {
                return false;
            }
        }
    }
    
    true
}

Data Structures

1. TSP-0007 Public Key Structure

#[derive(Debug, Clone, PartialEq)]
pub struct TSP0007PublicKey {
    pub key_type: u8,        // Always 0x01
    pub pubkey: [u8; 32],    // 32-byte Schnorr public key
}

impl TSP0007PublicKey {
    pub fn new(pubkey: [u8; 32]) -> Self {
        Self {
            key_type: 0x01,
            pubkey,
        }
    }
    
    pub fn to_bytes(&self) -> [u8; 33] {
        let mut bytes = [0u8; 33];
        bytes[0] = self.key_type;
        bytes[1..].copy_from_slice(&self.pubkey);
        bytes
    }
    
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
        if bytes.len() == 1 && bytes[0] == 0x01 {
            // Reference to Taproot internal key
            return Err(Error::InternalKeyReference);
        }
        
        if bytes.len() == 33 && bytes[0] == 0x01 {
            let mut pubkey = [0u8; 32];
            pubkey.copy_from_slice(&bytes[1..]);
            Ok(Self::new(pubkey))
        } else {
            Err(Error::InvalidKeyFormat)
        }
    }
}

2. ANYPREVOUT Signature Structure

#[derive(Debug, Clone)]
pub struct AnyPrevoutSignature {
    pub signature: [u8; 64],  // Schnorr signature
    pub hash_type: u8,       // Hash type (optional, defaults to 0x01)
}

impl AnyPrevoutSignature {
    pub fn new(signature: [u8; 64], hash_type: Option<u8>) -> Self {
        Self {
            signature,
            hash_type: hash_type.unwrap_or(0x01),
        }
    }
    
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(65);
        bytes.extend_from_slice(&self.signature);
        bytes.push(self.hash_type);
        bytes
    }
    
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
        match bytes.len() {
            64 => Ok(Self::new(
                bytes.try_into().unwrap(),
                Some(0x01)
            )),
            65 => Ok(Self::new(
                bytes[..64].try_into().unwrap(),
                Some(bytes[64])
            )),
            _ => Err(Error::InvalidSignatureLength),
        }
    }
}

3. Hash Type Validation Structure

#[derive(Debug, Clone, Copy)]
pub struct HashTypeValidator;

impl HashTypeValidator {
    const VALID_TYPES: [u8; 9] = [0x01, 0x02, 0x03, 0x41, 0x42, 0x43, 0xC1, 0xC2, 0xC3];
    
    pub fn is_valid(hash_type: u8) -> bool {
        Self::VALID_TYPES.contains(&hash_type)
    }
    
    pub fn get_base_type(hash_type: u8) -> u8 {
        hash_type & 0x03
    }
    
    pub fn has_anyprevout_bit(hash_type: u8) -> bool {
        hash_type & 0x40 != 0
    }
    
    pub fn has_anyscript_bit(hash_type: u8) -> bool {
        hash_type & 0x80 != 0
    }
    
    pub fn get_description(hash_type: u8) -> &'static str {
        match hash_type {
            0x01 => "SIGHASH_ALL",
            0x02 => "SIGHASH_NONE", 
            0x03 => "SIGHASH_SINGLE",
            0x41 => "SIGHASH_ALL | APO_BIT",
            0x42 => "SIGHASH_NONE | APO_BIT",
            0x43 => "SIGHASH_SINGLE | APO_BIT",
            0xC1 => "SIGHASH_ALL | APO_BIT | ANYSCRIPT_BIT",
            0xC2 => "SIGHASH_NONE | APO_BIT | ANYSCRIPT_BIT",
            0xC3 => "SIGHASH_SINGLE | APO_BIT | ANYSCRIPT_BIT",
            _ => "INVALID",
        }
    }
}

4. Binding Constraints Structure

#[derive(Debug, Clone)]
pub struct BindingConstraints {
    pub amount_binding: bool,
    pub script_binding: bool,
    pub sequence_binding: bool,
    pub tapleaf_binding: bool,
}

impl BindingConstraints {
    pub fn from_hash_type(hash_type: u8) -> Self {
        Self {
            amount_binding: hash_type & 0x80 == 0,
            script_binding: hash_type & 0x80 == 0,
            sequence_binding: true, // Always bound
            tapleaf_binding: hash_type & 0x80 == 0,
        }
    }
    
    pub fn check_compatibility(&self, utxo1: &UTXO, utxo2: &UTXO) -> bool {
        if self.amount_binding && utxo1.value != utxo2.value {
            return false;
        }
        
        if self.script_binding && utxo1.script_pubkey != utxo2.script_pubkey {
            return false;
        }
        
        if self.tapleaf_binding {
            if let (Some(leaf1), Some(leaf2)) = 
                (utxo1.tapleaf_hash, utxo2.tapleaf_hash) {
                if leaf1 != leaf2 {
                    return false;
                }
            }
        }
        
        true
    }
}

Signature Message (Domain Separation)

Use TaggedHash: H_tag(label, msg) = SHA256(SHA256(label) || SHA256(label) || msg); where label = "TSP-0007/APSighash".

Define MsgTSP(tx, in_idx, hash_type, annex?, leaf_hash?, codesep_pos):

  1. u8: hash_type
  2. le32: nVersion
  3. le32: nLockTime
  4. If BASE != NONE && BASE != SINGLE: H(Outputs), where Outputs = concat(CompactSizeLen(CTxOut_i) || CTxOut_i_serialized) consensus serialization concatenated sequentially.
  5. u8: spend_type (2=no annex, 3=has annex starting with 0x50)
  6. If hash_type & APO_BIT != 0:
    • If hash_type & ANYSCRIPT_BIT == 0: bind le64: amount, varbytes: scriptPubKey (CompactSizeLen + RawScript)
    • Else (ANYSCRIPT): do not bind amount and scriptPubKey
  7. le32: nSequence
  8. If annex: H(CompactSizeLen(annex) || annex)
  9. If BASE == SINGLE: H(Outputs[in_idx])

Define ExtTSP(hash_type, leaf_hash, codesep_pos):

  • If hash_type & ANYSCRIPT_BIT == 0: leaf_hash(32); else omit
  • u8: key_version = 0x01
  • le32: codesep_pos (0xFFFFFFFF if no OP_CODESEPARATOR)

Full hash: sig_msg = H_tag("TSP-0007/APSighash", MsgTSP || ExtTSP).

Validation Rules

  • Signature length MUST be 64 or 65 bytes; if 65 bytes, the 65th byte is hash_type. For 64 bytes, default hash_type = 0x01 (ALL).
  • If BASE == SINGLE and in_idx >= number_of_outputs, script MUST fail (no corresponding output).
  • Perform Schnorr validation with sig_msg and public key. Failure MUST cause script failure.

Context and Soft Fork

  • APO is effective only in tapscript (witness v1+). In tapscript, introduced via new opcodes OP_CHECKSIG_APO / OP_CHECKSIGADD_APO encoded as OP_SUCCESSx; unupgraded nodes treat as unconditional success, upgraded nodes tighten to Schnorr+APO validation, forming a soft fork.
  • Non-tapscript paths do not activate APO (future TSP MAY extend if needed).
  • This proposal does not modify key-path rules; address opt-in for APO is achieved only through script-path.

Script System Integration

ANYPREVOUT is tapscript-only, using Schnorr signatures for efficiency.

  • Opcode Extension: New OP_CHECKSIG_APO etc. use MsgTSP/ExtTSP for TSP keys, skipping prevout check, enforcing amount/nSequence (when not ANYSCRIPT).
  • MuSig2 Aggregation: Two-round nonce exchange, partial signature aggregation for compact multi-sig.
  • Example Script: <agg_pubkey> OP_CHECKSIG_APO.
  • Implementation: In script::opcode module, extend execution for new opcodes with TSP logic using secp256k1_schnorrsig_verify.

Non-Taproot (legacy) integration is not supported for ANYPREVOUT in this version; future proposals MAY add via new witness versions.

DAG/Mempool Policies (Replace-By-Sequence - RBS)

To leverage Tondi's DAG, introduce Replace-By-Sequence (RBS) and Last-State-Wins rules:

  • Channel transactions are clustered by {funding_outpoint, channel_id}.
  • Higher nSequence commitment transactions automatically replace lower versions in mempool and DAG conflict resolution.
  • Tie-break order (policy):
    1. Higher nSequence (latest state priority);
    2. Higher feerate;
    3. wtxid (or txid) lexicographic order. Nodes MUST NOT use local timestamps for consistency decisions.
  • Policy: Non-channel template APO transactions MUST NOT be relayed by default (opt-in whitelist) to reduce misuse.
  • ANYSCRIPT additional relay constraints (policy): When hash_type & ANYSCRIPT_BIT != 0, transactions MUST conform to channel template whitelist (e.g., 2-of-2 eltoo script family). Non-conforming transactions MUST NOT relay. This constraint does not affect consensus, only default relay policy.

Backward Compatibility and Activation

As a soft fork, older software will continue to operate without modification.

Unupgraded nodes treat the new OP_SUCCESSx opcodes as unconditional success, therefore they do not enforce signatures within that opcode path. Upgraded nodes tighten validation.

Nodes are strongly encouraged to upgrade to fully validate new signatures.

Non-upgraded wallets can use legacy outputs.

Activation: Soft fork via version bit 3 signaling. Phased: 3-month testnet, mainnet threshold 80% over 2016 blocks, observation period with Speedy Trial (LOT=true for activation lock-in). Fallback if not met within timeout.

Tondi Flash Protocol

Eltoo-based, with setup, negotiation, settlement phases.

Channel Establishment (Setup)

Funding Tx locks to 2-of-2. Trigger Tx (ANYPREVOUT signed) outputs to update script.

Pseudocode:

// Create 2-of-2 multisig funding script
let funding_script = Script::new(vec![
    Opcode::OP_2,
    Opcode::OP_PUSH32, agg_pubkey1,
    Opcode::OP_PUSH32, agg_pubkey2,
    Opcode::OP_2,
    Opcode::OP_CHECKMULTISIG,
]);

// Create funding transaction
let funding_out = Output::new(funding_script, amount);
let funding_tx = Transaction::new(inputs, vec![funding_out]);
sign_funding(&funding_tx, &parties);
broadcast(&funding_tx);

// Create update script with conditional logic
let update_script = Script::new(vec![
    Opcode::OP_IF,
    Opcode::OP_PUSH32, update_pubkey1,
    Opcode::OP_PUSH32, update_pubkey2,
    Opcode::OP_2,
    Opcode::OP_CHECKMULTISIG,
    Opcode::OP_ELSE,
    Opcode::OP_CHECKSEQUENCEVERIFY,
    Opcode::OP_DROP,
    Opcode::OP_PUSH32, settlement_pubkey1,
    Opcode::OP_PUSH32, settlement_pubkey2,
    Opcode::OP_2,
    Opcode::OP_CHECKMULTISIG,
    Opcode::OP_ENDIF,
]);

// Create trigger transaction with ANYPREVOUT
let trigger_out = Output::new(update_script, amount);
let trigger_tx = Transaction {
    inputs: vec![Input {
        previous_output: funding_tx.outputs[0].outpoint,
        script_sig: Script::new(vec![]),
        sequence: 0,
    }],
    outputs: vec![trigger_out],
    lock_time: 0,
};

// Sign with ANYPREVOUT (hash_type = 0x41)
let trigger_sig = sign_anyprevout(
    &channel_key,
    &trigger_tx,
    0, // input index
    0x41, // SIGHASH_ALL | APO_BIT
);
exchange_sigs(&trigger_tx, &trigger_sig);

State Update (Negotiation)

New Update Tx with incremented nSequence, ANYPREVOUT reuse sig.

Pseudocode:

// Calculate new balance after payment
let new_balance = update_balance(old_balance, payment_amount);
let alice_balance = new_balance.alice;
let bob_balance = new_balance.bob;

// Create new outputs for updated balances
let new_outputs = vec![
    Output::new(alice_address, alice_balance),
    Output::new(bob_address, bob_balance),
];

// Create update transaction with incremented sequence
let update_tx = Transaction {
    inputs: vec![Input {
        previous_output: previous_update_tx.outputs[0].outpoint,
        script_sig: Script::new(vec![]),
        sequence: sequence_number + 1, // Incremented sequence
    }],
    outputs: new_outputs,
    lock_time: 0,
};

// Reuse the same ANYPREVOUT signature from trigger transaction
// The signature is valid because it doesn't commit to the specific previous output
let update_sig = trigger_sig.clone(); // Same signature works!

// Verify the signature still works
assert!(verify_anyprevout_signature(
    &update_sig,
    &update_tx,
    0,
    &channel_pubkey,
));

// Create settlement transaction for dispute resolution
let settlement_tx = Transaction {
    inputs: vec![Input {
        previous_output: update_tx.outputs[0].outpoint,
        script_sig: Script::new(vec![]),
        sequence: settlement_timeout, // CSV timeout
    }],
    outputs: vec![
        Output::new(alice_address, alice_balance),
        Output::new(bob_address, bob_balance),
    ],
    lock_time: 0,
};

// Exchange and store both transactions
exchange_and_store(&update_tx, &settlement_tx);

High-Frequency Features

DAG confirms <1s, updates >10k/s/channel. Micropayments via HTLC variants, streaming payments.

Routing and Bridging

MPP for multi-path; RGB/Nexus for cross-chain (BTC/Kaspa).

Security

Signature Replay

By design, SIGHASH_ANYPREVOUT and SIGHASH_ANYPREVOUTANYSCRIPT introduce additional potential for signature replay compared to SIGHASH_ALL and SIGHASH_ANYONECANPAY.

Standard signatures prevent replay by committing to inputs; replay requires spending the same input multiple times, impossible on Tondi (unique txids/UTXOs).

With SIGHASH_ANYPREVOUT, replay possible for different UTXOs with same scriptPubKey and value; with SIGHASH_ANYPREVOUTANYSCRIPT, for any UTXOs reusing the same TSP-0007 key.

Implementers MUST ensure TSP-0007 keys are only reused where replay cannot cause fund loss (e.g., Eltoo with nSequence ordering), or where acceptable.

Wallet SDKs MUST default to allowing APO only in channel templates; enforce bindings for amount, nSequence, tapleaf_hash (non-ANYSCRIPT); recommend minimum CSV/CLTV timeouts.

Malleability

ANYPREVOUT MAY introduce malleability vectors.

Transactions using only ANYPREVOUT signatures are malleable by providing alternate satisfied inputs, creating new valid tx with different txid, potentially conflicting or double-paying.

For Eltoo chains, third parties MAY malleate without keys by omitting intermediates.

Mitigate by using ANYPREVOUT in child txs to adjust txids; or relative timelocks for SIGHASH_ALL/ANYONECANPAY children to ensure confirmations against reorgs.

Drawbacks: Timelocks prevent CPFP fee-bumping and delay funds.

Privacy Considerations

ANYPREVOUT signatures expected rare.

Designers SHOULD use Taproot key path spends for efficiency and privacy, avoiding distinction from other protocols.

ANYPREVOUT usage reveals info (e.g., non-cooperation, protocol details).

To maximize privacy, use TSP-0007 keys only in scripts spent with ANYPREVOUT; use key paths or alternate Merkle branches for non-ANYPREVOUT spends.

Additional branches MAY trade cost for privacy.

Audit and Validation

Implementations MUST undergo fuzz testing (e.g., sympy for replay probabilities) and simulations (networkx for DAG). Benchmarks (pandas) show Eltoo reduces complexity ~70% vs. penalty-based (no revocations/toxics).

Watchtowers SHOULD provide non-punitive proofs, with minimal data retention and on-chain response windows matching sub-second confirmations.

Rationale

New public key types for tapscript can be introduced in a soft fork by specifying new rules for unknown public key types, as this only requires adding restrictions to the pre-existing signature opcodes. Alternative approaches, such as defining new script opcodes, using a different Taproot leaf version, or different SegWit outputs, are more complicated and better reserved for upgrades needing additional flexibility. In this case, a new transaction digest is specified, but the same elliptic curve and signature algorithm (secp256k1 and Schnorr) are retained.

The Eltoo protocol provides an example where committing to the witness script is not always appropriate, using script and nLockTime to make signatures asymmetric, allowing earlier signatures to be spent by later ones but not vice versa. Thus, a single signature for a later transaction must spend prior ones with different tapscripts. However, committing to the script allows signatures for precisely one transaction. In Eltoo, this enables update transaction signatures applicable to any prior update and settlement signatures specific to corresponding updates, using the same key for compact scripts.

Committing to the input value enhances safety against malicious reuse for unintended funds, so it is default. However, excluding it allows consolidating UTXOs with identical spending conditions into one, useful for layered commitments with Eltoo.

This proposal supports ANYPREVOUT signatures only via script path spends, not key path spends, for independence from core Taproot changes and to allow addresses to opt-in or opt-out while remaining indistinguishable until spent.

Because OP_0 leaves an empty vector, it does not satisfy rules for unknown public key types. Using OP_1..OP_16 or OP_1NEGATE references the Taproot internal key simply; the first (0x01) is used, with the same byte as prefix for explicit keys.

Changing key_version ensures signatures for standard Taproot keys are not valid for TSP-0007 keys (and vice versa) if the same private key generates both.

OP_CODESEPARATOR affects both SIGHASH_ANYPREVOUT and SIGHASH_ANYPREVOUTANYSCRIPT signatures.

Deployment

This MAY be deployed as a soft-fork concurrent with or subsequent to Taproot upgrades in Tondi. Activation uses version bit 3 signaling, with 2016-block periods, 80% threshold, 3-month testnet prelude, observation period, and Speedy Trial with LOT=true for lock-in. Fallback if timeout unmet.

Backward Compatibility

As a soft fork, older software continues without modification.

Unupgraded nodes treat the new OP_SUCCESSx opcodes as unconditional success, therefore they do not enforce signatures within that opcode path. Upgraded nodes enforce fully.

Encourage upgrades for validation.

Non-upgraded wallets use legacy.

Revisions

ANYPREVOUT flag reflects commitment to nSequence (optionally conditions/amount).

Applies only to tapscript; addresses opt-in via paths.

SIGHASH_ANYPREVOUT commits to scriptPubKey/tapscript; SIGHASH_ANYPREVOUTANYSCRIPT does not.

SIGHASH_ANYPREVOUT commits to amount; SIGHASH_ANYPREVOUTANYSCRIPT does not.

OP_CODESEPARATOR affects signatures.

Tondi adaptations: Non-Taproot deferred, DAG RBS added.

Test Vectors

Positive (6 groups): ALL/NONE/SINGLE ร— (APO / APO|ANYSCRIPT), with/without annex, CODESEPARATOR.

Negative (4): Invalid flags (0x81), SINGLE out-of-bounds, illegal lengths/prefixes, failed Schnorr.

[Detailed vectors TBD in impl; pseudocode for failures in ref impl.]

Reference Implementation

Pseudocode for validation failures:

// Validate hash_type for TSP-0007
fn validate_hash_type(hash_type: u8) -> Result<(), TxScriptError> {
    const VALID_TYPES: [u8; 9] = [0x01, 0x02, 0x03, 0x41, 0x42, 0x43, 0xC1, 0xC2, 0xC3];
    
    if !VALID_TYPES.contains(&hash_type) {
        return Err(TxScriptError::InvalidSignature);
    }
    
    // Check for invalid legacy ANYONECANPAY combinations
    if hash_type & 0x40 != 0 && hash_type & 0x80 != 0 {
        // APO_BIT set but ANYSCRIPT_BIT also set - check if legacy ANYONECANPAY
        let base = hash_type & 0x03;
        if base == 0x01 || base == 0x02 || base == 0x03 {
            // This would be 0x81, 0x82, 0x83 - invalid for TSP-0007
            return Err(TxScriptError::InvalidSignature);
        }
    }
    
    Ok(())
}

// Validate signature length
fn validate_signature_length(signature: &[u8]) -> Result<(), TxScriptError> {
    if signature.len() != 64 && signature.len() != 65 {
        return Err(TxScriptError::InvalidSignature);
    }
    Ok(())
}

// Validate TSP-0007 public key format
fn validate_tsp0007_pubkey(pubkey: &[u8]) -> Result<(), TxScriptError> {
    if pubkey.len() == 1 && pubkey[0] == 0x01 {
        // Reference to Taproot internal key - not supported for TSP-0007
        return Err(TxScriptError::InvalidPublicKey);
    }
    
    if pubkey.len() != 33 || pubkey[0] != 0x01 {
        return Err(TxScriptError::InvalidPublicKey);
    }
    
    // Validate the 32-byte Schnorr public key
    let pubkey_bytes = &pubkey[1..];
    secp256k1::XOnlyPublicKey::from_slice(pubkey_bytes)
        .map_err(|_| TxScriptError::InvalidPublicKey)?;
    
    Ok(())
}

// Check SINGLE bounds
fn validate_single_bounds(input_index: usize, output_count: usize) -> Result<(), TxScriptError> {
    if input_index >= output_count {
        return Err(TxScriptError::InvalidSignature);
    }
    Ok(())
}

// Main validation function
fn validate_tsp0007_signature(
    signature: &[u8],
    pubkey: &[u8],
    tx: &Transaction,
    input_index: usize,
) -> Result<bool, TxScriptError> {
    // 1. Validate signature length
    validate_signature_length(signature)?;
    
    // 2. Extract and validate hash_type
    let hash_type = if signature.len() == 65 {
        signature[64]
    } else {
        0x01 // Default SIGHASH_ALL
    };
    validate_hash_type(hash_type)?;
    
    // 3. Validate public key format
    validate_tsp0007_pubkey(pubkey)?;
    
    // 4. Check SINGLE bounds if applicable
    let base = hash_type & 0x03;
    if base == 0x03 { // SINGLE
        validate_single_bounds(input_index, tx.outputs.len())?;
    }
    
    // 5. Perform Schnorr signature verification
    let sig_hash = calc_tsp0007_signature_hash(tx, input_index, hash_type, None, None, 0);
    let message = secp256k1::Message::from_digest(sig_hash.to_byte_array());
    let sig_bytes = &signature[..64];
    let signature_obj = secp256k1::schnorr::Signature::from_slice(sig_bytes)
        .map_err(|_| TxScriptError::InvalidSignature)?;
    let pubkey_obj = secp256k1::XOnlyPublicKey::from_slice(&pubkey[1..])
        .map_err(|_| TxScriptError::InvalidPublicKey)?;
    
    Ok(pubkey_obj.verify(&message, &signature_obj).is_ok())
}

BTC Community Historical Concerns and Resolutions

  • Replay/Misuse: Via valid flag set, mandatory bindings (non-ANYSCRIPT) + whitelists/wallet restrictions, confined to channels.
  • Upgrade Risk: Tapscript-only + OP_SUCCESS tightening โ†’ soft fork; phased activation/observation.
  • Necessity: Tondi prioritizes high-frequency payments; APO essential, not optional, with DAG+RBS enabling Eltoo UX/throughput.

Non-Normative Appendix: ZK/Bridge Extensions

[Deferred for future TSP.]

Implementation Analysis and Recommendations

Current Tondi Client Implementation Status

Based on analysis of the Tondi client codebase, the following components are already implemented:

Existing Infrastructure:

  • Complete Taproot support with TapSighashType enum
  • Standard SIGHASH types: SIG_HASH_ALL, SIG_HASH_NONE, SIG_HASH_SINGLE, SIG_HASH_ANY_ONE_CAN_PAY
  • secp256k1-Schnorr signature algorithm integration
  • Script execution engine with opcode framework
  • Signature hash calculation infrastructure (SigHashReusedValues)
  • Transaction validation pipeline

Missing Components for TSP-0007:

  • ANYPREVOUT signature mechanism
  • TSP-0007 public key type support
  • New opcodes: OP_CHECKSIG_APO, OP_CHECKSIGVERIFY_APO, OP_CHECKSIGADD_APO
  • Modified signature message construction for ANYPREVOUT
  • Domain separation with "TSP-0007/APSighash" tag

Code Updates Required

1. Extend SigHashType Support

File: consensus/core/src/hashing/sighash_type.rs

// Add new constants for TSP-0007
pub const SIG_HASH_ANYPREVOUT_BIT: SigHashType = SigHashType(0b01000000);
pub const SIG_HASH_ANYSCRIPT_BIT: SigHashType = SigHashType(0b10000000);

// Update allowed values
const ALLOWED_SIG_HASH_TYPES_VALUES: [u8; 15] = [
    // Standard types
    SIG_HASH_ALL.0,
    SIG_HASH_NONE.0,
    SIG_HASH_SINGLE.0,
    SIG_HASH_ALL.0 | SIG_HASH_ANY_ONE_CAN_PAY.0,
    SIG_HASH_NONE.0 | SIG_HASH_ANY_ONE_CAN_PAY.0,
    SIG_HASH_SINGLE.0 | SIG_HASH_ANY_ONE_CAN_PAY.0,
    // TSP-0007 ANYPREVOUT types
    SIG_HASH_ALL.0 | SIG_HASH_ANYPREVOUT_BIT.0,
    SIG_HASH_NONE.0 | SIG_HASH_ANYPREVOUT_BIT.0,
    SIG_HASH_SINGLE.0 | SIG_HASH_ANYPREVOUT_BIT.0,
    SIG_HASH_ALL.0 | SIG_HASH_ANYPREVOUT_BIT.0 | SIG_HASH_ANYSCRIPT_BIT.0,
    SIG_HASH_NONE.0 | SIG_HASH_ANYPREVOUT_BIT.0 | SIG_HASH_ANYSCRIPT_BIT.0,
    SIG_HASH_SINGLE.0 | SIG_HASH_ANYPREVOUT_BIT.0 | SIG_HASH_ANYSCRIPT_BIT.0,
];

impl SigHashType {
    pub fn is_sighash_anyprevout(self) -> bool {
        self.0 & SIG_HASH_ANYPREVOUT_BIT.0 == SIG_HASH_ANYPREVOUT_BIT.0
    }
    
    pub fn is_sighash_anyscript(self) -> bool {
        self.0 & SIG_HASH_ANYSCRIPT_BIT.0 == SIG_HASH_ANYSCRIPT_BIT.0
    }
    
    pub fn is_tsp0007_compatible(self) -> bool {
        ALLOWED_SIG_HASH_TYPES_VALUES.contains(&self.0)
    }
}

2. Add TSP-0007 Public Key Support

File: crypto/txscript/src/lib.rs

#[derive(Clone, Hash, PartialEq, Eq)]
enum PublicKey {
    Schnorr(secp256k1::XOnlyPublicKey),
    Ecdsa(secp256k1::PublicKey),
    TSP0007(secp256k1::XOnlyPublicKey), // New variant
}

impl PublicKey {
    pub fn is_tsp0007(&self) -> bool {
        matches!(self, PublicKey::TSP0007(_))
    }
    
    pub fn from_tsp0007_bytes(bytes: &[u8]) -> Result<Self, TxScriptError> {
        if bytes.len() == 1 && bytes[0] == 0x01 {
            // Reference to Taproot internal key - not supported for TSP-0007
            return Err(TxScriptError::InvalidPublicKey);
        }
        
        if bytes.len() == 33 && bytes[0] == 0x01 {
            let mut pubkey_bytes = [0u8; 32];
            pubkey_bytes.copy_from_slice(&bytes[1..]);
            let pubkey = secp256k1::XOnlyPublicKey::from_slice(&pubkey_bytes)
                .map_err(|_| TxScriptError::InvalidPublicKey)?;
            Ok(PublicKey::TSP0007(pubkey))
        } else {
            Err(TxScriptError::InvalidPublicKey)
        }
    }
}

3. Implement ANYPREVOUT Signature Hash Calculation

File: consensus/core/src/hashing/sighash.rs

impl SigHashReusedValues for SigHashReusedValuesUnsync {
    // Add new method for TSP-0007 signature hash
    fn calc_tsp0007_signature_hash(
        &self,
        tx: &Transaction,
        input_index: usize,
        hash_type: SigHashType,
        annex: Option<&[u8]>,
        leaf_hash: Option<&[u8; 32]>,
        codesep_pos: u32,
    ) -> Hash {
        let mut msg = Vec::new();
        
        // 1. Append hash_type
        msg.push(hash_type.to_u8());
        
        // 2. Append transaction version
        msg.extend_from_slice(&tx.version.to_le_bytes());
        
        // 3. Append lock time
        msg.extend_from_slice(&tx.lock_time.to_le_bytes());
        
        // 4. Append outputs hash if needed
        let base = hash_type.to_u8() & 0x03;
        if base != 0x02 && base != 0x03 { // Not NONE or SINGLE
            let outputs_hash = self.outputs_hash(|| hash_outputs(&tx.outputs));
            msg.extend_from_slice(outputs_hash.as_bytes());
        }
        
        // 5. Append spend type
        let spend_type = if annex.is_some() { 3 } else { 2 };
        msg.push(spend_type);
        
        // 6. Append amount and script binding if not ANYSCRIPT
        if !hash_type.is_sighash_anyscript() {
            let input = &tx.inputs[input_index];
            msg.extend_from_slice(&input.previous_output.value.to_le_bytes());
            msg.extend_from_slice(&compact_size_len(input.previous_output.script_pubkey.len()));
            msg.extend_from_slice(&input.previous_output.script_pubkey);
        }
        
        // 7. Append sequence
        msg.extend_from_slice(&tx.inputs[input_index].sequence.to_le_bytes());
        
        // 8. Append annex hash if present
        if let Some(annex_data) = annex {
            msg.extend_from_slice(&compact_size_len(annex_data.len()));
            msg.extend_from_slice(annex_data);
        }
        
        // 9. Append single output hash if SINGLE
        if base == 0x03 { // SINGLE
            if input_index < tx.outputs.len() {
                let output_hash = hash_single_output(&tx.outputs[input_index]);
                msg.extend_from_slice(output_hash.as_bytes());
            } else {
                return ZERO_HASH; // Invalid SINGLE
            }
        }
        
        // 10. Append extension data
        let mut ext = Vec::new();
        if !hash_type.is_sighash_anyscript() {
            if let Some(leaf) = leaf_hash {
                ext.extend_from_slice(leaf);
            }
        }
        ext.push(0x01); // key_version
        ext.extend_from_slice(&codesep_pos.to_le_bytes());
        
        // 11. Create tagged hash with TSP-0007 domain separation
        tagged_hash("TSP-0007/APSighash", &[&msg, &ext])
    }
}

4. Add New Opcodes

File: crypto/txscript/src/opcodes/mod.rs

// Add new opcode definitions
opcode_list! {
    opcode OP_CHECKSIG_APO<0xB2, 0>($self, $vm) {
        $vm.check_signature_apo()
    }
    
    opcode OP_CHECKSIGVERIFY_APO<0xB3, 0>($self, $vm) {
        $vm.check_signature_verify_apo()
    }
    
    opcode OP_CHECKSIGADD_APO<0xB4, 0>($self, $vm) {
        $vm.check_signature_add_apo()
    }
}

5. Implement TSP-0007 Signature Verification

File: crypto/txscript/src/lib.rs

impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<'a, T, Reused> {
    fn check_signature_tsp(&mut self) -> OpCodeResult {
        if self.dstack.len() < 2 {
            return Err(TxScriptError::InvalidStackOperation);
        }
        
        let signature_bytes = self.dstack.pop().unwrap();
        let pubkey_bytes = self.dstack.pop().unwrap();
        
        // Validate signature length
        if signature_bytes.len() != 64 && signature_bytes.len() != 65 {
            return Err(TxScriptError::InvalidSignature);
        }
        
        // Extract hash_type
        let hash_type = if signature_bytes.len() == 65 {
            SigHashType::from_u8(signature_bytes[64])
                .map_err(|_| TxScriptError::InvalidSignature)?
        } else {
            SigHashType::from_u8(0x01).unwrap() // Default SIGHASH_ALL
        };
        
        // Validate hash_type for TSP-0007
        if !hash_type.is_tsp0007_compatible() {
            return Err(TxScriptError::InvalidSignature);
        }
        
        // Parse TSP-0007 public key
        let pubkey = PublicKey::from_tsp0007_bytes(&pubkey_bytes)?;
        
        // Check SINGLE bounds
        let base = hash_type.to_u8() & 0x03;
        if base == 0x03 && self.get_input_index() >= self.get_output_count() {
            return Err(TxScriptError::InvalidSignature);
        }
        
        // Calculate TSP-0007 signature hash
        let sig_hash = self.reused_values.calc_tsp0007_signature_hash(
            self.get_transaction(),
            self.get_input_index(),
            hash_type,
            self.get_annex(),
            self.get_leaf_hash(),
            self.get_codesep_pos(),
        );
        
        // Perform Schnorr validation
        let sig_bytes = &signature_bytes[..64];
        let message = secp256k1::Message::from_digest(sig_hash.to_byte_array());
        let signature = secp256k1::schnorr::Signature::from_slice(sig_bytes)
            .map_err(|_| TxScriptError::InvalidSignature)?;
        
        let is_valid = match pubkey {
            PublicKey::TSP0007(pubkey) => {
                pubkey.verify(&message, &signature).is_ok()
            }
            _ => false,
        };
        
        self.dstack.push(if is_valid { vec![1] } else { vec![0] });
        Ok(())
    }
    
    fn check_signature_verify_tsp(&mut self) -> OpCodeResult {
        self.check_signature_tsp()?;
        let result = self.dstack.pop().unwrap();
        if result.is_empty() || result[0] == 0 {
            Err(TxScriptError::SignatureVerificationFailed)
        } else {
            Ok(())
        }
    }
    
    fn check_signature_add_tsp(&mut self) -> OpCodeResult {
        if self.dstack.len() < 3 {
            return Err(TxScriptError::InvalidStackOperation);
        }
        
        let signature_bytes = self.dstack.pop().unwrap();
        let pubkey_bytes = self.dstack.pop().unwrap();
        let count_bytes = self.dstack.pop().unwrap();
        
        let count = to_small_int(&count_bytes)?;
        
        // Perform signature check
        self.dstack.push(pubkey_bytes);
        self.dstack.push(signature_bytes);
        self.check_signature_tsp()?;
        
        let result = self.dstack.pop().unwrap();
        let sig_result = if result.is_empty() || result[0] == 0 { 0 } else { 1 };
        let new_count = count + sig_result;
        
        self.dstack.push(to_small_int_bytes(new_count));
        Ok(())
    }
}

Implementation Roadmap

Phase 1: Core Infrastructure (2-3 weeks)

  1. Extend SigHashType support - Add ANYPREVOUT and ANYSCRIPT bits
  2. Add TSP-0007 public key parsing - Support 0x01 prefix format
  3. Implement domain separation - Add "TSP-0007/APSighash" tagged hash

Phase 2: Signature Hash Calculation (2-3 weeks)

  1. Implement MsgTSP construction - Modified signature message format
  2. Add binding mechanisms - Amount, script, sequence, tapleaf binding
  3. Integrate with existing sighash infrastructure - Extend SigHashReusedValues

Phase 3: Opcode Implementation (2-3 weeks)

  1. Add new opcodes - OP_CHECKSIG_APO, OP_CHECKSIGVERIFY_APO, OP_CHECKSIGADD_APO
  2. Implement signature verification - Schnorr validation with TSP-0007 logic
  3. Add error handling - Proper validation and error reporting

Phase 4: Testing and Integration (2-3 weeks)

  1. Unit tests - Test all new functionality
  2. Integration tests - Test with existing Taproot infrastructure
  3. Performance testing - Ensure no regression in existing functionality
  4. Documentation - Update API documentation

Phase 5: Tondi Flash Protocol (4-6 weeks)

  1. Channel establishment - Implement Eltoo channel setup
  2. State updates - Implement update transaction mechanism
  3. Settlement - Implement settlement and dispute resolution
  4. Routing and bridging - Add multi-path and cross-chain support

Testing Strategy

Unit Tests

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_tsp0007_public_key_parsing() {
        // Test valid TSP-0007 public key parsing
        let pubkey_bytes = [0x01, 0x02, 0x03, /* ... 32 bytes ... */];
        let pubkey = PublicKey::from_tsp0007_bytes(&pubkey_bytes);
        assert!(pubkey.is_ok());
        assert!(pubkey.unwrap().is_tsp0007());
    }
    
    #[test]
    fn test_anyprevout_signature_hash() {
        // Test ANYPREVOUT signature hash calculation
        let tx = create_test_transaction();
        let hash_type = SigHashType::from_u8(0x41).unwrap(); // SIGHASH_ALL | APO_BIT
        let sig_hash = calc_tsp0007_signature_hash(&tx, 0, hash_type, None, None, 0);
        assert_ne!(sig_hash, ZERO_HASH);
    }
    
    #[test]
    fn test_hash_type_validation() {
        // Test valid hash types
        let valid_types = [0x01, 0x02, 0x03, 0x41, 0x42, 0x43, 0xC1, 0xC2, 0xC3];
        for hash_type in valid_types {
            let sig_hash_type = SigHashType::from_u8(hash_type);
            assert!(sig_hash_type.is_ok());
            assert!(sig_hash_type.unwrap().is_tsp0007_compatible());
        }
        
        // Test invalid hash types
        let invalid_types = [0x81, 0x82, 0x83]; // Legacy ANYONECANPAY with APO
        for hash_type in invalid_types {
            let sig_hash_type = SigHashType::from_u8(hash_type);
            assert!(sig_hash_type.is_ok());
            assert!(!sig_hash_type.unwrap().is_tsp0007_compatible());
        }
    }
}

Integration Tests

#[cfg(test)]
mod integration_tests {
    use super::*;
    
    #[test]
    fn test_eltoo_channel_lifecycle() {
        // Test complete Eltoo channel lifecycle
        let (alice_key, bob_key) = generate_test_keys();
        let channel_amount = 1000000; // 1 TONDI
        
        // 1. Channel establishment
        let funding_tx = create_funding_transaction(channel_amount);
        let trigger_tx = create_trigger_transaction(&funding_tx, &alice_key, &bob_key);
        
        // 2. State updates
        let mut update_tx = trigger_tx.clone();
        for i in 1..5 {
            update_tx = create_update_transaction(&update_tx, i, &alice_key, &bob_key);
            assert!(validate_transaction(&update_tx));
        }
        
        // 3. Settlement
        let settlement_tx = create_settlement_transaction(&update_tx, &alice_key, &bob_key);
        assert!(validate_transaction(&settlement_tx));
    }
}

Migration Strategy

Soft Fork Activation

  1. Version bit signaling - Use version bit 3 for activation
  2. Threshold requirement - 80% threshold over 2016 blocks
  3. Testnet deployment - 3-month testnet validation period
  4. Mainnet activation - Gradual rollout with observation period

Backward Compatibility

  • Unupgraded nodes treat new opcodes as unconditional success
  • Existing Taproot functionality remains unchanged
  • Legacy scripts continue to work without modification
  • Gradual migration path for wallet software

References

  • Eltoo: A Simple Layer2 Protocol for Bitcoin (Christian Decker et al.)
  • Kaspa Documentation: GHOSTDAG/PHANTOM Protocols
  • MuSig2: Simple Two-Round Schnorr Multi-Signatures
  • Tondi Whitepaper (internal)
  • RFC 2119: Key words for use in RFCs to Indicate Requirement Levels