UNPKG

bigblocks

Version:

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

17 lines 12.8 kB
{ "name": "developer-tools-shamirsecretsharing", "type": "registry:component", "dependencies": [ "@bsv/sdk" ], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/developer-tools/ShamirSecretSharing.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport { KeyShares, PrivateKey } from \"@bsv/sdk\";\nimport {\n\tCheckCircledIcon,\n\tCheckIcon,\n\tCopyIcon,\n\tCross2Icon,\n\tExclamationTriangleIcon,\n\tInfoCircledIcon,\n\tPlusIcon,\n} from \"@radix-ui/react-icons\";\nimport { useState } from \"react\";\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 { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs.js\";\n\nexport interface ShamirSecretSharingProps {\n\tprivateKey?: string; // WIF format\n\tonKeyReconstructed?: (wif: string) => void;\n\tclassName?: string;\n}\n\nexport function ShamirSecretSharing({\n\tprivateKey,\n\tonKeyReconstructed,\n\tclassName = \"\",\n}: ShamirSecretSharingProps) {\n\tconst [threshold, setThreshold] = useState(2);\n\tconst [totalShares, setTotalShares] = useState(3);\n\tconst [shares, setShares] = useState<string[]>([]);\n\tconst [inputShares, setInputShares] = useState([\n\t\t{ id: 1, value: \"\" },\n\t\t{ id: 2, value: \"\" },\n\t]);\n\tconst [nextId, setNextId] = useState(3);\n\tconst [reconstructedKey, setReconstructedKey] = useState<string>(\"\");\n\tconst [error, setError] = useState<string>(\"\");\n\tconst [copiedIndex, setCopiedIndex] = useState<number>(-1);\n\n\tconst handleGenerateShares = () => {\n\t\ttry {\n\t\t\tsetError(\"\");\n\t\t\tsetShares([]);\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\t// Use BSV SDK's built-in Shamir Secret Sharing\n\t\t\tconst privKey = PrivateKey.fromWif(privateKey);\n\t\t\tconst keyShares = privKey.toKeyShares(threshold, totalShares);\n\t\t\tconst shareStrings = keyShares.toBackupFormat();\n\n\t\t\tsetShares(shareStrings);\n\t\t} catch (err) {\n\t\t\tsetError(\n\t\t\t\terr instanceof Error ? err.message : \"Failed to generate shares\",\n\t\t\t);\n\t\t}\n\t};\n\n\tconst handleReconstruct = () => {\n\t\ttry {\n\t\t\tsetError(\"\");\n\t\t\tsetReconstructedKey(\"\");\n\n\t\t\tconst validShares = inputShares\n\t\t\t\t.map((s) => s.value)\n\t\t\t\t.filter((s) => s.trim().length > 0);\n\t\t\tif (validShares.length < threshold) {\n\t\t\t\tsetError(`At least ${threshold} shares are required`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Use BSV SDK's built-in reconstruction\n\t\t\tconst keyShares = KeyShares.fromBackupFormat(validShares);\n\t\t\tconst reconstructedPrivKey = PrivateKey.fromKeyShares(keyShares);\n\t\t\tconst wif = reconstructedPrivKey.toWif();\n\n\t\t\tsetReconstructedKey(wif);\n\t\t\tonKeyReconstructed?.(wif);\n\t\t} catch (err) {\n\t\t\tsetError(\n\t\t\t\terr instanceof Error\n\t\t\t\t\t? err.message\n\t\t\t\t\t: \"Failed to reconstruct key from shares\",\n\t\t\t);\n\t\t}\n\t};\n\n\tconst handleCopyShare = async (shareIndex: number) => {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(shares[shareIndex] || \"\");\n\t\t\tsetCopiedIndex(shareIndex);\n\t\t\tsetTimeout(() => setCopiedIndex(-1), 2000);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to copy share:\", error);\n\t\t}\n\t};\n\n\tconst handleShareInputChange = (id: number, value: string) => {\n\t\tsetInputShares(\n\t\t\tinputShares.map((share) =>\n\t\t\t\tshare.id === id ? { ...share, value } : share,\n\t\t\t),\n\t\t);\n\t};\n\n\tconst addShareInput = () => {\n\t\tsetInputShares([...inputShares, { id: nextId, value: \"\" }]);\n\t\tsetNextId(nextId + 1);\n\t};\n\n\tconst removeShareInput = (id: number) => {\n\t\tif (inputShares.length > 1) {\n\t\t\tsetInputShares(inputShares.filter((share) => share.id !== id));\n\t\t}\n\t};\n\n\treturn (\n\t\t<Card className={cn(\"p-6\", className)}>\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t<h2 className=\"text-xl font-bold\">Shamir Secret Sharing</h2>\n\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\tSplit your private key into multiple shares. Any {threshold} of{\" \"}\n\t\t\t\t\t{totalShares} shares can reconstruct the original key.\n\t\t\t\t</p>\n\n\t\t\t\t<Tabs defaultValue=\"generate\">\n\t\t\t\t\t<TabsList className=\"grid w-full grid-cols-2\">\n\t\t\t\t\t\t<TabsTrigger value=\"generate\">Generate Shares</TabsTrigger>\n\t\t\t\t\t\t<TabsTrigger value=\"reconstruct\">Reconstruct Key</TabsTrigger>\n\t\t\t\t\t</TabsList>\n\n\t\t\t\t\t<div className=\"mt-6\">\n\t\t\t\t\t\t<TabsContent value=\"generate\">\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t\t\t{/* Configuration */}\n\t\t\t\t\t\t\t\t<div className=\"grid grid-cols-2 gap-3\">\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\t\t\t\thtmlFor=\"threshold-input\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm font-medium mb-2 block\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tThreshold\n\t\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tid=\"threshold-input\"\n\t\t\t\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\t\t\t\tvalue={threshold.toString()}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\t\t\t\tsetThreshold(\n\t\t\t\t\t\t\t\t\t\t\t\t\tMath.max(2, Number.parseInt(e.target.value) || 2),\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}\n\t\t\t\t\t\t\t\t\t\t\tmin=\"2\"\n\t\t\t\t\t\t\t\t\t\t\tmax={totalShares.toString()}\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\t<div>\n\t\t\t\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\t\t\t\thtmlFor=\"total-shares-input\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm font-medium mb-2 block\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tTotal Shares\n\t\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tid=\"total-shares-input\"\n\t\t\t\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\t\t\t\tvalue={totalShares.toString()}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\t\t\t\tsetTotalShares(\n\t\t\t\t\t\t\t\t\t\t\t\t\tMath.max(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthreshold,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNumber.parseInt(e.target.value) || 3,\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}\n\t\t\t\t\t\t\t\t\t\t\tmin={threshold.toString()}\n\t\t\t\t\t\t\t\t\t\t\tmax=\"16\"\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\n\t\t\t\t\t\t\t\t<Button onClick={handleGenerateShares} disabled={!privateKey}>\n\t\t\t\t\t\t\t\t\tGenerate {totalShares} Shares (Threshold: {threshold})\n\t\t\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t\t\t{/* Generated Shares */}\n\t\t\t\t\t\t\t\t{shares.length > 0 && (\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<h3 className=\"text-lg font-medium mb-3\">\n\t\t\t\t\t\t\t\t\t\t\tGenerated Shares\n\t\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t\t<Alert className=\"mb-3\">\n\t\t\t\t\t\t\t\t\t\t\t<InfoCircledIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t<AlertDescription>\n\t\t\t\t\t\t\t\t\t\t\t\tStore these shares securely in different locations. Any{\" \"}\n\t\t\t\t\t\t\t\t\t\t\t\t{threshold} shares can reconstruct your private key.\n\t\t\t\t\t\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t\t\t\t\t\t</Alert>\n\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{shares.map((share, index) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<Card key={share} className=\"p-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Badge variant=\"secondary\" className=\"text-xs\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tShare {index + 1}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\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 max-w-xs overflow-hidden\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{share.slice(0, 20)}...{share.slice(-10)}\n\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</div>\n\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\tsize=\"sm\"\n\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\tonClick={() => handleCopyShare(index)}\n\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{copiedIndex === index ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CheckIcon className=\"h-4 w-4 text-green-600\" />\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<CopyIcon className=\"h-4 w-4\" />\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</Button>\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</Card>\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\t\t\t\t\t\t\t\t\t</div>\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</TabsContent>\n\n\t\t\t\t\t\t<TabsContent value=\"reconstruct\">\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tEnter at least {threshold} shares to reconstruct the private\n\t\t\t\t\t\t\t\t\tkey.\n\t\t\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t\t\t{/* Share Inputs */}\n\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{inputShares.map((share, index) => (\n\t\t\t\t\t\t\t\t\t\t<div key={share.id} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm min-w-[60px]\">\n\t\t\t\t\t\t\t\t\t\t\t\tShare {index + 1}:\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\t\tplaceholder={`Paste share #${index + 1}`}\n\t\t\t\t\t\t\t\t\t\t\t\tvalue={share.value}\n\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\thandleShareInputChange(share.id, 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\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\t\t\t\t/>\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=\"icon\"\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={() => removeShareInput(share.id)}\n\t\t\t\t\t\t\t\t\t\t\t\tdisabled={inputShares.length <= 1}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<Cross2Icon className=\"h-4 w-4\" />\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))}\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" onClick={addShareInput}>\n\t\t\t\t\t\t\t\t\t\t<PlusIcon />\n\t\t\t\t\t\t\t\t\t\tAdd Share\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t<Button onClick={handleReconstruct}>\n\t\t\t\t\t\t\t\t\t\tReconstruct Private Key\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t{/* Reconstructed Key */}\n\t\t\t\t\t\t\t\t{reconstructedKey && (\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<h3 className=\"text-lg font-medium mb-2\">\n\t\t\t\t\t\t\t\t\t\t\tReconstructed Private Key\n\t\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t\t<Card className=\"p-3\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t<code className=\"text-xs\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{reconstructedKey.slice(0, 10)}...\n\t\t\t\t\t\t\t\t\t\t\t\t\t{reconstructedKey.slice(-10)}\n\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<Button\n\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\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnavigator.clipboard.writeText(reconstructedKey)\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\t\t<CopyIcon />\n\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</div>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t\t<div className=\"bg-green-50 border border-green-200 rounded-md p-3 mt-2 flex items-start gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<CheckCircledIcon className=\"text-green-600 mt-0.5\" />\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm text-green-700\">\n\t\t\t\t\t\t\t\t\t\t\t\tPrivate key successfully reconstructed from shares!\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\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</TabsContent>\n\t\t\t\t\t</div>\n\t\t\t\t</Tabs>\n\n\t\t\t\t{/* Error Display */}\n\t\t\t\t{error && (\n\t\t\t\t\t<Alert variant=\"destructive\">\n\t\t\t\t\t\t<ExclamationTriangleIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t<AlertDescription>{error}</AlertDescription>\n\t\t\t\t\t</Alert>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</Card>\n\t);\n}\n", "target": "<%- config.aliases.components %>/shamirsecretsharing.tsx" } ] }