UNPKG

@bsv/wallet-toolbox

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

160 lines (141 loc) 5.58 kB
import { LookupAnswer, Transaction, PushDrop, VerifiableCertificate, Utils, ProtoWallet, LookupResolver, DiscoverCertificatesResult, IdentityCertificate, IdentityCertifier, Base64String } from '@bsv/sdk' import { Certifier, TrustSettings } from '../WalletSettingsManager' const OUTPUT_INDEX = 0 // Our extended certificate includes certifierInfo. export interface ExtendedVerifiableCertificate extends IdentityCertificate { certifierInfo: IdentityCertifier publiclyRevealedKeyring: Record<string, Base64String> } // --- Helper Types for Grouping --- interface IdentityGroup { totalTrust: number members: ExtendedVerifiableCertificate[] } /** * Transforms an array of VerifiableCertificate instances according to the trust settings. * Only certificates whose grouped total trust meets the threshold are returned, * and each certificate is augmented with a certifierInfo property. * * @param trustSettings - the user's trust settings including trustLevel and trusted certifiers. * @param certificates - an array of VerifiableCertificate objects. * @returns a DiscoverCertificatesResult with totalCertificates and ordered certificates. */ export const transformVerifiableCertificatesWithTrust = ( trustSettings: TrustSettings, certificates: VerifiableCertificate[] ): DiscoverCertificatesResult => { // Group certificates by subject while accumulating trust. const identityGroups: Record<string, IdentityGroup> = {} // Cache certifier lookups. const certifierCache: Record<string, Certifier> = {} certificates.forEach(cert => { const { subject, certifier } = cert if (!subject || !certifier) return // Lookup and cache certifier details from trustSettings. if (!certifierCache[certifier]) { const found = trustSettings.trustedCertifiers.find(x => x.identityKey === certifier) if (!found) return // Skip this certificate if its certifier is not trusted. certifierCache[certifier] = found } // Create the IdentityCertifier object that we want to attach. const certifierInfo: IdentityCertifier = { name: certifierCache[certifier].name, iconUrl: certifierCache[certifier].iconUrl || '', description: certifierCache[certifier].description, trust: certifierCache[certifier].trust } // Create an extended certificate that includes certifierInfo. // Note: We use object spread to copy over all properties from the original certificate. const extendedCert: IdentityCertificate = { ...cert, signature: cert.signature!, // We know it exists at this point decryptedFields: cert.decryptedFields as Record<string, string>, publiclyRevealedKeyring: cert.keyring, certifierInfo } // Group certificates by subject. if (!identityGroups[subject]) { identityGroups[subject] = { totalTrust: 0, members: [] } } identityGroups[subject].totalTrust += certifierInfo.trust identityGroups[subject].members.push(extendedCert) }) // Filter out groups that do not meet the trust threshold and flatten the results. const finalResults: ExtendedVerifiableCertificate[] = [] Object.values(identityGroups).forEach(group => { if (group.totalTrust >= trustSettings.trustLevel) { finalResults.push(...group.members) } }) // Sort the certificates by their certifier trust in descending order. finalResults.sort((a, b) => b.certifierInfo.trust - a.certifierInfo.trust) return { totalCertificates: finalResults.length, certificates: finalResults } } /** * Performs an identity overlay service lookup query and returns the parsed results * * @param query * @returns */ export const queryOverlay = async (query: unknown, resolver: LookupResolver): Promise<VerifiableCertificate[]> => { const results = await resolver.query({ service: 'ls_identity', query }) return await parseResults(results) } /** * Internal func: Parse the returned UTXOs Decrypt and verify the certificates and signatures Return the set of identity keys, certificates and decrypted certificate fields * * @param {Output[]} outputs * @returns {Promise<VerifiableCertificate[]>} */ export const parseResults = async (lookupResult: LookupAnswer): Promise<VerifiableCertificate[]> => { if (lookupResult.type === 'output-list') { const parsedResults: VerifiableCertificate[] = [] for (const output of lookupResult.outputs) { try { const tx = Transaction.fromBEEF(output.beef) // Decode the Identity token fields from the Bitcoin outputScript const decodedOutput = PushDrop.decode(tx.outputs[output.outputIndex].lockingScript) // Parse out the certificate and relevant data const certificate: VerifiableCertificate = JSON.parse(Utils.toUTF8(decodedOutput.fields[0])) // TEST const verifiableCert = new VerifiableCertificate( certificate.type, certificate.serialNumber, certificate.subject, certificate.certifier, certificate.revocationOutpoint, certificate.fields, certificate.keyring, certificate.signature ) const decryptedFields = await verifiableCert.decryptFields(new ProtoWallet('anyone')) // Verify the certificate signature is correct await verifiableCert.verify() verifiableCert.decryptedFields = decryptedFields parsedResults.push(verifiableCert) } catch (error) { console.error(error) // do nothing } } return parsedResults } return [] }