summaryrefslogtreecommitdiff
path: root/src/or/shared_random.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/or/shared_random.c')
-rw-r--r--src/or/shared_random.c395
1 files changed, 393 insertions, 2 deletions
diff --git a/src/or/shared_random.c b/src/or/shared_random.c
index 0f988af50a..e3fe1a9e73 100644
--- a/src/or/shared_random.c
+++ b/src/or/shared_random.c
@@ -6,6 +6,83 @@
*
* \brief Functions and data structure needed to accomplish the shared
* random protocol as defined in proposal #250.
+ *
+ * \details
+ *
+ * This file implements the dirauth-only commit-and-reveal protocol specified
+ * by proposal #250. The protocol has two phases (sr_phase_t): the commitment
+ * phase and the reveal phase (see get_sr_protocol_phase()).
+ *
+ * During the protocol, directory authorities keep state in memory (using
+ * sr_state_t) and in disk (using sr_disk_state_t). The synchronization between
+ * these two data structures happens in disk_state_update() and
+ * disk_state_parse().
+ *
+ * Here is a rough protocol outline:
+ *
+ * 1) In the beginning of the commitment phase, dirauths generate a
+ * commitment/reveal value for the current protocol run (see
+ * new_protocol_run() and sr_generate_our_commit()).
+ *
+ * 2) During voting, dirauths publish their commits in their votes
+ * depending on the current phase. Dirauths also include the two
+ * latest shared random values (SRV) in their votes.
+ * (see sr_get_string_for_vote())
+ *
+ * 3) Upon receiving a commit from a vote, authorities parse it, verify
+ * it, and attempt to save any new commitment or reveal information in
+ * their state file (see extract_shared_random_commits() and
+ * sr_handle_received_commits()). They also parse SRVs from votes to
+ * decide which SRV should be included in the final consensus (see
+ * extract_shared_random_srvs()).
+ *
+ * 3) After voting is done, we count the SRVs we extracted from the votes,
+ * to find the one voted by the majority of dirauths which should be
+ * included in the final consensus (see get_majority_srv_from_votes()).
+ * If an appropriate SRV is found, it is embedded in the consensus (see
+ * sr_get_string_for_consensus()).
+ *
+ * 4) At the end of the reveal phase, dirauths compute a fresh SRV for the
+ * day using the active commits (see sr_compute_srv()). This new SRV
+ * is embedded in the votes as described above.
+ *
+ * Some more notes:
+ *
+ * - To support rebooting authorities and to avoid double voting, each dirauth
+ * saves the current state of the protocol on disk so that it can resume
+ * normally in case of reboot. The disk state (sr_disk_state_t) is managed by
+ * shared_random_state.c:state_query() and we go to extra lengths to ensure
+ * that the state is flushed on disk everytime we receive any useful
+ * information like commits or SRVs.
+ *
+ * - When we receive a commit from a vote, we examine it to see if it's useful
+ * to us and whether it's appropriate to receive it according to the current
+ * phase of the protocol (see should_keep_commit()). If the commit is useful
+ * to us, we save it in our disk state using save_commit_to_state(). When we
+ * receive the reveal information corresponding to a commitment, we verify
+ * that they indeed match using verify_commit_and_reveal().
+ *
+ * - We treat consensuses as the ground truth, so everytime we generate a new
+ * consensus we update our SR state accordingly even if our local view was
+ * different (see sr_act_post_consensus()).
+ *
+ * - After a consensus has been composed, the SR protocol state gets prepared
+ * for the next voting session using sr_state_update(). That function takes
+ * care of housekeeping and also rotates the SRVs and commits in case a new
+ * protocol run is coming up. We also call sr_state_update() on bootup (in
+ * sr_state_init()), to prepare the state for the very first voting session.
+ *
+ * Terminology:
+ *
+ * - "Commitment" is the commitment value of the commit-and-reveal protocol.
+ *
+ * - "Reveal" is the reveal value of the commit-and-reveal protocol.
+ *
+ * - "Commit" is a struct (sr_commit_t) that contains a commitment value and
+ * optionally also a corresponding reveal value.
+ *
+ * - "SRV" is the Shared Random Value that gets generated as the result of the
+ * commit-and-reveal protocol.
**/
#define SHARED_RANDOM_PRIVATE
@@ -27,6 +104,22 @@ static const char current_srv_str[] = "shared-rand-current-value";
static const char commit_ns_str[] = "shared-rand-commit";
static const char sr_flag_ns_str[] = "shared-rand-participate";
+/* Return a heap allocated copy of the SRV <b>orig</b>. */
+STATIC sr_srv_t *
+srv_dup(const sr_srv_t *orig)
+{
+ sr_srv_t *dup = NULL;
+
+ if (!orig) {
+ return NULL;
+ }
+
+ dup = tor_malloc_zero(sizeof(sr_srv_t));
+ dup->num_reveals = orig->num_reveals;
+ memcpy(dup->value, orig->value, sizeof(dup->value));
+ return dup;
+}
+
/* Allocate a new commit object and initializing it with <b>identity</b>
* that MUST be provided. The digest algorithm is set to the default one
* that is supported. The rest is uninitialized. This never returns NULL. */
@@ -65,6 +158,59 @@ commit_log(const sr_commit_t *commit)
}
}
+/* Make sure that the commitment and reveal information in <b>commit</b>
+ * match. If they match return 0, return -1 otherwise. This function MUST be
+ * used everytime we receive a new reveal value. Furthermore, the commit
+ * object MUST have a reveal value and the hash of the reveal value. */
+STATIC int
+verify_commit_and_reveal(const sr_commit_t *commit)
+{
+ tor_assert(commit);
+
+ log_debug(LD_DIR, "SR: Validating commit from authority %s",
+ commit->rsa_identity_fpr);
+
+ /* Check that the timestamps match. */
+ if (commit->commit_ts != commit->reveal_ts) {
+ log_warn(LD_BUG, "SR: Commit timestamp %ld doesn't match reveal "
+ "timestamp %ld", commit->commit_ts, commit->reveal_ts);
+ goto invalid;
+ }
+
+ /* Verify that the hashed_reveal received in the COMMIT message, matches
+ * the reveal we just received. */
+ {
+ /* We first hash the reveal we just received. */
+ char received_hashed_reveal[sizeof(commit->hashed_reveal)];
+
+ /* Only sha3-256 is supported. */
+ if (commit->alg != SR_DIGEST_ALG) {
+ goto invalid;
+ }
+
+ /* Use the invariant length since the encoded reveal variable has an
+ * extra byte for the NUL terminated byte. */
+ if (crypto_digest256(received_hashed_reveal, commit->encoded_reveal,
+ SR_REVEAL_BASE64_LEN, commit->alg)) {
+ /* Unable to digest the reveal blob, this is unlikely. */
+ goto invalid;
+ }
+
+ /* Now compare that with the hashed_reveal we received in COMMIT. */
+ if (fast_memneq(received_hashed_reveal, commit->hashed_reveal,
+ sizeof(received_hashed_reveal))) {
+ log_warn(LD_BUG, "SR: Received reveal value from authority %s "
+ "does't match the commit value.",
+ commit->rsa_identity_fpr);
+ goto invalid;
+ }
+ }
+
+ return 0;
+ invalid:
+ return -1;
+}
+
/* Return true iff the commit contains an encoded reveal value. */
STATIC int
commit_has_reveal_value(const sr_commit_t *commit)
@@ -403,8 +549,176 @@ get_ns_str_from_sr_values(const sr_srv_t *prev_srv, const sr_srv_t *cur_srv)
return srv_str;
}
-/* Return the number of required participants of the SR protocol. This is
- * based on a consensus params. */
+/* Return 1 iff the two commits have the same commitment values. This
+ * function does not care about reveal values. */
+STATIC int
+commitments_are_the_same(const sr_commit_t *commit_one,
+ const sr_commit_t *commit_two)
+{
+ tor_assert(commit_one);
+ tor_assert(commit_two);
+
+ if (strcmp(commit_one->encoded_commit, commit_two->encoded_commit)) {
+ return 0;
+ }
+ return 1;
+}
+
+/* We just received a commit from the vote of authority with
+ * <b>identity_digest</b>. Return 1 if this commit is authorititative that
+ * is, it belongs to the authority that voted it. Else return 0 if not. */
+STATIC int
+commit_is_authoritative(const sr_commit_t *commit,
+ const char *voter_key)
+{
+ tor_assert(commit);
+ tor_assert(voter_key);
+
+ return !strcmp(commit->rsa_identity_fpr, voter_key);
+}
+
+/* Decide if the newly received <b>commit</b> should be kept depending on
+ * the current phase and state of the protocol. The <b>voter_key</b> is the
+ * RSA identity key fingerprint of the authority's vote from which the
+ * commit comes from. The <b>phase</b> is the phase we should be validating
+ * the commit for. Return 1 if the commit should be added to our state or 0
+ * if not. */
+STATIC int
+should_keep_commit(const sr_commit_t *commit, const char *voter_key,
+ sr_phase_t phase)
+{
+ sr_commit_t *saved_commit;
+
+ tor_assert(commit);
+ tor_assert(voter_key);
+
+ log_debug(LD_DIR, "SR: Inspecting commit from %s (voter: %s)?",
+ commit->rsa_identity_fpr, voter_key);
+
+ /* For a commit to be considered, it needs to be authoritative (it should
+ * be the voter's own commit). */
+ if (!commit_is_authoritative(commit, voter_key)) {
+ log_debug(LD_DIR, "SR: Ignoring non-authoritative commit.");
+ goto ignore;
+ }
+
+ /* Check if the authority that voted for <b>commit</b> has already posted
+ * a commit before. */
+ saved_commit = sr_state_get_commit(commit->rsa_identity_fpr);
+
+ switch (phase) {
+ case SR_PHASE_COMMIT:
+ /* Already having a commit for an authority so ignore this one. */
+ if (saved_commit) {
+ log_debug(LD_DIR, "SR: Ignoring known commit during COMMIT phase.");
+ goto ignore;
+ }
+
+ /* A commit with a reveal value during commitment phase is very wrong. */
+ if (commit_has_reveal_value(commit)) {
+ log_warn(LD_DIR, "SR: Commit from authority %s has a reveal value "
+ "during COMMIT phase. (voter: %s)",
+ commit->rsa_identity_fpr, voter_key);
+ goto ignore;
+ }
+ break;
+ case SR_PHASE_REVEAL:
+ /* We are now in reveal phase. We keep a commit if and only if:
+ *
+ * - We have already seen a commit by this auth, AND
+ * - the saved commit has the same commitment value as this one, AND
+ * - the saved commit has no reveal information, AND
+ * - this commit does have reveal information, AND
+ * - the reveal & commit information are matching.
+ *
+ * If all the above are true, then we are interested in this new commit
+ * for its reveal information. */
+
+ if (!saved_commit) {
+ log_debug(LD_DIR, "SR: Ignoring commit first seen in reveal phase.");
+ goto ignore;
+ }
+
+ if (!commitments_are_the_same(commit, saved_commit)) {
+ log_warn(LD_DIR, "SR: Commit from authority %s is different from "
+ "previous commit in our state (voter: %s)",
+ commit->rsa_identity_fpr, voter_key);
+ goto ignore;
+ }
+
+ if (commit_has_reveal_value(saved_commit)) {
+ log_debug(LD_DIR, "SR: Ignoring commit with known reveal info.");
+ goto ignore;
+ }
+
+ if (!commit_has_reveal_value(commit)) {
+ log_debug(LD_DIR, "SR: Ignoring commit without reveal value.");
+ goto ignore;
+ }
+
+ if (verify_commit_and_reveal(commit) < 0) {
+ log_warn(LD_BUG, "SR: Commit from authority %s has an invalid "
+ "reveal value. (voter: %s)",
+ commit->rsa_identity_fpr, voter_key);
+ goto ignore;
+ }
+ break;
+ default:
+ tor_assert(0);
+ }
+
+ return 1;
+
+ ignore:
+ return 0;
+}
+
+/* We are in reveal phase and we found a valid and verified <b>commit</b> in
+ * a vote that contains reveal values that we could use. Update the commit
+ * we have in our state. Never call this with an unverified commit. */
+STATIC void
+save_commit_during_reveal_phase(const sr_commit_t *commit)
+{
+ sr_commit_t *saved_commit;
+
+ tor_assert(commit);
+
+ /* Get the commit from our state. */
+ saved_commit = sr_state_get_commit(commit->rsa_identity_fpr);
+ tor_assert(saved_commit);
+ /* Safety net. They can not be different commitments at this point. */
+ int same_commits = commitments_are_the_same(commit, saved_commit);
+ tor_assert(same_commits);
+
+ /* Copy reveal information to our saved commit. */
+ sr_state_copy_reveal_info(saved_commit, commit);
+}
+
+/* Save <b>commit</b> to our persistent state. Depending on the current
+ * phase, different actions are taken. Steals reference of <b>commit</b>.
+ * The commit object MUST be valid and verified before adding it to the
+ * state. */
+STATIC void
+save_commit_to_state(sr_commit_t *commit)
+{
+ sr_phase_t phase = sr_state_get_phase();
+
+ switch (phase) {
+ case SR_PHASE_COMMIT:
+ /* During commit phase, just save any new authoritative commit */
+ sr_state_add_commit(commit);
+ break;
+ case SR_PHASE_REVEAL:
+ save_commit_during_reveal_phase(commit);
+ sr_commit_free(commit);
+ break;
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Return the number of required participants of the SR protocol. This is based
+ * on a consensus params. */
static int
get_n_voters_for_srv_agreement(void)
{
@@ -789,6 +1103,41 @@ sr_parse_commit(const smartlist_t *args)
return NULL;
}
+/* Called when we are done parsing a vote by <b>voter_key</b> that might
+ * contain some useful <b>commits</b>. Find if any of them should be kept
+ * and update our state accordingly. Once done, the list of commitments will
+ * be empty. */
+void
+sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key)
+{
+ char rsa_identity_fpr[FINGERPRINT_LEN + 1];
+
+ tor_assert(voter_key);
+
+ /* It's possible that the vote has _NO_ commits. */
+ if (commits == NULL) {
+ return;
+ }
+
+ /* Get the RSA identity fingerprint of this voter */
+ if (crypto_pk_get_fingerprint(voter_key, rsa_identity_fpr, 0) < 0) {
+ return;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(commits, sr_commit_t *, commit) {
+ /* We won't need the commit in this list anymore, kept or not. */
+ SMARTLIST_DEL_CURRENT(commits, commit);
+ /* Check if this commit is valid and should be stored in our state. */
+ if (!should_keep_commit(commit, rsa_identity_fpr,
+ sr_state_get_phase())) {
+ sr_commit_free(commit);
+ continue;
+ }
+ /* Everything lines up: save this commit to state then! */
+ save_commit_to_state(commit);
+ } SMARTLIST_FOREACH_END(commit);
+}
+
/* Return a heap-allocated string containing commits that should be put in
* the votes. It's the responsibility of the caller to free the string.
* This always return a valid string, either empty or with line(s). */
@@ -873,6 +1222,48 @@ sr_get_string_for_consensus(const smartlist_t *votes)
return NULL;
}
+/* We just computed a new <b>consensus</b>. Update our state with the SRVs
+ * from the consensus (might be NULL as well). Register the SRVs in our SR
+ * state and prepare for the upcoming protocol round. */
+void
+sr_act_post_consensus(const networkstatus_t *consensus)
+{
+ time_t interval_starts;
+ const or_options_t *options = get_options();
+
+ /* Don't act if our state hasn't been initialized. We can be called during
+ * boot time when loading consensus from disk which is prior to the
+ * initialization of the SR subsystem. We also should not be doing
+ * anything if we are _not_ a directory authority and if we are a bridge
+ * authority. */
+ if (!sr_state_is_initialized() || !authdir_mode_v3(options) ||
+ authdir_mode_bridge(options)) {
+ return;
+ }
+
+ /* Set the majority voted SRVs in our state even if both are NULL. It
+ * doesn't matter this is what the majority has decided. Obviously, we can
+ * only do that if we have a consensus. */
+ if (consensus) {
+ /* Start by freeing the current SRVs since the SRVs we believed during
+ * voting do not really matter. Now that all the votes are in, we use the
+ * majority's opinion on which are the active SRVs. */
+ sr_state_clean_srvs();
+ /* Reset the fresh flag of the SRV so we know that from now on we don't
+ * have a new SRV to vote for. We just used the one from the consensus
+ * decided by the majority. */
+ sr_state_unset_fresh_srv();
+ /* Set the SR values from the given consensus. */
+ sr_state_set_previous_srv(srv_dup(consensus->sr_info.previous_srv));
+ sr_state_set_current_srv(srv_dup(consensus->sr_info.current_srv));
+ }
+
+ /* Update our internal state with the next voting interval starting time. */
+ interval_starts = get_voting_schedule(options, time(NULL),
+ LOG_NOTICE)->interval_starts;
+ sr_state_update(interval_starts);
+}
+
/* Initialize shared random subsystem. This MUST be called early in the boot
* process of tor. Return 0 on success else -1 on error. */
int