1- const BLOCK_BYTES = 64 ;
2- const UTF8_CHUNK_CODE_UNITS = 8 * 1_024 ;
3-
4- const INITIAL_STATE = [
5- 0x6a09e667 , 0xbb67ae85 , 0x3c6ef372 , 0xa54ff53a , 0x510e527f , 0x9b05688c , 0x1f83d9ab , 0x5be0cd19 ,
6- ] as const ;
1+ import { createHash } from 'node:crypto' ;
72
8- const ROUND_CONSTANTS = new Uint32Array ( [
9- 0x428a2f98 , 0x71374491 , 0xb5c0fbcf , 0xe9b5dba5 , 0x3956c25b , 0x59f111f1 , 0x923f82a4 , 0xab1c5ed5 ,
10- 0xd807aa98 , 0x12835b01 , 0x243185be , 0x550c7dc3 , 0x72be5d74 , 0x80deb1fe , 0x9bdc06a7 , 0xc19bf174 ,
11- 0xe49b69c1 , 0xefbe4786 , 0x0fc19dc6 , 0x240ca1cc , 0x2de92c6f , 0x4a7484aa , 0x5cb0a9dc , 0x76f988da ,
12- 0x983e5152 , 0xa831c66d , 0xb00327c8 , 0xbf597fc7 , 0xc6e00bf3 , 0xd5a79147 , 0x06ca6351 , 0x14292967 ,
13- 0x27b70a85 , 0x2e1b2138 , 0x4d2c6dfc , 0x53380d13 , 0x650a7354 , 0x766a0abb , 0x81c2c92e , 0x92722c85 ,
14- 0xa2bfe8a1 , 0xa81a664b , 0xc24b8b70 , 0xc76c51a3 , 0xd192e819 , 0xd6990624 , 0xf40e3585 , 0x106aa070 ,
15- 0x19a4c116 , 0x1e376c08 , 0x2748774c , 0x34b0bcb5 , 0x391c0cb3 , 0x4ed8aa4a , 0x5b9cca4f , 0x682e6ff3 ,
16- 0x748f82ee , 0x78a5636f , 0x84c87814 , 0x8cc70208 , 0x90befffa , 0xa4506ceb , 0xbef9a3f7 , 0xc67178f2 ,
17- ] ) ;
3+ const UTF8_CHUNK_CODE_UNITS = 8 * 1_024 ;
184
19- /**
20- * Dependency-free incremental SHA-256 for AgentHost semantic identity.
21- * Text is encoded in bounded chunks so hashing one large History string does
22- * not allocate another payload-sized Buffer. This is intentionally internal;
23- * persistence digests continue to use their owning adapters.
24- */
5+ /** Native SHA-256 with bounded UTF-8 chunks and the existing update boundaries. */
256export class IncrementalSha256 {
26- private readonly state = new Uint32Array ( INITIAL_STATE ) ;
27- private readonly block = new Uint8Array ( BLOCK_BYTES ) ;
28- private readonly schedule = new Uint32Array ( 64 ) ;
29- private readonly textEncoder = new TextEncoder ( ) ;
30- private bufferedBytes = 0 ;
31- private totalBytes = 0 ;
7+ private readonly hash = createHash ( 'sha256' ) ;
328 private finalized = false ;
339
3410 update ( value : string ) : void {
3511 if ( this . finalized ) throw new Error ( 'SHA-256 digest is already finalized.' ) ;
3612 let offset = 0 ;
3713 while ( offset < value . length ) {
3814 const end = safeUtf8ChunkEnd ( value , offset ) ;
39- this . updateBytes ( this . textEncoder . encode ( value . slice ( offset , end ) ) ) ;
15+ this . hash . update ( value . slice ( offset , end ) , 'utf8' ) ;
4016 offset = end ;
4117 }
4218 }
4319
4420 digestHex ( ) : string {
4521 if ( this . finalized ) throw new Error ( 'SHA-256 digest is already finalized.' ) ;
4622 this . finalized = true ;
47- const messageBytes = this . totalBytes ;
48- this . block [ this . bufferedBytes ] = 0x80 ;
49- this . bufferedBytes += 1 ;
50- if ( this . bufferedBytes > 56 ) {
51- this . block . fill ( 0 , this . bufferedBytes ) ;
52- this . compress ( ) ;
53- this . bufferedBytes = 0 ;
54- }
55- this . block . fill ( 0 , this . bufferedBytes , 56 ) ;
56- const messageBits = messageBytes * 8 ;
57- writeUint32BigEndian ( this . block , 56 , Math . floor ( messageBits / 0x1_0000_0000 ) ) ;
58- writeUint32BigEndian ( this . block , 60 , messageBits >>> 0 ) ;
59- this . compress ( ) ;
60- return [ ...this . state ] . map ( ( word ) => word . toString ( 16 ) . padStart ( 8 , '0' ) ) . join ( '' ) ;
61- }
62-
63- private updateBytes ( bytes : Uint8Array ) : void {
64- this . totalBytes += bytes . length ;
65- let offset = 0 ;
66- while ( offset < bytes . length ) {
67- const copied = Math . min ( BLOCK_BYTES - this . bufferedBytes , bytes . length - offset ) ;
68- this . block . set ( bytes . subarray ( offset , offset + copied ) , this . bufferedBytes ) ;
69- this . bufferedBytes += copied ;
70- offset += copied ;
71- if ( this . bufferedBytes !== BLOCK_BYTES ) continue ;
72- this . compress ( ) ;
73- this . bufferedBytes = 0 ;
74- }
75- }
76-
77- private compress ( ) : void {
78- const words = this . schedule ;
79- for ( let index = 0 ; index < 16 ; index += 1 ) {
80- const offset = index * 4 ;
81- words [ index ] =
82- ( arrayValue ( this . block , offset ) << 24 ) |
83- ( arrayValue ( this . block , offset + 1 ) << 16 ) |
84- ( arrayValue ( this . block , offset + 2 ) << 8 ) |
85- arrayValue ( this . block , offset + 3 ) ;
86- }
87- for ( let index = 16 ; index < 64 ; index += 1 ) {
88- const prior15 = arrayValue ( words , index - 15 ) ;
89- const prior2 = arrayValue ( words , index - 2 ) ;
90- const sigma0 = rotateRight ( prior15 , 7 ) ^ rotateRight ( prior15 , 18 ) ^ ( prior15 >>> 3 ) ;
91- const sigma1 = rotateRight ( prior2 , 17 ) ^ rotateRight ( prior2 , 19 ) ^ ( prior2 >>> 10 ) ;
92- words [ index ] =
93- ( arrayValue ( words , index - 16 ) + sigma0 + arrayValue ( words , index - 7 ) + sigma1 ) >>> 0 ;
94- }
95- this . compressRounds ( words ) ;
96- }
97-
98- private compressRounds ( words : Uint32Array ) : void {
99- let a = arrayValue ( this . state , 0 ) ;
100- let b = arrayValue ( this . state , 1 ) ;
101- let c = arrayValue ( this . state , 2 ) ;
102- let d = arrayValue ( this . state , 3 ) ;
103- let e = arrayValue ( this . state , 4 ) ;
104- let f = arrayValue ( this . state , 5 ) ;
105- let g = arrayValue ( this . state , 6 ) ;
106- let h = arrayValue ( this . state , 7 ) ;
107- for ( let index = 0 ; index < 64 ; index += 1 ) {
108- const upperSigma1 = rotateRight ( e , 6 ) ^ rotateRight ( e , 11 ) ^ rotateRight ( e , 25 ) ;
109- const choose = ( e & f ) ^ ( ~ e & g ) ;
110- const temporary1 =
111- ( h +
112- upperSigma1 +
113- choose +
114- arrayValue ( ROUND_CONSTANTS , index ) +
115- arrayValue ( words , index ) ) >>>
116- 0 ;
117- const upperSigma0 = rotateRight ( a , 2 ) ^ rotateRight ( a , 13 ) ^ rotateRight ( a , 22 ) ;
118- const majority = ( a & b ) ^ ( a & c ) ^ ( b & c ) ;
119- const temporary2 = ( upperSigma0 + majority ) >>> 0 ;
120- h = g ;
121- g = f ;
122- f = e ;
123- e = ( d + temporary1 ) >>> 0 ;
124- d = c ;
125- c = b ;
126- b = a ;
127- a = ( temporary1 + temporary2 ) >>> 0 ;
128- }
129- this . state [ 0 ] = ( arrayValue ( this . state , 0 ) + a ) >>> 0 ;
130- this . state [ 1 ] = ( arrayValue ( this . state , 1 ) + b ) >>> 0 ;
131- this . state [ 2 ] = ( arrayValue ( this . state , 2 ) + c ) >>> 0 ;
132- this . state [ 3 ] = ( arrayValue ( this . state , 3 ) + d ) >>> 0 ;
133- this . state [ 4 ] = ( arrayValue ( this . state , 4 ) + e ) >>> 0 ;
134- this . state [ 5 ] = ( arrayValue ( this . state , 5 ) + f ) >>> 0 ;
135- this . state [ 6 ] = ( arrayValue ( this . state , 6 ) + g ) >>> 0 ;
136- this . state [ 7 ] = ( arrayValue ( this . state , 7 ) + h ) >>> 0 ;
23+ return this . hash . digest ( 'hex' ) ;
13724 }
13825}
13926
@@ -152,20 +39,3 @@ function isHighSurrogate(value: number): boolean {
15239function isLowSurrogate ( value : number ) : boolean {
15340 return value >= 0xdc00 && value <= 0xdfff ;
15441}
155-
156- function rotateRight ( value : number , bits : number ) : number {
157- return ( value >>> bits ) | ( value << ( 32 - bits ) ) ;
158- }
159-
160- function writeUint32BigEndian ( target : Uint8Array , offset : number , value : number ) : void {
161- target [ offset ] = ( value >>> 24 ) & 0xff ;
162- target [ offset + 1 ] = ( value >>> 16 ) & 0xff ;
163- target [ offset + 2 ] = ( value >>> 8 ) & 0xff ;
164- target [ offset + 3 ] = value & 0xff ;
165- }
166-
167- function arrayValue ( values : Uint8Array | Uint32Array , index : number ) : number {
168- const value = values [ index ] ;
169- if ( value === undefined ) throw new RangeError ( `SHA-256 array index is out of bounds: ${ index } .` ) ;
170- return value ;
171- }
0 commit comments