concepts

LockScript Language

Overview

LockScript is a purpose-built expression language for encoding asset unlock conditions. A lock script is stored as a plain string with the asset. At unlock time, the service compiles it to bytecode and executes it in a sandboxed VM; the asset is released only if the script evaluates to true.


Syntax

LockScript is expression-oriented. The top-level value of a script must be a boolean. Statements supported:

  • if / else blocks
  • require(condition, "message") — halts execution with an error if the condition is false
  • transfer(to, amount, token) — initiates a token transfer (used in automated unlock flows)
  • Function calls and binary / unary expressions

Built-in Functions

FunctionSignatureDescription
now()() → int64Current Unix timestamp
after(ts)(int64) → booltrue if current time > ts
before(ts)(int64) → booltrue if current time < ts
sha256(data)(string) → stringSHA-256 hash of input
verify_sig(pk, msg, sig)(string, string, string) → boolEd25519 signature verification
require_sigs(pks, msg, sigs, n)([]string, string, []string, int) → boolEd25519 m-of-n multi-sig
check_geo(region)(string) → boolGeographic region check
min(a, b, ...)(int...) → intMinimum of arguments
max(a, b, ...)(int...) → intMaximum of arguments

Execution Context Variables

The VM is initialised with these variables populated from the unlock request:

VariableTypeValue
current_timeint64Unix timestamp at unlock
unlock_timeint64The asset's configured unlock time
asset_idstringHex asset ID
ownerstringOwner public key (hex)
signaturestringUnlock signature (hex)
signatures[]stringAll supplied signatures
pubkeys[]stringAll supplied public keys
messagestringMessage that was signed (usually asset_id)

Example Scripts

// Simple time-lock: asset cannot be unlocked before a timestamp
after(1700000000)
// Time-lock + owner signature
after(unlock_time) && verify_sig(owner, asset_id, signature)
// 2-of-3 multi-sig (real Ed25519 verification)
require_sigs(pubkeys, message, signatures, 2)
// Geographic restriction
check_geo("us-east") || check_geo("eu-west")
// Complex: time-lock with owner OR emergency multi-sig
after(unlock_time) && (
    verify_sig(owner, message, signature) ||
    require_sigs(emergency_keys, message, emergency_sigs, 2)
)

VM Internals

The VM is a stack-based bytecode interpreter:

  1. Lexer — tokenises the script string into keywords, identifiers, literals, and operators.
  2. Parser — builds an AST (IfNode, RequireNode, BinaryExpr, CallExpr, etc.).
  3. Compiler — walks the AST and emits bytecode using 27+ opcodes (OpPush, OpLoad, OpCallBuiltin, OpJumpIf, OpReturn, etc.).
  4. VM — executes bytecode against a value stack with a 65 536-byte memory limit and a 5-second execution timeout.

Compiled scripts are cached to avoid repeated compilation for frequently-evaluated conditions.


Security Constraints

  • Memory limit: 65 536 bytes stack + heap.
  • Time limit: 5-second execution wall clock timeout.
  • No I/O: scripts cannot perform network calls, file access, or any system interaction.
  • No eval: scripts cannot compile or execute other scripts.
  • Fuzz-tested: the VM has a fuzz test (vm_fuzz_test.go) exercising malformed bytecode paths.

Scripts are validated at lock time (fail-fast compilation check) so malformed scripts never reach the execution path at unlock.