Skip to content

Commit

Permalink
Add event container
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulLeCam committed May 28, 2024
1 parent 1a42947 commit fede098
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 50 deletions.
54 changes: 54 additions & 0 deletions packages/events/src/container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { type Decoder, decode } from 'codeco'
import type { DID, VerifyJWSResult } from 'dids'

import { SignedEvent } from './codecs.js'
import { getSignedEventPayload } from './signing.js'

export type SignedEventContainer<Payload> = {
signed: true
payload: Payload
verified: VerifyJWSResult
cacaoBlock?: Uint8Array
}

export type UnsignedEventContainer<Payload> = {
signed: false
payload: Payload
}

export type EventContainer<Payload> =
| SignedEventContainer<Payload>
| UnsignedEventContainer<Payload>

export function unsignedEventToContainer<Payload>(
codec: Decoder<unknown, Payload>,
event: unknown,
): UnsignedEventContainer<Payload> {
return { signed: false, payload: decode(codec, event) }
}

export async function signedEventToContainer<Payload>(
did: DID,
codec: Decoder<unknown, Payload>,
event: SignedEvent,
): Promise<SignedEventContainer<Payload>> {
const cid = event.jws.link
if (cid == null) {
throw new Error('Missing linked block CID')
}
const [verified, payload] = await Promise.all([
did.verifyJWS(event.jws),
getSignedEventPayload(codec, event),
])
return { signed: true, verified, payload, cacaoBlock: event.cacaoBlock }
}

export async function eventToContainer<Payload>(
did: DID,
codec: Decoder<unknown, Payload>,
event: unknown,
): Promise<EventContainer<Payload>> {
return SignedEvent.is(event)
? await signedEventToContainer(did, codec, event)
: unsignedEventToContainer(codec, event)
}
12 changes: 6 additions & 6 deletions packages/events/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ export function eventToString(
return eventToCAR(codec, event).toString(base)
}

export function eventFromCAR<T = unknown>(
decoder: Decoder<unknown, T>,
export function eventFromCAR<Payload = unknown>(
decoder: Decoder<unknown, Payload>,
car: CAR,
): SignedEvent | T {
): SignedEvent | Payload {
const cid = car.roots[0]
const root = car.get(cid)

Expand All @@ -102,11 +102,11 @@ export function eventFromCAR<T = unknown>(
return decode(decoder, root)
}

export function eventFromString<T = unknown>(
decoder: Decoder<unknown, T>,
export function eventFromString<Payload = unknown>(
decoder: Decoder<unknown, Payload>,
value: string,
base: Base = DEFAULT_BASE,
): SignedEvent | T {
): SignedEvent | Payload {
const codec = bases[base]
if (codec == null) {
throw new Error(`Unsupported base: ${base}`)
Expand Down
10 changes: 8 additions & 2 deletions packages/events/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ export {
assertSignedEvent,
decodeSignedEvent,
} from './codecs.js'
export {
type EventContainer,
type SignedEventContainer,
type UnsignedEventContainer,
eventToContainer,
signedEventToContainer,
unsignedEventToContainer,
} from './container.js'
export {
type Base,
encodeEventToCAR,
Expand All @@ -16,9 +24,7 @@ export {
} from './encoding.js'
export {
type PartialInitEventHeader,
type VerifiedEvent,
createSignedInitEvent,
getSignedEventPayload,
signEvent,
verifyEvent,
} from './signing.js'
23 changes: 1 addition & 22 deletions packages/events/src/signing.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type DIDString, asDIDString } from '@didtools/codecs'
import * as dagCbor from '@ipld/dag-cbor'
import { type Decoder, decode } from 'codeco'
import type { DID, VerifyJWSResult } from 'dids'
import type { DID } from 'dids'
import * as Block from 'multiformats/block'
import { sha256 } from 'multiformats/hashes/sha2'

Expand All @@ -11,11 +11,6 @@ import {
type SignedEvent,
} from './codecs.js'

export type VerifiedEvent<Payload = Record<string, unknown>> = VerifyJWSResult &
Payload & {
cacaoBlock?: Uint8Array
}

export async function signEvent(
did: DID,
payload: Record<string, unknown>,
Expand Down Expand Up @@ -66,19 +61,3 @@ export async function getSignedEventPayload<Payload = Record<string, unknown>>(
})
return decode(decoder, block.value)
}

export async function verifyEvent<Payload = Record<string, unknown>>(
did: DID,
codec: Decoder<unknown, Payload>,
event: SignedEvent,
): Promise<VerifiedEvent<Payload>> {
const cid = event.jws.link
if (cid == null) {
throw new Error('Missing linked block CID')
}
const [verified, payload] = await Promise.all([
did.verifyJWS(event.jws),
getSignedEventPayload(codec, event),
])
return { ...verified, ...payload, cacaoBlock: event.cacaoBlock }
}
37 changes: 37 additions & 0 deletions packages/events/test/container.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { randomStreamID } from '@ceramic-sdk/identifiers'
import { getAuthenticatedDID } from '@ceramic-sdk/key-did'
import { asDIDString } from '@didtools/codecs'

import { InitEventPayload } from '../src/codecs.js'
import {
type SignedEventContainer,
eventToContainer,
} from '../src/container.js'
import { signEvent } from '../src/signing.js'

const authenticatedDID = await getAuthenticatedDID(new Uint8Array(32))

const testEventPayload: InitEventPayload = {
data: null,
header: {
controllers: [asDIDString(authenticatedDID.id)],
model: randomStreamID(),
sep: 'test',
},
}

const encodedTestPayload = InitEventPayload.encode(testEventPayload)

test('eventToContainer() verifies the signed event signature and extract the payload', async () => {
const signed = await signEvent(authenticatedDID, encodedTestPayload)
const container = await eventToContainer(
authenticatedDID,
InitEventPayload,
signed,
)
expect(container.signed).toBe(true)
expect(
(container as SignedEventContainer<InitEventPayload>).verified,
).toBeDefined()
expect(container.payload).toEqual(testEventPayload)
})
9 changes: 0 additions & 9 deletions packages/events/test/signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
createSignedInitEvent,
getSignedEventPayload,
signEvent,
verifyEvent,
} from '../src/signing.js'

const authenticatedDID = await getAuthenticatedDID(new Uint8Array(32))
Expand Down Expand Up @@ -44,14 +43,6 @@ test('getSignedEventPayload() returns the EventPayload of a SignedEvent', async
expect(event).toEqual(testEventPayload)
})

test('verifyEvent() verifies the signed event signature and extract the payload', async () => {
const signed = await signEvent(authenticatedDID, encodedTestPayload)
const verified = await verifyEvent(authenticatedDID, InitEventPayload, signed)
expect(verified.didResolutionResult).toBeDefined()
expect(verified.kid).toBeDefined()
expect(verified).toMatchObject(testEventPayload)
})

describe('createSignedInitEvent()', () => {
test('authenticates the DID', async () => {
await expect(async () => {
Expand Down
22 changes: 11 additions & 11 deletions packages/model-handler/src/handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type SignedEvent,
type VerifiedEvent,
verifyEvent,
type SignedEventContainer,
signedEventToContainer,
} from '@ceramic-sdk/events'
import {
ModelInitEventPayload,
Expand All @@ -19,26 +19,26 @@ import {
} from './interfaces-validation.js'
import type { Context } from './types.js'

export async function verifyInitEvent(
export async function getInitEventContainer(
verifier: DID,
event: SignedEvent,
): Promise<VerifiedEvent<ModelInitEventPayload>> {
return await verifyEvent(verifier, ModelInitEventPayload, event)
): Promise<SignedEventContainer<ModelInitEventPayload>> {
return await signedEventToContainer(verifier, ModelInitEventPayload, event)
}

export async function handleInitEvent(
event: SignedEvent,
context: Context,
): Promise<ModelState> {
const verified = await verifyInitEvent(context.verifier, event)
const { payload } = await getInitEventContainer(context.verifier, event)

const metadata = ModelMetadata.encode({
controller: verified.header.controllers[0],
model: verified.header.model,
controller: payload.header.controllers[0],
model: payload.header.model,
})
await validateController(metadata.controller, event.cacaoBlock)

const content = verified.data
const content = payload.data
assertValidModelContent(content)
if (content.version !== '1.0') {
if (content.interface) {
Expand All @@ -48,8 +48,8 @@ export async function handleInitEvent(
}

const streamID = await getModelStreamID({
data: verified.data,
header: verified.header,
data: payload.data,
header: payload.header,
})

return { id: streamID.toString(), content, metadata, log: [event] }
Expand Down

0 comments on commit fede098

Please sign in to comment.