Draft Spec for Standalone SES¶
In the Realms, Frozen Realms, Realms shim, and SES shim work, we’ve generally worked towards standardizing the APIs for dynamically creating a SES world from within a standard EcmaScript world. For IoT or blockchain purposes, the more relevant question is: What is the resulting standard SES world, independent of whether it was created from within a standard EcmaScript world, or whether it was implemented directly by a standalone SES engine that supports only SES?
(We use “blockchain” here as shorthand for the more general category of deterministically replicated SES computation, whether on a blockchain, permissioned BFT system, or whatever.)
Omissions and Simplifications¶
Since the primary purpose of the existing Realms/SES APIs and shims are to dynamically suppress parts of standard EcmaScript, a standalone SES engine would simply omit these elements, resulting in a simpler and smaller engine. Starting from standard EcmaScript, the simplification or omissions for the default configuration of SES are
- Omit all support for sloppy mode
- Aside from
BigInt
, omit everything else outside the EcmaScript 2018 spec.- In particular, omit the
import()
andimport.meta
expressions.- Omit annex B (except those our whitelist allows)
- In particular, omit the
RegExp
static properties that provide a global communications channel.- Omit
Math.random()
- Omit ambient access to current date/time:
Date.now()
returnsNaN
new Date()
return equivalent ofnew Date(NaN)
- By default, omit
Intl
, the internationalization APIs- If some of
Intl
is included, it must suppress ambient authority and non-determinism.
- For all forms of function expressible by syntax (function, generator, async-function, async-generator)
- func
.[[Prototype]].constructor
is a function constructor that always throws. Because these function constructors always throw, we do not consider them to be evaluators.
We define the shared globals as all the standard shared global
variable bindings defined by the above, i.e., without Intl
by
default, with Realm
(see below), without eval
, without
Function
, without anything outside the EcmaScript 2018 spec, and
with BigInt
. We define the shared primordials as all the objects
transitively reachable from the shared globals. Note that no global
objects or evaluators are reachable from the shared primordials.
Additions¶
Some IoT and blockchain configurations may omit all runtime evaluators. For standalone SES configurations that include runtime evaluators, they would appear as follows.
Include the portion of the Realm API for creating compartments, and for evaluating script code in a compartment with endowments:
Realm.makeCompartment(options={})
-> aRealm instance representing a new compartmentRealm.prototype.global
—> global object of compartment. This is a getter-only accessor.Realm.prototype.evaluateProgram(programSrcString, endowments={})
–> completion value- The own properties of the endowments which are legal variable names become the const variable bindings of the global lexical scope in which the program is evaluated. Unlike standard EcmaScript, there is no shared global lexical scope. Each global lexical scope comes only from the endowments.
Realm.prototype.evaluateExpr(exprSrcString, endowments={})
–> value of expression- Given that
exprSrcString
parses as an expression,js aRealm.evaluateExpr(exprSrcString, endowments)
is equivalent tojs aRealm.evaluateProgram(`(${exprSrcString});
, endowments)`
- Given that
The additional element from the proposed Realm API is
Realm.makeRootRealm(options={})
. SES allows but does not require this static method. IoT and blockchain uses of SES generally have no need for multiple root realms. However, browser-based and Node-based use of SES will often be coupled with creating multiple confined root realms. On platforms that do not supportRealm.makeRootRealm
, the property must be absent so that SES code can feature-test for it.Freeze all shared primordials. With the above omissions, there is no hidden state or ambient authority among the shared primordials, so transitive freezing means that the shared primordials are immutable and rom-able. Since no global objects or evaluators are reachable from the shared primordials. They can be placed in ROM without the bookkeeping needed for them to point at any objects not in ROM.
For each compartment, create a new global populated by:
- The shared globals with their standard global property names.
- An
eval
function andFunction
constructor that evaluates code in the scope of that global- Both this
eval
function andFunction
constructor inherit from the shared %FunctionPrototype% primordial. - Each of these
eval
functions is considered an initial eval function for purposes of determining whether a an expression in direct-eval syntax is indeed a direct-eval. (The direct-eval feature is impossible to shim and rarely needed anyway, and so is low priority. When omitted, the direct-eval syntax should also be statically rejected with an early error.) Function.prototype
is initialized to point at the same shared %FunctionPrototype% primordial.
- Both this
- All of these global properties are made non-configurable non-writable data properties. The new per-global objects (the eval function and Function constructor) are frozen. Since they have no hidden state, they are immutable and rom-able.
- This new global object is not frozen. It remains extensible. However, the global’s [[Prototype]] slot cannot be altered.
The host creates a start-compartment whose start-global is populated as above.
To that start-global object, the host adds global bindings to those host objects that provide initial access to the program’s outside world, e.g., the I/O environment of the device.
The program’s start scripts are then evaluated as program code in that start-compartment.
Each compartment scope has its own Function
, which does evaluate.
All compartment scopes share the same Function.prototype
and
therefore the same Function.prototype.constructor
which is a
function that only throws. Thus, in all compartment scopes,
Function !== Function.prototype.constructor
TBD: * What portion of the additions above are relevant to a standalone
SES without runtime evaluators? * Should eval
and Function
actually be on a compartment’s global object, or should we include them
in the compartment’s global lexical scope?
Work in Progress¶
We are still working towards specifying how SES supports modules.
Indeed, this is the main topic of the SES-strategy sessions. Somehow,
whether by import, require, or otherwise, a SES environment must provide
access to the exports of the packages currently named ‘@agoric/nat’ and
‘@agoric/harden’, which will normally be bound to const variable named
Nat
and harden
. We’ll revisit all this is a separate document.
TBD: * System
* error stacks * weak references * loader? *
Should SES provide support for require
and core CommonJS Modules? *
Where should Nat
and harden
come from? * SES
*
SES.confine
Stage Separated SES¶
Full SES, as embedded into EcmaScript, supports running vetted customization code in a freezable realm prior to freezing it into a SES realm. Such vetted customization code runs in an environment like that described above except: * The shared primordials are not yet frozen * No host objects have been added to the global. Thus the vetted customizations run fully confined, without access to any external world.
Although the custoimizations run confined, because they can arbitrarily mutate the shared primordial state before other code runs, all later code is fully vulnerable to these custiomizations. This is why we refer to them as vetted customization code. Once the shared primordial state is transitively frozen, then we can support the standalone SES environment described above, where compartments are units of protection between subgraphs of mutually suspicious objects.
A analogy is that vetted customizations are what a shopkeeper does to their shop in preparation for opening for business. Freezing the primordials is the last step before opening the doors and allowing in untrusted customers.
In an IoT context, we should associate these two stages with build-time and runtime. The build-time environment should support more of the Realms and SES APIs for creating a SES world, that would be absent from within the standalone SES world they are creating. The freezing of the primordials is the snapshotting of the post-constomization primordial state for transfer to ROM.