Built-in Script Overview
The built-in script language is a deterministic, statically typed Python subset designed for expressing data-dependent glue logic (conditional branches, bounded loops, custom framing, and helper functions) over bytes, int, bool, and string values inside the Script node.
Cryptographic primitives belong in dedicated cipher, hash, MAC, KDF, and signature nodes; the Script node handles the byte-level plumbing between them. It is executed by NodeCrypto's own interpreter (not a general Python runtime), guaranteeing identical, reproducible results across the Web app, Desktop app, CLI, and API Container.
Design Principles
- Pure Function: A script reads values from its input ports and computes values for its output ports. There is no I/O, clock, randomness, network access, or mutable global state. When random bytes are needed, wire a Random Value node into a Script input port.
- Closed World: There is no
importstatement, no attribute access (.), and no dynamic code evaluation. Only built-in functions and identifiers defined within the script exist. - Statically Typed: Every expression's type is resolved before execution. Syntax errors, type mismatches, and unassigned output paths are caught and rejected when saving the node properties.
- Guaranteed Termination: There are no
whileloops and no recursion. Every loop iterates over a finite list, abytesbuffer, or a boundedrange(...)under strict step and size limits.
Script Node Structure
| Component | Description |
|---|---|
| Input Ports | Each input port has a unique identifier (id / display name) and a static data type (bytes, int, bool, or string). Every added input port is required. |
| Output Ports | 0 or more output ports, each with a unique identifier and a static data type (bytes, int, bool, or string). |
| Code | Script source code (up to 64 KiB). |
Because port names serve directly as variable names inside the script, every port name must follow the identifier rules, must not collide with reserved keywords or built-in function names, and must be unique across all ports on the node. Neither any nor set ports are allowed on a Script node.
Input and Output Port Binding
- Each input port is pre-bound as a read-only top-level variable with the same name and type. Input variables cannot be reassigned.
- Assigning a value to a top-level variable whose name matches an output port sets that output port's result. The assigned value's type must match the output port's declared data type.
- By the end of the script, every output port variable must be definitely assigned along every possible non-failing execution path. If any path could finish without assigning an output variable, saving is blocked (see Definite Assignment Analysis).
- User-defined functions cannot read input port variables or assign output port variables directly; pass values as function arguments and unpack return values at the top level.
Execution Lifecycle
- When all connected input ports have values ready, the Script node runs.
- Top-level statements execute sequentially from top to bottom.
- If execution reaches the end normally, the values held in the output variables are emitted on their corresponding output ports.
- If
fail(code, message)is called, a runtime error occurs, or a resource limit is exceeded, execution halts immediately, no partial outputs are emitted, and the node enters anERRORstate (see Errors and Resource Limits).
Using the Code Editor
The first tab of the Script node's Properties window is the code editor; input and output ports are managed in the Ports tab (or directly on the node card). If static checking finds an error, the editor highlights the line:column location with the diagnostic message and prevents saving until resolved.
Verified Examples
Every example below is verified against the built-in script evaluator. Byte inputs and outputs are shown in hexadecimal.
CMAC Subkey Derivation (Conditional Branching)
Input l: bytes (AES encryption of the 16-byte zero block), outputs k1: bytes and k2: bytes. For input 7df76b0c1ab899b33e42f047b91b546f, k1 is fbeed618357133667c85e08f7236a8de and k2 is f7ddac306ae266ccf90bc11ee46d513b (RFC 4493 test vector).
def dbl(x: bytes) -> bytes:
rb = h"00" * (len(x) - 1) + (h"87" if len(x) == 16 else h"1b")
r = x << 1
if x[0] & 0x80 != 0:
r ^= rb
return r
k1 = dbl(l)
k2 = dbl(k1)
Counter Block Sequence (Lists and Loops)
Inputs seed: bytes and n: int, output out: bytes. When seed is aa and n is 3, produces the concatenation of aa00000000, aa00000001, and aa00000002.
parts: list[bytes] = []
for i in range(n):
parts += [seed + to_bytes(i, 4)]
out = concat(parts)
Length Validation and Tuple Unpacking
Input data: bytes, outputs head: bytes and body: bytes. Splits the first 4 bytes as the header and the remainder as the body. When data is 0102030405, head is 01020304 and body is 05.
def split2(b: bytes, n: int) -> tuple[bytes, bytes]:
if len(b) < n:
fail("DATA_TOO_SHORT", "data is shorter than the header")
return b[:n], b[n:]
head, body = split2(data, 4)
Passing a 1-byte input 01 to the same script raises DATA_TOO_SHORT and puts the node into an error state:
def split2(b: bytes, n: int) -> tuple[bytes, bytes]:
if len(b) < n:
fail("DATA_TOO_SHORT", "data is shorter than the header")
return b[:n], b[n:]
head, body = split2(data, 4)
DES Key Odd Parity Adjustment (Byte Iteration)
Input key: bytes, output fixed: bytes. Sets the least significant bit of each byte so that every byte has odd parity. For input 0022ff00, outputs 0123fe01.
out: list[int] = []
for v in key:
ones = 0
for s in range(1, 8):
ones += (v >> s) & 1
out += [(v & 0xFE) | (1 - ones % 2)]
fixed = from_ints(out)
Custom Validation Error with Formatted Message (f-string)
Input key: bytes, output ok: bytes. Aborts with a descriptive error message when key is not 16 bytes long.
if len(key) != 16:
fail("BAD_KEY_LENGTH", f"key must be 16 bytes, got {len(key)} ({key:x})")
ok = key
Further Reading
- Syntax: Indentation, literals, statements, functions, subscripts, loops, f-strings, and differences from Python.
- Types and Operators: Type system rules, definite assignment, value semantics, and operator table.
- Built-in Functions: Signatures and verified examples for all 24 built-in functions.
- Errors and Resource Limits: Compile-time diagnostic codes, runtime error codes, and execution limits.