UNPKG

bigblocks

Version:

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

197 lines (196 loc) 8.65 kB
import { BSM, BigNumber, Hash, Signature, Utils } from "@bsv/sdk"; // useBapSigning Hook - Sign/verify files with BAP identity using React Query pattern import { useMutation } from "@tanstack/react-query"; import { useBitcoinAuth } from "../../../hooks/useBitcoinAuth.js"; const { toArray, toHex } = Utils; export function useBapSigning(_options = {}) { const { user } = useBitcoinAuth(); // Get BAP instance from context const bapInstance = user?.bapInstance; // Convert file to array buffer const fileToArrayBuffer = async (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { if (e.target?.result instanceof ArrayBuffer) { resolve(e.target.result); } else { reject(new Error("Failed to read file")); } }; reader.onerror = () => reject(new Error("Failed to read file")); reader.readAsArrayBuffer(file); }); }; // Sign file mutation const signMutation = useMutation({ mutationFn: async ({ file, options: signOptions = {} }) => { if (!bapInstance) { throw new Error("BAP instance not available"); } try { const ids = bapInstance.listIds(); if (!ids || ids.length === 0) { throw new Error("No BAP identity available"); } const currentId = ids[0]; if (!currentId) { throw new Error("No current identity found"); } const identity = bapInstance.getId(currentId.idKey); if (!identity) { throw new Error("BAP identity not found"); } // Read file data const arrayBuffer = await fileToArrayBuffer(file); const fileData = new Uint8Array(arrayBuffer); // Hash the file content const fileHash = Hash.sha256(toArray(fileData)); // Create message to sign based on protocol let messageToSign; if (signOptions.protocol === "raw") { // Sign the raw file hash messageToSign = toArray(fileHash); } else { // Default to BAP attestation format const attestationUrn = `urn:bap:attest:file:${toHex(fileHash)}:${currentId.idKey}`; const attestationHash = Hash.sha256(attestationUrn, "utf8"); messageToSign = toArray(attestationHash); } // Sign the message using the identity's current signing key const { address, signature } = identity.signMessage(messageToSign); return { success: true, signature, address, fileHash: toHex(fileHash), metadata: { fileName: file.name, fileSize: file.size, fileType: file.type, signedAt: new Date().toISOString(), identityKey: currentId.idKey, protocol: signOptions.protocol || "bap", }, }; } catch (error) { return { success: false, error: error, }; } }, }); // Verify file mutation const verifyMutation = useMutation({ mutationFn: async ({ file, signature, signatureMetadata }) => { try { // Read file data const arrayBuffer = await fileToArrayBuffer(file); const fileData = new Uint8Array(arrayBuffer); // Hash the file content const fileHash = Hash.sha256(toArray(fileData)); // Determine protocol and create message to verify const protocol = signatureMetadata?.protocol || "bap"; let messageToVerify; if (protocol === "raw") { // Verify against raw file hash messageToVerify = toArray(fileHash); } else { // Verify against BAP attestation format const identityKey = signatureMetadata?.identityKey; if (!identityKey) { throw new Error("Identity key required for BAP verification"); } const attestationUrn = `urn:bap:attest:file:${toHex(fileHash)}:${identityKey}`; const attestationHash = Hash.sha256(attestationUrn, "utf8"); messageToVerify = toArray(attestationHash); } // Verify the signature const address = signatureMetadata?.address; if (!address || typeof address !== "string") { throw new Error("Signing address required for verification"); } // We need to recreate the signature verification similar to BAP's verifySignature method // BSM.verify expects (message: number[], sig: Signature, pubKey: PublicKey) // But we have signature as base64 string and address, not pubKey // So we'll use the BAP approach of trying recovery factors let isValid = false; try { const sig = Signature.fromCompact(signature, "base64"); const msgHash = new BigNumber(BSM.magicHash(messageToVerify)); // Try different recovery factors to find the right public key for (let recovery = 0; recovery < 4; recovery++) { try { const pubKey = sig.RecoverPublicKey(recovery, msgHash); const sigFitsPubkey = BSM.verify(messageToVerify, sig, pubKey); if (sigFitsPubkey && pubKey.toAddress() === address) { isValid = true; break; } } catch { // Try next recovery factor } } } catch (_error) { isValid = false; } return { valid: isValid, address: isValid ? address : undefined, identityKey: typeof signatureMetadata?.identityKey === "string" ? signatureMetadata.identityKey : undefined, fileHash: toHex(fileHash), }; } catch (error) { return { valid: false, error: error.message, }; } }, }); // Broadcast signature mutation const broadcastMutation = useMutation({ mutationFn: async ({ signature: _signature, metadata: _metadata }) => { // This would broadcast the signature to the blockchain // For now, return a mock transaction ID return `mock-signature-tx-${Date.now()}`; }, }); return { // React Query mutation interface signFile: signMutation.mutate, signFileAsync: signMutation.mutateAsync, verifyFile: verifyMutation.mutate, verifyFileAsync: verifyMutation.mutateAsync, broadcastSignature: broadcastMutation.mutate, broadcastSignatureAsync: broadcastMutation.mutateAsync, // State isLoading: signMutation.isPending || verifyMutation.isPending || broadcastMutation.isPending, isError: signMutation.isError || verifyMutation.isError || broadcastMutation.isError, isSuccess: signMutation.isSuccess || verifyMutation.isSuccess || broadcastMutation.isSuccess, error: signMutation.error || verifyMutation.error || broadcastMutation.error, data: signMutation.data || verifyMutation.data || broadcastMutation.data, // Reset reset: () => { signMutation.reset(); verifyMutation.reset(); broadcastMutation.reset(); }, }; }