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 & 0x030x01= SIGHASH_ALL: Commits to all inputs and outputs0x02= SIGHASH_NONE: Commits to all inputs, no outputs0x03= 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):
- u8: hash_type
- le32: nVersion
- le32: nLockTime
- If BASE != NONE && BASE != SINGLE: H(Outputs), where Outputs = concat(CompactSizeLen(CTxOut_i) || CTxOut_i_serialized) consensus serialization concatenated sequentially.
- u8: spend_type (2=no annex, 3=has annex starting with 0x50)
- 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
- le32: nSequence
- If annex: H(CompactSizeLen(annex) || annex)
- 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):
- Higher nSequence (latest state priority);
- Higher feerate;
- 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
TapSighashTypeenum - 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)
- Extend SigHashType support - Add ANYPREVOUT and ANYSCRIPT bits
- Add TSP-0007 public key parsing - Support 0x01 prefix format
- Implement domain separation - Add "TSP-0007/APSighash" tagged hash
Phase 2: Signature Hash Calculation (2-3 weeks)
- Implement MsgTSP construction - Modified signature message format
- Add binding mechanisms - Amount, script, sequence, tapleaf binding
- Integrate with existing sighash infrastructure - Extend SigHashReusedValues
Phase 3: Opcode Implementation (2-3 weeks)
- Add new opcodes - OP_CHECKSIG_APO, OP_CHECKSIGVERIFY_APO, OP_CHECKSIGADD_APO
- Implement signature verification - Schnorr validation with TSP-0007 logic
- Add error handling - Proper validation and error reporting
Phase 4: Testing and Integration (2-3 weeks)
- Unit tests - Test all new functionality
- Integration tests - Test with existing Taproot infrastructure
- Performance testing - Ensure no regression in existing functionality
- Documentation - Update API documentation
Phase 5: Tondi Flash Protocol (4-6 weeks)
- Channel establishment - Implement Eltoo channel setup
- State updates - Implement update transaction mechanism
- Settlement - Implement settlement and dispute resolution
- 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
- Version bit signaling - Use version bit 3 for activation
- Threshold requirement - 80% threshold over 2016 blocks
- Testnet deployment - 3-month testnet validation period
- 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