SHRINCS in Script and Simplicity

We translated Blockstream Research’s Simplicity verifier into GSR Bitcoin Script. Both implementations check the same public keys, messages, and signature fields.

The original experiment uses the SHRINCS BIP draft; this comparison uses Blockstream Research’s construction. They use different hash-tree parameters. The draft’s 48-byte public key stores two root hashes separately. Blockstream’s 32-byte public key stores a hash of both roots. Results from the two constructions are not directly comparable.

Program size

These bars show encoded instructions, without input data or transaction overhead. All three Script variants use shared functions and optimized CAT joins.

Stateful · program bytes
Script · functions + CAT joins6,584
Script · OP_MULTI all uses6,471
Script · OP_MULTI selective hashing6,581
Simplicity · pruned2,851
Stateless · program bytes
Script · functions + CAT joins10,215
Script · OP_MULTI all uses10,035
Script · OP_MULTI selective hashing10,202
Simplicity · pruned4,166

Simplicity removes unused branches before encoding. This operation is called pruning. Script keeps its conditional branches.

What about the 50 kB claim?

We previously cited an approximate 50 kB verifier size. We have not verified its program revision, compiler, or size definition.

Our current Simplicity build gives these sizes. One kB equals 1,000 bytes.

Program bytesStatefulStateless
Before pruning4,8114,813
After pruning2,8514,166

Pruning alone cannot explain 50 kB: these programs are already below 5 kB before pruning.

These are separate stateful and stateless programs, compiled with SimplicityHL 0.7.2. They exclude transaction data and execution-budget padding.

The upstream Liquid report measures transaction witness size and includes padding. It does not establish the source of the 50 kB figure.

We need the original program and build settings to explain the difference. Until then, 50 kB is an unverified reference.

Measurements

Each language encodes its input fields differently. Simplicity calls its encoded input data the witness.

Stateful

ImplementationProgram bytesInput bytesExecution charge or bound
384,8411,16023,265,133 varops
118,6761,16015,675,150 varops
6,8701,16012,379,290 varops
6,5841,16011,774,652 varops
6,4711,16011,986,038 varops
6,5811,16011,771,496 varops

Simplicity before pruning: 4,811 program bytes. C and Rust agree on its cost bound.

Stateless

ImplementationProgram bytesInput bytesExecution charge or bound
133,7444,456119,116,431 varops
44,7504,45645,048,045 varops
10,5854,45646,653,390 varops
10,2154,45644,384,370 varops
10,0354,45645,200,607 varops
10,2024,45644,379,477 varops

Simplicity before pruning: 4,813 program bytes. C and Rust agree on its cost bound.

GSR charges execution in varops. Simplicity gives an upper limit in milliweight. These units do not give a common performance scale.

Both OP_MULTI variants use shared functions and optimized CAT joins. “All uses” groups hashes, joins, and cleanup operations. “Selective hashing” groups only hashes that pass the compiler’s cost estimate.

Script input sizes exclude the length prefixes for stack items. Simplicity input sizes include witness encoding. Pruning can remove unused fields.

Why a small program can require padding

Liquid gives each Simplicity input a budget based on its complete serialized witness stack. It adds 50 free units.

C ≤ 1,000 × (W + 50)

C is the program’s cost bound in milliweight. W is the serialized witness stack size in bytes.

W includes the program, encoded input, control data, item lengths, and any annex. An annex can carry padding.

Sources: witness budget, 50-unit offset, and milliweight conversion.

Derived from the measured Simplicity checkerStatefulStateless
Program + encoded input, bytes4,0138,558
Minimum serialized witness stack, bytes33,94283,479

For these costs, the minimum is the bound in weight units, rounded up, minus 50. Program and input bytes already contribute to this minimum. Do not add them again.

The Liquid report records 37.755 kWU and 92.450 kWU and explicitly includes padding. Those figures are not encoded program sizes.

Our standalone build differs from those transaction programs. The budget rule explains why padding is needed; it does not reproduce those exact weights.

The GSR budget rule allows 10,000 varops per transaction weight unit. Its checker with shared functions already has enough allowance from its program bytes alone.

The GSR checker with optimized CAT joins needs 1,178 weight units for stateful verification and 4,439 for stateless verification. These are derived budget requirements, not measured transaction weights.

Complete spend comparisons still need transaction-message binding, a committed public key, and the same transaction structure. No complete-spend ratio is established here.

Verification steps

StepScript restorationSimplicity
Read the inputCheck byte lengths and extract fields.Decode the witness according to its data types.
Recover the signature rootRun hash chains and authentication paths with Script instructions.Run the same hash rules with typed functions and SHA-256 jets.
Check the public keyCombine the recovered root with the supplied unused root. Compare the resulting hash.Apply the same root-combination rule.
Remove unused codeKeep conditional branches in the Script.Remove branches that this input does not use.

A jet runs a specified Simplicity operation through native code. This verifier uses general hash and arithmetic jets. It has no dedicated SHRINCS jet.

Jets used by these programs

Execution time

The table shows the median of 11 runs on one machine. The timers exclude compilation and process startup.

Timed operationStateful, msStateless, ms
GSR interpreter · functions + CAT joins0.5291.851
Simplicity C · evaluation and bounds checks1.2253.976
Simplicity C · decode, check, and evaluate3.5047.864
Simplicity Rust · Bit Machine execution16.55950.847

The timers cover different work. Do not use these values to calculate a speed ratio between the languages. The experiment notes define each timer.

Limits

Source and data

Recorded host: macOS-26.4-arm64-arm-64bit-Mach-O · arm64