Rúnar

Contract Decorators & Types

Rúnar uses a specialized type system and language keywords to define contract classes, methods, and state. This page is a complete reference for every on-chain type, annotation pattern, and built-in function available in the runar-lang package.

Contract Annotations

Rúnar does not use TypeScript decorators (the @decorator syntax). Instead, contract structure is expressed through base class inheritance, the readonly keyword, and the public access modifier. The compiler’s validation pass explicitly rejects decorator syntax.

Contract Declaration

A contract is a class that extends either SmartContract (stateless) or StatefulSmartContract (mutable state):

import { SmartContract } from 'runar-lang';

class P2PKH extends SmartContract {
  // Extending SmartContract marks this class as a contract
}
import { StatefulSmartContract } from 'runar-lang';

class Counter extends StatefulSmartContract {
  // Extending StatefulSmartContract enables mutable state
}

Only one contract class is allowed per file. The file must use the .runar.ts extension.

Public Methods (Spending Conditions)

The public keyword marks a method as a spending condition — an entry point that callers can invoke to spend the contract UTXO:

class P2PKH extends SmartContract {
  readonly pubKeyHash: Ripemd160;

  public unlock(sig: Sig, pubKey: PubKey) {
    assert(hash160(pubKey) === this.pubKeyHash);
    assert(checkSig(sig, pubKey));
  }
}

Every public method must call assert() at least once. Methods without the public keyword are private helpers that the compiler inlines at each call site.

Readonly Properties (Immutable)

The readonly keyword marks a field as immutable. Readonly values are baked into the locking script at deployment time and cannot change:

class Escrow extends SmartContract {
  readonly buyer: PubKey;
  readonly seller: PubKey;
  readonly arbiter: PubKey;

  constructor(buyer: PubKey, seller: PubKey, arbiter: PubKey) {
    super(buyer, seller, arbiter);
    this.buyer = buyer;
    this.seller = seller;
    this.arbiter = arbiter;
  }
}

In a SmartContract, all fields must be readonly. In a StatefulSmartContract, readonly fields are fixed at deployment while non-readonly fields are mutable state.

Mutable State Fields

In a StatefulSmartContract, fields without readonly are mutable state. They are serialized into the locking script and can be updated across transactions:

class Counter extends StatefulSmartContract {
  count: bigint;  // Mutable state -- no readonly keyword

  constructor() {
    super();
    this.count = 0n;
  }

  public increment() {
    this.count = this.count + 1n;
    assert(true);
  }
}

State updates are enforced on-chain through the OP_PUSH_TX covenant mechanism. The compiler auto-injects the preimage verification and state continuation code.

Constructor Pattern

Every contract constructor must call super() with all readonly field values, in order:

constructor(buyer: PubKey, seller: PubKey, amount: bigint) {
  super(buyer, seller, amount);
  this.buyer = buyer;
  this.seller = seller;
  this.amount = amount;
}

The super() arguments define the constructor slots in the compiled artifact. When deploying via the SDK, these values are spliced into the script’s placeholder positions.

On-Chain Primitive Types

All on-chain values in Rúnar must use these types. JavaScript/TypeScript native types like number and string are not allowed in contract code.

bigint

Arbitrary-precision integer. The primary numeric type.

const amount: bigint = 50000n;
const negative: bigint = -1n;
const zero: bigint = 0n;

Script representation: Minimally-encoded signed little-endian integer.

Operations: +, -, *, /, % (modulo), comparison (<, >, <=, >=, ===).

Division is integer division (truncates toward zero). There are no floating-point numbers.

boolean

const isValid: boolean = true;
const isEmpty: boolean = false;

Script representation: OP_TRUE (0x01) or OP_FALSE (empty byte array).

ByteString

A raw byte sequence, represented as a hex string in TypeScript.

const data: ByteString = 'aabbccdd';
const empty: ByteString = '';

Operations:

const combined: ByteString = data1 + data2;       // Concatenation (cat)
const length: bigint = len(data);                  // Byte length
const chunk: ByteString = substr(data, 0n, 4n);   // Extract bytes at offset, length
const prefix: ByteString = left(data, 4n);         // First N bytes
const suffix: ByteString = right(data, 4n);        // Last N bytes
const [head, tail] = split(data, 16n);             // Split at position
const flipped: ByteString = reverseBytes(data);    // Reverse byte order

PubKey

A compressed secp256k1 public key (33 bytes). Starts with 02 or 03.

const key: PubKey = '02' + 'aa'.repeat(32);

Subtyping: PubKey is a subtype of ByteString. You can pass a PubKey anywhere a ByteString is expected, but not vice versa.

Sig

A DER-encoded ECDSA signature with sighash type byte appended (71-73 bytes).

const signature: Sig = '30' + 'aa'.repeat(35);

Affine type: A Sig value must be consumed exactly once. Using it zero times or more than once is a compile-time error. This prevents signature replay attacks.

Sha256

A SHA-256 hash digest (32 bytes).

const digest: Sha256 = sha256('aabbccdd');

Subtyping: Sha256 is a subtype of ByteString.

Ripemd160

A RIPEMD-160 hash digest (20 bytes).

const hash: Ripemd160 = hash160(pubKey); // SHA-256 then RIPEMD-160

Subtyping: Ripemd160 is a subtype of ByteString.

Addr

A BSV address (20-byte hash of a public key). Functionally identical to Ripemd160 but semantically distinct — indicates a value represents an address.

const address: Addr = hash160(pubKey);

SigHashPreimage

The serialized BIP-143 sighash preimage. Used for transaction introspection in covenants.

public spend(txPreimage: SigHashPreimage) {
  assert(checkPreimage(txPreimage));
  const outputs = extractOutputHash(txPreimage);
  const locktime = extractLocktime(txPreimage);
}

Affine type: Like Sig, a SigHashPreimage must be consumed exactly once.

Point

An elliptic curve point on secp256k1 (64 bytes: 32-byte x + 32-byte y). Used for EC operations in ZK proofs and advanced cryptographic constructions.

const G: Point = ecMulGen(1n);              // Generator point
const P: Point = ecMul(G, secretScalar);    // Scalar multiplication
const sum: Point = ecAdd(P, G);             // Point addition

RabinSig

A Rabin signature value (arbitrary-precision integer). Used with verifyRabinSig for oracle attestations.

const oracleSig: RabinSig = 123456789n;

Subtyping: RabinSig is a subtype of bigint.

RabinPubKey

A Rabin public key (arbitrary-precision integer).

const oracleKey: RabinPubKey = 987654321n;

Subtyping: RabinPubKey is a subtype of bigint.

Collection Types

FixedArray<T, N>

A fixed-length array. The length N must be a compile-time constant.

import { FixedArray, PubKey, Sig } from 'runar-lang';

// Array of 3 public keys
const keys: FixedArray<PubKey, 3> = [key1, key2, key3];

// Array of 5 bigints
const values: FixedArray<bigint, 5> = [1n, 2n, 3n, 4n, 5n];

Operations:

// Index access (index must be a compile-time constant or loop variable)
const first: PubKey = keys[0];

// Iteration (unrolled at compile time)
for (let i = 0; i < 3; i++) {
  assert(checkSig(sigs[i], keys[i]));
}

Variable-length arrays are not supported. The compiler unrolls all array operations into sequential opcodes.

Type Hierarchy

The Rúnar type system uses subtyping to allow domain-specific types where generic types are expected:

ByteString
  ├── PubKey (33 bytes)
  ├── Sig (71-73 bytes, affine)
  ├── Sha256 (32 bytes)
  ├── Ripemd160 (20 bytes)
  ├── Addr (20 bytes)
  ├── SigHashPreimage (variable, affine)
  └── Point (64 bytes)

bigint
  ├── RabinSig
  └── RabinPubKey

A PubKey can be passed where a ByteString is expected, but a ByteString cannot be passed where a PubKey is expected. Affine types (Sig, SigHashPreimage) must be consumed exactly once regardless of subtyping.

Constants

Elliptic Curve Constants

import { EC_P, EC_N, EC_G } from 'runar-lang';

// EC_P: The prime modulus of secp256k1
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
const p: bigint = EC_P;

// EC_N: The order of the generator point
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
const n: bigint = EC_N;

// EC_G: The generator point
const G: Point = EC_G;

SigHash Flags

import { SigHash } from 'runar-lang';

SigHash.ALL          // 0x01 -- Sign all inputs and outputs
SigHash.NONE         // 0x02 -- Sign all inputs, no outputs
SigHash.SINGLE       // 0x03 -- Sign all inputs, only the matching output
SigHash.FORKID       // 0x40 -- BSV fork ID flag (always set on BSV)
SigHash.ANYONECANPAY // 0x80 -- Sign only the current input

Combined with bitwise OR:

// Most common: sign everything with BSV fork ID
const sigType = SigHash.ALL | SigHash.FORKID; // 0x41

// Sign only current input, only matching output
const sigType = SigHash.SINGLE | SigHash.FORKID | SigHash.ANYONECANPAY; // 0xC3

Built-In Functions Reference

Cryptographic Hashing

FunctionInputOutputDescription
sha256(data)ByteStringSha256SHA-256 hash
hash256(data)ByteStringSha256Double SHA-256: sha256(sha256(data))
hash160(data)ByteStringRipemd160SHA-256 then RIPEMD-160
ripemd160(data)ByteStringRipemd160RIPEMD-160 hash
sha256Compress(state, block)ByteString, ByteStringSha256SHA-256 compression function (partial verification)
sha256Finalize(state, remaining, msgBitLen)ByteString, ByteString, bigintSha256SHA-256 finalization (partial verification)

Signature Verification

FunctionDescriptionReturn
checkSig(sig, pubKey)Verify ECDSA signatureboolean
checkMultiSig(sigs, pubKeys)Verify M-of-N ECDSA signaturesboolean
checkPreimage(preimage)Verify sighash preimage authenticity via OP_PUSH_TXboolean
verifyRabinSig(msg, sig, padding, pubKey)Verify Rabin oracle signatureboolean

Post-Quantum Verification

FunctionDescription
verifyWOTS(msg, sig, pubKey)Verify WOTS+ one-time signature (2,144B sig)
verifySLHDSA_SHA2_128s(msg, sig, pubKey)SLH-DSA SHA2-128s verification
verifySLHDSA_SHA2_128f(msg, sig, pubKey)SLH-DSA SHA2-128f verification
verifySLHDSA_SHA2_192s(msg, sig, pubKey)SLH-DSA SHA2-192s verification
verifySLHDSA_SHA2_192f(msg, sig, pubKey)SLH-DSA SHA2-192f verification
verifySLHDSA_SHA2_256s(msg, sig, pubKey)SLH-DSA SHA2-256s verification
verifySLHDSA_SHA2_256f(msg, sig, pubKey)SLH-DSA SHA2-256f verification

Preimage Introspection

FunctionReturnDescription
extractVersion(preimage)bigintTransaction version (4 bytes)
extractHashPrevouts(preimage)Sha256Double-SHA256 of all input outpoints
extractHashSequence(preimage)Sha256Double-SHA256 of all input sequences
extractOutpoint(preimage)ByteStringOutpoint of the current input (36 bytes)
extractInputIndex(preimage)bigintIndex of the current input
extractScriptCode(preimage)ByteStringLocking script being executed
extractAmount(preimage)bigintSatoshi value of the UTXO being spent
extractSequence(preimage)bigintSequence number of the current input
extractOutputHash(preimage)Sha256Double-SHA256 of all serialized outputs
extractOutputs(preimage)ByteStringRaw serialized outputs
extractLocktime(preimage)bigintTransaction locktime
extractSigHashType(preimage)bigintSighash flags

Elliptic Curve Operations

FunctionDescriptionReturn
ecAdd(p1, p2)EC point additionPoint
ecMul(point, scalar)EC scalar multiplicationPoint
ecMulGen(scalar)Multiply generator G by scalarPoint
ecNegate(point)Negate (reflect over x-axis)Point
ecOnCurve(point)Check if point is on secp256k1boolean
ecModReduce(value)Reduce modulo curve order Nbigint
ecEncodeCompressed(point)Encode as 33-byte compressed pubkeyByteString
ecMakePoint(x, y)Construct point from coordinatesPoint
ecPointX(point)Extract x-coordinatebigint
ecPointY(point)Extract y-coordinatebigint

Byte Operations

FunctionDescriptionReturn
len(data)Byte lengthbigint
cat(a, b)Concatenate two byte sequencesByteString
substr(data, offset, length)Extract bytes at offsetByteString
left(data, n)First N bytesByteString
right(data, n)Last N bytesByteString
split(data, position)Split into two parts[ByteString, ByteString]
reverseBytes(data)Reverse byte orderByteString
toByteString(value)Convert to byte representationByteString

Conversion Functions

FunctionDescriptionReturn
num2bin(num, length)Encode integer as fixed-length bytesByteString
bin2num(data)Decode bytes as integerbigint
int2str(num, length)Integer to fixed-length byte string representationByteString
bool(value)Convert to booleanboolean

Math Functions

FunctionDescriptionReturn
abs(x)Absolute valuebigint
min(a, b)Minimum of two valuesbigint
max(a, b)Maximum of two valuesbigint
within(x, low, high)Check if low <= x < highboolean
safediv(a, b)Division with zero-checkbigint
safemod(a, b)Modulo with zero-checkbigint
clamp(x, low, high)Clamp to rangebigint
mulDiv(a, b, c)(a * b) / c without intermediate overflowbigint
percentOf(value, percent)(value * percent) / 100bigint
sign(x)Sign of value: -1, 0, or 1bigint
pow(base, exp)Integer exponentiationbigint
sqrt(x)Integer square root (floor)bigint
gcd(a, b)Greatest common divisorbigint
divmod(a, b)Quotient and remainder[bigint, bigint]
log2(x)Integer log base 2 (floor)bigint

Control Functions

FunctionDescription
assert(condition)Abort script execution if condition is false

State Functions (StatefulSmartContract only)

FunctionDescription
this.addOutput(satoshis, ...stateValues)Add a continuation output with updated state
this.addRawOutput(satoshis, scriptBytes)Add an arbitrary output with caller-specified script bytes

Further Reading