bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
18 lines • 8.62 kB
JSON
{
"name": "bap-identity-hooks-usebapsigning",
"type": "registry:hook",
"dependencies": [
"@bsv/sdk",
"@tanstack/react-query"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/bap-identity/hooks/useBapSigning.ts",
"type": "registry:hook",
"content": "import { BSM, BigNumber, Hash, Signature, Utils } from \"@bsv/sdk\";\n// useBapSigning Hook - Sign/verify files with BAP identity using React Query pattern\nimport { useMutation } from \"@tanstack/react-query\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport type { AuthUserWithBAP } from \"../types/identity.js\";\nimport type {\n\tFileSigningResult,\n\tFileVerificationResult,\n\tSigningOptions,\n\tUseBapSigningOptions,\n} from \"../types/signing.js\";\n\nconst { toArray, toHex } = Utils;\n\nexport interface SigningParams {\n\tfile: File;\n\toptions?: SigningOptions;\n}\n\nexport interface VerificationParams {\n\tfile: File;\n\tsignature: string;\n\tsignatureMetadata?: Record<string, unknown>;\n}\n\nexport function useBapSigning(_options: UseBapSigningOptions = {}) {\n\tconst { user } = useBitcoinAuth();\n\n\t// Get BAP instance from context\n\tconst bapInstance = (user as AuthUserWithBAP)?.bapInstance;\n\n\t// Convert file to array buffer\n\tconst fileToArrayBuffer = async (file: File): Promise<ArrayBuffer> => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst reader = new FileReader();\n\t\t\treader.onload = (e) => {\n\t\t\t\tif (e.target?.result instanceof ArrayBuffer) {\n\t\t\t\t\tresolve(e.target.result);\n\t\t\t\t} else {\n\t\t\t\t\treject(new Error(\"Failed to read file\"));\n\t\t\t\t}\n\t\t\t};\n\t\t\treader.onerror = () => reject(new Error(\"Failed to read file\"));\n\t\t\treader.readAsArrayBuffer(file);\n\t\t});\n\t};\n\n\t// Sign file mutation\n\tconst signMutation = useMutation<FileSigningResult, Error, SigningParams>({\n\t\tmutationFn: async ({ file, options: signOptions = {} }) => {\n\t\t\tif (!bapInstance) {\n\t\t\t\tthrow new Error(\"BAP instance not available\");\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst ids = bapInstance.listIds();\n\t\t\t\tif (!ids || ids.length === 0) {\n\t\t\t\t\tthrow new Error(\"No BAP identity available\");\n\t\t\t\t}\n\n\t\t\t\tconst currentId = ids[0];\n\t\t\t\tif (!currentId) {\n\t\t\t\t\tthrow new Error(\"No current identity found\");\n\t\t\t\t}\n\n\t\t\t\tconst identity = bapInstance.getId(currentId.idKey);\n\n\t\t\t\tif (!identity) {\n\t\t\t\t\tthrow new Error(\"BAP identity not found\");\n\t\t\t\t}\n\n\t\t\t\t// Read file data\n\t\t\t\tconst arrayBuffer = await fileToArrayBuffer(file);\n\t\t\t\tconst fileData = new Uint8Array(arrayBuffer);\n\n\t\t\t\t// Hash the file content\n\t\t\t\tconst fileHash = Hash.sha256(toArray(fileData));\n\n\t\t\t\t// Create message to sign based on protocol\n\t\t\t\tlet messageToSign: number[];\n\t\t\t\tif (signOptions.protocol === \"raw\") {\n\t\t\t\t\t// Sign the raw file hash\n\t\t\t\t\tmessageToSign = toArray(fileHash);\n\t\t\t\t} else {\n\t\t\t\t\t// Default to BAP attestation format\n\t\t\t\t\tconst attestationUrn = `urn:bap:attest:file:${toHex(fileHash)}:${currentId.idKey}`;\n\t\t\t\t\tconst attestationHash = Hash.sha256(attestationUrn, \"utf8\");\n\t\t\t\t\tmessageToSign = toArray(attestationHash);\n\t\t\t\t}\n\n\t\t\t\t// Sign the message using the identity's current signing key\n\t\t\t\tconst { address, signature } = identity.signMessage(messageToSign);\n\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tsignature,\n\t\t\t\t\taddress,\n\t\t\t\t\tfileHash: toHex(fileHash),\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tfileName: file.name,\n\t\t\t\t\t\tfileSize: file.size,\n\t\t\t\t\t\tfileType: file.type,\n\t\t\t\t\t\tsignedAt: new Date().toISOString(),\n\t\t\t\t\t\tidentityKey: currentId.idKey,\n\t\t\t\t\t\tprotocol: signOptions.protocol || \"bap\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error as Error,\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t});\n\n\t// Verify file mutation\n\tconst verifyMutation = useMutation<\n\t\tFileVerificationResult,\n\t\tError,\n\t\tVerificationParams\n\t>({\n\t\tmutationFn: async ({ file, signature, signatureMetadata }) => {\n\t\t\ttry {\n\t\t\t\t// Read file data\n\t\t\t\tconst arrayBuffer = await fileToArrayBuffer(file);\n\t\t\t\tconst fileData = new Uint8Array(arrayBuffer);\n\n\t\t\t\t// Hash the file content\n\t\t\t\tconst fileHash = Hash.sha256(toArray(fileData));\n\n\t\t\t\t// Determine protocol and create message to verify\n\t\t\t\tconst protocol = signatureMetadata?.protocol || \"bap\";\n\t\t\t\tlet messageToVerify: number[];\n\n\t\t\t\tif (protocol === \"raw\") {\n\t\t\t\t\t// Verify against raw file hash\n\t\t\t\t\tmessageToVerify = toArray(fileHash);\n\t\t\t\t} else {\n\t\t\t\t\t// Verify against BAP attestation format\n\t\t\t\t\tconst identityKey = signatureMetadata?.identityKey;\n\t\t\t\t\tif (!identityKey) {\n\t\t\t\t\t\tthrow new Error(\"Identity key required for BAP verification\");\n\t\t\t\t\t}\n\t\t\t\t\tconst attestationUrn = `urn:bap:attest:file:${toHex(fileHash)}:${identityKey}`;\n\t\t\t\t\tconst attestationHash = Hash.sha256(attestationUrn, \"utf8\");\n\t\t\t\t\tmessageToVerify = toArray(attestationHash);\n\t\t\t\t}\n\n\t\t\t\t// Verify the signature\n\t\t\t\tconst address = signatureMetadata?.address;\n\t\t\t\tif (!address || typeof address !== \"string\") {\n\t\t\t\t\tthrow new Error(\"Signing address required for verification\");\n\t\t\t\t}\n\n\t\t\t\t// We need to recreate the signature verification similar to BAP's verifySignature method\n\t\t\t\t// BSM.verify expects (message: number[], sig: Signature, pubKey: PublicKey)\n\t\t\t\t// But we have signature as base64 string and address, not pubKey\n\t\t\t\t// So we'll use the BAP approach of trying recovery factors\n\t\t\t\tlet isValid = false;\n\t\t\t\ttry {\n\t\t\t\t\tconst sig = Signature.fromCompact(signature, \"base64\");\n\t\t\t\t\tconst msgHash = new BigNumber(BSM.magicHash(messageToVerify));\n\n\t\t\t\t\t// Try different recovery factors to find the right public key\n\t\t\t\t\tfor (let recovery = 0; recovery < 4; recovery++) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst pubKey = sig.RecoverPublicKey(recovery, msgHash);\n\t\t\t\t\t\t\tconst sigFitsPubkey = BSM.verify(messageToVerify, sig, pubKey);\n\t\t\t\t\t\t\tif (sigFitsPubkey && pubKey.toAddress() === address) {\n\t\t\t\t\t\t\t\tisValid = true;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Try next recovery factor\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (_error) {\n\t\t\t\t\tisValid = false;\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tvalid: isValid,\n\t\t\t\t\taddress: isValid ? address : undefined,\n\t\t\t\t\tidentityKey:\n\t\t\t\t\t\ttypeof signatureMetadata?.identityKey === \"string\"\n\t\t\t\t\t\t\t? signatureMetadata.identityKey\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\tfileHash: toHex(fileHash),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\treturn {\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: (error as Error).message,\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t});\n\n\t// Broadcast signature mutation\n\tconst broadcastMutation = useMutation<\n\t\tstring,\n\t\tError,\n\t\t{ signature: string; metadata: Record<string, unknown> }\n\t>({\n\t\tmutationFn: async ({ signature: _signature, metadata: _metadata }) => {\n\t\t\t// This would broadcast the signature to the blockchain\n\t\t\t// For now, return a mock transaction ID\n\t\t\treturn `mock-signature-tx-${Date.now()}`;\n\t\t},\n\t});\n\n\treturn {\n\t\t// React Query mutation interface\n\t\tsignFile: signMutation.mutate,\n\t\tsignFileAsync: signMutation.mutateAsync,\n\t\tverifyFile: verifyMutation.mutate,\n\t\tverifyFileAsync: verifyMutation.mutateAsync,\n\t\tbroadcastSignature: broadcastMutation.mutate,\n\t\tbroadcastSignatureAsync: broadcastMutation.mutateAsync,\n\n\t\t// State\n\t\tisLoading:\n\t\t\tsignMutation.isPending ||\n\t\t\tverifyMutation.isPending ||\n\t\t\tbroadcastMutation.isPending,\n\t\tisError:\n\t\t\tsignMutation.isError ||\n\t\t\tverifyMutation.isError ||\n\t\t\tbroadcastMutation.isError,\n\t\tisSuccess:\n\t\t\tsignMutation.isSuccess ||\n\t\t\tverifyMutation.isSuccess ||\n\t\t\tbroadcastMutation.isSuccess,\n\t\terror:\n\t\t\tsignMutation.error || verifyMutation.error || broadcastMutation.error,\n\t\tdata: signMutation.data || verifyMutation.data || broadcastMutation.data,\n\n\t\t// Reset\n\t\treset: () => {\n\t\t\tsignMutation.reset();\n\t\t\tverifyMutation.reset();\n\t\t\tbroadcastMutation.reset();\n\t\t},\n\t};\n}\n",
"target": "<%- config.aliases.hooks %>/usebapsigning.ts"
}
]
}