function testMajority($results, $signedData, $myPair = null, $inGroup = null, $error = true) { // Get the set of root public keys, indexed by endpoint. Note that they are raw bytes: $publicKeys = getRootKeys($inGroup); // How many are there? $nodesInRoot = count($publicKeys); if ($nodesInRoot < 3) { // This root isn't big enough. It's not able to obtain a majority. return false; } // The number of root nodes that we have successfully verified a response from. // The JSON to forward to other nodes. This only occurs when myPair is not null (which indicates // that this server is 'leading' the majority process). $fullSet = ''; if ($myPair == NULL) { // We start with 0 - we have to verify all signatures, including our own. $verifiedCount = 0; } else { global $thisEntity; // It starts with 1 because we know at least this ones signature was successful. $verifiedCount = 1; // This route also generates JSON to forward to other nodes too. $fullSet = '{"' . $thisEntity['Endpoint'] . '":' . $myPair; } foreach ($results as $endPoint => $result) { // Get this endpoints public key (bytes): if (!isset($publicKeys[$endPoint])) { continue; } // Get the key: $endpointKey = $publicKeys[$endPoint]; if (is_array($result)) { // JSON is already loaded. $resultJson =& $result; // Encode it to a string: $result = json_encode($resultJson); } else { // Load the JSON: $resultJson = json_decode($result, true); } // Get the signature (base64): $resultSig = $resultJson['signature']; // Verify it: if (verify($resultSig, $resultJson['challenge'] . $signedData, $endpointKey)) { // Increase the count! $verifiedCount++; if ($fullSet != '') { // Output the end point plus the JSON response: $fullSet .= ',"' . $endPoint . '":' . $result; } } } // Finish the full set: if ($fullSet != '') { $fullSet .= '}'; } // The important check occurs here - do we have a majority? Must be greater than half. if ($verifiedCount > $nodesInRoot / 2) { // Majority formed! if ($fullSet == '') { return true; } return $fullSet; } else { if ($error) { // No majority formed. error('majority/notformed'); } } // No majority formed (silent error). return false; }
function sendToRoot($payload, $pHeader, $decodeJson = false, $location = null, $rootGroup = null) { global $thisEntity, $path, $dz, $rootKeys; if ($location == null) { // Use the current path as the location (used by forwarding): $location = $path; } // Build the message: $message = '{"header":{"entity":"' . $thisEntity['Endpoint'] . '"},"protected":"' . $pHeader . '","payload":"' . $payload . '","signature":"' . base64_encode(sign($pHeader . '.' . $payload)) . '"}'; // Forward to all other root nodes in my group. $group = $thisEntity['Group']; // The responses: $responses = array(); // Are we using this group or another one? if ($rootGroup == null) { // Get the local root keys: if ($rootKeys == null) { // Root keys holds a mapping of endpoint to key. // We'll use it here to get those endpoints. getRootKeys(); } // Use this group: $keySet = $rootKeys; } else { // Sending to some other root group. // Only need to send to one node here. // (If we send to all of them, it defeats the object of having groups!) // Get a random node: $randomRow = randomRoot($rootGroup); // Create the set: $keySet = array(); // Add our single entry to it: $keySet[$randomRow['Endpoint']] = $randomRow['Key']; } global $rootErrors; // Clear root errors: $rootErrors = null; // For each one.. foreach ($keySet as $endPoint => $rootKey) { if ($endPoint == $thisEntity['Endpoint']) { // Don't send to myself! continue; } // Send it a message: // Warning! This will take a while with crowded roots // as it does the requests one after another. Use parallel requests instead. $error; $response = post('https://' . $endPoint . '/' . $location, $message, $error); // Got a response? if ($error) { // The remote node emitted an error. if (!$rootErrors) { $rootErrors = array(); } // Add the error: array_push($rootErrors, $error); } else { if ($decodeJson) { // Add to result: $responses[$endPoint] = json_decode($response, true); } else { // Add to result as-is (default): $responses[$endPoint] = $response; } } } // Return the responses: return $responses; }