@@ -27,6 +27,14 @@ export interface SealMetadata {
2727 iv ?: string ;
2828}
2929
30+ export interface SealReceipt {
31+ sealId : string ;
32+ blobHash : string ;
33+ unlockTime : number ;
34+ createdAt : number ;
35+ signature : string ;
36+ }
37+
3038import { AuditLogger , AuditEventType } from './auditLogger' ;
3139
3240export class SealService {
@@ -41,7 +49,7 @@ export class SealService {
4149 }
4250 }
4351
44- async createSeal ( request : CreateSealRequest , ip : string ) : Promise < { sealId : string ; iv : string ; pulseToken ?: string } > {
52+ async createSeal ( request : CreateSealRequest , ip : string ) : Promise < { sealId : string ; iv : string ; pulseToken ?: string ; receipt : SealReceipt } > {
4553 const sizeValidation = validateFileSize ( request . encryptedBlob . byteLength ) ;
4654 if ( ! sizeValidation . valid ) {
4755 throw new Error ( sizeValidation . error ) ;
@@ -60,21 +68,26 @@ export class SealService {
6068 }
6169
6270 const sealId = this . generateSealId ( ) ;
71+ const createdAt = Date . now ( ) ;
6372 const pulseToken = request . isDMS ? await generatePulseToken ( sealId , this . masterKey ) : undefined ;
6473
6574 const encryptedKeyB = await encryptKeyB ( request . keyB , this . masterKey , sealId ) ;
6675
76+ // Generate cryptographic receipt
77+ const blobHash = await this . hashBlob ( request . encryptedBlob ) ;
78+ const receipt = await this . generateReceipt ( sealId , blobHash , request . unlockTime , createdAt ) ;
79+
6780 // Create seal record first
6881 await this . db . createSeal ( {
6982 id : sealId ,
7083 unlockTime : request . unlockTime ,
7184 isDMS : request . isDMS || false ,
7285 pulseInterval : request . pulseInterval ,
73- lastPulse : request . isDMS ? Date . now ( ) : undefined ,
86+ lastPulse : request . isDMS ? createdAt : undefined ,
7487 keyB : encryptedKeyB ,
7588 iv : request . iv ,
7689 pulseToken,
77- createdAt : Date . now ( ) ,
90+ createdAt,
7891 } ) ;
7992
8093 // Then upload blob (D1BlobStorage needs the row to exist)
@@ -84,16 +97,16 @@ export class SealService {
8497
8598 auditSealCreated ( sealId , ip , request . isDMS || false ) ;
8699 this . auditLogger ?. log ( {
87- timestamp : Date . now ( ) ,
100+ timestamp : createdAt ,
88101 eventType : AuditEventType . SEAL_CREATED ,
89102 sealId,
90103 ip,
91- metadata : { isDMS : request . isDMS , unlockTime : request . unlockTime } ,
104+ metadata : { isDMS : request . isDMS , unlockTime : request . unlockTime , blobHash } ,
92105 } ) ;
93106 metrics . incrementSealCreated ( ) ;
94107 logger . info ( 'seal_created' , { sealId, isDMS : request . isDMS } ) ;
95108
96- return { sealId, iv : request . iv , pulseToken } ;
109+ return { sealId, iv : request . iv , pulseToken, receipt } ;
97110 }
98111
99112 async getSeal ( sealId : string , ip : string ) : Promise < SealMetadata > {
@@ -161,7 +174,7 @@ export class SealService {
161174 }
162175
163176 const now = Date . now ( ) ;
164- const newUnlockTime = now + ( seal . pulseInterval || 0 ) * 1000 ;
177+ const newUnlockTime = now + ( seal . pulseInterval || 0 ) ;
165178
166179 await this . db . updatePulse ( seal . id , now ) ;
167180 await this . db . updateUnlockTime ( seal . id , newUnlockTime ) ;
@@ -184,4 +197,29 @@ export class SealService {
184197 crypto . getRandomValues ( bytes ) ;
185198 return Array . from ( bytes , b => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
186199 }
200+
201+ private async hashBlob ( blob : ArrayBuffer ) : Promise < string > {
202+ const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , blob ) ;
203+ const hashArray = Array . from ( new Uint8Array ( hashBuffer ) ) ;
204+ return hashArray . map ( b => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
205+ }
206+
207+ private async generateReceipt ( sealId : string , blobHash : string , unlockTime : number , createdAt : number ) : Promise < SealReceipt > {
208+ const data = `${ sealId } :${ blobHash } :${ unlockTime } :${ createdAt } ` ;
209+ const encoder = new TextEncoder ( ) ;
210+
211+ const key = await crypto . subtle . importKey (
212+ 'raw' ,
213+ encoder . encode ( this . masterKey ) ,
214+ { name : 'HMAC' , hash : 'SHA-256' } ,
215+ false ,
216+ [ 'sign' ]
217+ ) ;
218+
219+ const signature = await crypto . subtle . sign ( 'HMAC' , key , encoder . encode ( data ) ) ;
220+ const sigArray = Array . from ( new Uint8Array ( signature ) ) ;
221+ const sigHex = sigArray . map ( b => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
222+
223+ return { sealId, blobHash, unlockTime, createdAt, signature : sigHex } ;
224+ }
187225}
0 commit comments