-
-
Save erights/342cd5104b2aa8e386ee1890dc98d218 to your computer and use it in GitHub Desktop.
Horton in JavaScript: delegation with blame attribution in an object-capability language
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Horton in JavaScript: delegation with blame attribution in an object-capability language | |
* | |
* See http://erights.org/elib/capability/horton/index.html for idea and paper. | |
* | |
* Implementation based on: http://erights.org/elib/capability/horton | |
* (with N-ary message support, lexical nesting and rights amplification) | |
* | |
* To run: | |
* | |
* ``` | |
* npm install lavamoat | |
* npx lavamoat horton.js | |
* ``` | |
* | |
* (with N-ary message support, lexical nesting and rights amplification) | |
*/ | |
function makeBrand(label) { | |
const entries = new WeakMap(); | |
function seal(val) { | |
const box = harden({}); | |
entries.set(box, val); | |
return box; | |
} | |
function unseal(box) { | |
if (entries.has(box)) { | |
return entries.get(box); | |
} else { | |
throw new Error("unseal: value not sealed by sealer, got: " + box); | |
} | |
} | |
function getBrand() { | |
return label; | |
} | |
return harden([ | |
{seal, getBrand}, | |
{unseal, getBrand} | |
]); | |
} | |
function makePrincipal(label) { | |
const log = (msg) => { console.log(`[${label}]: ${msg}`) }; | |
const [whoMe, beMe] = makeBrand(label); | |
// Map<proxy, [stub, whoBlame]> | |
const proxies = new WeakMap(); | |
// rights amplification: must know both the proxies map AND the proxy | |
// to retrieve the proxy's guts | |
function getGuts(proxy) { | |
return proxies.get(proxy); // return [stub, whoBlame] | |
} | |
function makeProxy(whoBlame, stub, report) { | |
const log = (msg) => { report(`I ask ${whoBlame.getBrand()} to: ${msg}`) }; | |
const proxy = new Proxy({}, { | |
get(_target, prop, _receiver) { | |
return function(...args) { | |
log(`${prop}/${args.length}`); | |
const pDescs = args.map((p2) => { | |
if (Object(p2) !== p2) { | |
// primitive values carry no authority, pass unwrapped | |
return p2; | |
} | |
if (!proxies.has(p2)) { | |
// getting an object that was not previously wrapped | |
// TODO: need exception for powerless objects (e.g. records) | |
throw new Error(`Argument to ${prop} of ${whoBlame.getBrand()}'s object not properly encoded: ${p2}`); | |
} | |
const [s2, whoCarol] = getGuts(p2); | |
const gs3 = s2.intro(whoBlame); | |
const p3Desc = [gs3, whoCarol]; | |
return p3Desc; | |
}); | |
return stub.deliver(prop, pDescs); | |
} | |
} | |
}); | |
proxies.set(proxy, [stub, whoBlame]); | |
return proxy; | |
} | |
function wrap(s3, whoBob, beCarol) { | |
function provide(fillBox) { | |
const fill = beCarol.unseal(fillBox); | |
fill(s3); | |
} | |
return whoBob.seal(provide); | |
} | |
function unwrap(gs3, whoCarol, beBob) { | |
const provide = beBob.unseal(gs3); | |
let result = null; | |
function fill(s3) { | |
result = s3; | |
} | |
const fillBox = whoCarol.seal(fill); | |
provide(fillBox); | |
return result; | |
} | |
function makeStub(beMe, whoBlame, targ, report) { | |
const log = (msg) => { report(`${whoBlame.getBrand()} asks me to: ${msg}`); }; | |
const stub = harden({ | |
intro(whoBob) { | |
log(`meet ${whoBob.getBrand()}`); | |
const s3 = makeStub(beMe, whoBob, targ, report); | |
return wrap(s3, whoBob, beMe); | |
}, | |
deliver(prop, pDescs) { | |
log(`${prop}/${pDescs.length}`); | |
const args = pDescs.map((p3Desc) => { | |
if (Object(p3Desc) !== p3Desc) { | |
// primitive values carry no authority, no need to unwrap | |
return p3Desc; | |
} | |
const [gs3, whoCarol] = p3Desc; | |
const s3 = unwrap(gs3, whoCarol, beMe); | |
const p3 = makeProxy(whoCarol, s3, report); | |
return p3; | |
}); | |
return targ[prop].apply(targ, args); | |
} | |
}); | |
return stub; | |
} | |
const principal = harden({ | |
toString() { | |
return `[principal: ${label}]`; | |
}, | |
who() { | |
return whoMe; | |
}, | |
encodeFor(targ, whoBlame) { | |
const stub = makeStub(beMe, whoBlame, targ, log); | |
return wrap(stub, whoBlame, beMe) | |
}, | |
decodeFrom(gift, whoBlame) { | |
const stub = unwrap(gift, whoBlame, beMe); | |
return makeProxy(whoBlame, stub, log); | |
} | |
}); | |
return principal; | |
} | |
function test() { | |
const alice = makePrincipal("Alice"); | |
const bob = makePrincipal("Bob"); | |
const carol = makePrincipal("Carol"); | |
const c = harden({ | |
hi() { console.log('hi') } | |
}); | |
const b = harden({ | |
foo(c) { c.hi() } | |
}); | |
function makeA(b, c) { | |
const a = harden({ | |
start() { b.foo(c) } | |
}); | |
return a; | |
} | |
const gs1 = bob.encodeFor(b, alice.who()); | |
const gs2 = carol.encodeFor(c, alice.who()); | |
const p1 = alice.decodeFrom(gs1, bob.who()); | |
const p2 = alice.decodeFrom(gs2, carol.who()); | |
const a = makeA(p1, p2); | |
// we now have: | |
// Alice: | |
// a's b --> p1 --> [Bob]b | |
// a's c --> p2 --> [Carol]c | |
// Bob: | |
// gs1 --> [Alice]b --> b | |
// Carol: | |
// gs2 --> [Alice]c --> c | |
a.start(); | |
// This should print: | |
// [Alice]: I ask Bob to: foo/1 | |
// [Carol]: Alice asks me to: meet Bob | |
// [Bob]: Alice asks me to: foo/1 | |
// [Bob]: I ask Carol to: hi/0 | |
// [Carol]: Bob asks me to: hi/0 | |
// hi | |
} | |
test(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment