Skip to content

Commit

Permalink
Moved the verification and config loading to AllowedArchiversManager …
Browse files Browse the repository at this point in the history
…class
  • Loading branch information
jintukumardas committed Jan 16, 2025
1 parent e1c33a0 commit 95b0c28
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 554 deletions.
8 changes: 4 additions & 4 deletions allowed-archivers.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
"publicKey": "e8a5c26b9e2c3c31eb7c7d73eaed9484374c16d983ce95f3ab18a62521964a94"
}
],
"allowedAccounts": [
"0x002D3a2BfE09E3E29b6d38d58CaaD16EEe4C9BC5"
],
"allowedAccounts": {
"0x002D3a2BfE09E3E29b6d38d58CaaD16EEe4C9BC5": 3
},
"counter": 1,
"minSigRequired": 1,
"signatures": [
{
"owner": "0x002D3a2BfE09E3E29b6d38d58CaaD16EEe4C9BC5",
"sig": "0x95b32d241e1885b9db2f2a5850c99a10a6a1df7f8142b0914715a1a5bdfd9bed09753f32948cb9fee69b20cfefee89d3f90be183de721d1b5d88157a6affda821b"
"sig": "0xc4f25a2156c9151c0515d1e5636b94de5ab2e21cc924207a2d2b5d679d8b3dec0f3b7aab78a0dfa444ee4794cf3a61aa6b9a70f8a51ebac46b9560d50cb019b51b"
}
]
}
8 changes: 1 addition & 7 deletions scripts/archiver_config_sign.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { ethers } from 'ethers';
import * as fs from 'fs';
import { Utils as StringUtils } from '@shardus/types';
import { Utils as StringUtils } from '@shardeum-foundation/lib-types';

interface ConfigData {
allowedArchivers: string[];
allowedAccounts: string[];
minSigRequired: number;
counter: number;
}

interface SignaturePayload {
allowedArchivers: string[];
allowedAccounts: string[];
minSigRequired: number;
counter: number;
}

Expand All @@ -33,8 +29,6 @@ async function generateSignature(): Promise<void> {
// Create payload
const rawPayload: SignaturePayload = {
allowedArchivers: configData.allowedArchivers,
allowedAccounts: configData.allowedAccounts,
minSigRequired: configData.minSigRequired,
counter: configData.counter
};

Expand Down
161 changes: 21 additions & 140 deletions src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,8 @@ import {
failureReceiptCount,
} from './primary-process'
import * as ServiceQueue from './ServiceQueue'
import { readFileSync } from 'fs'
import { join } from 'path'
import ticketRoutes from './routes/tickets'
import path = require('path')
import fs = require('fs')
import { verifyMultiSigs } from './Utils'
import { ethers } from 'ethers'
import { allowedArchiversManager } from './shardeum/allowedArchiversManager'

const { version } = require('../package.json') // eslint-disable-line @typescript-eslint/no-var-requires
const TXID_LENGTH = 64
Expand All @@ -54,8 +49,6 @@ const {
} = config.REQUEST_LIMIT

let reachabilityAllowed = true
let previousConfigHash = ''
let lastSeenCounter = 0

export function registerRoutes(server: FastifyInstance<Server, IncomingMessage, ServerResponse>): void {
type Request = FastifyRequest<{
Expand Down Expand Up @@ -287,67 +280,17 @@ export function registerRoutes(server: FastifyInstance<Server, IncomingMessage,
reply.send(res)
})

server.get('/allowed-archivers', async (request, reply) => {
server.get('/allowed-archivers', async (_request, reply) => {
try {
// Get path to allowed archivers config file
const allowedArchiversPath = path.resolve(__dirname, '../allowed-archivers.json')

// Load and parse initial config
const loadConfig = () => {
const data = fs.readFileSync(allowedArchiversPath, 'utf8')
return StringUtils.safeJsonParse(data)
}

let allowedArchivers = loadConfig()

// Watch for config file changes
fs.watchFile(allowedArchiversPath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
allowedArchivers = loadConfig()
}
})

// Extract payload fields for signature verification
const payload = {
allowedArchivers: allowedArchivers.allowedArchivers,
allowedAccounts: allowedArchivers.allowedAccounts,
minSigRequired: allowedArchivers.minSigRequired,
counter: allowedArchivers.counter
}

// Verify signatures on config
const isValidList = verifyMultiSigs(
payload,
allowedArchivers.signatures,
allowedArchivers.allowedAccounts,
allowedArchivers.minSigRequired
)

if (!isValidList) {
return reply.status(403).send({
error: 'Forbidden' // Validators will use previous valid list
const config = allowedArchiversManager.getCurrentConfig()
if (!config) {
return reply.status(500).send({
error: 'Internal server error'
})
}

const payload_hash = ethers.keccak256(ethers.toUtf8Bytes(StringUtils.safeStringify(payload)))
if (previousConfigHash == '') {
previousConfigHash = payload_hash
} else if (previousConfigHash != payload_hash) {
// New list found; increment counter and update previous hash
if (payload.counter > lastSeenCounter) {
lastSeenCounter += 1
previousConfigHash = payload_hash
}
else { // New list without incrementing counter should be rejected
Logger.mainLogger.error('Forbidden: counter is not incrementing')
return reply.status(403).send({
error: 'Forbidden' // Validators will use previous valid list
})
}
}
return reply.send(allowedArchivers)
return reply.send(config)
} catch (error) {
Logger.mainLogger.error('Error reading/validating allowed-archivers.json:', error)
Logger.mainLogger.error('Error serving allowed-archivers:', error)
return reply.status(500).send({
error: 'Internal server error'
})
Expand Down Expand Up @@ -1369,91 +1312,29 @@ export const validateRequestData = (
Logger.mainLogger.error('Data sender publicKey and sign owner key does not match')
return { success: false, error: 'Data sender publicKey and sign owner key does not match' }
}

if (!Crypto.verify(data)) {
Logger.mainLogger.error('Invalid signature', data)

Check warning

Code scanning / CodeQL

Log injection Medium

Log entry depends on a
user-provided value
.
Log entry depends on a
user-provided value
.
Log entry depends on a
user-provided value
.
Log entry depends on a
user-provided value
.
Log entry depends on a
user-provided value
.
Log entry depends on a
user-provided value
.
return { success: false, error: 'Invalid signature' }
}

if (!skipArchiverCheck && config.limitToArchiversOnly) {
try {
// Load and validate allowed archivers config
const allowedArchiversPath = path.resolve(__dirname, '../allowed-archivers.json')
const allowedArchiversConfig = StringUtils.safeJsonParse(
fs.readFileSync(allowedArchiversPath, 'utf8')
)

// Extract payload for signature verification
const payload = {
allowedArchivers: allowedArchiversConfig.allowedArchivers,
allowedAccounts: allowedArchiversConfig.allowedAccounts,
minSigRequired: allowedArchiversConfig.minSigRequired,
counter: allowedArchiversConfig.counter
}
// Check if the sender is in the allowed archivers list
const isAllowedArchiver = allowedArchiversManager.isArchiverAllowed(data.sender)

// Verify signatures on config
const isValidConfig = Utils.verifyMultiSigs(
payload,
allowedArchiversConfig.signatures,
allowedArchiversConfig.allowedAccounts,
allowedArchiversConfig.minSigRequired
)

if (!isValidConfig) {
Logger.mainLogger.error('Invalid allowed-archivers.json signatures')
return {
success: false,
error: 'Invalid archiver configuration'
}
}

const payload_hash = ethers.keccak256(ethers.toUtf8Bytes(StringUtils.safeStringify(payload)))
if (previousConfigHash == '') {
previousConfigHash = payload_hash
}

if (previousConfigHash != payload_hash) {
if (payload.counter > lastSeenCounter) {
lastSeenCounter = payload.counter
previousConfigHash = payload_hash
}
else {
Logger.mainLogger.error('Forbidden: counter is not incrementing')
return {
success: false,
error: 'Forbidden: counter is not incrementing'
}
}
}

// Check if sender is in the allowed archivers list
const isAllowedArchiver = allowedArchiversConfig.allowedArchivers.some(
(archiver) => archiver.publicKey === data.sender
)

// Check if the sender is in the active archiver list or is the devPublicKey
const isActiveArchiver = State.activeArchivers.some(
(archiver) => archiver.publicKey === data.sender
)

const approvedSender =
(isAllowedArchiver && isActiveArchiver) ||
config.DevPublicKey === data.sender
// Check if the sender is in the active archiver list or is the devPublicKey
const isActiveArchiver = State.activeArchivers.some(
(archiver) => archiver.publicKey === data.sender
)

if (!approvedSender) {
return {
success: false,
error: isAllowedArchiver
? 'Archiver is not active'
: 'Data request sender is not an authorized archiver'
}
}
const approvedSender =
(isAllowedArchiver && isActiveArchiver) ||
config.DevPublicKey === data.sender

} catch (error) {
Logger.mainLogger.error('Error checking allowed archivers:', error)
if (!approvedSender) {
return {
success: false,
error: 'Failed to verify archiver authorization'
error: isAllowedArchiver
? 'Archiver is not active'
: 'Data request sender is not an authorized archiver'
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/GlobalAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { postJson, getJson } from './P2P'
import { robustQuery, deepCopy } from './Utils'
import { isDeepStrictEqual } from 'util'
import { accountSpecificHash } from './shardeum/calculateAccountHash'
import { allowedArchiversManager } from './shardeum/allowedArchiversManager'

let cachedGlobalNetworkAccount: AccountDB.AccountsCopy
let cachedGlobalNetworkAccountHash: string
Expand Down Expand Up @@ -35,6 +36,7 @@ export function getGlobalNetworkAccount(hash: boolean): object | string {
export function setGlobalNetworkAccount(account: AccountDB.AccountsCopy): void {
cachedGlobalNetworkAccount = rfdc()(account)
cachedGlobalNetworkAccountHash = account.hash
allowedArchiversManager.setGlobalAccountConfig(account.data?.listOfChanges?.dev?.change?.debug?.multisigKeys, account.data?.listOfChanges?.dev?.change?.debug?.minMultiSigRequiredForGlobalTxs)
}

interface NetworkConfigChanges {
Expand Down
2 changes: 2 additions & 0 deletions src/LostArchivers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { calcIncomingTimes } from './Data/Data'
import { postJson } from './P2P'
import { sign } from './Crypto'
import { SignedObject } from '@shardeum-foundation/lib-types/build/src/p2p/P2PTypes'
import { allowedArchiversManager } from './shardeum/allowedArchiversManager'

let shouldSendRefutes = false

Expand Down Expand Up @@ -101,5 +102,6 @@ function die(): void {
Logger.mainLogger.debug(
'Archiver was found in `removedArchivers` and will exit now without sending a leave request'
)
allowedArchiversManager.stopWatching()
process.exit(2)
}
6 changes: 5 additions & 1 deletion src/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { publicKey, secretKey, curvePublicKey, curveSecretKey } from '@shardeum-
import fetch from 'node-fetch'
import { getAdjacentLeftAndRightArchivers } from './Data/GossipData'
import { closeDatabase } from './dbstore'
import { allowedArchiversManager } from './shardeum/allowedArchiversManager'

export interface ArchiverNodeState {
ip: string
Expand Down Expand Up @@ -136,6 +137,7 @@ export async function exitArchiver(): Promise<void> {
}
Logger.mainLogger.debug('Archiver will exit in 3 seconds.')
setTimeout(() => {
allowedArchiversManager.stopWatching()
process.exit()
}, 3000)
} catch (e) {
Expand All @@ -148,6 +150,7 @@ export function addSigListeners(sigint = true, sigterm = true): void {
process.on('SIGINT', async () => {
Logger.mainLogger.debug('Exiting on SIGINT', process.pid)
await closeDatabase()
allowedArchiversManager.stopWatching()
if (isActive) exitArchiver()
else process.exit(0)
})
Expand All @@ -156,11 +159,12 @@ export function addSigListeners(sigint = true, sigterm = true): void {
process.on('SIGTERM', async () => {
Logger.mainLogger.debug('Exiting on SIGTERM', process.pid)
await closeDatabase()
allowedArchiversManager.stopWatching()
if (isActive) exitArchiver()
else process.exit(0)
})
}
Logger.mainLogger.debug('Registerd exit signal listeners.')
Logger.mainLogger.debug('Registered exit signal listeners.')
}

export function addArchiver(archiver: ArchiverNodeInfo): void {
Expand Down
37 changes: 0 additions & 37 deletions src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as path from 'path'
import { Utils as StringUtils } from '@shardeum-foundation/lib-types'
import * as util from 'util'
import * as Logger from './Logger'
import { ethers } from 'ethers'

export interface CountSchema {
count: string
Expand Down Expand Up @@ -569,39 +568,3 @@ export function createDirectories(pathname: string): void {
pathname = pathname.replace(/^\.*\/|\/?[^/]+\.[a-z]+|\/$/g, '') // Remove leading directory markers, and remove ending /file-name.extension
fs.mkdirSync(path.resolve(__dirname, pathname), { recursive: true }) // eslint-disable-line security/detect-non-literal-fs-filename
}

/**
@param rawPayload: any - The original payload stripped of the signatures
@param sigs: Sign[] - The signatures to verify
@param allowedPubkeys: string[] - The public keys that are allowed to sign the payload
@param minSigRequired: number - The minimum number of signatures required
@returns boolean - True if the payload is signed by the required number of authorized public keys
**/
export function verifyMultiSigs(
rawPayload: object,
sigs: any[],
allowedPubkeys: string[],
minSigRequired: number
): boolean {
if (!rawPayload || !sigs || !allowedPubkeys || !Array.isArray(sigs)) {
return false
}
if (sigs.length < minSigRequired) return false
if (sigs.length > allowedPubkeys.length) return false
let validSigs = 0
const payload_hash = ethers.keccak256(ethers.toUtf8Bytes(StringUtils.safeStringify(rawPayload)))
const seen = new Set()
for (const sig of sigs) {
if (
!seen.has(sig.owner) &&
allowedPubkeys.includes(sig.owner) &&
ethers.verifyMessage(payload_hash, sig.sig).toLowerCase() === sig.owner.toLowerCase()
) {
validSigs++
seen.add(sig.owner)
}
if (validSigs >= minSigRequired) break
}

return validSigs >= minSigRequired
}
Loading

0 comments on commit 95b0c28

Please sign in to comment.