UNPKG

bigblocks

Version:

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

121 lines (120 loc) 10.1 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { PrivateKey, PublicKey } from "@bsv/sdk"; import { ComponentInstanceIcon, ExclamationTriangleIcon, InfoCircledIcon, } from "@radix-ui/react-icons"; import { useState } from "react"; import { cn } from "../../lib/utils.js"; import { Alert, AlertDescription } from "../ui/alert.js"; import { Badge } from "../ui/badge.js"; import { Button } from "../ui/button.js"; import { Card, CardContent } from "../ui/card.js"; import { Input } from "../ui/input.js"; import { Separator } from "../ui/separator.js"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs.js"; import { Textarea } from "../ui/textarea.js"; export function Type42KeyDerivation({ privateKey, onKeyDerived, className = "", }) { const [counterpartyPubKey, setCounterpartyPubKey] = useState(""); const [invoice, setInvoice] = useState(""); const [derivedKeys, setDerivedKeys] = useState([]); const [error, setError] = useState(""); const [copiedIndex, setCopiedIndex] = useState(-1); const [bulkInput, setBulkInput] = useState(""); const [activeTab, setActiveTab] = useState("single"); const handleDerive = () => { try { setError(""); if (!privateKey) { setError("No private key provided"); return; } if (!counterpartyPubKey || !invoice) { setError("Both counterparty public key and invoice are required"); return; } const privKey = PrivateKey.fromWif(privateKey); const pubKey = PublicKey.fromString(counterpartyPubKey); const derivedPrivKey = privKey.deriveChild(pubKey, invoice); const newDerivedKey = { wif: derivedPrivKey.toWif(), address: derivedPrivKey.toAddress(), publicKey: derivedPrivKey.toPublicKey().toString(), counterpartyPubKey, invoice, }; setDerivedKeys([...derivedKeys, newDerivedKey]); onKeyDerived?.(newDerivedKey); // Clear inputs after successful derivation setCounterpartyPubKey(""); setInvoice(""); } catch (err) { setError(err instanceof Error ? err.message : "Failed to derive key"); } }; const handleBulkDerive = () => { try { setError(""); if (!privateKey) { setError("No private key provided"); return; } const lines = bulkInput .trim() .split("\n") .filter((line) => line.trim()); const newDerivedKeys = []; for (const line of lines) { const [pubKeyStr, invoiceStr] = line.split(",").map((s) => s.trim()); if (!pubKeyStr || !invoiceStr) { setError(`Invalid format in line: ${line}`); return; } const privKey = PrivateKey.fromWif(privateKey); const pubKey = PublicKey.fromString(pubKeyStr); const derivedPrivKey = privKey.deriveChild(pubKey, invoiceStr); newDerivedKeys.push({ wif: derivedPrivKey.toWif(), address: derivedPrivKey.toAddress(), publicKey: derivedPrivKey.toPublicKey().toString(), counterpartyPubKey: pubKeyStr, invoice: invoiceStr, }); } setDerivedKeys([...derivedKeys, ...newDerivedKeys]); for (const key of newDerivedKeys) { onKeyDerived?.(key); } setBulkInput(""); } catch (err) { setError(err instanceof Error ? err.message : "Failed to derive keys"); } }; const copyValue = async (value, index) => { try { await navigator.clipboard.writeText(value); setCopiedIndex(index); setTimeout(() => setCopiedIndex(-1), 2000); } catch (err) { console.error("Failed to copy:", err); } }; const exportDerivedKeys = () => { const csv = [ "Invoice,Counterparty Public Key,Derived Address,Derived Public Key,Derived Private Key (WIF)", ...derivedKeys.map((key) => `${key.invoice},${key.counterpartyPubKey},${key.address},${key.publicKey},${key.wif}`), ].join("\n"); const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `type42-derived-keys-${Date.now()}.csv`; a.click(); URL.revokeObjectURL(url); }; const clearDerivedKeys = () => { setDerivedKeys([]); }; return (_jsx("div", { className: cn("flex flex-col gap-4", className), children: _jsx(Card, { children: _jsx(CardContent, { className: "p-6", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "flex justify-between items-center", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ComponentInstanceIcon, { width: "20", height: "20", className: "text-primary" }), _jsx("h3", { className: "font-medium", children: "Type42/BRC-42 Key Derivation" }), derivedKeys.length > 0 && (_jsxs(Badge, { variant: "secondary", children: [derivedKeys.length, " derived"] }))] }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Derive child keys using the BRC-42 standard for privacy-preserving key generation with counterparties." }), !privateKey && (_jsxs(Alert, { children: [_jsx(InfoCircledIcon, { className: "h-4 w-4" }), _jsx(AlertDescription, { children: "No private key provided. Pass a WIF format private key to derive child keys." })] })), _jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [_jsx(TabsTrigger, { value: "single", children: "Single Derivation" }), _jsx(TabsTrigger, { value: "bulk", children: "Bulk Derivation" })] }), _jsxs("div", { className: "mt-6", children: [_jsx(TabsContent, { value: "single", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { htmlFor: "counterparty-pubkey", className: "text-sm text-muted-foreground", children: "Counterparty Public Key" }), _jsx(Input, { id: "counterparty-pubkey", placeholder: "02abc123... (33 or 65 byte hex)", value: counterpartyPubKey, onChange: (e) => setCounterpartyPubKey(e.target.value) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { htmlFor: "invoice-ref", className: "text-sm text-muted-foreground", children: "Invoice/Reference" }), _jsx(Input, { id: "invoice-ref", placeholder: "invoice-123, order-456, etc.", value: invoice, onChange: (e) => setInvoice(e.target.value) })] })] }), _jsx(Button, { onClick: handleDerive, disabled: !privateKey || !counterpartyPubKey || !invoice, size: "sm", children: "Derive Child Key" })] }) }), _jsx(TabsContent, { value: "bulk", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { htmlFor: "bulk-input", className: "text-sm text-muted-foreground", children: "Bulk Input (CSV format: publicKey,invoice)" }), _jsx(Textarea, { id: "bulk-input", placeholder: "02abc123...,invoice-001\n03def456...,invoice-002\n02ghi789...,order-123", value: bulkInput, onChange: (e) => setBulkInput(e.target.value), rows: 6 })] }), _jsx(Button, { onClick: handleBulkDerive, disabled: !privateKey || !bulkInput.trim(), size: "sm", children: "Derive All Keys" })] }) })] })] }), derivedKeys.length > 0 && (_jsxs("div", { children: [_jsx(Separator, { className: "my-4" }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("p", { className: "text-sm font-medium", children: "Derived Keys" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: exportDerivedKeys, children: "Export CSV" }), _jsx(Button, { size: "sm", variant: "outline", className: "text-destructive hover:text-destructive", onClick: clearDerivedKeys, children: "Clear All" })] })] }), _jsx("div", { className: "flex flex-col gap-2 overflow-y-auto", style: { maxHeight: "400px" }, children: derivedKeys.map((key, index) => (_jsx(Card, { children: _jsx(CardContent, { className: "p-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsx(Badge, { variant: "secondary", children: key.invoice }), _jsx(Button, { size: "sm", variant: "ghost", className: cn(copiedIndex === index && "text-green-600"), onClick: () => copyValue(key.wif, index), children: copiedIndex === index ? "Copied!" : "Copy WIF" })] }), _jsxs("dl", { className: "space-y-2", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx("dt", { className: "text-sm text-muted-foreground min-w-[120px]", children: "Address" }), _jsx("dd", { children: _jsx("code", { className: "text-xs bg-muted px-1 py-0.5 rounded break-all", children: key.address }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("dt", { className: "text-sm text-muted-foreground min-w-[120px]", children: "Counterparty" }), _jsx("dd", { children: _jsxs("code", { className: "text-xs bg-muted px-1 py-0.5 rounded break-all", children: [key.counterpartyPubKey.substring(0, 20), "..."] }) })] })] })] }) }) }, key.address))) })] })] })), error && (_jsxs(Alert, { variant: "destructive", className: "mt-3", children: [_jsx(ExclamationTriangleIcon, { className: "h-4 w-4" }), _jsx(AlertDescription, { children: error })] }))] }) }) }) })); }