UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

218 lines (217 loc) 8.68 kB
import { sha256 } from '@noble/hashes/sha256'; import { bytesToHex } from '@noble/hashes/utils'; import { DiscoverySecurityValidator } from '../discovery/security.js'; import { isValidSignerAdvertisement, isValidSigningRequestAdvertisement, publicKeyToHex, } from './discovery-types.js'; export class MuSig2DiscoverySecurityValidator extends DiscoverySecurityValidator { async validateAdvertisement(advertisement, criteria) { const baseResult = await super.validateAdvertisement(advertisement, criteria); if (!baseResult.valid) { return baseResult; } if (advertisement.protocol === 'musig2') { return this.validateSignerAdvertisement(advertisement); } else if (advertisement.protocol === 'musig2-request') { return this.validateSigningRequestAdvertisement(advertisement); } return baseResult; } validateSignerAdvertisement(advertisement) { if (!isValidSignerAdvertisement(advertisement)) { return { valid: false, error: 'Invalid signer advertisement structure', securityScore: 0, details: { signatureValid: false, notExpired: true, reputationAcceptable: false, criteriaMatch: false, customValidation: false, }, }; } const details = { signatureValid: true, notExpired: true, reputationAcceptable: true, criteriaMatch: true, customValidation: true, }; let securityScore = 100; if (!advertisement.publicKey || typeof advertisement.publicKey !== 'object') { details.customValidation = false; securityScore -= 30; } if (!Array.isArray(advertisement.transactionTypes) || advertisement.transactionTypes.length === 0) { details.customValidation = false; securityScore -= 20; } if (advertisement.amountRange && advertisement.amountRange.min !== undefined && advertisement.amountRange.max !== undefined && advertisement.amountRange.min > advertisement.amountRange.max) { details.customValidation = false; securityScore -= 10; } if (advertisement.signature && advertisement.signature.length > 0) { const isValid = this.verifySignerSignature(advertisement); if (!isValid) { details.signatureValid = false; securityScore -= 40; } } const minScore = 50; const valid = securityScore >= minScore && details.customValidation; return { valid, error: valid ? undefined : 'MuSig2 signer validation failed', securityScore, details, }; } validateSigningRequestAdvertisement(advertisement) { if (!isValidSigningRequestAdvertisement(advertisement)) { return { valid: false, error: 'Invalid signing request advertisement structure', securityScore: 0, details: { signatureValid: false, notExpired: true, reputationAcceptable: false, criteriaMatch: false, customValidation: false, }, }; } const details = { signatureValid: true, notExpired: true, reputationAcceptable: true, criteriaMatch: true, customValidation: true, }; let securityScore = 100; if (!advertisement.requestId || typeof advertisement.requestId !== 'string') { details.customValidation = false; securityScore -= 20; } if (!Array.isArray(advertisement.requiredPublicKeys) || advertisement.requiredPublicKeys.length === 0) { details.customValidation = false; securityScore -= 30; } if (!advertisement.messageHash || typeof advertisement.messageHash !== 'string') { details.customValidation = false; securityScore -= 30; } if (!advertisement.creatorPeerId || typeof advertisement.creatorPeerId !== 'string') { details.customValidation = false; securityScore -= 10; } if (advertisement.creatorSignature && advertisement.creatorSignature.length > 0) { const isValid = this.verifyRequestSignature(advertisement); if (!isValid) { details.signatureValid = false; securityScore -= 40; } } const minScore = 50; const valid = securityScore >= minScore && details.customValidation; return { valid, error: valid ? undefined : 'MuSig2 signing request validation failed', securityScore, details, }; } verifySignerSignature(advertisement) { if (!advertisement.signature || advertisement.signature.length === 0) { return true; } try { const publicKeyHex = publicKeyToHex(advertisement.publicKey); const message = this.constructSignerMessage(advertisement.id, publicKeyHex, advertisement.transactionTypes, advertisement.createdAt); return true; } catch (error) { return false; } } verifyRequestSignature(advertisement) { if (!advertisement.creatorSignature || advertisement.creatorSignature.length === 0) { return true; } try { const message = this.constructRequestMessage(advertisement.requestId, advertisement.requiredPublicKeys, advertisement.messageHash, advertisement.createdAt); return true; } catch (error) { return false; } } constructSignerMessage(id, publicKeyHex, transactionTypes, timestamp) { const data = `musig2-signer${id}${publicKeyHex}${transactionTypes.sort().join(',')}${timestamp.toString()}`; return bytesToHex(sha256(new TextEncoder().encode(data))); } constructRequestMessage(requestId, requiredPublicKeys, messageHash, timestamp) { const data = `musig2-request${requestId}${requiredPublicKeys.sort().join(',')}${messageHash}${timestamp.toString()}`; return bytesToHex(sha256(new TextEncoder().encode(data))); } async validateBurnIdentity(advertisement, minBurnAmount, minMaturationBlocks) { const identity = advertisement.signerMetadata?.identity; if (!identity) { return false; } if (identity.totalBurned < minBurnAmount) { return false; } if (identity.maturationBlocks < minMaturationBlocks) { return false; } return true; } validateRequiredSigner(publicKey, requiredPublicKeys) { const publicKeyHex = typeof publicKey === 'string' ? publicKey : publicKeyToHex(publicKey); return requiredPublicKeys.includes(publicKeyHex); } validateTransactionTypeSupport(advertisement, requiredType) { return advertisement.transactionTypes.some(type => type === requiredType); } validateAmountRange(advertisement, requiredAmount) { if (!advertisement.amountRange) { return true; } const { min, max } = advertisement.amountRange; if (min !== undefined && requiredAmount < min) { return false; } if (max !== undefined && requiredAmount > max) { return false; } return true; } } export function createMuSig2SecurityPolicy(options) { return { minReputation: options?.minReputation ?? 50, maxAdvertisementAge: options?.maxAdvertisementAge ?? 30 * 60 * 1000, enableSignatureVerification: options?.enableSignatureVerification ?? true, enableReplayPrevention: options?.enableReplayPrevention ?? true, enableRateLimiting: options?.enableRateLimiting ?? true, rateLimits: { maxAdvertisementsPerPeer: options?.rateLimits?.maxAdvertisementsPerPeer ?? 5, maxDiscoveryQueriesPerPeer: options?.rateLimits?.maxDiscoveryQueriesPerPeer ?? 20, windowSizeMs: options?.rateLimits?.windowSizeMs ?? 60 * 1000, }, customValidators: options?.customValidators ?? [], }; }