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