UNPKG

bigblocks

Version:

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

91 lines (90 loc) 8.77 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { KeyShares, PrivateKey } from "@bsv/sdk"; import { CheckCircledIcon, CheckIcon, CopyIcon, Cross2Icon, ExclamationTriangleIcon, InfoCircledIcon, PlusIcon, } 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 } from "../ui/card.js"; import { Input } from "../ui/input.js"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs.js"; export function ShamirSecretSharing({ privateKey, onKeyReconstructed, className = "", }) { const [threshold, setThreshold] = useState(2); const [totalShares, setTotalShares] = useState(3); const [shares, setShares] = useState([]); const [inputShares, setInputShares] = useState([ { id: 1, value: "" }, { id: 2, value: "" }, ]); const [nextId, setNextId] = useState(3); const [reconstructedKey, setReconstructedKey] = useState(""); const [error, setError] = useState(""); const [copiedIndex, setCopiedIndex] = useState(-1); const handleGenerateShares = () => { try { setError(""); setShares([]); if (!privateKey) { setError("No private key provided"); return; } // Use BSV SDK's built-in Shamir Secret Sharing const privKey = PrivateKey.fromWif(privateKey); const keyShares = privKey.toKeyShares(threshold, totalShares); const shareStrings = keyShares.toBackupFormat(); setShares(shareStrings); } catch (err) { setError(err instanceof Error ? err.message : "Failed to generate shares"); } }; const handleReconstruct = () => { try { setError(""); setReconstructedKey(""); const validShares = inputShares .map((s) => s.value) .filter((s) => s.trim().length > 0); if (validShares.length < threshold) { setError(`At least ${threshold} shares are required`); return; } // Use BSV SDK's built-in reconstruction const keyShares = KeyShares.fromBackupFormat(validShares); const reconstructedPrivKey = PrivateKey.fromKeyShares(keyShares); const wif = reconstructedPrivKey.toWif(); setReconstructedKey(wif); onKeyReconstructed?.(wif); } catch (err) { setError(err instanceof Error ? err.message : "Failed to reconstruct key from shares"); } }; const handleCopyShare = async (shareIndex) => { try { await navigator.clipboard.writeText(shares[shareIndex] || ""); setCopiedIndex(shareIndex); setTimeout(() => setCopiedIndex(-1), 2000); } catch (error) { console.error("Failed to copy share:", error); } }; const handleShareInputChange = (id, value) => { setInputShares(inputShares.map((share) => share.id === id ? { ...share, value } : share)); }; const addShareInput = () => { setInputShares([...inputShares, { id: nextId, value: "" }]); setNextId(nextId + 1); }; const removeShareInput = (id) => { if (inputShares.length > 1) { setInputShares(inputShares.filter((share) => share.id !== id)); } }; return (_jsx(Card, { className: cn("p-6", className), children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("h2", { className: "text-xl font-bold", children: "Shamir Secret Sharing" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Split your private key into multiple shares. Any ", threshold, " of", " ", totalShares, " shares can reconstruct the original key."] }), _jsxs(Tabs, { defaultValue: "generate", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [_jsx(TabsTrigger, { value: "generate", children: "Generate Shares" }), _jsx(TabsTrigger, { value: "reconstruct", children: "Reconstruct Key" })] }), _jsxs("div", { className: "mt-6", children: [_jsx(TabsContent, { value: "generate", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "threshold-input", className: "text-sm font-medium mb-2 block", children: "Threshold" }), _jsx(Input, { id: "threshold-input", type: "number", value: threshold.toString(), onChange: (e) => setThreshold(Math.max(2, Number.parseInt(e.target.value) || 2)), min: "2", max: totalShares.toString() })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "total-shares-input", className: "text-sm font-medium mb-2 block", children: "Total Shares" }), _jsx(Input, { id: "total-shares-input", type: "number", value: totalShares.toString(), onChange: (e) => setTotalShares(Math.max(threshold, Number.parseInt(e.target.value) || 3)), min: threshold.toString(), max: "16" })] })] }), _jsxs(Button, { onClick: handleGenerateShares, disabled: !privateKey, children: ["Generate ", totalShares, " Shares (Threshold: ", threshold, ")"] }), shares.length > 0 && (_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-medium mb-3", children: "Generated Shares" }), _jsxs(Alert, { className: "mb-3", children: [_jsx(InfoCircledIcon, { className: "h-4 w-4" }), _jsxs(AlertDescription, { children: ["Store these shares securely in different locations. Any", " ", threshold, " shares can reconstruct your private key."] })] }), _jsx("div", { className: "flex flex-col gap-2", children: shares.map((share, index) => (_jsx(Card, { className: "p-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", className: "text-xs", children: ["Share ", index + 1] }), _jsxs("code", { className: "text-xs bg-muted px-1 py-0.5 rounded max-w-xs overflow-hidden", children: [share.slice(0, 20), "...", share.slice(-10)] })] }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleCopyShare(index), children: copiedIndex === index ? (_jsx(CheckIcon, { className: "h-4 w-4 text-green-600" })) : (_jsx(CopyIcon, { className: "h-4 w-4" })) })] }) }, share))) })] }))] }) }), _jsx(TabsContent, { value: "reconstruct", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Enter at least ", threshold, " shares to reconstruct the private key."] }), _jsx("div", { className: "flex flex-col gap-2", children: inputShares.map((share, index) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-sm min-w-[60px]", children: ["Share ", index + 1, ":"] }), _jsx(Input, { placeholder: `Paste share #${index + 1}`, value: share.value, onChange: (e) => handleShareInputChange(share.id, e.target.value), className: "flex-1" }), _jsx(Button, { size: "icon", variant: "outline", className: "text-destructive hover:text-destructive", onClick: () => removeShareInput(share.id), disabled: inputShares.length <= 1, children: _jsx(Cross2Icon, { className: "h-4 w-4" }) })] }, share.id))) }), _jsxs("div", { className: "flex justify-between", children: [_jsxs(Button, { variant: "secondary", onClick: addShareInput, children: [_jsx(PlusIcon, {}), "Add Share"] }), _jsx(Button, { onClick: handleReconstruct, children: "Reconstruct Private Key" })] }), reconstructedKey && (_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-medium mb-2", children: "Reconstructed Private Key" }), _jsx(Card, { className: "p-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("code", { className: "text-xs", children: [reconstructedKey.slice(0, 10), "...", reconstructedKey.slice(-10)] }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => navigator.clipboard.writeText(reconstructedKey), children: _jsx(CopyIcon, {}) })] }) }), _jsxs("div", { className: "bg-green-50 border border-green-200 rounded-md p-3 mt-2 flex items-start gap-2", children: [_jsx(CheckCircledIcon, { className: "text-green-600 mt-0.5" }), _jsx("p", { className: "text-sm text-green-700", children: "Private key successfully reconstructed from shares!" })] })] }))] }) })] })] }), error && (_jsxs(Alert, { variant: "destructive", children: [_jsx(ExclamationTriangleIcon, { className: "h-4 w-4" }), _jsx(AlertDescription, { children: error })] }))] }) })); }