UNPKG

lotus-sdk

Version:

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

814 lines (813 loc) 38.8 kB
import { Hash } from '../../bitcore/crypto/hash.js'; import { Schnorr } from '../../bitcore/crypto/schnorr.js'; import { Signature } from '../../bitcore/crypto/signature.js'; import { Address } from '../../bitcore/address.js'; import { Transaction } from '../../bitcore/transaction/index.js'; import { Script } from '../../bitcore/script.js'; import { createMuSigTaprootAddress } from '../../bitcore/taproot/musig2.js'; import { MuSig2P2PCoordinator } from '../musig2/coordinator.js'; import { MuSig2Event, TransactionType } from '../musig2/types.js'; import { SwapPoolManager } from './pool.js'; import { SwapSigBurnMechanism } from './burn.js'; import { SwapSigP2PProtocolHandler } from './protocol-handler.js'; import { TransactionMonitor } from '../blockchain-utils.js'; import { SwapPhase, SwapSigEvent } from './types.js'; export class SwapSigCoordinator extends MuSig2P2PCoordinator { swapConfig; poolManager; burnMechanism; privateKey; swapSigProtocolHandler; txMonitor; constructor(privateKey, p2pConfig, musig2Config, swapSigConfig) { super(p2pConfig, musig2Config); this.swapConfig = { minParticipants: 3, maxParticipants: 10, feeRate: 1, setupTimeout: 600000, settlementTimeout: 600000, requireEncryptedDestinations: true, randomizeOutputOrder: true, chronikUrl: 'https://chronik.lotusia.org', requiredConfirmations: 1, confirmationPollInterval: 3000, ...swapSigConfig, }; this.privateKey = privateKey; this.poolManager = new SwapPoolManager(); this.burnMechanism = new SwapSigBurnMechanism(); this.txMonitor = new TransactionMonitor(this.swapConfig.chronikUrl); this.swapSigProtocolHandler = new SwapSigP2PProtocolHandler(); this.swapSigProtocolHandler.setCoordinator(this); this.registerProtocol(this.swapSigProtocolHandler); this._setupSwapSigEventHandlers(); } async start() { await super.start(); } async stop() { const pools = this.poolManager.getAllPools(); for (const pool of pools) { if (pool.phase !== SwapPhase.COMPLETE && pool.phase !== SwapPhase.ABORTED) { this.poolManager.abortPool(pool.poolId, 'Coordinator stopped'); this.emit(SwapSigEvent.POOL_ABORTED, pool.poolId, 'Coordinator stopped'); } } await super.stop(); } async discoverPools(filters) { const localPools = this.poolManager .getAllPools() .filter(pool => pool.phase === SwapPhase.DISCOVERY || pool.phase === SwapPhase.REGISTRATION) .map(pool => this._createPoolAnnouncement(pool)); const filtered = localPools.filter(announcement => { if (filters?.denomination && announcement.denomination !== filters.denomination) { return false; } if (filters?.minParticipants && announcement.currentParticipants < filters.minParticipants) { return false; } if (filters?.maxParticipants && announcement.maxParticipants > filters.maxParticipants) { return false; } if (!filters?.denomination && this.swapConfig.preferredDenominations && this.swapConfig.preferredDenominations.length > 0) { return this.swapConfig.preferredDenominations.includes(announcement.denomination); } return true; }); return filtered; } async createPool(params) { const poolParams = { denomination: params.denomination, minParticipants: params.minParticipants ?? this.swapConfig.minParticipants, maxParticipants: params.maxParticipants ?? this.swapConfig.maxParticipants, feeRate: params.feeRate ?? this.swapConfig.feeRate, burnPercentage: params.burnPercentage, setupTimeout: params.setupTimeout ?? this.swapConfig.setupTimeout, settlementTimeout: params.settlementTimeout ?? this.swapConfig.settlementTimeout, }; const poolId = this.poolManager.createPool(this.libp2pNode.peerId.toString(), poolParams); const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error('Failed to create pool'); } await this._announcePool(pool); this.emit(SwapSigEvent.POOL_CREATED, pool); return poolId; } async joinPool(poolId, input, finalDestination) { let pool = this.poolManager.getPool(poolId); if (!pool) { console.log(`[SwapSig] Pool ${poolId.substring(0, 8)}... not found locally, discovering from DHT...`); const announcement = await this.discoverResource('swapsig-pool', poolId, 5000); if (!announcement) { throw new Error(`Pool ${poolId} not found locally or in DHT`); } const poolData = announcement.data; const localPoolId = this.poolManager.createPool(poolData.creatorPeerId, { denomination: poolData.denomination, minParticipants: poolData.minParticipants, maxParticipants: poolData.maxParticipants, burnPercentage: poolData.burnConfig.burnPercentage, setupTimeout: poolData.setupTimeout, settlementTimeout: poolData.settlementTimeout, }); pool = this.poolManager.getPool(localPoolId); if (pool) { this.poolManager['pools'].delete(localPoolId); pool.poolId = poolId; this.poolManager['pools'].set(poolId, pool); } console.log(`[SwapSig] Pool ${poolId.substring(0, 8)}... discovered and imported`); } if (!pool) { throw new Error('Failed to get or create pool'); } if (input.satoshis !== pool.denomination) { throw new Error(`Input amount ${input.satoshis} does not match denomination ${pool.denomination}`); } const ownershipMessage = Buffer.concat([ Buffer.from(poolId, 'hex'), Buffer.from(input.txId, 'hex'), Buffer.from([input.outputIndex]), ]); const ownershipHash = Hash.sha256(ownershipMessage); const ownershipProof = Schnorr.sign(ownershipHash, this.privateKey).toBuffer(); const finalOutputEncrypted = this._encryptAddress(finalDestination, poolId); const finalOutputCommitment = Hash.sha256(finalOutputEncrypted); const participantInput = { txId: input.txId, outputIndex: input.outputIndex, amount: input.satoshis, script: input.script, address: input.address || Address.fromPublicKeyHash(Hash.sha256ripemd160(input.script.toBuffer())), }; const participantIndex = this.poolManager.addParticipant(poolId, this.libp2pNode.peerId.toString(), this.privateKey.publicKey, participantInput, ownershipProof, finalOutputEncrypted, finalOutputCommitment); await this._advertiseSwapSigner(pool); await this._broadcastParticipantRegistered(pool, participantIndex); this.emit(SwapSigEvent.POOL_JOINED, poolId, participantIndex); return participantIndex; } async executeSwap(poolId, input, finalDestination) { await this.joinPool(poolId, input, finalDestination); await this._waitForMinimumParticipants(poolId); await this._executeSetupRound(poolId); await this._waitForSetupConfirmations(poolId); await this._revealFinalDestinations(poolId); await this._executeSettlementRound(poolId); await this._waitForSettlementConfirmations(poolId); const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error('Pool not found'); } const myParticipant = this._getMyParticipant(pool); const settlementInfo = pool.settlementMapping.get(myParticipant.participantIndex); if (!settlementInfo?.txId) { throw new Error('Settlement transaction not found'); } return settlementInfo.txId; } getPoolStats(poolId) { return this.poolManager.getPoolStats(poolId); } getActivePools() { return this.poolManager.getAllPools(); } getPoolManager() { return this.poolManager; } getBurnMechanism() { return this.burnMechanism; } getSwapConfig() { return this.swapConfig; } _setupSwapSigEventHandlers() { super.on(MuSig2Event.SIGNING_REQUEST_RECEIVED, async (request) => { try { const metadata = request.metadata; if (metadata?.transactionType !== TransactionType.SWAP) { return; } if (metadata?.swapPhase !== SwapPhase.SETTLEMENT) { return; } const myPubKey = this.privateKey.publicKey.toString(); const isRequiredSigner = request.requiredPublicKeys.some((pk) => pk.toString() === myPubKey); if (!isRequiredSigner) { return; } const poolId = metadata.swapPoolId; if (!poolId) return; const pool = this.poolManager.getPool(poolId); if (!pool) { console.log(`[SwapSig] Received signing request for unknown pool ${poolId.substring(0, 8)}...`); return; } console.log(`[SwapSig] Discovered signing request ${request.requestId.substring(0, 8)}... for pool ${poolId.substring(0, 8)}...`); await this.joinSigningRequest(request.requestId, this.privateKey); console.log(`[SwapSig] Auto-joined signing request ${request.requestId.substring(0, 8)}...`); this.emit(SwapSigEvent.SWAPSIG_REQUEST_JOINED, request.requestId, poolId); } catch (error) { console.error('[SwapSig] Error handling signing request discovery:', error); } }); super.on(MuSig2Event.SESSION_READY, ({ sessionId, requestId }) => { console.log(`[SwapSig] Session ${sessionId.substring(0, 8)}... ready for signing (all participants joined)`); this.emit(SwapSigEvent.SWAPSIG_SESSION_READY, sessionId, requestId); }); super.on(MuSig2Event.SESSION_COMPLETE, sessionId => { console.log(`[SwapSig] Session ${sessionId.substring(0, 8)}... completed`); this.emit(SwapSigEvent.SWAPSIG_SESSION_COMPLETE, sessionId); }); } async _announcePool(pool) { const announcement = this._createPoolAnnouncement(pool); await this.announceResource('swapsig-pool', pool.poolId, announcement, { ttl: pool.setupTimeout + pool.settlementTimeout, }); await this._broadcastMessage({ type: 'swapsig:pool-announce', poolId: pool.poolId, from: this.libp2pNode.peerId.toString(), payload: announcement, timestamp: Date.now(), messageId: this._generateMessageId(), }); } _createPoolAnnouncement(pool) { const announcementData = Buffer.concat([ Buffer.from(pool.poolId, 'hex'), Buffer.from(pool.denomination.toString()), Buffer.from(pool.createdAt.toString()), ]); const announcementHash = Hash.sha256(announcementData); const signature = Schnorr.sign(announcementHash, this.privateKey); return { poolId: pool.poolId, denomination: pool.denomination, minParticipants: pool.minParticipants, maxParticipants: pool.maxParticipants, currentParticipants: pool.participants.length, burnConfig: pool.burnConfig, createdAt: pool.createdAt, expiresAt: pool.createdAt + pool.setupTimeout + pool.settlementTimeout, setupTimeout: pool.setupTimeout, settlementTimeout: pool.settlementTimeout, creatorPeerId: pool.creatorPeerId, creatorSignature: signature.toBuffer(), }; } async _advertiseSwapSigner(pool) { await this.advertiseSigner(this.privateKey, { transactionTypes: [TransactionType.SWAP], minAmount: pool.denomination, maxAmount: pool.denomination, }, { ttl: pool.setupTimeout + pool.settlementTimeout, metadata: { description: `SwapSig signer for pool ${pool.poolId}`, fees: 0, }, }); } async _broadcastParticipantRegistered(pool, participantIndex) { await this._broadcastMessage({ type: 'swapsig:participant-registered', poolId: pool.poolId, from: this.libp2pNode.peerId.toString(), payload: { participantIndex }, timestamp: Date.now(), messageId: this._generateMessageId(), }); } async _broadcastMessage(message) { await this.broadcast({ type: message.type, from: message.from, payload: message.payload, timestamp: message.timestamp, messageId: message.messageId, protocol: 'swapsig', }); } async _waitForMinimumParticipants(poolId) { const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error('Pool not found'); } return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Timeout waiting for minimum participants')); }, pool.setupTimeout); const checkMinimum = () => { if (this.poolManager.hasMinimumParticipants(poolId)) { clearTimeout(timeout); resolve(); } else { setTimeout(checkMinimum, 1000); } }; checkMinimum(); }); } _computeOutputGroups(participants, groupSize) { const groups = []; const n = participants.length; if (groupSize === 2) { for (let i = 0; i < n; i++) { const partner = (i + 1) % n; groups.push([i, partner]); } } else { const numCompleteGroups = Math.floor(n / groupSize); for (let g = 0; g < numCompleteGroups; g++) { const group = []; for (let i = 0; i < groupSize; i++) { group.push(g * groupSize + i); } groups.push(group); } const remaining = n % groupSize; if (remaining > 0) { const lastGroup = []; for (let i = 0; i < remaining; i++) { lastGroup.push(numCompleteGroups * groupSize + i); } while (lastGroup.length < groupSize) { lastGroup.push(lastGroup.length % n); } groups.push(lastGroup); } } return groups; } _computeSettlementMapping(pool) { const mapping = new Map(); const n = pool.participants.length; const numGroups = pool.outputGroups.length; for (let g = 0; g < numGroups; g++) { const sourceOutput = pool.sharedOutputs[g]; let receiverIndex; if (!pool.groupSizeStrategy) { throw new Error('Pool group size strategy not initialized'); } if (pool.groupSizeStrategy.groupSize === 2) { receiverIndex = (g + 1) % n; } else { const nextGroup = (g + 1) % numGroups; receiverIndex = pool.outputGroups[nextGroup][0]; } const receiver = pool.participants[receiverIndex]; if (!receiver.finalAddress) { throw new Error(`Receiver ${receiverIndex} has not revealed final address`); } mapping.set(receiverIndex, { receiverIndex, sourceOutputIndex: g, sourceOutput, finalDestination: receiver.finalAddress, signers: sourceOutput.signers, confirmed: false, }); } return mapping; } async _executeSetupRound(poolId) { const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error(`Pool ${poolId} not found`); } if (pool.phase !== SwapPhase.REGISTRATION) { throw new Error(`Cannot execute setup round: pool is in ${pool.phase} phase`); } console.log(`[SwapSig] Executing setup round for pool ${poolId.substring(0, 8)}...`); const groupStrategy = this.poolManager.determineOptimalGroupSize(pool.participants.length); console.log(`[SwapSig] Group strategy: ${groupStrategy.reasoning}`); const outputGroups = this._computeOutputGroups(pool.participants, groupStrategy.groupSize); console.log(`[SwapSig] Created ${outputGroups.length} output groups`); pool.groupSizeStrategy = groupStrategy; pool.outputGroups = outputGroups; pool.sharedOutputs = []; for (let i = 0; i < outputGroups.length; i++) { const group = outputGroups[i]; const signerPubKeys = group.map(idx => pool.participants[idx].publicKey); const { address, keyAggContext, commitment } = createMuSigTaprootAddress(signerPubKeys, 'livenet'); console.log(`[SwapSig] Group ${i}: ${group.length}-of-${group.length} MuSig2 address: ${address.toString()}`); pool.sharedOutputs.push({ outputIndex: i, signers: signerPubKeys, aggregatedKey: keyAggContext.aggregatedPubKey, taprootAddress: address, amount: pool.denomination, txId: '', confirmed: false, }); } const myParticipant = this._getMyParticipant(pool); const myGroupIndex = outputGroups.findIndex(group => group.includes(myParticipant.participantIndex)); if (myGroupIndex === -1) { throw new Error('My participant not found in any group'); } const mySharedOutput = pool.sharedOutputs[myGroupIndex]; const setupTx = await this._buildSetupTransaction(myParticipant, mySharedOutput.taprootAddress, pool.denomination, poolId); setupTx.sign(this.privateKey); const txHex = setupTx.toString(); const txId = await this.txMonitor.broadcastTransaction(txHex); if (!txId) { throw new Error('Failed to broadcast setup transaction'); } console.log(`[SwapSig] Setup transaction broadcast: ${txId.substring(0, 8)}...`); myParticipant.setupTxId = txId; mySharedOutput.txId = txId; this.poolManager.transitionPhase(poolId, SwapPhase.SETUP_CONFIRM); console.log(`[SwapSig] Setup round complete, waiting for confirmations...`); } async _buildSetupTransaction(participant, sharedAddress, amount, poolId) { const burnAmount = this.burnMechanism.calculateBurnAmount(amount); const tx = new Transaction(); tx.from(participant.input); tx.to(sharedAddress, amount); const burnOutput = this.burnMechanism.createBurnOutput(burnAmount, poolId); tx.addOutput(burnOutput); const feeRate = this.swapConfig.feeRate || 1; tx.feePerByte(feeRate); tx.change(participant.input.address); return tx; } async _waitForSetupConfirmations(poolId) { const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error(`Pool ${poolId} not found`); } console.log(`[SwapSig] Waiting for setup confirmations for pool ${poolId.substring(0, 8)}...`); const setupTxIds = []; for (const participant of pool.participants) { if (participant.setupTxId) { setupTxIds.push(participant.setupTxId); } } if (setupTxIds.length !== pool.participants.length) { console.warn(`[SwapSig] Not all participants have broadcast setup transactions: ${setupTxIds.length}/${pool.participants.length}`); } const requiredConfs = this.swapConfig.requiredConfirmations || 1; const pollInterval = this.swapConfig.confirmationPollInterval || 3000; console.log(`[SwapSig] Waiting for ${requiredConfs} confirmations on ${setupTxIds.length} transactions...`); const confirmationPromises = setupTxIds.map(txId => this.txMonitor.waitForConfirmations(txId, requiredConfs, pollInterval, this.swapConfig.setupTimeout)); const results = await Promise.all(confirmationPromises); const allConfirmed = results.every(result => result?.isConfirmed); if (!allConfirmed) { console.error(`[SwapSig] Setup confirmation timeout for pool ${poolId.substring(0, 8)}...`); this.poolManager.abortPool(poolId, 'Setup confirmation timeout'); this.emit(SwapSigEvent.POOL_ABORTED, poolId, 'Setup confirmation timeout'); throw new Error('Setup confirmation timeout'); } console.log(`[SwapSig] All setup transactions confirmed!`); for (const participant of pool.participants) { participant.setupConfirmed = true; } for (const output of pool.sharedOutputs) { output.confirmed = true; } this.poolManager.transitionPhase(poolId, SwapPhase.REVEAL); this.emit(SwapSigEvent.SETUP_COMPLETE, poolId); } async _revealFinalDestinations(poolId) { const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error(`Pool ${poolId} not found`); } console.log(`[SwapSig] Revealing final destinations for pool ${poolId.substring(0, 8)}...`); if (!this.swapConfig.requireEncryptedDestinations) { console.log(`[SwapSig] Encryption disabled, addresses already plaintext`); } else { console.log(`[SwapSig] Destination reveal/decryption not yet implemented (proceeding with test addresses)`); } this.poolManager.transitionPhase(poolId, SwapPhase.SETTLEMENT); this.emit(SwapSigEvent.REVEAL_COMPLETE, poolId); } async _executeSettlementRound(poolId) { const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error(`Pool ${poolId} not found`); } if (pool.phase !== SwapPhase.SETTLEMENT) { throw new Error(`Cannot execute settlement round: pool is in ${pool.phase} phase`); } console.log(`[SwapSig] Executing settlement round for pool ${poolId.substring(0, 8)}...`); const settlementMapping = this._computeSettlementMapping(pool); console.log(`[SwapSig] Computed settlement mapping for ${settlementMapping.size} participants`); pool.settlementMapping = settlementMapping; const settlementTxs = []; for (const [receiverIndex, settlementInfo] of settlementMapping) { const tx = await this._buildSettlementTransaction(settlementInfo.sourceOutput, settlementInfo.finalDestination, pool); settlementTxs.push({ tx, outputIndex: settlementInfo.sourceOutputIndex, }); console.log(`[SwapSig] Built settlement tx for output ${settlementInfo.sourceOutputIndex} → receiver ${receiverIndex}`); } console.log(`[SwapSig] Announcing ${settlementTxs.length} MuSig2 signing requests...`); for (const { tx, outputIndex } of settlementTxs) { const sharedOutput = pool.sharedOutputs[outputIndex]; const sigHashBuffer = Buffer.from(tx.id, 'hex'); const requestId = await this.announceSigningRequest(sharedOutput.signers, sigHashBuffer, this.privateKey, { metadata: { transactionType: TransactionType.SWAP, swapPhase: SwapPhase.SETTLEMENT, swapPoolId: poolId, outputIndex, transactionHex: tx.toString(), taprootKeyPath: true, }, }); console.log(`[SwapSig] Announced signing request ${requestId.substring(0, 8)}... for output ${outputIndex}`); } this.poolManager.transitionPhase(poolId, SwapPhase.SETTLEMENT_CONFIRM); console.log(`[SwapSig] Settlement round announced, waiting for MuSig2 coordination and confirmations...`); } async _buildSettlementTransaction(sharedOutput, destination, pool) { const tx = new Transaction(); tx.from({ txId: sharedOutput.txId, outputIndex: sharedOutput.outputIndex, satoshis: sharedOutput.amount, script: Script.fromAddress(sharedOutput.taprootAddress), }); const feeRate = this.swapConfig.feeRate || 1; const outputAmount = sharedOutput.amount - feeRate * 200; tx.to(destination, outputAmount); tx.feePerByte(feeRate); return tx; } async _waitForSettlementConfirmations(poolId) { const pool = this.poolManager.getPool(poolId); if (!pool) { throw new Error(`Pool ${poolId} not found`); } console.log(`[SwapSig] Waiting for settlement confirmations for pool ${poolId.substring(0, 8)}...`); console.log(`[SwapSig] Waiting for MuSig2 coordination...`); await new Promise(resolve => setTimeout(resolve, 5000)); console.log(`[SwapSig] MuSig2 coordination complete (placeholder)`); if (!pool.settlementMapping || pool.settlementMapping.size === 0) { console.warn(`[SwapSig] No settlement transactions to monitor`); this.poolManager.transitionPhase(poolId, SwapPhase.COMPLETE); this.emit(SwapSigEvent.SETTLEMENT_COMPLETE, poolId); this.emit(SwapSigEvent.POOL_COMPLETE, poolId); return; } const settlementTxIds = []; for (const settlement of pool.settlementMapping.values()) { } if (settlementTxIds.length > 0) { const requiredConfs = this.swapConfig.requiredConfirmations || 1; const pollInterval = this.swapConfig.confirmationPollInterval || 3000; console.log(`[SwapSig] Waiting for ${requiredConfs} confirmations on ${settlementTxIds.length} settlement transactions...`); const confirmationPromises = settlementTxIds.map(txId => this.txMonitor.waitForConfirmations(txId, requiredConfs, pollInterval, this.swapConfig.settlementTimeout)); const results = await Promise.all(confirmationPromises); const allConfirmed = results.every(result => result?.isConfirmed); if (!allConfirmed) { console.error(`[SwapSig] Settlement confirmation timeout for pool ${poolId.substring(0, 8)}...`); this.poolManager.abortPool(poolId, 'Settlement confirmation timeout'); this.emit(SwapSigEvent.POOL_ABORTED, poolId, 'Settlement confirmation timeout'); throw new Error('Settlement confirmation timeout'); } console.log(`[SwapSig] All settlement transactions confirmed!`); } else { console.log(`[SwapSig] No settlement transaction IDs available (placeholder mode)`); } for (const settlement of pool.settlementMapping.values()) { settlement.confirmed = true; } this.poolManager.transitionPhase(poolId, SwapPhase.COMPLETE); this.emit(SwapSigEvent.SETTLEMENT_COMPLETE, poolId); this.emit(SwapSigEvent.POOL_COMPLETE, poolId); console.log(`[SwapSig] Pool ${poolId.substring(0, 8)}... complete! 🎉`); } _getMyParticipant(pool) { const myPeerId = this.libp2pNode.peerId.toString(); const participant = pool.participantMap.get(myPeerId); if (!participant) { throw new Error('Not a participant in this pool'); } return participant; } _encryptAddress(address, poolId) { const addressStr = address.toString(); const addressBuf = Buffer.from(addressStr, 'utf8'); if (!this.swapConfig.requireEncryptedDestinations) { return addressBuf; } const poolSecret = Hash.sha256(Buffer.from(poolId, 'hex')); const encrypted = Buffer.alloc(addressBuf.length); for (let i = 0; i < addressBuf.length; i++) { encrypted[i] = addressBuf[i] ^ poolSecret[i % poolSecret.length]; } return encrypted; } _decryptAddress(encryptedAddress, poolId) { if (!this.swapConfig.requireEncryptedDestinations) { return Address.fromString(encryptedAddress.toString('utf8')); } const poolSecret = Hash.sha256(Buffer.from(poolId, 'hex')); const decrypted = Buffer.alloc(encryptedAddress.length); for (let i = 0; i < encryptedAddress.length; i++) { decrypted[i] = encryptedAddress[i] ^ poolSecret[i % poolSecret.length]; } return Address.fromString(decrypted.toString('utf8')); } _generateMessageId() { return Hash.sha256(Buffer.concat([ Buffer.from(Date.now().toString()), Buffer.from(Math.random().toString()), ])).toString('hex'); } _validatePoolAnnouncement(announcement) { try { const announcementData = Buffer.concat([ Buffer.from(announcement.poolId, 'hex'), Buffer.from(announcement.denomination.toString()), Buffer.from(announcement.createdAt.toString()), ]); const announcementHash = Hash.sha256(announcementData); const signature = Signature.fromBuffer(announcement.creatorSignature, false); return Schnorr.verify(announcementHash, signature, this.privateKey.publicKey); } catch (error) { console.error('[SwapSig] Error validating pool announcement:', error); return false; } } async _handlePoolJoin(poolId, participantIndex, peerId) { console.log(`[SwapSig] Peer ${peerId} joined pool ${poolId.substring(0, 8)}... as participant ${participantIndex}`); this.emit(SwapSigEvent.POOL_JOINED, poolId, participantIndex); } async _handleParticipantRegistered(poolId, participantIndex, peerId, publicKey, inputTxId, inputIndex, ownershipProof, finalOutputCommitment, fromPeerId) { const pool = this.poolManager.getPool(poolId); if (!pool) { console.warn(`[SwapSig] Received participant registration for unknown pool ${poolId.substring(0, 8)}...`); return; } if (pool.participantMap.has(peerId)) { console.warn(`[SwapSig] Participant ${peerId} already registered in pool ${poolId.substring(0, 8)}...`); return; } console.log(`[SwapSig] Participant ${peerId} registered in pool ${poolId.substring(0, 8)}... from ${fromPeerId}`); this.emit(SwapSigEvent.PARTICIPANT_JOINED, poolId, { peerId, participantIndex, publicKey, input: { txId: inputTxId, outputIndex: inputIndex, amount: pool.denomination, script: pool.participants[participantIndex]?.input.script, address: pool.participants[participantIndex]?.input.address, }, ownershipProof, finalOutputEncrypted: Buffer.alloc(0), finalOutputCommitment, setupConfirmed: false, joinedAt: Date.now(), }); } async _handleRegistrationAck(poolId, participantIndex, acknowledgedBy, fromPeerId) { console.log(`[SwapSig] Registration ACK for participant ${participantIndex} in pool ${poolId.substring(0, 8)}... from ${fromPeerId}`); } async _handleSetupTxBroadcast(poolId, participantIndex, txId, fromPeerId) { const pool = this.poolManager.getPool(poolId); if (!pool) { console.warn(`[SwapSig] Received setup tx broadcast for unknown pool ${poolId.substring(0, 8)}...`); return; } console.log(`[SwapSig] Setup tx ${txId.substring(0, 8)}... broadcast for participant ${participantIndex} in pool ${poolId.substring(0, 8)}... from ${fromPeerId}`); if (pool.participants[participantIndex]) { pool.participants[participantIndex].setupTxId = txId; } this.emit(SwapSigEvent.SETUP_TX_BROADCAST, poolId, participantIndex, txId); } async _handleSetupConfirmed(poolId, participantIndex, txId, confirmations, fromPeerId) { const pool = this.poolManager.getPool(poolId); if (!pool) return; console.log(`[SwapSig] Setup tx ${txId.substring(0, 8)}... confirmed (${confirmations} confs) for participant ${participantIndex} in pool ${poolId.substring(0, 8)}...`); if (pool.participants[participantIndex]) { pool.participants[participantIndex].setupConfirmed = true; } this.emit(SwapSigEvent.SETUP_CONFIRMED, poolId, participantIndex); if (this.poolManager.allSetupsConfirmed(poolId)) { this.poolManager.transitionPhase(poolId, SwapPhase.REVEAL); this.emit(SwapSigEvent.SETUP_COMPLETE, poolId); } } async _handleSetupComplete(poolId, fromPeerId) { console.log(`[SwapSig] Setup complete for pool ${poolId.substring(0, 8)}... from ${fromPeerId}`); this.emit(SwapSigEvent.SETUP_COMPLETE, poolId); } async _handleDestinationReveal(poolId, participantIndex, finalAddress, revealProof, fromPeerId) { const pool = this.poolManager.getPool(poolId); if (!pool) return; console.log(`[SwapSig] Destination revealed for participant ${participantIndex} in pool ${poolId.substring(0, 8)}...`); const participant = pool.participants[participantIndex]; if (participant) { participant.finalAddress = finalAddress; } this.emit(SwapSigEvent.DESTINATION_REVEALED, poolId, participantIndex, finalAddress); if (this.poolManager.allDestinationsRevealed(poolId)) { this.poolManager.transitionPhase(poolId, SwapPhase.SETTLEMENT); this.emit(SwapSigEvent.REVEAL_COMPLETE, poolId); } } async _handleRevealComplete(poolId, fromPeerId) { console.log(`[SwapSig] Reveal complete for pool ${poolId.substring(0, 8)}... from ${fromPeerId}`); this.emit(SwapSigEvent.REVEAL_COMPLETE, poolId); } async _handleSettlementTxBroadcast(poolId, outputIndex, txId, fromPeerId) { const pool = this.poolManager.getPool(poolId); if (!pool) return; console.log(`[SwapSig] Settlement tx ${txId.substring(0, 8)}... broadcast for output ${outputIndex} in pool ${poolId.substring(0, 8)}...`); if (pool.sharedOutputs[outputIndex]) { pool.sharedOutputs[outputIndex].settlementTxId = txId; } this.emit(SwapSigEvent.SETTLEMENT_TX_BROADCAST, poolId, outputIndex, txId); } async _handleSettlementConfirmed(poolId, outputIndex, txId, confirmations, fromPeerId) { const pool = this.poolManager.getPool(poolId); if (!pool) return; console.log(`[SwapSig] Settlement tx ${txId.substring(0, 8)}... confirmed (${confirmations} confs) for output ${outputIndex} in pool ${poolId.substring(0, 8)}...`); if (pool.sharedOutputs[outputIndex]) { pool.sharedOutputs[outputIndex].settlementConfirmed = true; } this.emit(SwapSigEvent.SETTLEMENT_CONFIRMED, poolId, outputIndex); if (this.poolManager.allSettlementsConfirmed(poolId)) { this.poolManager.transitionPhase(poolId, SwapPhase.COMPLETE); this.emit(SwapSigEvent.SETTLEMENT_COMPLETE, poolId); this.emit(SwapSigEvent.POOL_COMPLETE, poolId); } } async _handleSettlementComplete(poolId, fromPeerId) { console.log(`[SwapSig] Settlement complete for pool ${poolId.substring(0, 8)}... from ${fromPeerId}`); this.emit(SwapSigEvent.SETTLEMENT_COMPLETE, poolId); } async _handlePoolAbort(poolId, reason, fromPeerId) { console.log(`[SwapSig] Pool ${poolId.substring(0, 8)}... aborted by ${fromPeerId}: ${reason}`); this.poolManager.abortPool(poolId, reason); this.emit(SwapSigEvent.POOL_ABORTED, poolId, reason); } async _handleParticipantDropped(poolId, peerId, reason, fromPeerId) { console.log(`[SwapSig] Participant ${peerId} dropped from pool ${poolId.substring(0, 8)}... by ${fromPeerId}: ${reason}`); this.poolManager.removeParticipant(poolId, peerId); this.emit(SwapSigEvent.PARTICIPANT_DROPPED, poolId, peerId); } _onSwapSigPeerConnected(peerId) { console.log(`[SwapSig] Peer connected: ${peerId}`); } _onSwapSigPeerDisconnected(peerId) { console.log(`[SwapSig] Peer disconnected: ${peerId}`); const pools = this.poolManager.getAllPools(); for (const pool of pools) { if (pool.participantMap.has(peerId)) { if (pool.phase === SwapPhase.DISCOVERY || pool.phase === SwapPhase.REGISTRATION) { this.poolManager.removeParticipant(pool.poolId, peerId); this.emit(SwapSigEvent.PARTICIPANT_DROPPED, pool.poolId, peerId); } else { this.poolManager.abortPool(pool.poolId, `Participant ${peerId} disconnected`); this.emit(SwapSigEvent.POOL_ABORTED, pool.poolId, `Participant ${peerId} disconnected`); } } } } async _sendMessageToPeer(peerId, messageType, payload) { await this.broadcast({ type: messageType, from: this.libp2pNode.peerId.toString(), to: peerId, payload, timestamp: Date.now(), messageId: this._generateMessageId(), protocol: 'swapsig', }); } }