diff --git a/main/platform/hardened-js.md b/main/platform/hardened-js.md new file mode 100644 index 000000000..3a7878f39 --- /dev/null +++ b/main/platform/hardened-js.md @@ -0,0 +1,260 @@ +# Intro to Hardened JavaScript + +_Status: WIP_. + + - JavaScript is popular + - JavaScript has some messy parts + - Stick to Hardened JavaScript + +Overview: + - Objects + - Defensive Correctness + - **Electronic Rights** + - Data Structures + - Reference: JSON Data, Justin expressions, Jessie programs + +## Getting Started: Tools + +TL;DR: Here are the steps to play whack-a-mole with the Jessie linter: + +1. If not already configured, run `yarn add eslint @jessie.js/eslint-plugin` +2. If not already configured, add the following to your `package.json`: + +```json + "eslintConfig": { + "extends": [ + "@jessie.js" + ] + } +``` + +3. Put `// @jessie-check` at the beginning of your `.js` source file. +4. Run `yarn eslint --fix path/to/your-source.js` +5. Follow the linter's advice to edit your file, then go back to step 4. + +## Simple Objects + +Singleton, stateless: + +```js +const origin = { + getX: () => 0, + getY: () => 0, +}; +``` + +```console +> origin.getY() +0 +``` + + +## Object makers + +```js +const makeCounter = init => { + let value = init; + return { + increment: () => { + value += 1; + return value; + }, + makeOffsetCounter: delta => makeCounter(value + delta), + }; +}; +``` + +```console +$ node +Welcome to Node.js v14.16.0. +Type ".help" for more information +> const c1 = makeCounter(1); +> c1.increment(); +2 +> const c2 = c1.makeOffsetCounter(10); +> c1.increment(); +3 +> c2.increment(); +13 +> [c1.increment(), c2.increment()]; +[ 4, 14 ] +``` + + - An object is a record of functions that close over shared state. + - The `this` keyword is not part of Jessie, so neither are constructors. + - A `makeCounter` function takes the place of the `class Counter` constructor syntax. + + +_TODO: `decrement()`, facets_ + +## WARNING: Pervasive Mutability + +```console +> c1.increment = () => { console.log('launch the missiles!'); } +[Function (anonymous)] +> c1.increment() +launch the missiles! +``` + + +## Defensive objects with `harden()` + +```js +const makeCounter = init => { + let value = init; + return harden({ + increment: () => { + value += 1; + return value; + }, + makeOffsetCounter: delta => makeCounter(value + delta), + }); +}; +``` + +``` +> const c3 = Object.freeze(c1.makeOffsetCounter(10)); +undefined +> c3.increment = () => { console.log('launch the missiles!'); } +TypeError: Cannot assign to read only property 'increment' of object '#' +> c3.increment() +15 +``` + + - _caveat_: exception is thrown in strict mode. REPL might not throw. + - regardless, the object defended itself + - Jessie pre-defines `harden` (_as does Agoric smart contract framework_) + - see the `ses` [hardened Javascript package](https://github.com/endojs/endo/tree/master/packages/ses#harden) otherwise + + +## Types: advisory + +```js +// @ts-check + +/** @param {number} init */ +const makeCounter = init => { + let value = init; + return { + increment: () => { + value += 1; + return value; + }, + /** @param {number} delta */ + makeOffsetCounter: delta => makeCounter(value + delta), + }; +}; +``` + + - [TypeScript: Documentation \- Type Checking JavaScript Files](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) + + +## Types: advisory (cont.) + +If we're not careful, our clients can cause us to mis-behave: + +``` +> const evil = makeCounter('poison') +> evil2.increment() +'poison1' +``` + +or worse: + +``` +> const evil2 = makeCounter({ valueOf: () => { console.log('launch the missiles!'); return 1; } }); +> evil2.increment() +launch the missiles! +2 +``` + +## Types: defensive + +```js +/** @param {number | bignum} init */ +const makeCounter = init => { + let value = Nat(init); + return harden({ + increment: () => { + value += 1n; + return value; + }, + /** @param {number | bignum} delta */ + makeOffsetCounter: delta => makeCounter(value + Nat(delta)), + }); +}; +``` + +``` +> makeCounter('poison') +Uncaught TypeError: poison is a string but must be a bigint or a number +``` + + - **defensive correctness**: a program is _defensively correct_ if it remains correct despite arbitrary behavior on the part of its clients. + - [Miller, Tribble, Shapiro 2005](http://erights.org/talks/promises/paper/tgc05.pdf) + +## Details: Stay Tuned + + - Ordinary programming in JavaScript follows in a later section + - Much overlap with Java, Python, C, etc. + +_If you are **not** familiar with programming in some language, study details a bit and then come back here._ + +## Electronic Rights: Mint and Purse + +**Watch**: [the mint pattern](https://youtu.be/iyuo0ymTt4g?t=1525), +an 8 minute segment starting at 25:00 in + + - [Agoric \+ Protocol Labs // Higher\-order Smart Contracts across Chains \- Mark Miller \- YouTube](https://www.youtube.com/watch?v=iyuo0ymTt4g). + +[![image](https://user-images.githubusercontent.com/150986/129462162-4599c0f4-8519-4a04-a707-88ef6e6044d7.png) +](https://youtu.be/iyuo0ymTt4g?t=1525) + + +```js +const makeMint = () => { + const ledger = makeWeakMap(); + + const issuer = harden({ + makeEmptyPurse: () => mint.makePurse(0), + }); + + const mint = harden({ + makePurse: initialBalance => { + const purse = harden({ + getIssuer: () => issuer, + getBalance: () => ledger.get(purse), + + deposit: (amount, src) => { + Nat(ledger.get(purse) + Nat(amount)); + ledger.set(src, Nat(ledger.get(src) - amount)); + ledger.set(purse, ledger.get(purse) + amount); + }, + withdraw: amount => { + const newPurse = issuer.makeEmptyPurse(); + newPurse.deposit(amount, purse); + return newPurse; + }, + }); + ledger.set(purse, initialBalance); + return purse; + }, + }); + + return mint; +}; +``` + +## Agoric JavaScript APIs + + - [ERTP Introduction](https://agoric.com/documentation/getting-started/ertp-introduction.html#creating-assets-with-ertp) + - [Introduction to Zoe](https://agoric.com/documentation/getting-started/intro-zoe.html#what-is-zoe) + - [Remote object communication with E\(\)](https://agoric.com/documentation/guides/js-programming/eventual-send.html) + +## Appendix / Colophon: Fodder / Brainstorm + + - structure from [E in a Walnut](http://www.skyhunter.com/marcs/ewalnut.html#SEC8) + - as adapted in [Practical Security: The Mafia game — Monte 0\.1 documentation](https://monte.readthedocs.io/en/latest/ordinary-programming.html) + - JSON / Justin / Jessie as in [Jessica](https://github.com/agoric-labs/jessica) + - Build slides with [Remark](https://remarkjs.com/#1) + - example: [kumc\-bmi/naaccr\-tumor\-data](https://github.com/kumc-bmi/naaccr-tumor-data) diff --git a/main/platform/json-justin-data-expr.md b/main/platform/json-justin-data-expr.md new file mode 100644 index 000000000..16ff95ea1 --- /dev/null +++ b/main/platform/json-justin-data-expr.md @@ -0,0 +1,290 @@ + +## DIY Data Structures + +Using only the object patterns we have seen so far: + +```js +const makeFlexList = () => { + let head; + let tail; + const list = harden({ + push: item => { + const cell = { item, next: undefined, previous: tail }; + tail = cell; + if (head === undefined) { + head = cell; + } + }, + at: target => { + if (!Number.isInteger(target) || target < 0) { + throw RangeError(target); + } + const position = 0; + for (let cell = head; cell !== undefined; cell = cell.next) { + if (position === target) { + return cell.item; + } + position += 1; + } + return undefined; + }, + forEach: f => { + let position = 0; + for (let cell = head; cell !== undefined; cell = cell.next) { + f(cell.item, position, list); + position += 1; + } + return false; + }, + }); + return list; +}; +``` + +_TODO: harden the cells? They don't escape, so technically we don't need to._ + +## Built-in Data Structures: Array + + - syntax: `[...]` expressions + - built-in `Array` objects + - methods: `indexOf`, `reverse`, `sort`, ... + - full API: [Array in MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) + +## `Array` and `harden` + +**WARNING: mutable by default**: + +```js +const makeGame = (...players) => { + // TODO: harden(players) + return harden({ + playing: () => players, + }); +}; +``` + +Game creator: +``` +> const g1 = makeGame('alice', 'bob'); +``` + +Malicious client: +``` +> const who = g1.playing(); +> who.push('mallory'); g1.playing() +[ 'alice', 'bob', 'mallory' ] +``` + +So we `harden(players)` before we `return ...`: + +``` +> who.push('mallory'); +Uncaught TypeError: Cannot add property 2, object is not extensible +``` + +## Built-in Data Structures: Map + +`m.set(key, value)`, `m.get(key)`, `m.has(key)`: + +``` +> const m1 = makeMap([['alice', 1], ['bob', 2], ['charlie', 3]]) +Map(3) { 'alice' => 1, 'bob' => 2, 'charlie' => 3 } +> m1.has('bob') +true +> m1.get('charlie') +3 +> m1.set('dave', 'pickles') +Map(4) { + 'alice' => 1, + 'bob' => 2, + 'charlie' => 3, + 'dave' => 'pickles' +} +``` + +Full API: [Map \- JavaScript \| MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) + +**NOTE:** `makeMap()` is a Jessie library function +because `new Map()` is not included in Jessie. + +## Built-in Data Structures: Set + +`s.delete(key)`, `s.size`: (_also on `Map`_) + +``` +> const s1 = makeSet([1, 2, 3]); +Set(3) { 1, 2, 3 } +> s1.has(2) +true +> s1.delete(2) +true +> s1.size +2 +> [...s1] +[ 1, 3 ] +``` + +Full API: [Set \- JavaScript \| MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) + +## Reference: JSON for Data + +JSON is a ubiquitous language for structured data: + + - Literals: + - `1, 2, 3, true, false, "abc", null` + - `"emoji: \uD83D\uDC36"`, `"🐶"` + - Array: `[1, true, "three"]` + - Record (aka Object): `{ "size": 5, "color": "blue" }` + + +## Reference: Justin for Safe Expressions + + - more flexible syntax: + - string delimiters: `"abc"` or `'abc'` + - trailing commas: `{ size: 1, }`, `[1, 2, 3,]` + - un-quoted property names: `{ size: 1, color: "blue" }` + - short-hand properties: `{ size }` + - comments: + - single line: `// ...` + - multi-line: `/* ... */` + - property lookup: `{ size: 1 }.size` + - array index: `[1, 2, 3][2]` + + +## Justin: both bottom values (**:-/**) + + - `null` + - `undefined` + +## Justin: function and variable use + +In an environment with pre-declared variables: + + - variable use: `x` + - excludes reserved words / keywords: `if`, `else`, ... + - function call: `sqrt(2)`, `inventory.total()` + - spread: `sum(1, 2, ...rest)`, `{ color: 'red', ...options }` + +## Justin: Quasi-Literals + + - interpolation: `the answer is: ${a}` + - tagged templates: ``html`${linkText}` `` + +## Justin: operators + + - add, subtract, etc.: `+`, `-`, `*`, `/`, `%` + - parens `(x + 4) * y` + - comparison: `===`, `!==`, `<`, `>`, `<=`, `>=` + - **no `==`!** + - relational: `&&`, `||` + - bitwise: `&`, `|`, `^` + - shift: `<<`, `>>` + - unsigned: `>>>` + - runtime type check: `typeof thing === "string"` + - conditional (ternary operator): ``a > b ? a : b`` + - void operator: `void ignoreMyResult()` + +## Jessie: operators + + - pre-increment, decrement: `++x`, `--x` + - post-increment, decrement: `x++`, `x--` + +## Reference: Jessie for simple, universal safe mobile code + +statements, declarations: + + - end with `;` + - _TODO: tooling: Agoric lint configuration, based on Airbnb; @jessie-check?_ + - arrow functions: + - simple: `const double = x => x * 2;` + - compound: + +```js + const f = (a, b) => { + g(a); + h(b); + return a + b; + }; +``` + + - bindings: `const c = a + b;`, `let x = 1;` + - assignment: `x = x + 2`; + - combined: `x += 2`; + - `if (cond) { /* then block */ } else { /* else block */ }` + - `switch`: + +```js + switch (value) { + case 'a': + case 'b': { + /* 'a' and 'b' block */ + break; + } + default: { + /* default block */ + } + } +```// TODO: delete this comment to close the js fence + - `while (condition) { /* body */ }` + - `break`, `continue` + - `try` / `catch` / `finally` + - `for (const x of items) { /* body */ }` + - **no `for (const x in items)`!** Use `for (const x of Object.keys(items))` instead + +_TODO: accessor methods?_ + + +## Destructuring Assignments, Patterns, and Shorthand Properties + +```js +const s = order.size; // better as... +const { size: s } = order; + +const size = order.size; // better as... +const { size } = order; + +const s1 = sizes[0]; +const s2 = sizes[1]; +const rest = sizes.slice(2); // better as... + +const [s1, s2, ...rest] = sizes; + +// combine them and go nuts +const [{ size: s1, color, ...details }, { size: s2 }] = orders; + +``` + +## Parameters: destructuring, optional, defaults + +```js +const serviceOrder = ({ size, shape }) => { + ... +}; +``` + +```js +const f = (a, opt) => { + if (typeof opt !== 'undefined') { + ... + } +}; +``` + +```js +const f = (a, b = 0) => { +} +``` + +_TODO: examples that concretely motivate these features_ + +## Modules + + - import: + - `import harden from '@agoric/harden';` + - `import { Nat } from '@agoric/nat';` + - `import { Nat as N } from '@agoric/nat';` + - `import * as fs from 'fs';` + - export: + - `export const f = ...`; + - `export default f;` + - `export { f, g, h};`