|
| 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