Skip to content

Commit 4d2cdd7

Browse files
feat(sdk-api): add key validation function
Add utility function to validate that an encrypted key can be succesfully decrypted and is a valid key. Ticket: WP-4242 TICKET: WP-4242
1 parent 831c65f commit 4d2cdd7

File tree

9 files changed

+520
-0
lines changed

9 files changed

+520
-0
lines changed

modules/sdk-api/src/audit.ts

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import * as base32 from 'hi-base32';
2+
import { decrypt } from './encrypt';
3+
import { KeyPair as hbarKeypair } from '@bitgo/sdk-coin-hbar';
4+
import { bitcoin } from '@bitgo/sdk-core';
5+
import * as stellar from 'stellar-sdk';
6+
import { coins, KeyCurve } from '@bitgo/statics';
7+
import { BitGoAPI } from './bitgoAPI';
8+
9+
export type AuditKeyParams = {
10+
coinName: string;
11+
encryptedPrv: string;
12+
walletPassphrase: string;
13+
} & (
14+
| {
15+
multiSigType: 'tss';
16+
publicKey: string;
17+
}
18+
| {
19+
multiSigType?: 'onchain';
20+
publicKey?: string;
21+
}
22+
);
23+
24+
/**
25+
* Audit a key by decrypting the encrypted private key and verifying it matches the provided public key
26+
*
27+
* @param coinName - The coin type
28+
* @param multiSigType - Optional type of multi-signature wallet
29+
* @param encryptedPrv - The encrypted private key to audit
30+
* @param publicKey - The public key to verify against
31+
* @param walletPassphrase - The passphrase to decrypt the encrypted private key
32+
* @returns Object containing audit results
33+
*/
34+
export async function auditKey({
35+
coinName,
36+
publicKey,
37+
encryptedPrv,
38+
walletPassphrase,
39+
multiSigType,
40+
}: AuditKeyParams): Promise<{ isValid: boolean; message?: string }> {
41+
const coin = coins.get(coinName);
42+
if (!coin) {
43+
return { isValid: false, message: `Unsupported coin ${coinName}` };
44+
}
45+
try {
46+
// 1. Decrypt the encrypted private key
47+
const secret = decrypt(walletPassphrase, encryptedPrv);
48+
49+
// 2. Determine curve type based on coin and multiSigType
50+
const curveType = coin.primaryKeyCurve; // default
51+
52+
// 3. Validate the key
53+
54+
switch (curveType) {
55+
case KeyCurve.Secp256k1:
56+
if (multiSigType === 'tss') {
57+
if (publicKey.length !== 130 && publicKey.length !== 0) {
58+
return { isValid: false, message: 'Incorrect TSS common keychain' };
59+
}
60+
61+
// DKLs key chains do not have a fixed length but we know for sure they are greater than 192 in length
62+
if (secret.length !== 128 && secret.length !== 192 && secret.length <= 192) {
63+
return { isValid: false, message: 'Incorrect TSS keychain' };
64+
}
65+
} else if (multiSigType === 'onchain' && publicKey) {
66+
const genPubKey = bitcoin.HDNode.fromBase58(secret).neutered().toBase58();
67+
if (genPubKey !== publicKey) {
68+
return { isValid: false, message: 'Incorrect xpub' };
69+
}
70+
}
71+
break;
72+
case KeyCurve.Ed25519:
73+
if (multiSigType === 'tss') {
74+
// For TSS validation, we would need GPG private key for full implementation
75+
// This is a simplified validation of key format
76+
if (publicKey.length !== 128) {
77+
return { isValid: false, message: 'Incorrect TSS common keychain' };
78+
}
79+
80+
try {
81+
const parsedKey = JSON.parse(secret);
82+
if ('uShare' in parsedKey) {
83+
// If the key is in JSON format, we need to check the private key length
84+
const privateKeyLength = parsedKey.uShare.seed.length + parsedKey.uShare.chaincode.length;
85+
if (privateKeyLength !== 128) {
86+
return { isValid: false, message: 'Incorrect TSS keychain' };
87+
}
88+
} else {
89+
// If the key is not in JSON format, we need to check the length directly
90+
if (secret.length !== 128) {
91+
return { isValid: false, message: 'Incorrect TSS keychain' };
92+
}
93+
}
94+
} catch (e) {
95+
return { isValid: false, message: 'Invalid TSS keychain format' };
96+
}
97+
} else {
98+
// Validate Non-TSS Ed25519 keys (e.g., XLM, ALGO, HBAR)
99+
const sdk = new BitGoAPI();
100+
101+
switch (coinName) {
102+
case 'xlm':
103+
if (secret.substr(0, 1) !== 'S') {
104+
return { isValid: false, message: 'Incorrect XLM key' };
105+
}
106+
if (publicKey) {
107+
const { register: registerXLM } = await import('@bitgo/sdk-coin-xlm');
108+
registerXLM(sdk);
109+
const xlmCoin = sdk.coin('xlm');
110+
const genPubKey = xlmCoin.generateKeyPair((stellar.Keypair.fromSecret(secret) as any)._secretSeed).pub;
111+
if (genPubKey !== publicKey) {
112+
return { isValid: false, message: 'Incorrect XLM public key' };
113+
}
114+
}
115+
break;
116+
case 'algo':
117+
const { register: registerAlgo } = await import('@bitgo/sdk-coin-algo');
118+
registerAlgo(sdk);
119+
120+
// Validate Secret length
121+
if (secret.length !== 58) {
122+
return { isValid: false, message: 'Incorrect ALGO key' };
123+
}
124+
// Validate Public key length if provided
125+
if (publicKey) {
126+
const algoCoin = sdk.coin('algo');
127+
const decoded = base32.decode.asBytes(secret);
128+
const ALGORAND_SEED_BYTE_LENGTH = 36;
129+
const ALGORAND_CHECKSUM_BYTE_LENGTH = 4;
130+
const seed = Buffer.from(decoded.slice(0, ALGORAND_SEED_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH));
131+
const genPubKey = algoCoin.generateKeyPair(seed).pub;
132+
if (genPubKey !== publicKey) {
133+
return { isValid: false, message: 'Incorrect ALGO public key' };
134+
}
135+
}
136+
break;
137+
case 'hbar':
138+
if (secret.length !== 96) {
139+
return { isValid: false, message: 'Incorrect HBAR key' };
140+
}
141+
142+
if (publicKey) {
143+
const { register: registerHbar } = await import('@bitgo/sdk-coin-hbar');
144+
registerHbar(sdk);
145+
const genPubKey = new hbarKeypair({ prv: secret }).getKeys().pub;
146+
if (genPubKey !== publicKey) return { isValid: false, message: 'Incorrect HBAR public key' };
147+
}
148+
break;
149+
default:
150+
return { isValid: false, message: `Unsupported coin ${coinName}` };
151+
}
152+
}
153+
break;
154+
default: {
155+
return { isValid: false, message: `Unsupported curve ${curveType}` };
156+
}
157+
}
158+
159+
return { isValid: true };
160+
} catch (error) {
161+
return { isValid: false, message: error.message };
162+
}
163+
}

0 commit comments

Comments
 (0)