@@ -16,6 +16,7 @@ import * as fixtures from './fixtures/mpcv1shares';
1616import * as openpgp from 'openpgp' ;
1717import { decode } from 'cbor-x' ;
1818import { generate2of2KeyShares , generateDKGKeyShares } from '../../../../src/tss/ecdsa-dkls/util' ;
19+ import { createHash } from 'crypto' ;
1920
2021describe ( 'DKLS Dkg 2x3' , function ( ) {
2122 it ( `should create key shares` , async function ( ) {
@@ -112,6 +113,153 @@ describe('DKLS Dkg 2x3', function () {
112113 assert . deepEqual ( bKeyCombine . xShare . y , Buffer . from ( decode ( backupKeyShare ) . public_key ) . toString ( 'hex' ) ) ;
113114 } ) ;
114115
116+ it ( `should create retrofit key shares with non-zero final_session_id` , async function ( ) {
117+ const aKeyCombine = {
118+ xShare : fixtures . mockDKeyShare . xShare ,
119+ } ;
120+ const bKeyCombine = {
121+ xShare : fixtures . mockEKeyShare . xShare ,
122+ } ;
123+ const cKeyCombine = {
124+ xShare : fixtures . mockFKeyShare . xShare ,
125+ } ;
126+ const retrofitDataA : RetrofitData = {
127+ xShare : aKeyCombine . xShare ,
128+ } ;
129+ const retrofitDataB : RetrofitData = {
130+ xShare : bKeyCombine . xShare ,
131+ } ;
132+ const retrofitDataC : RetrofitData = {
133+ xShare : cKeyCombine . xShare ,
134+ } ;
135+ const [ user ] = await generateDKGKeyShares ( retrofitDataA , retrofitDataB , retrofitDataC ) ;
136+
137+ const userKeyShare = user . getKeyShare ( ) ;
138+ const decodedKeyShare = decode ( userKeyShare ) ;
139+ const finalSessionId = decodedKeyShare . final_session_id ;
140+
141+ // Assert final_session_id is NOT all zeros
142+ assert ( ! finalSessionId . every ( ( byte : number ) => byte === 0 ) , 'final_session_id should not be all zeros' ) ;
143+ } ) ;
144+
145+ it ( `should create retrofit key shares with 32-byte final_session_id` , async function ( ) {
146+ const aKeyCombine = {
147+ xShare : fixtures . mockDKeyShare . xShare ,
148+ } ;
149+ const bKeyCombine = {
150+ xShare : fixtures . mockEKeyShare . xShare ,
151+ } ;
152+ const cKeyCombine = {
153+ xShare : fixtures . mockFKeyShare . xShare ,
154+ } ;
155+ const retrofitDataA : RetrofitData = {
156+ xShare : aKeyCombine . xShare ,
157+ } ;
158+ const retrofitDataB : RetrofitData = {
159+ xShare : bKeyCombine . xShare ,
160+ } ;
161+ const retrofitDataC : RetrofitData = {
162+ xShare : cKeyCombine . xShare ,
163+ } ;
164+ const [ user ] = await generateDKGKeyShares ( retrofitDataA , retrofitDataB , retrofitDataC ) ;
165+
166+ const userKeyShare = user . getKeyShare ( ) ;
167+ const decodedKeyShare = decode ( userKeyShare ) ;
168+ const finalSessionId = decodedKeyShare . final_session_id ;
169+
170+ // Assert final_session_id is exactly 32 bytes
171+ assert . strictEqual ( finalSessionId . length , 32 , 'final_session_id must be 32 bytes' ) ;
172+ } ) ;
173+
174+ it ( `should produce deterministic final_session_id for same retrofit inputs` , async function ( ) {
175+ const aKeyCombine = {
176+ xShare : fixtures . mockDKeyShare . xShare ,
177+ } ;
178+ const retrofitDataA : RetrofitData = {
179+ xShare : aKeyCombine . xShare ,
180+ } ;
181+
182+ // Test the INPUT keyshare (before WASM protocol), not the output
183+ // Create first Dkg instance and call _createDKLsRetrofitKeyShare
184+ const dkg1 = new DklsDkg . Dkg ( 3 , 2 , 0 , undefined , retrofitDataA ) ;
185+ await ( dkg1 as any ) . loadDklsWasm ( ) ;
186+ ( dkg1 as any ) . _createDKLsRetrofitKeyShare ( ) ;
187+ const keyshareObj1 = ( dkg1 as any ) . dklsKeyShareRetrofitObject ;
188+ const decoded1 = decode ( keyshareObj1 . toBytes ( ) ) ;
189+ const finalSessionId1 = decoded1 . final_session_id ;
190+
191+ // Create second Dkg instance with same retrofit data
192+ const dkg2 = new DklsDkg . Dkg ( 3 , 2 , 0 , undefined , retrofitDataA ) ;
193+ await ( dkg2 as any ) . loadDklsWasm ( ) ;
194+ ( dkg2 as any ) . _createDKLsRetrofitKeyShare ( ) ;
195+ const keyshareObj2 = ( dkg2 as any ) . dklsKeyShareRetrofitObject ;
196+ const decoded2 = decode ( keyshareObj2 . toBytes ( ) ) ;
197+ const finalSessionId2 = decoded2 . final_session_id ;
198+
199+ // Assert both runs produce identical final_session_id
200+ assert . deepEqual ( finalSessionId1 , finalSessionId2 , 'final_session_id should be deterministic for same inputs' ) ;
201+ } ) ;
202+
203+ it ( `should derive final_session_id as sha256(public_key || chaincode)` , async function ( ) {
204+ const aKeyCombine = {
205+ xShare : fixtures . mockDKeyShare . xShare ,
206+ } ;
207+ const retrofitDataA : RetrofitData = {
208+ xShare : aKeyCombine . xShare ,
209+ } ;
210+
211+ // Test the INPUT keyshare (before WASM protocol), not the output
212+ const dkg = new DklsDkg . Dkg ( 3 , 2 , 0 , undefined , retrofitDataA ) ;
213+ await ( dkg as any ) . loadDklsWasm ( ) ;
214+ ( dkg as any ) . _createDKLsRetrofitKeyShare ( ) ;
215+ const keyshareObj = ( dkg as any ) . dklsKeyShareRetrofitObject ;
216+ const decoded = decode ( keyshareObj . toBytes ( ) ) ;
217+ const finalSessionId = decoded . final_session_id ;
218+
219+ // Compute expected final_session_id: sha256(public_key_bytes || chaincode_bytes)
220+ const publicKeyBuffer = Buffer . from ( aKeyCombine . xShare . y , 'hex' ) ;
221+ const chaincodeBuffer = Buffer . from ( aKeyCombine . xShare . chaincode , 'hex' ) ;
222+ const expectedHash = Array . from ( createHash ( 'sha256' ) . update ( publicKeyBuffer ) . update ( chaincodeBuffer ) . digest ( ) ) ;
223+
224+ // Assert actual final_session_id matches the computed hash
225+ assert . deepEqual ( finalSessionId , expectedHash , 'final_session_id should be sha256(public_key || chaincode)' ) ;
226+ } ) ;
227+
228+ it ( `should produce the same final_session_id for all parties in a retrofit` , async function ( ) {
229+ const aKeyCombine = {
230+ xShare : fixtures . mockDKeyShare . xShare ,
231+ } ;
232+ const bKeyCombine = {
233+ xShare : fixtures . mockEKeyShare . xShare ,
234+ } ;
235+ const cKeyCombine = {
236+ xShare : fixtures . mockFKeyShare . xShare ,
237+ } ;
238+ const retrofitDataA : RetrofitData = {
239+ xShare : aKeyCombine . xShare ,
240+ } ;
241+ const retrofitDataB : RetrofitData = {
242+ xShare : bKeyCombine . xShare ,
243+ } ;
244+ const retrofitDataC : RetrofitData = {
245+ xShare : cKeyCombine . xShare ,
246+ } ;
247+ const [ user , backup , bitgo ] = await generateDKGKeyShares ( retrofitDataA , retrofitDataB , retrofitDataC ) ;
248+
249+ const userKeyShare = user . getKeyShare ( ) ;
250+ const backupKeyShare = backup . getKeyShare ( ) ;
251+ const bitgoKeyShare = bitgo . getKeyShare ( ) ;
252+
253+ const userFinalSessionId = decode ( userKeyShare ) . final_session_id ;
254+ const backupFinalSessionId = decode ( backupKeyShare ) . final_session_id ;
255+ const bitgoFinalSessionId = decode ( bitgoKeyShare ) . final_session_id ;
256+
257+ // Assert all parties have the same final_session_id
258+ assert . deepEqual ( userFinalSessionId , backupFinalSessionId , 'user and backup final_session_id should match' ) ;
259+ assert . deepEqual ( backupFinalSessionId , bitgoFinalSessionId , 'backup and bitgo final_session_id should match' ) ;
260+ assert . deepEqual ( userFinalSessionId , bitgoFinalSessionId , 'user and bitgo final_session_id should match' ) ;
261+ } ) ;
262+
115263 it ( `should create key shares with authenticated encryption` , async function ( ) {
116264 const user = new DklsDkg . Dkg ( 3 , 2 , 0 ) ;
117265 const backup = new DklsDkg . Dkg ( 3 , 2 , 1 ) ;
0 commit comments