@@ -24,6 +24,7 @@ import {
2424 issueTreecrdtCapabilityTokenV1 ,
2525 issueTreecrdtDelegatedCapabilityTokenV1 ,
2626 issueTreecrdtRevocationRecordV1 ,
27+ signTreecrdtOpV1 ,
2728 verifyTreecrdtRevocationRecordV1 ,
2829} from '../dist/treecrdt-auth.js' ;
2930import type { Capability , Filter , OpRef , SyncBackend } from '@treecrdt/sync-protocol' ;
@@ -320,6 +321,8 @@ test('auth: signOps selects proof_ref per op when multiple tokens exist', async
320321 expect ( auth ?. length ) . toBe ( 2 ) ;
321322 expect ( auth ?. [ 0 ] ?. proofRef ) . toBeTruthy ( ) ;
322323 expect ( auth ?. [ 1 ] ?. proofRef ) . toBeTruthy ( ) ;
324+ expect ( auth ?. [ 0 ] ?. claims ) . toBeUndefined ( ) ;
325+ expect ( auth ?. [ 1 ] ?. claims ) . toBeUndefined ( ) ;
323326
324327 const tokenStructureId = deriveTokenIdV1 ( tokenStructure ) ;
325328 const tokenDeleteId = deriveTokenIdV1 ( tokenDelete ) ;
@@ -335,9 +338,8 @@ test('auth: signOps selects proof_ref per op when multiple tokens exist', async
335338 ) ;
336339} ) ;
337340
338- test ( 'auth: signOps signs authoredAt claims ' , async ( ) => {
341+ test ( 'auth: v2 signs authoredAt and defensive-delete knownState ' , async ( ) => {
339342 const docId = 'doc-auth-authored-at' ;
340- const root = '0' . repeat ( 32 ) ;
341343 const authoredAtMs = 1_700_000_000_123 ;
342344
343345 const issuerSk = ed25519Utils . randomSecretKey ( ) ;
@@ -351,7 +353,7 @@ test('auth: signOps signs authoredAt claims', async () => {
351353 issuerPrivateKey : issuerSk ,
352354 subjectPublicKey : writerPk ,
353355 docId,
354- actions : [ 'write_structure ' ] ,
356+ actions : [ 'delete ' ] ,
355357 } ) ;
356358
357359 const authWriter = createTreecrdtCoseCwtAuth ( {
@@ -360,6 +362,7 @@ test('auth: signOps signs authoredAt claims', async () => {
360362 localPublicKey : writerPk ,
361363 localCapabilityTokens : [ token ] ,
362364 requireProofRef : true ,
365+ includeAuthoredAt : true ,
363366 nowMs : ( ) => authoredAtMs ,
364367 } ) ;
365368
@@ -376,19 +379,54 @@ test('auth: signOps signs authoredAt claims', async () => {
376379 { docId } ,
377380 ) ;
378381
379- const op = makeOp ( writerPk , 1 , 1 , {
380- type : 'insert' ,
381- parent : root ,
382- node : nodeIdFromInt ( 1 ) ,
383- orderKey : orderKeyFromPosition ( 0 ) ,
382+ const op : Operation = {
383+ meta : {
384+ id : { replica : writerPk , counter : 1 } ,
385+ lamport : 1 ,
386+ knownState : new Uint8Array ( [ 0x01 , 0x02 , 0x03 ] ) ,
387+ } ,
388+ kind : { type : 'delete' , node : nodeIdFromInt ( 1 ) } ,
389+ } ;
390+
391+ const legacyWriter = createTreecrdtCoseCwtAuth ( {
392+ issuerPublicKeys : [ issuerPk ] ,
393+ localPrivateKey : writerSk ,
394+ localPublicKey : writerPk ,
395+ localCapabilityTokens : [ token ] ,
396+ requireProofRef : true ,
384397 } ) ;
385398
386399 const ctx = { docId, purpose : 'reconcile' as const , filterId : 'all' } ;
400+ await expect ( legacyWriter . signOps ?.( [ op ] , ctx ) ) . rejects . toThrow (
401+ / r e f u s i n g t o s i g n m e t a \. k n o w n S t a t e .* v 1 / i,
402+ ) ;
403+
404+ const legacySig = await signTreecrdtOpV1 ( { docId, op, privateKey : writerSk } ) ;
405+ await expect (
406+ authVerifier . verifyOps ?.( [ op ] , [ { sig : legacySig , proofRef : deriveTokenIdV1 ( token ) } ] , ctx ) ,
407+ ) . rejects . toThrow ( / v 1 d o e s n o t a u t h e n t i c a t e m e t a \. k n o w n S t a t e / i) ;
408+
387409 const auth = await authWriter . signOps ?.( [ op ] , ctx ) ;
388410 expect ( auth ?. [ 0 ] ?. claims ) . toEqual ( { authoredAtMs } ) ;
389411
390412 await expect ( authVerifier . verifyOps ?.( [ op ] , auth , ctx ) ) . resolves . toBeUndefined ( ) ;
391413
414+ const mutatedKnownState : Operation = {
415+ ...op ,
416+ meta : { ...op . meta , knownState : new Uint8Array ( [ 0x01 , 0x02 , 0x04 ] ) } ,
417+ } ;
418+ await expect ( authVerifier . verifyOps ?.( [ mutatedKnownState ] , auth , ctx ) ) . rejects . toThrow (
419+ / i n v a l i d o p s i g n a t u r e / i,
420+ ) ;
421+
422+ const omittedKnownState : Operation = {
423+ ...op ,
424+ meta : { id : op . meta . id , lamport : op . meta . lamport } ,
425+ } ;
426+ await expect ( authVerifier . verifyOps ?.( [ omittedKnownState ] , auth , ctx ) ) . rejects . toThrow (
427+ / i n v a l i d o p s i g n a t u r e / i,
428+ ) ;
429+
392430 await expect (
393431 authVerifier . verifyOps ?.(
394432 [ op ] ,
@@ -408,7 +446,7 @@ test('auth: signOps signs authoredAt claims', async () => {
408446 ] ,
409447 ctx ,
410448 ) ,
411- ) . rejects . toThrow ( / i n v a l i d o p s i g n a t u r e / i) ;
449+ ) . rejects . toThrow ( / v 1 d o e s n o t a u t h e n t i c a t e m e t a \. k n o w n S t a t e / i) ;
412450} ) ;
413451
414452test ( 'auth ignores foreign peer capability tokens during hello and still verifies known authors' , async ( ) => {
0 commit comments