Skip to content

Instantly share code, notes, and snippets.

@erights
Forked from tvcutsem/horton.js
Created May 26, 2023 05:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erights/342cd5104b2aa8e386ee1890dc98d218 to your computer and use it in GitHub Desktop.
Save erights/342cd5104b2aa8e386ee1890dc98d218 to your computer and use it in GitHub Desktop.
Horton in JavaScript: delegation with blame attribution in an object-capability language
/*
* 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