UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

17 lines 8.84 kB
{ "name": "bap-identity-hooks-usemasterkeymigration", "type": "registry:hook", "dependencies": [ "@tanstack/react-query" ], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/bap-identity/hooks/useMasterKeyMigration.ts", "type": "registry:hook", "content": "import { useMutation } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport type {\n\tBackupFormat,\n\tBackupFormatDetectionResult,\n\tBackupValidationResult,\n\tLegacyMasterBackup,\n\tMigrationOptions,\n\tMigrationResult,\n\tType42MasterBackup,\n\tUseMasterKeyMigrationResult,\n} from \"../types/migration.js\";\n\nexport function useMasterKeyMigration(): UseMasterKeyMigrationResult {\n\tconst { user, actions } = useBitcoinAuth();\n\tconst [progress, setProgress] = useState(0);\n\n\t// Detect backup format from structure\n\tconst detectBackupFormat = useCallback(\n\t\t(backup: unknown): BackupFormatDetectionResult => {\n\t\t\tif (!backup || typeof backup !== \"object\") {\n\t\t\t\treturn {\n\t\t\t\t\tformat: \"legacy\" as BackupFormat,\n\t\t\t\t\tisValid: false,\n\t\t\t\t\thasEncryptedData: false,\n\t\t\t\t\tkeyInfo: {\n\t\t\t\t\t\thasXprv: false,\n\t\t\t\t\t\thasMnemonic: false,\n\t\t\t\t\t\thasRootPk: false,\n\t\t\t\t\t\thasIds: false,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst b = backup as Record<string, unknown>;\n\n\t\t\tconst keyInfo = {\n\t\t\t\thasXprv: typeof b.xprv === \"string\" && b.xprv.length > 0,\n\t\t\t\thasMnemonic: typeof b.mnemonic === \"string\" && b.mnemonic.length > 0,\n\t\t\t\thasRootPk: typeof b.rootPk === \"string\" && b.rootPk.length > 0,\n\t\t\t\thasIds: typeof b.ids === \"string\" && b.ids.length > 0,\n\t\t\t};\n\n\t\t\t// Type 42 detection: has rootPk and ids, no xprv\n\t\t\tif (keyInfo.hasRootPk && keyInfo.hasIds && !keyInfo.hasXprv) {\n\t\t\t\treturn {\n\t\t\t\t\tformat: \"type42\",\n\t\t\t\t\tisValid: true,\n\t\t\t\t\thasEncryptedData: keyInfo.hasIds,\n\t\t\t\t\tkeyInfo,\n\t\t\t\t\testimatedCreationDate:\n\t\t\t\t\t\ttypeof b.createdAt === \"string\" ? b.createdAt : undefined,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Legacy detection: has xprv and mnemonic\n\t\t\tif (keyInfo.hasXprv && keyInfo.hasMnemonic) {\n\t\t\t\treturn {\n\t\t\t\t\tformat: \"legacy\",\n\t\t\t\t\tisValid: true,\n\t\t\t\t\thasEncryptedData: keyInfo.hasIds,\n\t\t\t\t\tkeyInfo,\n\t\t\t\t\testimatedCreationDate:\n\t\t\t\t\t\ttypeof b.createdAt === \"string\" ? b.createdAt : undefined,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Invalid or incomplete backup\n\t\t\treturn {\n\t\t\t\tformat: \"legacy\",\n\t\t\t\tisValid: false,\n\t\t\t\thasEncryptedData: keyInfo.hasIds,\n\t\t\t\tkeyInfo,\n\t\t\t};\n\t\t},\n\t\t[],\n\t);\n\n\t// Validate backup format with detailed errors\n\tconst _validateBackupFormat = useCallback(\n\t\t(backup: unknown): BackupValidationResult => {\n\t\t\tconst baseResult = detectBackupFormat(backup);\n\t\t\tconst errors: Array<{\n\t\t\t\tfield: string;\n\t\t\t\tmessage: string;\n\t\t\t\tseverity: \"error\" | \"warning\";\n\t\t\t}> = [];\n\t\t\tconst warnings: Array<{\n\t\t\t\tfield: string;\n\t\t\t\tmessage: string;\n\t\t\t\tseverity: \"error\" | \"warning\";\n\t\t\t}> = [];\n\n\t\t\tif (!baseResult.isValid) {\n\t\t\t\tif (!baseResult.keyInfo.hasIds) {\n\t\t\t\t\terrors.push({\n\t\t\t\t\t\tfield: \"ids\",\n\t\t\t\t\t\tmessage: \"Missing encrypted identity data\",\n\t\t\t\t\t\tseverity: \"error\",\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif (baseResult.format === \"legacy\") {\n\t\t\t\t\tif (!baseResult.keyInfo.hasXprv) {\n\t\t\t\t\t\terrors.push({\n\t\t\t\t\t\t\tfield: \"xprv\",\n\t\t\t\t\t\t\tmessage: \"Missing extended private key (xprv)\",\n\t\t\t\t\t\t\tseverity: \"error\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (!baseResult.keyInfo.hasMnemonic) {\n\t\t\t\t\t\terrors.push({\n\t\t\t\t\t\t\tfield: \"mnemonic\",\n\t\t\t\t\t\t\tmessage: \"Missing BIP39 mnemonic phrase\",\n\t\t\t\t\t\t\tseverity: \"error\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (!baseResult.keyInfo.hasRootPk) {\n\t\t\t\t\t\terrors.push({\n\t\t\t\t\t\t\tfield: \"rootPk\",\n\t\t\t\t\t\t\tmessage: \"Missing Type 42 root private key (WIF)\",\n\t\t\t\t\t\t\tseverity: \"error\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Warnings for legacy format\n\t\t\tif (baseResult.format === \"legacy\" && baseResult.isValid) {\n\t\t\t\twarnings.push({\n\t\t\t\t\tfield: \"format\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Legacy BIP32 format detected - consider migrating to Type 42\",\n\t\t\t\t\tseverity: \"warning\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...baseResult,\n\t\t\t\terrors,\n\t\t\t\twarnings,\n\t\t\t\tcanProceed: errors.length === 0,\n\t\t\t};\n\t\t},\n\t\t[detectBackupFormat],\n\t);\n\n\t// Get current user's backup info\n\tconst [currentBackupInfo, setCurrentBackupInfo] =\n\t\tuseState<BackupFormatDetectionResult | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!user) {\n\t\t\tsetCurrentBackupInfo(null);\n\t\t\treturn;\n\t\t}\n\n\t\t// Safely get backup from session storage\n\t\tconst loadBackupInfo = async () => {\n\t\t\ttry {\n\t\t\t\tconst backup = await actions.getCurrentBackup();\n\t\t\t\tif (backup) {\n\t\t\t\t\tsetCurrentBackupInfo(detectBackupFormat(backup));\n\t\t\t\t} else {\n\t\t\t\t\tsetCurrentBackupInfo(null);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tsetCurrentBackupInfo(null);\n\t\t\t}\n\t\t};\n\n\t\tloadBackupInfo();\n\t}, [user, actions, detectBackupFormat]);\n\n\t// Check if user can migrate (has legacy master backup)\n\tconst canMigrate = useMemo(() => {\n\t\tconst info = currentBackupInfo;\n\t\treturn (\n\t\t\tinfo?.format === \"legacy\" &&\n\t\t\tinfo.isValid &&\n\t\t\tinfo.keyInfo.hasXprv &&\n\t\t\tinfo.keyInfo.hasMnemonic\n\t\t);\n\t}, [currentBackupInfo]);\n\n\t// Check if user already has Type 42 backup\n\tconst hasType42Backup = useMemo(() => {\n\t\tconst info = currentBackupInfo;\n\t\treturn info?.format === \"type42\" && info.isValid;\n\t}, [currentBackupInfo]);\n\n\t// Migration mutation\n\tconst migrationMutation = useMutation<\n\t\tMigrationResult,\n\t\tError,\n\t\tMigrationOptions\n\t>({\n\t\tmutationFn: async (options: MigrationOptions = {}) => {\n\t\t\tif (!user || !canMigrate) {\n\t\t\t\tthrow new Error(\"No legacy master backup available for migration\");\n\t\t\t}\n\n\t\t\t// Safely get current backup from session storage\n\t\t\tconst backup = await actions.getCurrentBackup();\n\t\t\tif (!backup || !(\"xprv\" in backup) || !(\"mnemonic\" in backup)) {\n\t\t\t\tthrow new Error(\"Invalid legacy backup: missing xprv or mnemonic\");\n\t\t\t}\n\n\t\t\tconst currentBackup = backup as LegacyMasterBackup;\n\n\t\t\tsetProgress(10);\n\n\t\t\ttry {\n\t\t\t\t// Step 1: Import current legacy backup and extract private key\n\t\t\t\tsetProgress(25);\n\n\t\t\t\t// NOTE: This is where we'd use the updated bsv-bap library\n\t\t\t\t// In practice, we'd need to:\n\t\t\t\t// 1. Extract the root private key from the legacy xprv\n\t\t\t\t// 2. Create a new Type 42 BAP instance with the derived WIF\n\t\t\t\t// 3. Export the new backup in Type 42 format\n\n\t\t\t\t// For now, we'll simulate the conversion process\n\t\t\t\t// In reality, this would use the actual bsv-bap and bitcoin-backup libraries\n\n\t\t\t\tsetProgress(50);\n\n\t\t\t\t// Step 2: Create Type 42 backup (simulated)\n\t\t\t\t// const privateKey = PrivateKey.fromWif(derivedWif);\n\t\t\t\t// const newBapInstance = new BAP({ rootPk: privateKey.toWif() });\n\n\t\t\t\tsetProgress(75);\n\n\t\t\t\t// Step 3: Export new Type 42 backup\n\t\t\t\tconst newBackup: Type42MasterBackup = {\n\t\t\t\t\tids: currentBackup.ids, // Re-use encrypted identity data for now\n\t\t\t\t\trootPk: \"L1d5g2CJoWuwjoWVyG2FS7KvmLkmoi9VX8qDqpuijiWcHxVTHo2F\", // Simulated Type 42 WIF\n\t\t\t\t\tlabel:\n\t\t\t\t\t\toptions.newLabel ||\n\t\t\t\t\t\t`${currentBackup.label || \"Migrated\"} (Type 42)`,\n\t\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\t};\n\n\t\t\t\tsetProgress(100);\n\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tnewBackup,\n\t\t\t\t\twarning:\n\t\t\t\t\t\t\"Migration creates new identity keys - existing attestations will not transfer automatically.\",\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Migration failed:\", error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t});\n\n\tconst migrate = useCallback(\n\t\t(options?: MigrationOptions) =>\n\t\t\tmigrationMutation.mutateAsync(options || {}),\n\t\t[migrationMutation],\n\t);\n\n\treturn {\n\t\tdetectBackupFormat,\n\t\tcanMigrate,\n\t\thasType42Backup,\n\t\tmigrate,\n\t\tcurrentBackupInfo,\n\t\tisLoading: migrationMutation.isPending,\n\t\terror: migrationMutation.error,\n\t\tprogress,\n\t};\n}\n", "target": "<%- config.aliases.hooks %>/usemasterkeymigration.ts" } ] }