Skip to content

Commit 3d8b119

Browse files
authored
feat: signing and verification of memory invocations (#433)
1 parent 5949a1c commit 3d8b119

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2459
-1267
lines changed

.prettierignore

Lines changed: 0 additions & 7 deletions
This file was deleted.

.prettierrc.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

typescript/packages/clipper-extension/src/background.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-nocheck
12
import browser from "webextension-polyfill";
23

34
browser.runtime.onInstalled.addListener(() => {

typescript/packages/common-charm/src/charm.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { storage } from "./storage.ts";
2323
import { syncRecipeBlobby } from "./syncRecipe.ts";
2424
import { getSpace, Space } from "@commontools/runner";
25+
import { DID, Identity, Signer } from "@commontools/identity";
2526

2627
export type Charm = {
2728
[NAME]?: string;
@@ -85,11 +86,25 @@ export class CharmManager {
8586
private charms: Cell<Cell<Charm>[]>;
8687
private pinnedCharms: Cell<Cell<Charm>[]>;
8788

88-
constructor(private spaceId: string) {
89+
static async open(
90+
{ space, signer }: { space: DID; signer?: Signer },
91+
) {
92+
return new this(
93+
space,
94+
signer ?? await Identity.fromPassphrase("charm manager"),
95+
);
96+
}
97+
98+
constructor(
99+
private spaceId: string,
100+
private signer: Signer,
101+
) {
89102
this.space = getSpace(this.spaceId);
90103
this.charmsDoc = getDoc<DocLink[]>([], "charms", this.space);
91104
this.pinned = getDoc<DocLink[]>([], "pinned-charms", this.space);
92105
this.charms = this.charmsDoc.asCell([], undefined, charmListSchema);
106+
107+
storage.setSigner(signer);
93108
this.pinnedCharms = this.pinned.asCell([], undefined, charmListSchema);
94109
}
95110

typescript/packages/common-charm/src/storage.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import {
1515
Space,
1616
useCancelGroup,
1717
} from "@commontools/runner";
18+
1819
import { isStatic, markAsStatic } from "@commontools/builder";
1920
import { StorageProvider, StorageValue } from "./storage/base.ts";
2021
import { RemoteStorageProvider } from "./storage/remote.ts";
2122
import { debug } from "@commontools/html"; // FIXME(ja): can we move debug to somewhere else?
2223
import { InMemoryStorageProvider } from "./storage/memory.ts";
24+
import { Signer } from "@commontools/identity";
2325

2426
export function log(fn: () => any[]) {
2527
debug(() => {
@@ -52,6 +54,8 @@ export interface Storage {
5254
*/
5355
setRemoteStorage(url: URL): void;
5456

57+
setSigner(signer: Signer): void;
58+
5559
/**
5660
* Load cell from storage. Will also subscribe to new changes.
5761
*
@@ -157,6 +161,8 @@ class StorageImpl implements Storage {
157161
private storageProviders = new Map<Space, StorageProvider>();
158162
private remoteStorageUrl: URL | undefined;
159163

164+
private signer: Signer | undefined;
165+
160166
// Map from entity ID to doc, set at stage 2, i.e. already while loading
161167
private docsById = new Map<string, DocImpl<any>>();
162168

@@ -196,6 +202,10 @@ class StorageImpl implements Storage {
196202
this.remoteStorageUrl = url;
197203
}
198204

205+
setSigner(signer: Signer): void {
206+
this.signer = signer;
207+
}
208+
199209
syncCellById<T>(
200210
space: Space,
201211
id: EntityId | string,
@@ -240,6 +250,7 @@ class StorageImpl implements Storage {
240250
// TODO(seefeld,gozala): Should just be one again.
241251
private _getStorageProviderForSpace(space: Space): StorageProvider {
242252
if (!space) throw new Error("No space set");
253+
if (!this.signer) throw new Error("No signer set");
243254

244255
let provider = this.storageProviders.get(space);
245256

@@ -258,6 +269,7 @@ class StorageImpl implements Storage {
258269
provider = new RemoteStorageProvider({
259270
address: new URL("/api/storage/memory", this.remoteStorageUrl!),
260271
space: space.uri as `did:${string}:${string}`,
272+
as: this.signer,
261273
});
262274
} else if (type === "memory") {
263275
provider = new InMemoryStorageProvider(space.uri);
@@ -578,7 +590,7 @@ class StorageImpl implements Storage {
578590
}
579591
}
580592
log(
581-
() => ["loading", [...loading].map((c) => JSON.stringify(c.entityId))],
593+
() => ["loading", [...loading].map((c) => JSON.stringify(c.entityId))]
582594
);
583595
log(() => [
584596
"docIsLoading",

typescript/packages/common-charm/src/storage/remote.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import type {
55
Entity,
66
JSONValue,
77
MemorySpace,
8+
Protocol,
9+
UCAN,
810
} from "@commontools/memory/interface";
911
import * as Memory from "@commontools/memory/consumer";
1012
import { assert } from "@commontools/memory/fact";
1113
import * as Changes from "@commontools/memory/changes";
1214
export * from "@commontools/memory/interface";
15+
import * as Codec from "@commontools/memory/codec";
1316

1417
/**
1518
* Represents a state of the memory space.
@@ -36,36 +39,36 @@ interface MemoryState<Space extends MemorySpace = MemorySpace> {
3639
* ed25519 key derived from the sha256 of the "common knowledge".
3740
*/
3841
const HOME = "did:key:z6Mko2qR9b8mbdPnaEKXvcYwdK7iDnRkh8mEcEP2719aCu6P";
39-
/**
40-
* ed25519 key derived from the sha256 of the "common operator".
41-
*/
42-
const AS = "did:key:z6Mkge3xkXc4ksLsf8CtRxunUxcX6dByT4QdWCVEHbUJ8YVn";
42+
4343
export class RemoteStorageProvider implements StorageProvider {
4444
connection: WebSocket | null = null;
4545
address: URL;
4646
workspace: MemorySpace;
4747
the: string;
4848
state: Map<MemorySpace, MemoryState> = new Map();
49-
session: Memory.MemorySession;
49+
session: Memory.MemorySession<MemorySpace>;
5050

5151
/**
5252
* queue that holds commands that we read from the session, but could not
5353
* send because connection was down.
5454
*/
55-
queue: Set<Memory.ConsumerCommand<Memory.Protocol>> = new Set();
55+
queue: Set<UCAN<Memory.ConsumerCommandInvocation<Memory.Protocol>>> =
56+
new Set();
5657
writer: WritableStreamDefaultWriter<Memory.ProviderCommand<Memory.Protocol>>;
57-
reader: ReadableStreamDefaultReader<Memory.ConsumerCommand<Memory.Protocol>>;
58+
reader: ReadableStreamDefaultReader<
59+
UCAN<Memory.ConsumerCommandInvocation<Memory.Protocol>>
60+
>;
5861

5962
connectionCount = 0;
6063

6164
constructor({
6265
address,
63-
as = AS,
66+
as,
6467
space = HOME,
6568
the = "application/json",
6669
}: {
6770
address: URL;
68-
as?: Memory.Principal;
71+
as: Memory.Signer;
6972
space?: MemorySpace;
7073
the?: string;
7174
}) {
@@ -74,6 +77,7 @@ export class RemoteStorageProvider implements StorageProvider {
7477
this.the = the;
7578

7679
const session = Memory.create({ as });
80+
7781
this.reader = session.readable.getReader();
7882
this.writer = session.writable.getWriter();
7983
this.session = session;
@@ -199,6 +203,7 @@ export class RemoteStorageProvider implements StorageProvider {
199203
local.set(of, fact);
200204
facts.push(fact);
201205
}
206+
202207

203208
const result = await memory.transact({ changes: Changes.from(facts) });
204209

@@ -233,7 +238,7 @@ export class RemoteStorageProvider implements StorageProvider {
233238
}
234239

235240
receive(data: string) {
236-
return this.writer.write(JSON.parse(data));
241+
return this.writer.write(Codec.Receipt.fromString(data));
237242
}
238243

239244
handleEvent(event: MessageEvent) {
@@ -283,7 +288,7 @@ export class RemoteStorageProvider implements StorageProvider {
283288
while (this.connection === socket) {
284289
// First drain the queued commands if we have them.
285290
for (const command of queue) {
286-
socket.send(JSON.stringify(command));
291+
socket.send(Codec.UCAN.toString(command));
287292
queue.delete(command);
288293
}
289294

@@ -299,7 +304,7 @@ export class RemoteStorageProvider implements StorageProvider {
299304
// Now we make that our socket is still a current connection as we may
300305
// have lost connection while waiting to read a command.
301306
if (this.connection === socket) {
302-
socket.send(JSON.stringify(command));
307+
socket.send(Codec.UCAN.toString(command));
303308
} // If it is no longer our connection we simply add the command into a
304309
// queue so it will be send once connection is reopen.
305310
else {
@@ -391,7 +396,7 @@ export interface Subscriber {
391396
class Query<Space extends MemorySpace> {
392397
reader: ReadableStreamDefaultReader;
393398
constructor(
394-
public query: Memory.QueryView<Space>,
399+
public query: Memory.QueryView<Space, Protocol<Space>>,
395400
public subscribers: Set<Subscriber> = new Set(),
396401
) {
397402
this.reader = query.subscribe().getReader();

typescript/packages/common-charm/test/storage.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { storage } from "../src/storage.ts";
44
import { StorageProvider } from "../src/storage/base.ts";
55
import { InMemoryStorageProvider } from "../src/storage/memory.ts";
66
import { createRef, DocImpl, getDoc, getSpace } from "@commontools/runner";
7+
import { Identity } from "@commontools/identity";
78

89
storage.setRemoteStorage(new URL("memory://"));
10+
storage.setSigner(await Identity.fromPassphrase("test operator"));
911

1012
describe("Storage", () => {
1113
let storage2: StorageProvider;

typescript/packages/common-cli/charm_test.ts renamed to typescript/packages/common-cli/charm_demo.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { DocImpl, getDoc } from "../common-runner/src/doc.ts";
1111
import { EntityId } from "../common-runner/src/doc-map.ts";
1212
import { storage } from "../common-charm/src/storage.ts";
1313
import { getSpace, Space } from "../common-runner/src/space.ts";
14+
import { Identity } from "../common-identity/src/index.ts";
1415

15-
const replica = "ellyse7";
1616
const TOOLSHED_API_URL = "https://toolshed.saga-castor.ts.net/";
1717

1818
// simple log function
@@ -26,7 +26,7 @@ function createCell(space: Space): Cell<Charm> {
2626
const myCharm: Charm = {
2727
NAME: "mycharm",
2828
UI: "someui",
29-
"somekey": "some value",
29+
somekey: "some value",
3030
};
3131

3232
// make this a DocImpl<Charm> because we need to return a Cell<Charm> since
@@ -40,12 +40,16 @@ function createCell(space: Space): Cell<Charm> {
4040
}
4141

4242
async function main() {
43+
const authority = await Identity.fromPassphrase("charm manager");
4344
// create a charm manager to start things off
44-
const charmManager = new CharmManager(replica);
45+
const charmManager = await CharmManager.open({
46+
space: authority.did(),
47+
signer: authority,
48+
});
4549
log(charmManager, "charmManager");
4650

4751
// let's try to create a cell
48-
const space: Space = getSpace(replica);
52+
const space: Space = getSpace(authority.did());
4953
const cell: Cell<Charm> = createCell(space);
5054
log(cell.get(), "cell value from Cell.get()");
5155

typescript/packages/common-cli/cli_test.ts

Whitespace-only changes.

typescript/packages/common-cli/main.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
storage,
88
} from "@commontools/charm";
99
import { getEntityId, isStream } from "@commontools/runner";
10+
import { Identity } from "@commontools/identity";
1011

1112
let { space, charmId, recipeFile, cause } = parse(Deno.args);
1213

@@ -17,10 +18,12 @@ storage.setRemoteStorage(new URL(toolshedUrl));
1718
setBobbyServerUrl(toolshedUrl);
1819

1920
async function main() {
20-
if (!space) space = "common-cli";
2121
console.log("params:", { space, charmId, recipeFile, cause });
22-
23-
const manager = new CharmManager(space);
22+
const identity = await Identity.fromPassphrase("common-cli");
23+
const manager = await CharmManager.open({
24+
space: space ?? identity.did(),
25+
signer: identity,
26+
});
2427
const charms = await manager.getCharms();
2528

2629
charms.sink((charms) => {

0 commit comments

Comments
 (0)