Skip to content

Commit

Permalink
Merge branch 'main' into rjwebb/update-message-type
Browse files Browse the repository at this point in the history
  • Loading branch information
rjwebb committed Feb 12, 2025
2 parents 61475f1 + 33ff05f commit 4f2ba98
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 99 deletions.
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as json from "@ipld/dag-json"
import { StatusCodes } from "http-status-codes"

import type { Action, Message, Session, SessionSigner, Signer } from "@canvas-js/interfaces"
import type { Action, Message, MessageType, Session, SessionSigner, Signer } from "@canvas-js/interfaces"
import { assert } from "@canvas-js/utils"

export class Client {
Expand Down Expand Up @@ -60,7 +60,7 @@ export class Client {
})
}

private async insert(delegateSigner: Signer<Action | Session>, message: Message<Action | Session>): Promise<string> {
private async insert(delegateSigner: Signer<MessageType>, message: Message<MessageType>): Promise<string> {
const signature = await delegateSigner.sign(message)

const res = await fetch(`${this.host}/api/messages`, {
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@libp2p/logger": "^5.1.7",
"@libp2p/peer-id": "^5.0.11",
"@multiformats/multiaddr": "^12.3.4",
"@noble/curves": "^1.8.1",
"@noble/hashes": "^1.7.1",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
Expand Down
19 changes: 5 additions & 14 deletions packages/core/src/Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,7 @@ import { logger } from "@libp2p/logger"
import type pg from "pg"
import type { SqlStorage } from "@cloudflare/workers-types"

import {
Signature,
Action,
Session,
Message,
MessageType,
Snapshot,
SessionSigner,
SignerCache,
Updates,
} from "@canvas-js/interfaces"
import { Signature, Action, Message, Snapshot, SessionSigner, SignerCache, MessageType } from "@canvas-js/interfaces"
import { AbstractModelDB, Model, ModelSchema, Effect } from "@canvas-js/modeldb"
import { SIWESigner } from "@canvas-js/chain-ethereum"
import { AbstractGossipLog, GossipLogEvents, SignedMessage } from "@canvas-js/gossiplog"
Expand Down Expand Up @@ -159,9 +149,9 @@ export class Canvas<
let resultCount: number
let start: string | undefined = undefined
do {
const results: { id: string; message: Message<Action | Session | Updates> }[] = await db.query<{
const results: { id: string; message: Message<MessageType> }[] = await db.query<{
id: string
message: Message<Action | Session | Updates>
message: Message<MessageType>
}>("$messages", {
limit,
select: { id: true, message: true },
Expand Down Expand Up @@ -249,11 +239,12 @@ export class Canvas<
for (const name of runtime.actionNames) {
const action = async (signer: SessionSigner<any> | null, ...args: any) => {
this.log("executing action %s %o", name, args)
const timestamp = Date.now()

const sessionSigner = signer ?? signers.getFirst()
assert(sessionSigner !== undefined, "signer not found")

const timestamp = sessionSigner.getCurrentTimestamp()

this.log("using session signer %s", sessionSigner.key)
let session = await sessionSigner.getSession(this.topic)

Expand Down
1 change: 0 additions & 1 deletion packages/core/src/ExecutionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export class ExecutionContext {

constructor(
public readonly messageLog: AbstractGossipLog<MessageType>,
// TODO: why is this just action? should it be `SignedMessage<Action | Updates>`?
public readonly signedMessage: SignedMessage<Action>,
public readonly address: string,
) {
Expand Down
80 changes: 0 additions & 80 deletions packages/core/test/_signers.test.ts

This file was deleted.

34 changes: 34 additions & 0 deletions packages/core/test/signers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import test from "ava"

import { Canvas } from "@canvas-js/core"
import { PRNGSigner } from "./utils.js"
import { setTimeout } from "timers/promises"

test("test PRNGSigner", async (t) => {
const app1 = await Canvas.initialize({
topic: "com.example.app",
contract: {
models: {},
actions: { hello: () => {} },
},
signers: [new PRNGSigner(0)],
})

const app2 = await Canvas.initialize({
topic: "com.example.app",
contract: {
models: {},
actions: { hello: () => {} },
},
signers: [new PRNGSigner(0)],
})

for (let i = 0; i < 3; i++) {
await app1.actions.hello()
await setTimeout(10)
await app2.actions.hello()
await setTimeout(10)
}

t.deepEqual(await app1.db.query("$messages"), await app2.db.query("$messages"))
})
88 changes: 88 additions & 0 deletions packages/core/test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import fs from "node:fs"
import path from "node:path"

import test, { ExecutionContext } from "ava"

import { nanoid } from "nanoid"
import Prando from "prando"
import * as cbor from "@ipld/dag-cbor"
import { ed25519 as ed25519curve } from "@noble/curves/ed25519"

import type { Session, AbstractSessionData, DidIdentifier } from "@canvas-js/interfaces"
import { AbstractSessionSigner, ed25519, encodeURI, decodeURI } from "@canvas-js/signatures"
import { Canvas, Config } from "@canvas-js/core"
import { assert } from "@canvas-js/utils"

export function getDirectory(t: ExecutionContext<unknown>): string {
const directory = path.resolve(os.tmpdir(), nanoid())
Expand Down Expand Up @@ -48,3 +55,84 @@ export const testPlatforms = (
// return log
// })
}

const randomBytes = (prng: Prando.default, length: number) => {
const result = new Uint8Array(length)
for (let i = 0; i < length; i++) result[i] = prng.nextInt(0, 255)
return result
}

export class PRNGSigner extends AbstractSessionSigner<{ signature: Uint8Array }> {
public static addressPattern = /^did:key:[a-zA-Z0-9]+$/
public readonly match = (address: string) => PRNGSigner.addressPattern.test(address)

public readonly key: string
public readonly privateKey: Uint8Array
public readonly publicKey: Uint8Array
public readonly did: string

public constructor(seed: number | string = 0) {
const prng = new Prando.default(seed)
super("prng", {
type: ed25519.type,
codecs: ed25519.codecs,
verify: ed25519.verify,
create: (init) => ed25519.create(init ?? { type: ed25519.type, privateKey: randomBytes(prng, 32) }),
})
this.privateKey = randomBytes(prng, 32)
this.publicKey = ed25519curve.getPublicKey(this.privateKey)
this.did = encodeURI("ed25519", this.publicKey)
this.key = "prng"
}

public getCurrentTimestamp(): number {
return 0
}

public async getDid(): Promise<DidIdentifier> {
return this.did as DidIdentifier
}

public getDidParts(): number {
return 3
}

public getAddressFromDid(did: DidIdentifier) {
return did.split(":").pop()!
}

public async authorize(sessionData: AbstractSessionData): Promise<Session<{ signature: Uint8Array }>> {
const {
topic,
did,
context: { timestamp, duration },
publicKey,
} = sessionData

assert(duration === null)

const data = cbor.encode({ topic, timestamp, did, publicKey })
const signature = ed25519curve.sign(data, this.privateKey)

return {
type: "session",
did: did,
publicKey: publicKey,
authorizationData: { signature },
context: { timestamp },
}
}

public verifySession(topic: string, session: Session<{ signature: Uint8Array }>) {
const {
publicKey,
did,
authorizationData: { signature },
context: { timestamp, duration },
} = session

const key = decodeURI(publicKey)
const data = cbor.encode({ topic, timestamp, did, publicKey })
ed25519curve.verify(signature, data, key.publicKey)
}
}
1 change: 1 addition & 0 deletions packages/interfaces/src/SessionSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface SessionSigner<AuthorizationData = any> {
getDid: () => Awaitable<DidIdentifier>
getDidParts: () => number
getAddressFromDid: (did: DidIdentifier) => string
getCurrentTimestamp: () => number

hasSession: (topic: string, did: DidIdentifier) => boolean
getSession: (
Expand Down
2 changes: 1 addition & 1 deletion packages/interfaces/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export * from "./Message.js"
export * from "./Session.js"
export * from "./Snapshot.js"
export * from "./Awaitable.js"
export * from "./Updates.js"
export * from "./MessageType.js"
export * from "./Updates.js"
9 changes: 9 additions & 0 deletions packages/modeldb/test/operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,12 @@ testOnModelDB("set and get a relation on a composite primary key", async (t, ope
],
})
})

testOnModelDB("set and get top-level string JSON values", async (t, openDB) => {
const db = await openDB(t, {
foo: { id: "primary", value: "json" },
})

await db.set("foo", { id: "abc", value: "hello world" })
t.deepEqual(await db.get("foo", "abc"), { id: "abc", value: "hello world" })
})
6 changes: 5 additions & 1 deletion packages/signatures/src/AbstractSessionSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export abstract class AbstractSessionSigner<
*/
public abstract authorize(data: AbstractSessionData): Awaitable<Session<AuthorizationData>>

public getCurrentTimestamp() {
return Date.now()
}

/**
* Start a new session, either by requesting a signature from a wallet right now,
* or by using a provided AuthorizationData and timestamp (for services like Farcaster).
Expand All @@ -80,7 +84,7 @@ export abstract class AbstractSessionSigner<
did,
publicKey: signer.publicKey,
context: {
timestamp: Date.now(),
timestamp: this.getCurrentTimestamp(),
duration: this.sessionDuration,
},
}
Expand Down

0 comments on commit 4f2ba98

Please sign in to comment.