Skip to content

feat(root): add key validation function #6064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions modules/bitgo/src/audit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import * as base32 from 'hi-base32';
import { decrypt, BitGoAPI } from '@bitgo/sdk-api';
import { bitcoin } from '@bitgo/sdk-core';
import * as stellar from 'stellar-sdk';
import { coins, KeyCurve } from '@bitgo/statics';

export type AuditKeyParams = {
coinName: string;
encryptedPrv: string;
walletPassphrase: string;
} & (
| {
multiSigType: 'tss';
publicKey: string;
}
| {
multiSigType?: 'onchain';
publicKey?: string;
}
);

/**
* Audit a key by decrypting the encrypted private key and verifying it matches the provided public key
*
* @param coinName - The coin type
* @param multiSigType - Optional type of multi-signature wallet
* @param encryptedPrv - The encrypted private key to audit
* @param publicKey - The public key to verify against
* @param walletPassphrase - The passphrase to decrypt the encrypted private key
* @returns Object containing audit results
*/
export async function auditKey({
coinName,
publicKey,
encryptedPrv,
walletPassphrase,
multiSigType,
}: AuditKeyParams): Promise<{ isValid: boolean; message?: string }> {
const coin = coins.get(coinName);
if (!coin) {
return { isValid: false, message: `Unsupported coin ${coinName}` };
}
try {
// 1. Decrypt the encrypted private key
const secret = decrypt(walletPassphrase, encryptedPrv);

// 2. Determine curve type based on coin and multiSigType
const curveType = coin.primaryKeyCurve; // default

// 3. Validate the key

switch (curveType) {
case KeyCurve.Secp256k1:
if (multiSigType === 'tss') {
if (publicKey.length !== 130 && publicKey.length !== 0) {
return { isValid: false, message: 'Incorrect TSS common keychain' };
}

// DKLs key chains do not have a fixed length but we know for sure they are greater than 192 in length
if (secret.length !== 128 && secret.length !== 192 && secret.length <= 192) {
return { isValid: false, message: 'Incorrect TSS keychain' };
}
} else if (multiSigType === 'onchain' && publicKey) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if publicKey isn't provided we should be returning isValid:false for multisig no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually we should throw an error, saying public key needs to be provided to validate multisig key

Copy link
Contributor Author

@mohammadalfaiyazbitgo mohammadalfaiyazbitgo May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if publicKey isn't provided we should be returning isValid:false for multisig no?

Actually the problem is that the encrypted prv on the key card doesn't have a public key for user/backup on multiSig Wallets. It just has BitGo's public key.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok if that's the case, we need to at least validate the private key somehow, i.e. length, format, etc... as it stands the code wont validate anything if there isnt a public key provided for multisig

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i understand though length could be tricky since private keys are probably stored encoded based on how the coin likes to encode its prv's . If we can instantiate a coin instance and that coin instance has some validate key function that would be nice

const genPubKey = bitcoin.HDNode.fromBase58(secret).neutered().toBase58();
if (genPubKey !== publicKey) {
return { isValid: false, message: 'Incorrect xpub' };
}
}
break;
case KeyCurve.Ed25519:
if (multiSigType === 'tss') {
// For TSS validation, we would need GPG private key for full implementation
// This is a simplified validation of key format
if (publicKey.length !== 128) {
return { isValid: false, message: 'Incorrect TSS common keychain' };
}

try {
const parsedKey = JSON.parse(secret);
if ('uShare' in parsedKey) {
// If the key is in JSON format, we need to check the private key length
const privateKeyLength = parsedKey.uShare.seed.length + parsedKey.uShare.chaincode.length;
if (privateKeyLength !== 128) {
return { isValid: false, message: 'Incorrect TSS keychain' };
}
} else {
// If the key is not in JSON format, we need to check the length directly
if (secret.length !== 128) {
return { isValid: false, message: 'Incorrect TSS keychain' };
}
}
} catch (e) {
return { isValid: false, message: 'Invalid TSS keychain format' };
}
} else {
// Validate Non-TSS Ed25519 keys (e.g., XLM, ALGO, HBAR)
const sdk = new BitGoAPI();

switch (coinName) {
case 'xlm':
if (secret.substr(0, 1) !== 'S') {
return { isValid: false, message: 'Incorrect XLM key' };
}
if (publicKey) {
const { register: registerXLM } = await import('@bitgo/sdk-coin-xlm');
registerXLM(sdk);
const xlmCoin = sdk.coin('xlm');
const genPubKey = xlmCoin.generateKeyPair((stellar.Keypair.fromSecret(secret) as any)._secretSeed).pub;
if (genPubKey !== publicKey) {
return { isValid: false, message: 'Incorrect XLM public key' };
}
}
break;
case 'algo':
const { register: registerAlgo } = await import('@bitgo/sdk-coin-algo');
registerAlgo(sdk);

// Validate Secret length
if (secret.length !== 58) {
return { isValid: false, message: 'Incorrect ALGO key' };
}
// Validate Public key length if provided
if (publicKey) {
const algoCoin = sdk.coin('algo');
const decoded = base32.decode.asBytes(secret);
const ALGORAND_SEED_BYTE_LENGTH = 36;
const ALGORAND_CHECKSUM_BYTE_LENGTH = 4;
const seed = Buffer.from(decoded.slice(0, ALGORAND_SEED_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH));
const genPubKey = algoCoin.generateKeyPair(seed).pub;
if (genPubKey !== publicKey) {
return { isValid: false, message: 'Incorrect ALGO public key' };
}
}
break;
case 'hbar':
if (secret.length !== 96) {
return { isValid: false, message: 'Incorrect HBAR key' };
}

if (publicKey) {
const { register: registerHbar, KeyPair: hbarKeypair } = await import('@bitgo/sdk-coin-hbar');
registerHbar(sdk);
const genPubKey = new hbarKeypair({ prv: secret }).getKeys().pub;
if (genPubKey !== publicKey) return { isValid: false, message: 'Incorrect HBAR public key' };
}
break;
default:
return { isValid: false, message: `Unsupported coin ${coinName}` };
}
}
break;
default: {
return { isValid: false, message: `Unsupported curve ${curveType}` };
}
}

return { isValid: true };
} catch (error) {
return { isValid: false, message: error.message };
}
}
Loading
Loading