@@ -2,6 +2,7 @@ import { secp256k1 } from '@noble/curves/secp256k1';
22import { sha256 } from '@noble/hashes/sha256' ;
33import { Effect , Schema } from 'effect' ;
44import type { ParseError } from 'effect/ParseResult' ;
5+ import type { InvalidIdentityError , PublicIdentity } from '../identity/types.js' ;
56import { canonicalize , stringToUint8Array } from '../utils/index.js' ;
67import { hashEvent } from './hash-event.js' ;
78import {
@@ -16,14 +17,16 @@ import {
1617type Params = {
1718 state : SpaceState | undefined ;
1819 event : SpaceEvent ;
20+ getVerifiedIdentity : ( accountId : string ) => Effect . Effect < PublicIdentity , InvalidIdentityError > ;
1921} ;
2022
2123const decodeSpaceEvent = Schema . decodeUnknownEither ( SpaceEvent ) ;
2224
2325export const applyEvent = ( {
2426 state,
2527 event : rawEvent ,
26- } : Params ) : Effect . Effect < SpaceState , ParseError | VerifySignatureError | InvalidEventError > => {
28+ getVerifiedIdentity,
29+ } : Params ) : Effect . Effect < SpaceState , ParseError | VerifySignatureError | InvalidEventError | InvalidIdentityError > => {
2730 const decodedEvent = decodeSpaceEvent ( rawEvent ) ;
2831 if ( decodedEvent . _tag === 'Left' ) {
2932 return decodedEvent . left ;
@@ -43,90 +46,92 @@ export const applyEvent = ({
4346
4447 let signatureInstance = secp256k1 . Signature . fromCompact ( event . author . signature . hex ) ;
4548 signatureInstance = signatureInstance . addRecoveryBit ( event . author . signature . recovery ) ;
46- // @ts -expect-error
47- const authorPublicKey = signatureInstance . recoverPublicKey ( sha256 ( encodedTransaction ) ) ;
48- // TODO compare it to the public key from the author accountId (this already verifies the signature)
49- // in case of a failure we return Effect.fail(new VerifySignatureError());
50-
51- // biome-ignore lint/correctness/noConstantCondition: wip
52- if ( false ) {
53- return Effect . fail ( new VerifySignatureError ( ) ) ;
54- }
55-
56- let id = '' ;
57- let members : { [ accountId : string ] : SpaceMember } = { } ;
58- let removedMembers : { [ accountId : string ] : SpaceMember } = { } ;
59- let invitations : { [ id : string ] : SpaceInvitation } = { } ;
49+ const authorPublicKey = `0x${ signatureInstance . recoverPublicKey ( sha256 ( encodedTransaction ) ) . toHex ( ) } ` ;
6050
61- if ( event . transaction . type === 'create-space' ) {
62- id = event . transaction . id ;
63- members [ event . transaction . creatorAccountId ] = {
64- accountId : event . transaction . creatorAccountId ,
65- role : 'admin' ,
66- } ;
67- } else if ( state !== undefined ) {
68- id = state . id ;
69- members = { ...state . members } ;
70- removedMembers = { ...state . removedMembers } ;
71- invitations = { ...state . invitations } ;
72-
73- if ( event . transaction . type === 'accept-invitation' ) {
74- // is already a member
75- if ( members [ event . author . accountId ] !== undefined ) {
76- return Effect . fail ( new InvalidEventError ( ) ) ;
77- }
51+ return Effect . gen ( function * ( ) {
52+ const identity = yield * getVerifiedIdentity ( event . author . accountId ) ;
53+ if ( authorPublicKey !== identity . signaturePublicKey ) {
54+ yield * Effect . fail ( new VerifySignatureError ( ) ) ;
55+ }
7856
79- // find the invitation
80- const result = Object . entries ( invitations ) . find (
81- ( [ , invitation ] ) => invitation . inviteeAccountId === event . author . accountId ,
82- ) ;
83- if ( ! result ) {
84- return Effect . fail ( new InvalidEventError ( ) ) ;
85- }
86- const [ id , invitation ] = result ;
57+ let id = '' ;
58+ let members : { [ accountId : string ] : SpaceMember } = { } ;
59+ let removedMembers : { [ accountId : string ] : SpaceMember } = { } ;
60+ let invitations : { [ id : string ] : SpaceInvitation } = { } ;
8761
88- members [ invitation . inviteeAccountId ] = {
89- accountId : invitation . inviteeAccountId ,
90- role : 'member' ,
62+ if ( event . transaction . type === 'create-space' ) {
63+ id = event . transaction . id ;
64+ members [ event . transaction . creatorAccountId ] = {
65+ accountId : event . transaction . creatorAccountId ,
66+ role : 'admin' ,
9167 } ;
92- delete invitations [ id ] ;
93- if ( removedMembers [ event . author . accountId ] !== undefined ) {
94- delete removedMembers [ event . author . accountId ] ;
95- }
96- } else {
97- // check if the author is an admin
98- if ( members [ event . author . accountId ] ?. role !== 'admin' ) {
99- return Effect . fail ( new InvalidEventError ( ) ) ;
100- }
68+ } else if ( state !== undefined ) {
69+ id = state . id ;
70+ members = { ...state . members } ;
71+ removedMembers = { ...state . removedMembers } ;
72+ invitations = { ...state . invitations } ;
10173
102- if ( event . transaction . type === 'delete-space' ) {
103- removedMembers = { ...members } ;
104- members = { } ;
105- invitations = { } ;
106- } else if ( event . transaction . type === 'create-invitation' ) {
107- if ( members [ event . transaction . inviteeAccountId ] !== undefined ) {
108- return Effect . fail ( new InvalidEventError ( ) ) ;
74+ if ( event . transaction . type === 'accept-invitation' ) {
75+ // is already a member
76+ if ( members [ event . author . accountId ] !== undefined ) {
77+ yield * Effect . fail ( new InvalidEventError ( ) ) ;
10978 }
110- for ( const invitation of Object . values ( invitations ) ) {
111- if ( invitation . inviteeAccountId === event . transaction . inviteeAccountId ) {
112- return Effect . fail ( new InvalidEventError ( ) ) ;
113- }
79+
80+ // find the invitation
81+ const result = Object . entries ( invitations ) . find (
82+ ( [ , invitation ] ) => invitation . inviteeAccountId === event . author . accountId ,
83+ ) ;
84+ if ( ! result ) {
85+ yield * Effect . fail ( new InvalidEventError ( ) ) ;
11486 }
11587
116- invitations [ event . transaction . id ] = {
117- inviteeAccountId : event . transaction . inviteeAccountId ,
88+ // @ts -expect-error type issue? we checked that result is not undefined before
89+ const [ id , invitation ] = result ;
90+
91+ members [ invitation . inviteeAccountId ] = {
92+ accountId : invitation . inviteeAccountId ,
93+ role : 'member' ,
11894 } ;
95+ delete invitations [ id ] ;
96+ if ( removedMembers [ event . author . accountId ] !== undefined ) {
97+ delete removedMembers [ event . author . accountId ] ;
98+ }
11999 } else {
120- throw new Error ( 'State is required for all events except create-space' ) ;
100+ // check if the author is an admin
101+ if ( members [ event . author . accountId ] ?. role !== 'admin' ) {
102+ yield * Effect . fail ( new InvalidEventError ( ) ) ;
103+ }
104+
105+ if ( event . transaction . type === 'delete-space' ) {
106+ removedMembers = { ...members } ;
107+ members = { } ;
108+ invitations = { } ;
109+ } else if ( event . transaction . type === 'create-invitation' ) {
110+ if ( members [ event . transaction . inviteeAccountId ] !== undefined ) {
111+ yield * Effect . fail ( new InvalidEventError ( ) ) ;
112+ }
113+ for ( const invitation of Object . values ( invitations ) ) {
114+ if ( invitation . inviteeAccountId === event . transaction . inviteeAccountId ) {
115+ yield * Effect . fail ( new InvalidEventError ( ) ) ;
116+ }
117+ }
118+
119+ invitations [ event . transaction . id ] = {
120+ inviteeAccountId : event . transaction . inviteeAccountId ,
121+ } ;
122+ } else {
123+ // state is required for all events except create-space
124+ yield * Effect . fail ( new InvalidEventError ( ) ) ;
125+ }
121126 }
122127 }
123- }
124128
125- return Effect . succeed ( {
126- id,
127- members,
128- removedMembers,
129- invitations,
130- lastEventHash : hashEvent ( event ) ,
129+ return {
130+ id,
131+ members,
132+ removedMembers,
133+ invitations,
134+ lastEventHash : hashEvent ( event ) ,
135+ } ;
131136 } ) ;
132137} ;
0 commit comments