Skip to content

Reuse input templates for output-state validation#144

Open
michaelsutton wants to merge 6 commits into
kaspanet:masterfrom
michaelsutton:reuse-input-template-builtin
Open

Reuse input templates for output-state validation#144
michaelsutton wants to merge 6 commits into
kaspanet:masterfrom
michaelsutton:reuse-input-template-builtin

Conversation

@michaelsutton

@michaelsutton michaelsutton commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Motivation

validateOutputStateWithTemplate requires the caller to provide the target
template prefix and suffix. In many multi-input covenant transactions, those
exact bytes are already present at the end of another input's sigscript as its
P2SH redeem script.

Passing or embedding them again duplicates potentially large contract
templates:

referenced input sigscript
┌──────────────┬─────────────────┬───────────────┬─────────────────┐
│ witness args │ template prefix │ encoded state │ template suffix │
└──────────────┴─────────────────┴───────────────┴─────────────────┘
               └──────────── redeem script at the end ─────────────┘
                                  │
                                  ▼ reuse
               P2SH(prefix || next state || suffix)

This PR adds:

validateOutputStateWithInputTemplate(
    outputIndex,
    nextState,
    templateInputIndex,
    templatePrefixLen,
    templateSuffixLen,
    expectedTemplateHash
);

The builtin:

  1. Locates the redeem script at the end of the input identified by
    templateInputIndex.
  2. Extracts only the required prefix and suffix using
    OpTxInputScriptSigSubstr, the provided lengths, and the encoded state size
    derived by the compiler from nextState's layout.
  3. Feeds those bytes into the existing validateOutputStateWithTemplate path,
    which authenticates their length-delimited template hash against
    expectedTemplateHash and validates the output state.

The referenced input supplies the template bytes, but it is not the trust
anchor. Its existing UTXO commits to the full redeem script, while
expectedTemplateHash remains the protocol commitment that authenticates the
extracted template.

Chess settlement

Chess settlement spends one settlement input and two Player inputs, then
recreates both Player outputs. Previously the settlement sigscript carried a
full Player template again:

entrypoint function settle(
    byte[] playerPrefix,
    byte[] playerSuffix
)

It now carries only the lengths and reuses the Player template from the first
Player input:

entrypoint function settle(
    int playerPrefixLen,
    int playerSuffixLen
) {
    int playerInputIdx = OpCovInputIdx(covId, 1);

    validateOutputStateWithInputTemplate(
        whiteOutputIdx,
        nextWhite,
        playerInputIdx,
        playerPrefixLen,
        playerSuffixLen,
        playerTemplate
    );

    validateOutputStateWithInputTemplate(
        blackOutputIdx,
        nextBlack,
        playerInputIdx,
        playerPrefixLen,
        playerSuffixLen,
        playerTemplate
    );
}

The chess tests now track complete P2SH sigscript bytes across the actual
transactions they execute.

Settlement metric Before After Change
Settlement contract 2,656 2,865 +209
Three-input sigscript total 13,407 10,424 -2,983
Net sigscript reduction 22.2%

The additional extraction logic costs 209 bytes in the settlement redeem
script, but removes 3,192 bytes of duplicated template arguments, producing a
net reduction of 2,983 bytes.

KCC20 minter

The same reuse applies to KCC20 minting. The minter validates two KCC20 outputs whose template is already present in the co-spent KCC20 input, so the new builtin avoids carrying the same prefix and suffix again in the minter sigscript.

Applying this also cleaned up an inefficiency in the original KCC20 sketch. Its prefix and suffix were constructor constants, although only expectedTemplateHash needs to be committed. The template bytes themselves may remain untrusted as long as output validation checks them against that hash.

Embedding them as constants was also costly. Each validateOutputStateWithTemplate call uses the prefix and suffix three times: for their encoded lengths, the template-hash preimage, and the expected output script. Since minting validates two outputs, the complete template pair appeared six times in the minter redeem script.

Design Minter redeem script Minter sigscript Total input sigscripts
Template in constructor 10,194 10,376 12,038
Template passed as mint arguments 1,079 2,787 4,449
Template reused from KCC20 input 1,195 1,377 3,039

Moving the template bytes out of the constructor accounts for the larger initial reduction. The new builtin then removes the remaining sigscript copy by extracting those bytes from the KCC20 input. expectedTemplateHash remains committed in the minter contract and authenticates the reused template.

Tests and tooling

  • Tests canonical hash equivalence across templateHash,
    readInputStateWithTemplate, validateOutputStateWithTemplate, and the new
    builtin.
  • Tests invalid template lengths and nested state layouts.
  • Executes the optimized chess settlement transaction end to end.
  • Executes the optimized multi-transaction KCC20 minter flow.
  • Adds the builtin to the language tutorial, builtin reference, and editor
    highlighting files.

Adds validateOutputStateWithInputTemplate for validating an output with a template already present in another input's redeem script.

The new builtin extracts only the required prefix and suffix through sigscript substring introspection, binds each slice once on the stack, and lowers validation through the existing validateOutputStateWithTemplate path.

Adds builtin documentation, editor highlighting, and runtime tests covering canonical template hashes, invalid lengths, nested state layouts, and shared lowering semantics.
Adds expected sigscript totals to the chess size snapshots and checks them against the complete P2SH sigscripts of the transactions exercised by the runtime tests.

This establishes a witness-size baseline. A follow-up will replace eligible validateOutputStateWithTemplate calls with validateOutputStateWithInputTemplate and record the resulting reductions.
Updates ChessSettle to reconstruct the shared Player template from an existing
Player input instead of receiving its full prefix and suffix in the settle
sigscript.

The settlement transaction now supplies only the template lengths. Total
sigscript size falls from 13,407 to 10,424 bytes, a 2,983-byte reduction, while
the settlement contract grows by 209 bytes.
Updates KCC20Minter to validate both asset outputs using the template already
present in the sole KCC20 input.

Removes the full template prefix and suffix from the minter constructor while
retaining their lengths and the committed template hash. The same input index
is reused for prior-state reading and both output validations.

Synchronizes the KCC20 book source, scenario, and template-validation
explanations with the optimized flow.
@michaelsutton

Copy link
Copy Markdown
Contributor Author

Noticed after the fact that this addresses and closes #140

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant