BSV.Tokens.Script.Stas3Pieces (bsv_sdk v2.0.1)

Copy Markdown View Source

STAS 3.0 v0.2.3 §8 / §9.5 atomic-swap and merge "piece array" trailing parameters for STAS 3.0 unlocking scripts.

Background

For atomic-swap (txType = 1) and merge transactions (txType = 2..7), the STAS unlocking script appends a trailing block whose layout depends on txType. Per spec v0.2.3, each piece is its own OP_PUSHDATA operation in the unlocking script, and piece_count is a minimal Bitcoin numeric push (DXS convention from dxs-bsv-token-sdk's ScriptBuilder.addNumber: for n ∈ 1..=16 emit OP_<n> = single byte 0x50 + n; for n ∈ 17..=127 emit 0x01 <n>; for larger emit script-num-encoded with sign-bit sentinel):

# txType = 1 (atomic swap)
pushdata(counterparty_locking_script)
minimal_numeric_push(piece_count)
pushdata(piece_1) ... pushdata(piece_N)

# txType = 2..7 (merge)
minimal_numeric_push(piece_count)         # must equal txType
pushdata(piece_1) ... pushdata(piece_N)

What "pieces" are (spec §9.5)

The canonical engine (github.com/stassso/STAS-3-script-templates) consumes them via an unrolled, counter-driven block:

OP_OVER OP_IF OP_SWAP OP_1SUB OP_SWAP OP_3 OP_PICK OP_CAT OP_10 OP_ROLL OP_CAT OP_ENDIF

repeated 5×, driven by a decrementing piece_count counter. There is no concatenated length-prefixed blob and no per-piece size limit — the earlier 127-byte limit was a phantom of the obsolete encoding.

Given the preceding transaction (the tx that produced the input UTXO):

  1. For each named asset output (asset_output_indices), locate the locking script. Within that script, identify the "asset script" — the bytes from the engine prefix (0x6D 0x82 0x73 0x63) to the end of the script. Everything BEFORE that prefix belongs to the two var fields (owner push + var2 push) and is NOT excised.
  2. The remaining tx bytes (parts that AREN'T excised) are the pieces: one before the first excised region, one between each pair, and one after the last.
  3. Reverse the piece order (head on top of stack).
  4. Concatenate independent pushdata pushes — no length prefix inside.

This module exposes:

Strict boundaries: at least one asset output must be named, the merge piece count must be in 2..7, and parsed piece_count must match the number of pieces read back.

Summary

Types

Result of parse/2 for a merge trailing block.

Result of parse/2 for a swap trailing block.

Functions

Build the trailing-parameters block for a txType = 1 atomic-swap unlocking script.

Build the trailing-parameters block for a merge unlocking script (txType = 2..7).

Parse a previously-encoded trailing parameter block.

Types

parsed_merge()

@type parsed_merge() :: %{piece_count: 2..7, pieces: [binary()]}

Result of parse/2 for a merge trailing block.

parsed_swap()

@type parsed_swap() :: %{
  counterparty_script: binary(),
  piece_count: non_neg_integer(),
  pieces: [binary()]
}

Result of parse/2 for a swap trailing block.

Functions

encode_atomic_swap_pieces(counterparty_locking_script, preceding_tx, asset_output_indices)

@spec encode_atomic_swap_pieces(binary(), binary(), [non_neg_integer()]) ::
  {:ok, binary()} | {:error, term()}

Build the trailing-parameters block for a txType = 1 atomic-swap unlocking script.

Returns the byte sequence formed by appending independent Bitcoin pushdata operations:

pushdata(counterparty_script)  minimal_numeric_push(piece_count) 
pushdata(piece_1)    pushdata(piece_N)

ready to splice verbatim into an unlocking script.

asset_output_indices MUST list at least one valid output index in preceding_tx.

encode_merge_pieces(piece_count, preceding_tx, asset_output_indices)

@spec encode_merge_pieces(2..7, binary(), [non_neg_integer()]) ::
  {:ok, binary()} | {:error, term()}

Build the trailing-parameters block for a merge unlocking script (txType = 2..7).

Returns the byte sequence:

minimal_numeric_push(piece_count) 
pushdata(piece_1)    pushdata(piece_N)

Per spec §8, piece_count MUST equal the merge txType (2..7) and MUST equal the resulting number of pieces. With K excised asset-script regions in preceding_tx, the resulting array has K + 1 pieces, so length(asset_output_indices) MUST equal piece_count - 1.

parse(bin, tx_type)

@spec parse(binary(), 1..7) ::
  {:ok, parsed_swap() | parsed_merge()} | {:error, term()}

Parse a previously-encoded trailing parameter block.

tx_type selects the layout:

  • 1 — atomic swap: leading pushdata-framed counterparty script,

          then a minimal-numeric-push piece_count, then `piece_count`
          independent pushdata piece operations. Returns
          `{:ok, %{counterparty_script: _, piece_count: _, pieces: _}}`.
  • 2..7 — merge: leading minimal-numeric-push piece_count (must

          equal `tx_type`), then `piece_count` independent pushdata
          piece operations. Returns
          `{:ok, %{piece_count: _, pieces: _}}`.

On malformed input — bad framing, count mismatch with array length, unsupported tx_type — returns {:error, reason}.