Pay-to-Multiple-Public-Key-Hash (P2MPKH) signing template — STAS 3.0 v0.1 §10.2.
Module purpose
This module implements the wire-format helpers needed to construct, parse, hash, lock, and spend P2MPKH redeem-buffers as specified in the STAS 3.0 protocol specification v0.1 (section 10.2). It is used at issuance and redemption boundaries of a STAS token's lifecycle, and to derive the MPKH (HASH160 of the redeem buffer) used as the owner / arbitrator anchor inside STAS 3 in-life UTXOs.
Wire format (per STAS 3.0 v0.1 §10.2)
The "redeem script" (a.k.a. redeem buffer / multisig script) is exactly:
[m: 1B raw 0x01..0x05]
[0x21][pk1: 33B compressed SEC1]
[0x21][pk2: 33B compressed SEC1]
...
[0x21][pkN: 33B compressed SEC1]
[n: 1B raw 0x01..0x05]Total length = 2 + 34*N bytes. Constraints: 1 <= m <= n <= 5.
Note: m and n are raw threshold bytes (not OP_m / OP_n opcodes),
and there is no trailing OP_CHECKMULTISIG. The template / engine
re-builds the multisig script when verifying signatures.
MPKH
MPKH = HASH160(redeem_buffer). The 20-byte MPKH is what appears in the
fixed 70-byte P2MPKH locking script and inside STAS 3 token UTXOs.
Unlocking stack (P2MPKH spend)
Per spec §10.2 line 414/434:
OP_0 <sig_1> <sig_2> ... <sig_m> <redeem_buffer>The leading OP_0 is the OP_CHECKMULTISIG dummy.
Functions
new_multisig/2— validate and wrap a (threshold, public keys) tuple.to_script_bytes/1— emit the spec wire format described above.from_script_bytes/1— parse the spec wire format. Rejects any other form.mpkh/1— HASH160 of the redeem buffer.lock/1— wrap the redeem buffer as aBSV.Script.t()(used as the "redemption script" data push, NOT a P2MPKH locking script — for the 70-byte fixed locking script seeBSV.Tokens.Script.Templates).unlock/3+sign/3— produce the P2MPKH unlocking scriptOP_0 <sigs...> <redeem_buffer>.estimate_length/3— size estimate of the unlocking script bytes.
Summary
Functions
Estimate the unlocking script length in bytes.
Parse a STAS 3.0 v0.1 §10.2 redeem-buffer back into a multisig script map.
Build a BSV.Script.t() whose only chunk is the redeem-buffer push.
Compute the MPKH — 20-byte HASH160 of the serialized redeem buffer.
Create a new multisig script map.
Sign a transaction input producing a P2MPKH unlocking script.
Serialize a multisig script to the STAS 3.0 v0.1 §10.2 redeem-buffer bytes.
Create a P2MPKH unlocker.
Types
@type multisig_script() :: %{threshold: pos_integer(), public_keys: [binary()]}
@type t() :: %BSV.Transaction.P2MPKH{ multisig: multisig_script(), private_keys: [BSV.PrivateKey.t()], sighash_flag: non_neg_integer() }
Functions
Estimate the unlocking script length in bytes.
Layout: OP_0 (1B) + m * 73B (sig pushes) + push prefix for redeem (2B PUSHDATA1) + redeem buffer (2 + 34*n) = m*73 + 34*n + 5.
@spec from_script_bytes(binary()) :: {:ok, multisig_script()} | {:error, term()}
Parse a STAS 3.0 v0.1 §10.2 redeem-buffer back into a multisig script map.
Input
- Binary produced by
to_script_bytes/1. Length must equal2 + 34*Nfor someNin 1..5, and the leading bytemand trailing bytenmust satisfy1 <= m <= n <= 5.
Output
{:ok, multisig_script()} on success, {:error, reason} on any deviation
from the wire format. The function deliberately rejects any other shape
(no support for the legacy OP_m / OP_CHECKMULTISIG form).
@spec lock(multisig_script()) :: {:ok, BSV.Script.t()}
Build a BSV.Script.t() whose only chunk is the redeem-buffer push.
This is convenient when the SDK needs to embed the redeem buffer as a
data push (e.g. as the last item on the unlocking stack). It is not
the fixed 70-byte P2MPKH locking script — for that, see
BSV.Tokens.Script.Templates.p2mpkh_locking_script/1.
Input
multisig_script()map.
Output
{:ok, %BSV.Script{chunks: [{:data, redeem_buffer}]}}.
@spec mpkh(multisig_script()) :: <<_::160>>
Compute the MPKH — 20-byte HASH160 of the serialized redeem buffer.
Input
multisig_script()map.
Output
20-byte binary (<<_::160>>).
@spec new_multisig(pos_integer(), [binary()]) :: {:ok, multisig_script()} | {:error, term()}
Create a new multisig script map.
Arguments
threshold— minimum signatures required (m), 1..5public_keys— list of 33-byte compressed SEC1 public keys, length 1..5
Returns
{:ok, multisig_script()} or {:error, reason}
Sign a transaction input producing a P2MPKH unlocking script.
Output
{:ok, %BSV.Script{}} whose chunks (in order) are:
OP_0 (as {:data, <<>>}),
<sig_1>, <sig_2>, ..., <sig_m>,
<redeem_buffer>per STAS 3.0 v0.1 §10.2 line 414/434.
@spec to_script_bytes(multisig_script()) :: binary()
Serialize a multisig script to the STAS 3.0 v0.1 §10.2 redeem-buffer bytes.
Input
multisig_script()map with:threshold(1..5) and:public_keys(1..5 compressed 33-byte keys).
Output
Binary of length 2 + 34*N shaped as:
<m::8> (<<0x21, pk_i::binary-size(33)>> for each pk_i) <n::8>where both m and n are raw bytes (NOT OP_m / OP_n).
@spec unlock([BSV.PrivateKey.t()], multisig_script(), keyword()) :: {:ok, t()} | {:error, term()}
Create a P2MPKH unlocker.
Arguments
private_keys— exactlymprivate keys satisfying the thresholdmultisig— the multisig script the UTXO commits toopts—:sighash_flag(default0x41= SIGHASH_ALL | SIGHASH_FORKID)
Returns
{:ok, t()} on success, {:error, term()} if the key count does not
match the threshold.