Airship\Engine\Keyggdrasil::verifyResponseWithPeers PHP Method

verifyResponseWithPeers() protected method

Dear future security auditors: This is important. This employs challenge-response authentication:
protected verifyResponseWithPeers ( Channel $channel, MerkleTree $originalTree, variadic $updates ) : boolean
$channel Channel
$originalTree MerkleTree
$updates variadic
return boolean
    protected function verifyResponseWithPeers(Channel $channel, MerkleTree $originalTree, TreeUpdate ...$updates) : bool
    {
        $state = State::instance();
        $nodes = $this->updatesToNodes($updates);
        $tree = $originalTree->getExpandedTree(...$nodes);
        $maxUpdateIndex = \count($updates) - 1;
        $expectedRoot = $updates[$maxUpdateIndex]->getRoot();
        if (!\hash_equals($tree->getRoot(), $expectedRoot)) {
            // Calculated root did not match.
            self::$continuumLogger->store(LogLevel::EMERGENCY, 'Calculated Merkle root did not match the update.', [$tree->getRoot(), $expectedRoot]);
            throw new CouldNotUpdate(\__('Calculated Merkle root did not match the update.'));
        }
        if ($state->universal['auto-update']['ignore-peer-verification']) {
            // The user has expressed no interest in verification
            return true;
        }
        $peers = $channel->getPeerList();
        $numPeers = \count($peers);
        /**
         * These numbers are negotiable in future versions.
         *
         * If P is the set of trusted peer notaries (where ||P|| is the number
         * of trusted peer notaries):
         *
         * 1. At least 1 must return 'success'.
         * 2. At least ln(||P||) must return 'success'.
         * 3. At most e * ln(||P||) can timeout.
         * 4. If any peer disagrees with what we see, our
         *    result is discarded as invalid.
         *
         * The most harm a malicious peer can do is DoS if they
         * are selected.
         */
        $minSuccess = $channel->getAppropriatePeerSize();
        $maxFailure = (int) \min(\floor($minSuccess * M_E), $numPeers - 1);
        if ($maxFailure < 1) {
            $maxFailure = 1;
        }
        \Airship\secure_shuffle($peers);
        $success = $networkError = 0;
        /**
         * If any peers give a different answer, we're under attack.
         * If too many peers don't respond, assume they're being DDoS'd.
         * If enough peers respond in absolute agreement, we're good.
         */
        for ($i = 0; $i < $numPeers; ++$i) {
            try {
                if (!$this->checkWithPeer($peers[$i], $tree->getRoot())) {
                    // Merkle root mismatch? Abort.
                    return false;
                }
                ++$success;
            } catch (TransferException $ex) {
                self::$continuumLogger->store(LogLevel::EMERGENCY, 'A transfer exception occurred', \Airship\throwableToArray($ex));
                ++$networkError;
            }
            if ($success >= $minSuccess) {
                // We have enough good responses.
                return true;
            } elseif ($networkError >= $maxFailure) {
                // We can't give a confident response here.
                return false;
            }
        }
        self::$continuumLogger->store(LogLevel::EMERGENCY, 'We ran out of peers.', [$numPeers, $minSuccess, $maxFailure]);
        // Fail closed:
        return false;
    }