@ergots/ergoscript
v0.3.0
Published
Pure-TS ErgoScript / ErgoTree parser + interpreter (phase 2 of ergots)
Maintainers
Readme
@ergots/ergoscript
Pure-TypeScript ErgoTree parser, serializer, partial evaluator, and sigma-protocol verifier. Part of ergots. Browser-compatible. Validated byte-for-byte against ergotree-ir + ergotree-interpreter (sigma-rust).
Install
npm install @ergots/ergoscriptUsage
import {
parseTree,
serializeTree,
isP2PK,
p2pkPublicKey,
addressFromErgoTree,
ergoTreeFromAddress
} from '@ergots/ergoscript';
// Parse a serialized ErgoTree:
const treeBytes: Uint8Array = /* on-wire ErgoTree bytes (e.g. from a box.ergoTree field) */;
const tree = parseTree(treeBytes);
console.log(tree.header.version, tree.constants.length, tree.body.tag);
// Re-serialize — byte-identical to the input:
const roundTripped = serializeTree(tree);
// roundTripped equals treeBytes
// Recognize a P2PK guarding script and extract its public key:
if (isP2PK(tree)) {
const pk = p2pkPublicKey(tree); // 33-byte compressed secp256k1 point
}
// Derive a base58 Ergo address from a tree:
const address = addressFromErgoTree(tree, 'mainnet');
// And back:
const reconstructed = ergoTreeFromAddress(address);Evaluator
import { evaluate, evaluateWith, makeContext } from '@ergots/ergoscript';
// Evaluate a tree with default context (no box, no block, no transaction):
const result = evaluate(tree);
// Or supply context explicitly:
const ctx = makeContext({ /* EvalOpts */ });
const result2 = evaluateWith(tree, ctx);evaluate returns an SValue (discriminated union keyed on .kind). Unimplemented Expr arms throw EvalError with code 'not-implemented-yet'. 52 of ~70 arms are wired today, with a 42-entry method-handler registry covering SBox.*, SColl.*, SContext.*, SHeader.* (×16), SAvlTree.* (×16), SGlobal.*, SPreHeader.*. Cost values are sigma-rust-accurate per arm.
Sigma-protocol verifier
import { verifySignature } from '@ergots/ergoscript';
// sigmaBoolean comes from an SValue.SigmaProp (from evaluate, or via parseSigmaBoolean)
const ok: boolean = verifySignature(sigmaBoolean, message, signature);Verifies a Schnorr-style sigma-protocol proof against the full SigmaBoolean 6-variant surface (TrivialProp, ProveDlog, ProveDhTuple, Cand, Cor, Cthreshold including GF(2^192) polynomial threshold). Throws VerifyError on malformed signature bytes or off-curve points.
See API.md for the full reference (every export, its signature, error codes, and type definitions).
Public surface
The package exports a small consumer-facing API:
- Wire format:
parseTree,serializeTree,MAX_TREE_SIZE - Addresses:
isP2PK,p2pkPublicKey,addressFromErgoTree,ergoTreeFromAddress,base58Encode,base58Decode - Evaluator:
evaluate,evaluateWith,makeContext - Sigma-protocol verifier:
verifySignature - Types:
ErgoTree,TreeHeader,SType,SValue,Expr,SigmaBoolean,Network,AddressType,EvalContext,EvalOpts - Errors:
ErgoTreeParseError,ErgoTreeSerializeError,AddressDecodeError,EvalError,VerifyError
The boundary contract — what other packages may rely on, with preconditions, postconditions, invariants, and the full error taxonomy — is documented in facts/ergoscript.md at the repo root.
Browser compatibility
Runs unchanged in evergreen browsers and Node >= 20. No Buffer, no node:crypto, no dynamic Node built-ins, no WASM. ESM-only. The bundle is scanned in CI for forbidden references (Buffer/process/node:* and Scala.js identifier patterns) before any release.
The package is stateless and pure: bytes in, structured result out. No I/O, no clock, no PRNG, no globalThis reads.
What this package does NOT do
- Partial evaluator — 52 of ~70
Exprarms are wired; the remaining ~18 throwEvalError 'not-implemented-yet'. Notably missing: predefs (DecodePoint,SubstConstants,CalcBlake2b256, byte-array + hash predefs), theXorbyte-array op, modular-arithmetic ops (ModQ,PlusModQ,MinusModQ), theCollshift/rotate family, and a handful of niche structural ops. Real-context cost-limit calibration (Layer C3) is also pending. - No sigma-protocol prover.
verifySignatureis the verifier side of the sigma protocol — it checks proofs produced by sigma-rust's prover or any conformant prover. Proof generation is out of scope. - No
.essource compiler. This is a binary AST parser —.essource compilation (sigma-rust'sergoscript-compiler) is out of scope. - No transaction building, no key derivation, no mnemonic/BIP32. Those belong to the future wallet / transaction-broadcaster package.
Validation strategy
Every parse + serialize primitive is validated byte-for-byte against fixtures generated by a Rust crate (fixture-gen/) that calls directly into sigma-rust's ergotree-ir at branch integration/ergots. The corpus covers:
- Synthetic edge cases — VLQ boundary values, every
STypevariant, everySValuekind, every MIRExprvariant individually. - Real-world contracts — 45 legacy + 14 ecosystem + 15 significant-15 contracts pulled from sigma-rust's PR 862
ergoscript-compiler-v2corpus. - Mainnet box scripts — guarding scripts from real Ergo mainnet outputs.
Six fixtures in the upstream sigma-rust corpus are flagged known_unstable because sigma-rust itself does not round-trip them; those are excluded from byte-equality but still parse-tested. Mutation testing single-byte-flips each fixture and asserts every flip either throws a typed error class or is byte-equal (a flip landing in a tolerated padding region) — total taxonomy coverage on every documented error code.
Evaluator validation adds two further layers:
- Layer C1 — per-arm fixtures (one or more
eval/<arm>.jsonfiles per arm, each entry covering both the evaluatedSValueand the jit cost) validated byte-for-byte againstergotree-interpreterviatry_eval_out/try_eval_out_with_version. - Layer C2 — corpus eval-filter: real mainnet box scripts are run through the evaluator and the subset that the current arm set can fully reduce is compared against sigma-rust's output value-for-value.
- Layer C3.a — operator-driven mutation testing on the higher-order Coll arms and the AVL+ method handlers, targeting ≥ 90% kill rate per arm.
License
MIT
