Proposal: Transaction Chaining in Neptune Cash

Proposal: Transaction Chaining in Neptune Cash

This post outlines a proposal for introducing transaction chaining into Neptune Cash. The idea is to enable one transaction to immediately spend the outputs of another unconfirmed transaction, eliminating the need to wait for intermediate confirmations. While the concept may seem straightforward, its implications for scalability, privacy, and protocol design are profound.

The problem today

Right now, if a transaction in the mempool creates a new UTXO, that UTXO cannot be spent until the transaction is confirmed in a block. This delay cascades:

  • If you spend a UTXO, you must wait for the transaction to confirm before you can use its change output.
  • Even when transactions are confirmed quickly, you still must wait for a new block each time you want to spend a just-created output.
  • For smart contracts, the bottleneck is worse. Each interaction is locked behind a confirmed block, and multiple transactions targeting the same contract compete for inclusion. The losers must reissue their transactions in later blocks.

This sequential dependency dramatically limits throughput and responsiveness. A mechanism for safe transaction chaining could allow dependent transactions to propagate and verify within the mempool, rather than block by block, yielding substantial scalability benefits.

What is transaction chaining?

A transaction chain consists of two or more transactions where one spends the outputs of another that has not yet confirmed. During chaining, each UTXO that is simultaneously spent and created can effectively disappear through cut-through: a process that removes the redundant intermediate UTXO while preserving the cryptographic integrity of the chain.

The subtlety of privacy-preserving cut-through

In Neptune Cash, the transaction kernel defines:

inputs: Vec<RemovalRecord>
outputs: Vec<AdditionRecord>

Both the addition record and the removal record are commitments to the same underlying UTXO, but they are designed to look entirely different. This separation ensures privacy — inputs and outputs cannot easily be linked. However, it also raises a challenge: how can we certify that a cut-through between two transactions is legal if the matching records are intentionally unlinkable?

Proposed solution: a new Chain pathway

The proposal introduces a new branch into the SingleProof program, called a Chain pathway. (The other branches are Raise, Update, and Merge.) Each branch defines a control flow path that allows the program to halt gracefully — enabling Triton VM to produce a zk-STARK proof of that fact.

A Chain operation would take:

  1. A standard transaction backed by a SingleProof.
  2. A secondary transaction of a new type, ChainTransaction, which links to the prior transaction through one or more consumed outputs.

The proposal also adds a fourth proof type, called a ChainProof. Transactions in Neptune Cash are supported by one of three proof types today — SingleProof, ProofCollection, and PrimitiveWitness. The ChainProof would join this family, with the same overarching responsibility: to certify that a transaction is valid. What distinguishes this mechanism is that the legality of the cut-through itself is certified by the new Chain branch within SingleProof.

On hard fork implications

Extending SingleProof with a new branch constitutes a hard fork, with several cascading technical implications. The hash of the SingleProof program changes, which means that every dependent program — such as BlockProgram — changes as well. Consequently, nodes must verify incoming block proofs against a different program after the fork’s activation height. The ConsensusRuleSet already supports version-dependent verification logic, but it will need to properly handle this change and ensure the correct program hashes are used at and after the fork point.

While some consensus programs change under this proposal, it is worth noting that no existing transaction formats are affected.

Technical next steps

  1. Define the ChainTransaction type.
    A ChainTransaction mirrors Transaction in shape but introduces explicit support for chaining:
struct Transaction {
    kernel: TransactionKernel,
    proof: TransactionProof,
}

struct ChainTransaction {
    kernel: ChainKernel,
    proof: ChainProof,
}

The distinction lies in the kernel. A TransactionKernel lists independent inputs: Vec<RemovalRecord> and outputs: Vec<AdditionRecord>. A ChainKernel adds a new field:

cut_throughs: Vec<AdditionRecord>

The cut_throughs field enumerates the outputs from the immediately preceding transaction that are spent by the current transaction but never appear as on-chain UTXOs. These addition records are the intermediates that will be cut through when the two transactions are chained. Their inclusion makes the relationship between chained transactions explicit.

  1. Specify the ChainProof type and its variants.
    A ChainProof is structurally similar to the TransactionProof enum, but with a more focused purpose. While a TransactionProof can be one of three kinds — PrimitiveWitness, ProofCollection, or SingleProof — a ChainProof has exactly two:
enum ChainProof {
    ChainPrimitiveWitness,
    ChainProofCollection,
}
  • ChainPrimitiveWitness extends PrimitiveWitness by adding witnesses for each intermediate UTXO referenced in the cut_throughs field. For every such UTXO, it contains:(utxo, sender_randomness, receiver_preimage, lock_script, lock_script_hash, lock_script_witness, type_script_witnesses...). These additional witnesses prove that the cut-through UTXOs were validly created in the preceding transaction and validly spent in the current one.
  • ChainProofCollection parallels ProofCollection but includes three key differences:
    • It introduces a new proof of a program (tentatively named KernelToCutThroughs) that outputs a salted_cut_throughs_hash — a salted hash of all intermediate UTXOs.
    • The CollectLockScripts and CollectTypeScripts programs are replaced by new variants that also process intermediate UTXOs. Their inputs now include the salted_cut_throughs_hash.
    • As part of verification, the lock scripts and type scripts for all intermediate UTXOs must halt gracefully. This requirement certifies that the transaction initiator is allowed to spend the UTXOs.
  1. Add the Chain branch in SingleProof.
    The Chain branch performs a task that neither Merge nor Raise can handle.
    • The Merge branch recursively verifies two SingleProofs corresponding to independent transactions with disjoint inputs and outputs.
    • The Raise branch verifies all proofs contained within a ProofCollection.
    • The Chain branch instead recursively verifies one SingleProof (the transaction on the left) and one ChainProofCollection (the transaction on the right) and then performs cut-through on the right-hand side’s declared cut_throughs.

During execution:

  1. The branch verifies the SingleProof of the left-hand transaction.
  2. It verifies all proofs in the right-hand ChainProofCollection.
  3. It compares the right-hand side’s cut_throughs (of type Vec<AdditionRecord>) against the list of outputs from the left-hand side. Each cut-through on the right must correspond exactly to a subset of those outputs on the left.

When these actions complete successfully, the two transactions can be replaced by a single transaction identical to having the intermediates already cut through — that is, with the intermediate UTXOs removed from both sides. This outcome preserves consistency while enabling chaining without waiting for block confirmation.

Conclusion

The proposed mechanism adds a new Chain branch in SingleProof and supporting types (ChainTransaction, ChainKernel, ChainProof). Together they let one transaction spend outputs from a preceding unconfirmed transaction, cutting through the intermediate UTXOs while keeping all proofs sound and private.

This turns the mempool into a locally composable execution context: a space where dependent transactions can execute against one another before confirmation. By allowing these short chains to verify end‑to‑end, Neptune Cash could process dependent transactions faster. The intermediate states never reach the blockchain, yet they remain provably valid — improving transaction throughput without compromising privacy or consensus rules.

Design questions for discussion

  1. Chaining order.
    In a chain A → B → C → D, where A is a standard Transaction and the rest are ChainTransactions, should the system permit chaining away links in any order, or only starting from the left? Allowing arbitrary orders would require introducing a Chain counterpart of SingleProof, ultimately increasing the engineering workload.

  2. Direct proof path.
    At present, a ChainTransaction proceeds from ChainPrimitiveWitness through ChainProofCollection into SingleProof. Should we allow a direct path from ChainPrimitiveWitness into SingleProof for speed, at the expense of some modular structure?

Discussion, critique, and refinement are encouraged before any implementation work begins. The goal is to ensure the mechanism is theoretically sound, cryptographically secure, and worth the added complexity to Neptune Cash’s proof system.

Perhaps all transactions should be ChainTransactions? A “regular” tx could just be a ChainTransaction of length 1.

Pros:

  • A single codepath for all transactions. Instead of a “Regular” tx codepath and a “Chained” tx codepath.

Cons:

  • ???