bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
91 lines (90 loc) • 8.77 kB
JavaScript
"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 })] }))] }) }));
}