bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
17 lines • 14.2 kB
JSON
{
"name": "developer-tools-type42keyderivation",
"type": "registry:component",
"dependencies": [
"@bsv/sdk"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/developer-tools/Type42KeyDerivation.tsx",
"type": "registry:component",
"content": "\"use client\";\n\nimport { PrivateKey, PublicKey } from \"@bsv/sdk\";\nimport {\n\tComponentInstanceIcon,\n\tExclamationTriangleIcon,\n\tInfoCircledIcon,\n} from \"@radix-ui/react-icons\";\nimport { useState } from \"react\";\nimport { SPACING } from \"../../lib/layout-constants.js\";\nimport { cn } from \"../../lib/utils.js\";\nimport { Alert, AlertDescription } from \"../ui/alert.js\";\nimport { Badge } from \"../ui/badge.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"../ui/card.js\";\nimport { Input } from \"../ui/input.js\";\nimport { Separator } from \"../ui/separator.js\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs.js\";\nimport { Textarea } from \"../ui/textarea.js\";\n\nexport interface DerivedKey {\n\twif: string;\n\taddress: string;\n\tpublicKey: string;\n\tcounterpartyPubKey: string;\n\tinvoice: string;\n}\n\nexport interface Type42KeyDerivationProps {\n\tprivateKey?: string; // WIF format\n\tonKeyDerived?: (derivedKey: DerivedKey) => void;\n\tclassName?: string;\n}\n\nexport function Type42KeyDerivation({\n\tprivateKey,\n\tonKeyDerived,\n\tclassName = \"\",\n}: Type42KeyDerivationProps) {\n\tconst [counterpartyPubKey, setCounterpartyPubKey] = useState(\"\");\n\tconst [invoice, setInvoice] = useState(\"\");\n\tconst [derivedKeys, setDerivedKeys] = useState<DerivedKey[]>([]);\n\tconst [error, setError] = useState(\"\");\n\tconst [copiedIndex, setCopiedIndex] = useState<number>(-1);\n\tconst [bulkInput, setBulkInput] = useState(\"\");\n\tconst [activeTab, setActiveTab] = useState(\"single\");\n\n\tconst handleDerive = () => {\n\t\ttry {\n\t\t\tsetError(\"\");\n\n\t\t\tif (!privateKey) {\n\t\t\t\tsetError(\"No private key provided\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!counterpartyPubKey || !invoice) {\n\t\t\t\tsetError(\"Both counterparty public key and invoice are required\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst privKey = PrivateKey.fromWif(privateKey);\n\t\t\tconst pubKey = PublicKey.fromString(counterpartyPubKey);\n\t\t\tconst derivedPrivKey = privKey.deriveChild(pubKey, invoice);\n\n\t\t\tconst newDerivedKey: DerivedKey = {\n\t\t\t\twif: derivedPrivKey.toWif(),\n\t\t\t\taddress: derivedPrivKey.toAddress(),\n\t\t\t\tpublicKey: derivedPrivKey.toPublicKey().toString(),\n\t\t\t\tcounterpartyPubKey,\n\t\t\t\tinvoice,\n\t\t\t};\n\n\t\t\tsetDerivedKeys([...derivedKeys, newDerivedKey]);\n\t\t\tonKeyDerived?.(newDerivedKey);\n\n\t\t\t// Clear inputs after successful derivation\n\t\t\tsetCounterpartyPubKey(\"\");\n\t\t\tsetInvoice(\"\");\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"Failed to derive key\");\n\t\t}\n\t};\n\n\tconst handleBulkDerive = () => {\n\t\ttry {\n\t\t\tsetError(\"\");\n\n\t\t\tif (!privateKey) {\n\t\t\t\tsetError(\"No private key provided\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst lines = bulkInput\n\t\t\t\t.trim()\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter((line) => line.trim());\n\t\t\tconst newDerivedKeys: DerivedKey[] = [];\n\n\t\t\tfor (const line of lines) {\n\t\t\t\tconst [pubKeyStr, invoiceStr] = line.split(\",\").map((s) => s.trim());\n\n\t\t\t\tif (!pubKeyStr || !invoiceStr) {\n\t\t\t\t\tsetError(`Invalid format in line: ${line}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst privKey = PrivateKey.fromWif(privateKey);\n\t\t\t\tconst pubKey = PublicKey.fromString(pubKeyStr);\n\t\t\t\tconst derivedPrivKey = privKey.deriveChild(pubKey, invoiceStr);\n\n\t\t\t\tnewDerivedKeys.push({\n\t\t\t\t\twif: derivedPrivKey.toWif(),\n\t\t\t\t\taddress: derivedPrivKey.toAddress(),\n\t\t\t\t\tpublicKey: derivedPrivKey.toPublicKey().toString(),\n\t\t\t\t\tcounterpartyPubKey: pubKeyStr,\n\t\t\t\t\tinvoice: invoiceStr,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tsetDerivedKeys([...derivedKeys, ...newDerivedKeys]);\n\t\t\tfor (const key of newDerivedKeys) {\n\t\t\t\tonKeyDerived?.(key);\n\t\t\t}\n\t\t\tsetBulkInput(\"\");\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"Failed to derive keys\");\n\t\t}\n\t};\n\n\tconst copyValue = async (value: string, index: number) => {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(value);\n\t\t\tsetCopiedIndex(index);\n\t\t\tsetTimeout(() => setCopiedIndex(-1), 2000);\n\t\t} catch (err) {\n\t\t\tconsole.error(\"Failed to copy:\", err);\n\t\t}\n\t};\n\n\tconst exportDerivedKeys = () => {\n\t\tconst csv = [\n\t\t\t\"Invoice,Counterparty Public Key,Derived Address,Derived Public Key,Derived Private Key (WIF)\",\n\t\t\t...derivedKeys.map(\n\t\t\t\t(key) =>\n\t\t\t\t\t`${key.invoice},${key.counterpartyPubKey},${key.address},${key.publicKey},${key.wif}`,\n\t\t\t),\n\t\t].join(\"\\n\");\n\n\t\tconst blob = new Blob([csv], { type: \"text/csv\" });\n\t\tconst url = URL.createObjectURL(blob);\n\t\tconst a = document.createElement(\"a\");\n\t\ta.href = url;\n\t\ta.download = `type42-derived-keys-${Date.now()}.csv`;\n\t\ta.click();\n\t\tURL.revokeObjectURL(url);\n\t};\n\n\tconst clearDerivedKeys = () => {\n\t\tsetDerivedKeys([]);\n\t};\n\n\treturn (\n\t\t<div className={cn(\"flex flex-col gap-4\", className)}>\n\t\t\t<Card>\n\t\t\t\t<CardContent className=\"p-6\">\n\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<ComponentInstanceIcon\n\t\t\t\t\t\t\t\t\twidth=\"20\"\n\t\t\t\t\t\t\t\t\theight=\"20\"\n\t\t\t\t\t\t\t\t\tclassName=\"text-primary\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<h3 className=\"font-medium\">Type42/BRC-42 Key Derivation</h3>\n\t\t\t\t\t\t\t\t{derivedKeys.length > 0 && (\n\t\t\t\t\t\t\t\t\t<Badge variant=\"secondary\">\n\t\t\t\t\t\t\t\t\t\t{derivedKeys.length} derived\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\tDerive child keys using the BRC-42 standard for privacy-preserving\n\t\t\t\t\t\t\tkey generation with counterparties.\n\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t{!privateKey && (\n\t\t\t\t\t\t\t<Alert>\n\t\t\t\t\t\t\t\t<InfoCircledIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t<AlertDescription>\n\t\t\t\t\t\t\t\t\tNo private key provided. Pass a WIF format private key to\n\t\t\t\t\t\t\t\t\tderive child keys.\n\t\t\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t\t\t</Alert>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t<Tabs value={activeTab} onValueChange={setActiveTab}>\n\t\t\t\t\t\t\t<TabsList className=\"grid w-full grid-cols-2\">\n\t\t\t\t\t\t\t\t<TabsTrigger value=\"single\">Single Derivation</TabsTrigger>\n\t\t\t\t\t\t\t\t<TabsTrigger value=\"bulk\">Bulk Derivation</TabsTrigger>\n\t\t\t\t\t\t\t</TabsList>\n\n\t\t\t\t\t\t\t<div className=\"mt-6\">\n\t\t\t\t\t\t\t\t<TabsContent value=\"single\">\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t\t\t\t<div className=\"space-y-3\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\t\t\t\t\t\thtmlFor=\"counterparty-pubkey\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\tCounterparty Public Key\n\t\t\t\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\t\t\tid=\"counterparty-pubkey\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"02abc123... (33 or 65 byte hex)\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={counterpartyPubKey}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetCounterpartyPubKey(e.target.value)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\t\t\t\t\t\thtmlFor=\"invoice-ref\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\tInvoice/Reference\n\t\t\t\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\t\t\tid=\"invoice-ref\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"invoice-123, order-456, etc.\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={invoice}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => setInvoice(e.target.value)}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tonClick={handleDerive}\n\t\t\t\t\t\t\t\t\t\t\tdisabled={!privateKey || !counterpartyPubKey || !invoice}\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tDerive Child Key\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</TabsContent>\n\n\t\t\t\t\t\t\t\t<TabsContent value=\"bulk\">\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\t\t\t\t\thtmlFor=\"bulk-input\"\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\tBulk Input (CSV format: publicKey,invoice)\n\t\t\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\t\t\t\t\t\tid=\"bulk-input\"\n\t\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"02abc123...,invoice-001\n03def456...,invoice-002\n02ghi789...,order-123\"\n\t\t\t\t\t\t\t\t\t\t\t\tvalue={bulkInput}\n\t\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => setBulkInput(e.target.value)}\n\t\t\t\t\t\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tonClick={handleBulkDerive}\n\t\t\t\t\t\t\t\t\t\t\tdisabled={!privateKey || !bulkInput.trim()}\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tDerive All Keys\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</TabsContent>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</Tabs>\n\n\t\t\t\t\t\t{derivedKeys.length > 0 && (\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<Separator className=\"my-4\" />\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Derived Keys</p>\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={exportDerivedKeys}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\tExport CSV\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-destructive hover:text-destructive\"\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={clearDerivedKeys}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\tClear All\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex flex-col gap-2 overflow-y-auto\"\n\t\t\t\t\t\t\t\t\t\tstyle={{ maxHeight: \"400px\" }}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{derivedKeys.map((key, index) => (\n\t\t\t\t\t\t\t\t\t\t\t<Card key={key.address}>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardContent className=\"p-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Badge variant=\"secondary\">{key.invoice}</Badge>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcopiedIndex === index && \"text-green-600\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => copyValue(key.wif, index)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{copiedIndex === index ? \"Copied!\" : \"Copy WIF\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dl className=\"space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt className=\"text-sm text-muted-foreground min-w-[120px]\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tAddress\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<code className=\"text-xs bg-muted px-1 py-0.5 rounded break-all\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{key.address}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt className=\"text-sm text-muted-foreground min-w-[120px]\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCounterparty\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<code className=\"text-xs bg-muted px-1 py-0.5 rounded break-all\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{key.counterpartyPubKey.substring(0, 20)}...\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dl>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{error && (\n\t\t\t\t\t\t\t<Alert variant=\"destructive\" className=\"mt-3\">\n\t\t\t\t\t\t\t\t<ExclamationTriangleIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t<AlertDescription>{error}</AlertDescription>\n\t\t\t\t\t\t\t</Alert>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t</div>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/type42keyderivation.tsx"
}
]
}