Skip to content

Instantly share code, notes, and snippets.

@jamesdiacono
Last active February 11, 2023 03:01
Show Gist options
  • Save jamesdiacono/a542527e84ddd538582ff6c8384d7556 to your computer and use it in GitHub Desktop.
Save jamesdiacono/a542527e84ddd538582ff6c8384d7556 to your computer and use it in GitHub Desktop.
Safely translate capabilities between string and object reference representations, in JavaScript.
// capability_sealer.js
// James Diacono
// 2023-02-11
// Public Domain
// A capability sealer is a two-way mapping between strings and opaque object
// references. A capability's string representation is suitable for transmission
// over the network, whereas its object representation is suitable for use
// within a single memory address space.
// Capabilities with matching string representations are guaranteed to have
// identical object references, making it trivial to test for equality.
// This module exports a constructor function
// make_capability_sealer(mint_ref, mint_string)
// that returns an object with two methods:
// seal(string)
// Returns an object reference unique to the capability string.
// unseal(ref)
// Returns a string unique to the capability object reference.
// The constructor's two parameters 'mint_ref' and 'mint_string' are functions
// that return new, unique capabilities.
// Here is how a capability sealer can be used:
// function mint_ref() {
// return Object.freeze({});
// }
// function mint_string() {
// return String(Math.random());
// }
// const {seal, unseal} = make_capability_sealer(mint_ref, mint_string);
// const apple = seal("apple");
// const mango = seal("mango");
// const peach = mint_ref();
// seal("apple") === apple // true
// seal("mango") === apple // false
// unseal(apple) === "apple" // true
// unseal(mango) === "apple" // false
// unseal(peach) // e.g. "0.8220673618476588"
function ReverseWeakMap() {
// A WeakMap weakly references its keys, but there is no built-in structure that
// weakly references its values. To make such a structure, we are forced to
// manage memory directly using the WeakRef and FinalizationRegistry constructs.
// The keys of a ReverseWeakMap must be primitive values, and its values must be
// objects (or functions). In all other respects, ReverseWeakMap's interface is
// identical to that of WeakMap.
let object = Object.create(null);
let registry = new FinalizationRegistry(function on_garbage_collected(key) {
if (object[key] !== undefined && object[key].deref() === undefined) {
delete object[key];
}
});
return Object.freeze({
delete(key) {
delete object[key];
},
get(key) {
if (object[key] !== undefined) {
return object[key].deref();
}
},
has(key) {
return (
object[key] !== undefined
&& object[key].deref() !== undefined
);
},
set(key, value) {
object[key] = new WeakRef(value);
registry.register(value, key);
}
});
}
function make_capability_sealer(mint_ref, mint_string) {
// To maintain an efficient two-way mapping, we index by both object reference
// and string. WeakMap and ReverseWeakMap are used to ensure that objects or
// strings no longer in use can be garbage collected.
let strings = new WeakMap();
let refs = new ReverseWeakMap();
return Object.freeze({
seal(string) {
let ref = refs.get(string);
if (ref === undefined && mint_ref !== undefined) {
ref = mint_ref();
strings.set(ref, string);
refs.set(string, ref);
}
return ref;
},
unseal(ref) {
let string = strings.get(ref);
if (string === undefined && mint_string !== undefined) {
string = mint_string();
strings.set(ref, string);
refs.set(string, ref);
}
return string;
}
});
}
export default Object.freeze(make_capability_sealer);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment