UNPKG

lotus-sdk

Version:

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

206 lines (205 loc) 7.65 kB
import { createHash } from 'crypto'; export var ElectionMethod; (function (ElectionMethod) { ElectionMethod["LEXICOGRAPHIC"] = "lexicographic"; ElectionMethod["HASH_BASED"] = "hash-based"; ElectionMethod["FIRST_SIGNER"] = "first-signer"; ElectionMethod["LAST_SIGNER"] = "last-signer"; })(ElectionMethod || (ElectionMethod = {})); export function electCoordinator(signers, method = ElectionMethod.LEXICOGRAPHIC) { if (signers.length === 0) { throw new Error('Cannot elect coordinator: no signers provided'); } if (signers.length === 1) { return { coordinatorIndex: 0, coordinatorPublicKey: signers[0], sortedSigners: [signers[0]], indexMapping: new Map([[0, 0]]), electionProof: computeElectionProof(signers), }; } switch (method) { case ElectionMethod.LEXICOGRAPHIC: return electByLexicographic(signers); case ElectionMethod.HASH_BASED: return electByHash(signers); case ElectionMethod.FIRST_SIGNER: return electFirstSigner(signers); case ElectionMethod.LAST_SIGNER: return electLastSigner(signers); default: throw new Error(`Unknown election method: ${method}`); } } function electByLexicographic(signers) { const indexed = signers.map((pk, idx) => ({ originalIndex: idx, publicKey: pk, buffer: pk.toBuffer(), })); indexed.sort((a, b) => a.buffer.compare(b.buffer)); const coordinator = indexed[0]; const sortedSigners = indexed.map(item => item.publicKey); const indexMapping = new Map(); indexed.forEach((item, sortedIdx) => { indexMapping.set(item.originalIndex, sortedIdx); }); return { coordinatorIndex: 0, coordinatorPublicKey: coordinator.publicKey, sortedSigners, indexMapping, electionProof: computeElectionProof(signers), }; } function electByHash(signers) { const indexed = signers.map((pk, idx) => ({ originalIndex: idx, publicKey: pk, buffer: pk.toBuffer(), })); indexed.sort((a, b) => a.buffer.compare(b.buffer)); const sortedSigners = indexed.map(item => item.publicKey); const indexMapping = new Map(); indexed.forEach((item, sortedIdx) => { indexMapping.set(item.originalIndex, sortedIdx); }); const concatenated = sortedSigners.map(pk => pk.toString()).join(''); const hash = createHash('sha256').update(concatenated).digest(); const hashValue = hash.readUInt32BE(0); const coordinatorIndex = hashValue % sortedSigners.length; return { coordinatorIndex, coordinatorPublicKey: sortedSigners[coordinatorIndex], sortedSigners, indexMapping, electionProof: computeElectionProof(signers), }; } function electFirstSigner(signers) { const indexed = signers.map((pk, idx) => ({ originalIndex: idx, publicKey: pk, buffer: pk.toBuffer(), })); indexed.sort((a, b) => a.buffer.compare(b.buffer)); const sortedSigners = indexed.map(item => item.publicKey); const indexMapping = new Map(); indexed.forEach((item, sortedIdx) => { indexMapping.set(item.originalIndex, sortedIdx); }); return { coordinatorIndex: 0, coordinatorPublicKey: sortedSigners[0], sortedSigners, indexMapping, electionProof: computeElectionProof(signers), }; } function electLastSigner(signers) { const indexed = signers.map((pk, idx) => ({ originalIndex: idx, publicKey: pk, buffer: pk.toBuffer(), })); indexed.sort((a, b) => a.buffer.compare(b.buffer)); const sortedSigners = indexed.map(item => item.publicKey); const coordinatorIndex = sortedSigners.length - 1; const indexMapping = new Map(); indexed.forEach((item, sortedIdx) => { indexMapping.set(item.originalIndex, sortedIdx); }); return { coordinatorIndex, coordinatorPublicKey: sortedSigners[coordinatorIndex], sortedSigners, indexMapping, electionProof: computeElectionProof(signers), }; } function computeElectionProof(signers) { const concatenated = signers .map(pk => pk.toString()) .sort() .join(''); return createHash('sha256').update(concatenated).digest('hex'); } export function verifyElectionResult(signers, result, method = ElectionMethod.LEXICOGRAPHIC) { const expectedResult = electCoordinator(signers, method); return (expectedResult.coordinatorIndex === result.coordinatorIndex && expectedResult.electionProof === result.electionProof); } export function isCoordinator(signers, signerIndex, method = ElectionMethod.LEXICOGRAPHIC) { if (signerIndex < 0 || signerIndex >= signers.length) { return false; } const election = electCoordinator(signers, method); return election.coordinatorIndex === signerIndex; } export function getCoordinatorPublicKey(signers, method = ElectionMethod.LEXICOGRAPHIC) { const election = electCoordinator(signers, method); return election.coordinatorPublicKey; } export function getBackupCoordinator(signers, currentCoordinatorIndex, method = ElectionMethod.LEXICOGRAPHIC) { if (signers.length === 1) { return null; } switch (method) { case ElectionMethod.LEXICOGRAPHIC: return getBackupForLexicographic(signers, currentCoordinatorIndex); case ElectionMethod.HASH_BASED: return getBackupForHashBased(signers, currentCoordinatorIndex); case ElectionMethod.FIRST_SIGNER: return getBackupForFirstSigner(signers, currentCoordinatorIndex); case ElectionMethod.LAST_SIGNER: return getBackupForLastSigner(signers, currentCoordinatorIndex); default: throw new Error(`Unknown election method: ${method}`); } } function getBackupForLexicographic(signers, currentCoordinatorIndex) { const indexed = signers.map((pk, idx) => ({ originalIndex: idx, publicKey: pk, hex: pk.toString(), })); indexed.sort((a, b) => a.hex.localeCompare(b.hex)); const currentSortedIndex = indexed.findIndex(item => item.originalIndex === currentCoordinatorIndex); if (currentSortedIndex === -1) { throw new Error('Current coordinator not found in signers list'); } const nextSortedIndex = (currentSortedIndex + 1) % indexed.length; return indexed[nextSortedIndex].originalIndex; } function getBackupForHashBased(signers, currentCoordinatorIndex) { return (currentCoordinatorIndex + 1) % signers.length; } function getBackupForFirstSigner(signers, currentCoordinatorIndex) { const nextIndex = currentCoordinatorIndex + 1; if (nextIndex >= signers.length) { return null; } return nextIndex; } function getBackupForLastSigner(signers, currentCoordinatorIndex) { const nextIndex = currentCoordinatorIndex - 1; if (nextIndex < 0) { return null; } return nextIndex; } export function getCoordinatorPriorityList(signers, method = ElectionMethod.LEXICOGRAPHIC) { const election = electCoordinator(signers, method); const priorityList = [election.coordinatorIndex]; let currentIndex = election.coordinatorIndex; while (true) { const backup = getBackupCoordinator(signers, currentIndex, method); if (backup === null || priorityList.includes(backup)) { break; } priorityList.push(backup); currentIndex = backup; } return priorityList; }