22
33import * as automerge from '@automerge/automerge' ;
44import { uuid } from '@automerge/automerge' ;
5+ import type { DocHandle } from '@automerge/automerge-repo' ;
56import { RepoContext } from '@automerge/automerge-repo-react-hooks' ;
67import { Identity , Key , Messages , SpaceEvents , type SpaceStorageEntry , Utils , store } from '@graphprotocol/hypergraph' ;
78import { getSessionNonce , identityExists , prepareSiweMessage } from '@graphprotocol/hypergraph/identity/login' ;
@@ -329,11 +330,66 @@ export function HypergraphAppProvider({
329330 // Handle WebSocket messages in a separate effect
330331 useEffect ( ( ) => {
331332 if ( ! websocketConnection ) return ;
333+ if ( ! accountId ) {
334+ console . error ( 'No accountId found' ) ;
335+ return ;
336+ }
332337 const encryptionPrivateKey = keys ?. encryptionPrivateKey ;
333338 if ( ! encryptionPrivateKey ) {
334339 console . error ( 'No encryption private key found' ) ;
335340 return ;
336341 }
342+ const signaturePrivateKey = keys ?. signaturePrivateKey ;
343+ if ( ! signaturePrivateKey ) {
344+ console . error ( 'No signature private key found.' ) ;
345+ return ;
346+ }
347+
348+ const applyUpdates = async (
349+ spaceId : string ,
350+ spaceSecretKey : string ,
351+ automergeDocHandle : DocHandle < unknown > ,
352+ updates : Messages . Updates ,
353+ ) => {
354+ const verifiedUpdates = await Promise . all (
355+ updates . updates . map ( async ( update ) => {
356+ const signer = Messages . recoverUpdateMessageSigner ( {
357+ update : update . update ,
358+ spaceId,
359+ updateId : update . updateId ,
360+ signature : update . signature ,
361+ accountId : update . accountId ,
362+ } ) ;
363+ const authorIdentity = await getUserIdentity ( update . accountId ) ;
364+ if ( authorIdentity . signaturePublicKey !== signer ) {
365+ console . error (
366+ `Received invalid signature, recovered signer is ${ signer } ,
367+ expected ${ authorIdentity . signaturePublicKey } ` ,
368+ ) ;
369+ return { valid : false , update : new Uint8Array ( [ ] ) } ;
370+ }
371+ return {
372+ valid : true ,
373+ update : Messages . decryptMessage ( {
374+ nonceAndCiphertext : update . update ,
375+ secretKey : Utils . hexToBytes ( spaceSecretKey ) ,
376+ } ) ,
377+ } ;
378+ } ) ,
379+ ) ;
380+ const validUpdates = verifiedUpdates . filter ( ( update ) => update . valid ) . map ( ( update ) => update . update ) ;
381+ automergeDocHandle . update ( ( existingDoc ) => {
382+ const [ newDoc ] = automerge . applyChanges ( existingDoc , validUpdates ) ;
383+ return newDoc ;
384+ } ) ;
385+
386+ store . send ( {
387+ type : 'applyUpdate' ,
388+ spaceId,
389+ firstUpdateClock : updates . firstUpdateClock ,
390+ lastUpdateClock : updates . lastUpdateClock ,
391+ } ) ;
392+ } ;
337393
338394 const onMessage = async ( event : MessageEvent ) => {
339395 const data = Messages . deserialize ( event . data ) ;
@@ -395,26 +451,7 @@ export function HypergraphAppProvider({
395451 }
396452
397453 if ( response . updates ) {
398- const updates = response . updates ?. updates . map ( ( update ) => {
399- return Messages . decryptMessage ( {
400- nonceAndCiphertext : update ,
401- secretKey : Utils . hexToBytes ( keys [ 0 ] . key ) ,
402- } ) ;
403- } ) ;
404-
405- for ( const update of updates ) {
406- automergeDocHandle . update ( ( existingDoc ) => {
407- const [ newDoc ] = automerge . applyChanges ( existingDoc , [ update ] ) ;
408- return newDoc ;
409- } ) ;
410- }
411-
412- store . send ( {
413- type : 'applyUpdate' ,
414- spaceId : response . id ,
415- firstUpdateClock : response . updates ?. firstUpdateClock ,
416- lastUpdateClock : response . updates ?. lastUpdateClock ,
417- } ) ;
454+ await applyUpdates ( response . id , keys [ 0 ] . key , automergeDocHandle , response . updates ) ;
418455 }
419456
420457 automergeDocHandle . on ( 'change' , ( result ) => {
@@ -427,19 +464,16 @@ export function HypergraphAppProvider({
427464 const storeState = store . getSnapshot ( ) ;
428465 const space = storeState . context . spaces [ 0 ] ;
429466
430- const ephemeralId = uuid ( ) ;
467+ const updateId = uuid ( ) ;
431468
432- const nonceAndCiphertext = Messages . encryptMessage ( {
469+ const messageToSend = Messages . signedUpdateMessage ( {
470+ accountId,
471+ updateId,
472+ spaceId : space . id ,
433473 message : lastLocalChange ,
434- secretKey : Utils . hexToBytes ( space . keys [ 0 ] . key ) ,
474+ secretKey : space . keys [ 0 ] . key ,
475+ signaturePrivateKey,
435476 } ) ;
436-
437- const messageToSend = {
438- type : 'create-update' ,
439- ephemeralId,
440- update : nonceAndCiphertext ,
441- spaceId : space . id ,
442- } as const satisfies Messages . RequestCreateUpdate ;
443477 websocketConnection . send ( Messages . serialize ( messageToSend ) ) ;
444478 } catch ( error ) {
445479 console . error ( 'Error sending message' , error ) ;
@@ -483,7 +517,7 @@ export function HypergraphAppProvider({
483517 case 'update-confirmed' : {
484518 store . send ( {
485519 type : 'removeUpdateInFlight' ,
486- ephemeralId : response . ephemeralId ,
520+ updateId : response . updateId ,
487521 } ) ;
488522 store . send ( {
489523 type : 'updateConfirmed' ,
@@ -500,25 +534,12 @@ export function HypergraphAppProvider({
500534 console . error ( 'Space not found' , response . spaceId ) ;
501535 return ;
502536 }
537+ if ( ! space . automergeDocHandle ) {
538+ console . error ( 'No automergeDocHandle found' , response . spaceId ) ;
539+ return ;
540+ }
503541
504- const automergeUpdates = response . updates . updates . map ( ( update ) => {
505- return Messages . decryptMessage ( {
506- nonceAndCiphertext : update ,
507- secretKey : Utils . hexToBytes ( space . keys [ 0 ] . key ) ,
508- } ) ;
509- } ) ;
510-
511- space ?. automergeDocHandle ?. update ( ( existingDoc ) => {
512- const [ newDoc ] = automerge . applyChanges ( existingDoc , automergeUpdates ) ;
513- return newDoc ;
514- } ) ;
515-
516- store . send ( {
517- type : 'applyUpdate' ,
518- spaceId : response . spaceId ,
519- firstUpdateClock : response . updates . firstUpdateClock ,
520- lastUpdateClock : response . updates . lastUpdateClock ,
521- } ) ;
542+ await applyUpdates ( response . spaceId , space . keys [ 0 ] . key , space . automergeDocHandle , response . updates ) ;
522543 break ;
523544 }
524545 default : {
@@ -533,7 +554,7 @@ export function HypergraphAppProvider({
533554 return ( ) => {
534555 websocketConnection . removeEventListener ( 'message' , onMessage ) ;
535556 } ;
536- } , [ websocketConnection , spaces , keys ?. encryptionPrivateKey ] ) ;
557+ } , [ websocketConnection , spaces , accountId , keys ?. encryptionPrivateKey , keys ?. signaturePrivateKey ] ) ;
537558
538559 const createSpaceForContext = async ( ) => {
539560 if ( ! accountId ) {
0 commit comments