Skip to content

Instantly share code, notes, and snippets.

@markjenkins
Last active February 23, 2022 00:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markjenkins/42f92959f33ce6ef1296afb7781ea128 to your computer and use it in GitHub Desktop.
Save markjenkins/42f92959f33ce6ef1296afb7781ea128 to your computer and use it in GitHub Desktop.
Skullspace Board Election 2022
Elected by acclamation
Edwin Amsler
Pietra Shirley
Thor Robinson
Michael Kozakewich

The easiest way to vote is to just send your voting code ("masterkey:" prefiex) and selection of 5 candidates by email to mark@parit.ca

There's two tiers of higher privacy options.

first level of privacy

  1. Download hmac_vote_2022-02-22.py and 2022-02-22_boardcandidates.txt . Be sure 2022-02-22_boardcandidates.txt has the candidates you want and the proper spelling.

  2. Run hmac_vote_2022-02-22.py with python3 $ python3 hmac_vote_2022-02-22.py

  3. Choose the relevant election (board or mock)

  4. Hit enter at the sub-key prompt to accept the default

  5. Pay close attention to the candidate list presented, as it is in a random order on each run. List your preferred candidates by number, separating them with commas.

  6. The program outputs a "ballot:" line for each candidate you selected. Get those lines to Mark Jenkins in one of the following manners:

  • email mark@parit.ca or mark@markjenkins.ca
  • use the contact form https://markjenkins.ca/contact/
  • PM "@Mark Jenkins" on the Skullspace slack or post to #general with a "@Mark Jenkins" mention
  • PM markjenkinssksp on #skullspace on Libera or drop in the #skullspace channel with a mention of markjenkinssksp You can opt to have someone else deliver these ballot codes in the above manners as well

second level of privacy

TODO on this section, will fill in if an election is taking place

#!/usr/bin/env python3
from __future__ import print_function
from __future__ import division
from random import SystemRandom
from base64 import b64encode
from sys import argv, version_info
if version_info[0] >= 3:
def eight_bit_int_to_byte(n):
return int.to_bytes(n, 1, 'big')
else:
eight_bit_int_to_byte = chr
KEY_SIZE = 64 # 64 bits
assert( KEY_SIZE % 8 == 0 ) # KEY_SIZE must be a multiple of 8 bits
sysrandom = SystemRandom()
def generate_voting_code():
random_64_bits_as_bytes = b''.join(
eight_bit_int_to_byte(sysrandom.getrandbits(8))
for i in range(KEY_SIZE//8) )
return "masterkey:" + b64encode(random_64_bits_as_bytes).decode('ascii')
n_codes = 1 if len(argv) < 2 else int(argv[1])
print( '\n'.join( generate_voting_code() for i in range(n_codes) ) )
#!/usr/bin/env python3
BOARD_ELECTION_VOTE_LIMIT = 5 # at most 5 positions on the board
MOCK_ELECTION_VOTE_LIMIT = 2
ELECTIONS = (
('2022-02-22 board', '2022-02-22_boardcandidates.txt',
[], BOARD_ELECTION_VOTE_LIMIT),
('2022-02-22 mock', '2022-02-22_mockcandidates.txt',
[], MOCK_ELECTION_VOTE_LIMIT),
)
SHORT_MASTER_KEY_LENGTH = 8 # 64 bits, 8 bytes
SUB_KEY_SIG_TRUNCATE = 8 # 64 bits, 8 bytes
BALLOT_SIZE_BYTES = 8
MASTERKEY_PREFIX = "masterkey:"
REKEY_PREFIX = "rekey:"
BALLOT_PREFIX = "ballot:"
from random import SystemRandom
import hmac
import hashlib
import base64
import binascii
import sys
from os.path import exists
if sys.version_info[0] < 3:
print("Python 3 required. (tested on 3.5.2)")
exit(1)
# load candidate lists into the empty lists ELECTIONS[0][2]
# and ELECTIONS[1][2] above
for election, candidate_list_filename, candidate_list, limit in ELECTIONS:
if not exists(candidate_list_filename):
print(
"%s was not found in current working directory" %
candidate_list_filename)
exit(1)
with open(candidate_list_filename) as f:
candidate_list.extend( line.strip() for line in f )
SystemRandom().shuffle(candidate_list)
def create_code_signed_ballot(sub_key, vote):
ballot_hmac = hmac.new(sub_key, vote.encode('UTF-8'),
digestmod=hashlib.sha256)
base_64_bytes = base64.b64encode(ballot_hmac.digest()[0:BALLOT_SIZE_BYTES])
return BALLOT_PREFIX + base_64_bytes.decode('ascii')
def valid_candidate(candidate_selection,
candidates, candidate_lookup_by_number):
if candidate_selection in candidates:
return candidate_selection
else:
try:
candidate_number = int(candidate_selection)
except ValueError:
return None
else: # only if conversion of candidate_selection to int works
if candidate_number in candidate_lookup_by_number:
return candidate_lookup_by_number[candidate_number]
return None
def get_valid_votes(candidates, max_votes):
candidates_enumerated = list(enumerate(candidates, 1))
candidate_lookup_by_number = dict( candidates_enumerated )
print("Vote for up to %d candidates by matching name or number, "
"seperated by comma (',')" % max_votes)
print("write-in ballots are not supported by this interface")
print()
while True:
print() # blank line
for i, candidate in candidates_enumerated:
print("%d) %s" % (i, candidate))
candidate_selection_w_newline = input("Who are you voting for? > ")
candidate_selection = candidate_selection_w_newline.strip()
# split up the candidate selection by comma
selected_candidates = candidate_selection.split(",")
# ask again if too many were voted for or none were
if len(selected_candidates) > max_votes or len(selected_candidates) <1:
continue
# remove any whitespace
selected_candidates = [ c.strip() for c in selected_candidates ]
# validate each candidate selection, replacing with None if
# invalid, and replacing any numbers with names
selected_candidates = [
valid_candidate(c, candidates, candidate_lookup_by_number)
for c in selected_candidates]
# verify all selected candidates came out okay, no None
if all( c !=None for c in selected_candidates ):
return selected_candidates
def get_valid_master_code():
while True:
code_w_newline = input("What's your original masterkey? > ")
mastercode_ascii = code_w_newline.strip()
if mastercode_ascii.startswith(MASTERKEY_PREFIX):
# remove masterkey: prefix
mastercode_ascii = mastercode_ascii[len(MASTERKEY_PREFIX):]
prefix_seen = True
else:
prefix_seen = False
try:
decoded_code = base64.b64decode(mastercode_ascii, validate=True)
except binascii.Error:
print("invalid code")
else:
if len(decoded_code) == SHORT_MASTER_KEY_LENGTH:
break
else:
print("invalid code")
print("Your code is:")
print(mastercode_ascii)
print()
return decoded_code
def get_election_choice():
while True:
print("Which election are you voting in?")
# start enumeration at 1 so they are numbered 1), 2)...
for i, (election_name, a, b, c) in enumerate(ELECTIONS, 1):
print("%d) %s" % (i, election_name) )
try:
election_choice = int(input("> "))
except ValueError:
print()
else:
if 1<= election_choice <= len(ELECTIONS):
# election_choice-1 because the UI enumerates from 1)
choice_of_election = ELECTIONS[election_choice-1]
return (
choice_of_election[0], # election name
choice_of_election[2], # candidate list
choice_of_election[3], # vote limit
) # end tuple
def get_valid_sub_key_derivation_string(election_name):
print("If you provided the election officer a sub-key derivation string "
"add it here. Otherwise, leave this blank (hit enter) or "
"type default")
print()
rekey_prefix = REKEY_PREFIX + election_name + ' '
print("""accepted input formats for non-default are:
%sYOUR_DERIVATION_STRING
or
YOUR_DERIVATION_STRING
""" % rekey_prefix)
while True:
sub_key_derivation_string = input(
"sub-key derivation string > ").strip()
if ( sub_key_derivation_string.startswith(rekey_prefix) and
len(sub_key_derivation_string) > len(rekey_prefix) ):
return sub_key_derivation_string[ len(rekey_prefix): ]
elif sub_key_derivation_string in ("", "default", election_name):
return election_name
else:
return sub_key_derivation_string
def main():
election_name, candidates, vote_limit = get_election_choice()
master_key_64 = get_valid_master_code()
master_key_256_sha = hashlib.sha256(master_key_64)
master_key_256 = master_key_256_sha.digest()
subkey_derivation_string = get_valid_sub_key_derivation_string(
election_name)
print()
if subkey_derivation_string == election_name:
print("you picked the default sub-key derivation string")
else:
print("your custom sub-key derivation string was")
print(subkey_derivation_string)
rekey_msg = (
REKEY_PREFIX + election_name + ' ' + subkey_derivation_string)
sub_key_hmac = hmac.new(
master_key_256, rekey_msg.encode('ascii'), hashlib.sha256)
sig_64 = sub_key_hmac.digest()[0:SUB_KEY_SIG_TRUNCATE]
print("your signature when you provided this to the election officer "
"was: ")
print("rekeysig:" + base64.b64encode(sig_64).decode('ascii'))
print()
sub_key_sha256 = master_key_256_sha.copy()
sub_key_sha256.update(subkey_derivation_string.encode('ascii'))
sub_key = sub_key_sha256.digest()
votes = get_valid_votes(candidates, vote_limit)
print("your ballots are:")
for vote in votes:
code_signed_ballot = create_code_signed_ballot(sub_key, vote)
print(code_signed_ballot)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
ELECTIONS = ('2022-02-22 board', '2022-02-22 mock')
MASTERKEY_PREFIX = "masterkey:"
SHORT_MASTER_KEY_LENGTH = 8 # 64 bits, 8 bytes
SUB_KEY_DERIVE_LENGTH = 256//8 # 256 bits, 32 bytes
SUB_KEY_SIG_TRUNCATE = 8 # 64 bits, 8 bytes
import sys
import hmac
import binascii
from random import SystemRandom
from base64 import b64encode, b64decode
from hashlib import sha256
if sys.version_info[0] < 3:
print("Python 3 is required to run this script. Tested on 3.5.2")
exit(1)
def eight_bit_int_to_byte(n):
return int.to_bytes(n, 1, 'big')
def rand_bytes(num_bytes):
return b''.join(
eight_bit_int_to_byte(sysrandom.getrandbits(8))
for i in range(num_bytes))
sysrandom = SystemRandom()
mastercode_ascii = input(
"enter your master voting code (%s prefix) > " % MASTERKEY_PREFIX)
mastercode_ascii = mastercode_ascii.strip()
if mastercode_ascii.startswith(MASTERKEY_PREFIX):
# remove masterkey: prefix
mastercode_ascii = mastercode_ascii[len(MASTERKEY_PREFIX):]
prefix_seen = True
else:
prefix_seen = False
try:
master_key_64 = b64decode(mastercode_ascii, validate=True)
except binascii.Error:
print("invalid master voting code")
exit(1)
if len(master_key_64) != SHORT_MASTER_KEY_LENGTH:
print("invalid master voting code length")
exit(1)
if not prefix_seen:
print("master voting code accepted without %s prefix" % MASTERKEY_PREFIX)
master_key_256_sha = sha256(master_key_64)
master_key_256 = master_key_256_sha.digest()
for election in ELECTIONS:
sub_key_derive_bytes = rand_bytes(SUB_KEY_DERIVE_LENGTH)
sub_key_derive_string = b64encode(sub_key_derive_bytes).decode('ascii')
# uncomment this to test that
# rekey:2021-02-23 mock 7OJyWgwvhS8=
# results in
# rekeysig:35+MYeBRF4E=
#sub_key_derive_string = '7OJyWgwvhS8='
rekey_msg = "rekey:%s %s" %(election, sub_key_derive_string)
print(rekey_msg)
sub_key_hmac = hmac.new(master_key_256, rekey_msg.encode('ascii'), sha256)
sig_64 = sub_key_hmac.digest()[0:SUB_KEY_SIG_TRUNCATE]
print("rekeysig:" + b64encode(sig_64).decode('ascii'))
print("""
Send the above rekey: and rekeysig: lines to """
"""Mark Jenkins <mark@parit.ca> prior to 2022-02-22 21:00.
""")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment