@@ -75,6 +75,8 @@ import {
7575 SplitSecretOptions ,
7676 TokenIssuance ,
7777 TokenIssuanceResponse ,
78+ ProcessV4TokenIssuanceParams ,
79+ ProcessV4TokenIssuanceResult ,
7880 UnlockOptions ,
7981 User ,
8082 VerifyPasswordOptions ,
@@ -123,6 +125,7 @@ export class BitGoAPI implements BitGoBase {
123125 protected _extensionKey ?: ECPairInterface ;
124126 protected _reqId ?: IRequestTracer ;
125127 protected _token ?: string ;
128+ protected _tokenId ?: string ; // V4: separate token identifier
126129 protected _version = pjson . version ;
127130 protected _userAgent ?: string ;
128131 protected _ecdhXprv ?: string ;
@@ -735,6 +738,7 @@ export class BitGoAPI implements BitGoBase {
735738 return {
736739 user : this . _user ,
737740 token : this . _token ,
741+ tokenId : this . _tokenId ,
738742 extensionKey : this . _extensionKey ? this . _extensionKey . toWIF ( ) : undefined ,
739743 ecdhXprv : this . _ecdhXprv ,
740744 } ;
@@ -758,6 +762,7 @@ export class BitGoAPI implements BitGoBase {
758762 fromJSON ( json : BitGoJson ) : void {
759763 this . _user = json . user ;
760764 this . _token = json . token ;
765+ this . _tokenId = json . tokenId ;
761766 this . _ecdhXprv = json . ecdhXprv ;
762767 if ( json . extensionKey ) {
763768 const network = common . Environments [ this . getEnv ( ) ] . network ;
@@ -980,6 +985,11 @@ export class BitGoAPI implements BitGoBase {
980985 this . _token = responseDetails . token ;
981986 this . _ecdhXprv = responseDetails . ecdhXprv ;
982987
988+ // V4: store separate token identifier
989+ if ( this . _authVersion === 4 && body . id ) {
990+ this . _tokenId = body . id ;
991+ }
992+
983993 // verify the response's authenticity
984994 verifyResponse ( this , responseDetails . token , 'post' , request , response , this . _authVersion ) ;
985995
@@ -1109,6 +1119,39 @@ export class BitGoAPI implements BitGoBase {
11091119 return response ;
11101120 }
11111121
1122+ /**
1123+ * Process V4 token issuance response by decrypting the signing key.
1124+ * This helper decrypts the ECDH-encrypted signing key from V4 issuance
1125+ * responses (reuses existing V2/V3 ECDH decryption logic).
1126+ *
1127+ * V4 issuance provides:
1128+ * - tokenId (plaintext): Used to identify the token in API requests
1129+ * - encryptedToken: ECDH-encrypted signing key for HMAC authentication
1130+ *
1131+ * @param params - Token issuance parameters
1132+ * @param params.tokenId - Token identifier from server response (response.id)
1133+ * @param params.encryptedToken - ECDH-encrypted signing key from server response
1134+ * @param params.derivationPath - BIP32 derivation path for ECDH key agreement
1135+ * @returns Object containing token ID and decrypted signing key
1136+ */
1137+ processV4TokenIssuance ( params : ProcessV4TokenIssuanceParams ) : ProcessV4TokenIssuanceResult {
1138+ common . validateParams ( params , [ 'tokenId' , 'encryptedToken' , 'derivationPath' ] , [ ] ) ;
1139+
1140+ // Reuse existing ECDH decryption logic from V2/V3 to decrypt the signing key
1141+ const tokenIssuanceResponse : TokenIssuanceResponse = {
1142+ encryptedToken : params . encryptedToken ,
1143+ derivationPath : params . derivationPath ,
1144+ } ;
1145+
1146+ const result = this . handleTokenIssuance ( tokenIssuanceResponse ) ;
1147+ const signingKey = result . token ;
1148+
1149+ return {
1150+ tokenId : params . tokenId ,
1151+ signingKey,
1152+ } ;
1153+ }
1154+
11121155 /**
11131156 */
11141157 verifyPassword ( params : VerifyPasswordOptions = { } ) : Promise < any > {
@@ -1131,6 +1174,7 @@ export class BitGoAPI implements BitGoBase {
11311174 // TODO: are there any other fields which should be cleared?
11321175 this . _user = undefined ;
11331176 this . _token = undefined ;
1177+ this . _tokenId = undefined ;
11341178 this . _refreshToken = undefined ;
11351179 this . _ecdhXprv = undefined ;
11361180 }
@@ -1271,8 +1315,30 @@ export class BitGoAPI implements BitGoBase {
12711315 // verify the authenticity of the server's response before proceeding any further
12721316 verifyResponse ( this , this . _token , 'post' , request , response , this . _authVersion ) ;
12731317
1274- const responseDetails = this . handleTokenIssuance ( response . body ) ;
1275- response . body . token = responseDetails . token ;
1318+ // V4 token issuance includes separate tokenId and encrypted signing key
1319+ if ( this . _authVersion === 4 ) {
1320+ // Validate V4 response structure
1321+ if ( ! response . body . id || ! response . body . encryptedToken || ! response . body . derivationPath ) {
1322+ throw new Error (
1323+ 'Invalid V4 token issuance response: missing required fields (id, encryptedToken, or derivationPath)'
1324+ ) ;
1325+ }
1326+
1327+ const { tokenId, signingKey } = this . processV4TokenIssuance ( {
1328+ tokenId : response . body . id ,
1329+ encryptedToken : response . body . encryptedToken ,
1330+ derivationPath : response . body . derivationPath ,
1331+ } ) ;
1332+
1333+ // Return the decrypted signing key in the response (like V2/V3)
1334+ response . body . token = signingKey ;
1335+ // Include tokenId in response for V4
1336+ response . body . tokenId = tokenId ;
1337+ } else {
1338+ // V2/V3 token issuance
1339+ const responseDetails = this . handleTokenIssuance ( response . body ) ;
1340+ response . body . token = responseDetails . token ;
1341+ }
12761342
12771343 return handleResponseResult < AddAccessTokenResponse > ( ) ( response ) ;
12781344 } catch ( e ) {
0 commit comments