Skip to content

Commit

Permalink
Secret Sharing in RSA (#99)
Browse files Browse the repository at this point in the history
* 📦 Add cryptography package

- Demotes hypothesis and typish to dev package

* ✨Replace ElGamal with RSA for Auxiliary Key

* 🐛Missing TearDown in Decryption tests

* 📝Update Documentation for Auxiliary Encrypt/Decrypt override
  • Loading branch information
keithrfung authored Jul 14, 2020
1 parent 2d45b50 commit 5bd36dc
Show file tree
Hide file tree
Showing 10 changed files with 388 additions and 148 deletions.
5 changes: 3 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ verify_ssl = true
black = "*"
coverage = "*"
docutils = "*"
hypothesis = "==5.15.1"
mkdocs = "*"
mypy = "*"
pydeps = "*"
pydocstyle = "*"
pylint = "*"
pytest = "*"
typish = '*'

[packages]
hypothesis = "==5.15.1"
numpy = '==1.18.2'
jsons = '==1.1.2'
typish = '*'
cryptography = "*"

[requires]
python_version = "3.8"
Expand Down
210 changes: 141 additions & 69 deletions Pipfile.lock

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions docs/1_Key_Ceremony.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,29 @@ This is a detailed description of the entire Key Ceremony Process

1. The ceremony details are decided upon. These include a `number_of_guardians` and `quorum` of guardians required for decryption.
2. Each guardian creates a unique `id` and `sequence_order`.
3. Each guardian must generate their `auxiliary key pair`.
3. Each guardian must generate their `auxiliary key pair`.\*
4. Each guardian must give the other guardians their `auxiliary public key` directly or through a mediator.
5. Each guardian must check if all `auxiliary public keys` are received.
6. Each guardian must generate their `election key pair` _(ElGamal key pair)_. This will generate a corresponding Schnorr `proof` and `polynomial` used for generating `election partial key backups` for sharing.
7. Each guardian must give the other guardians their `election public key` directly or through a mediator.
8. Each guardian must check if all `election public keys` are received.
9. Each guardian must generate `election partial key backup` for each other guardian. The guardian will use their `polynomial` and the designated guardian's `sequence_order` to create the value. The backup will be encrypted with the designated guardian's `auxiliary public key`
9. Each guardian must generate `election partial key backup` for each other guardian. The guardian will use their `polynomial` and the designated guardian's `sequence_order` to create the value. The backup will be encrypted with the designated guardian's `auxiliary public key`\*
10. Each guardian must send each encrypted `election partial key backup` to the designated guardian directly or through a `mediator`.
11. Each guardian checks if all encrypted `election partial key backups` have been received by their recipient guardian directly or through a mediator.
12. Each recipient guardian decrypts each received encrypted `election partial key backup` with their own `auxiliary private key`
12. Each recipient guardian decrypts each received encrypted `election partial key backup` with their own `auxiliary private key`\*
13. Each recipient guardian verifies each `election partial key backup` and sends confirmation of verification
- If the proof verifies, continue
- If the proof fails
1. Sender guardian publishes the `election partial key backup` value sent to recipient as a `election partial key challenge` where the value is **unencrypted** to all the other guardians \*
1. Sender guardian publishes the `election partial key backup` value sent to recipient as a `election partial key challenge` where the value is **unencrypted** to all the other guardians \*\*
2. Alternate guardian (outside sender or original recipient) attempts to verify key
- If the proof verifies, continue
- If the proof fails again, the accused (sender guardian) should be evicted and process should be restarted with new guardian.
14. On receipt of all verifications of `election partial private keys` by all guardians, generate and publish `joint key` from election public keys

\* **Note:** _The confidentiality of this value is now gone, but since the two Guardians are in the dispute, at least one is misbehaving and could be revealing this data._

\* **Note:** _The auxiliary encrypt and decrypt functions can be overridden to allow different encryption mechanisms other than the default._

\*\* **Note:** _The confidentiality of this value is now gone, but since the two Guardians are in the dispute, at least one is misbehaving and could be revealing this data._

## Files

Expand Down
43 changes: 43 additions & 0 deletions src/electionguard/auxiliary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Callable, Optional, NamedTuple

from .types import GUARDIAN_ID

MESSAGE = str
PUBLIC_KEY = str
SECRET_KEY = str
ENCRYPTED_MESSAGE = str


class AuxiliaryKeyPair(NamedTuple):
"""A tuple of a secret key and public key."""

secret_key: SECRET_KEY
"""The secret or private key"""
public_key: PUBLIC_KEY


class AuxiliaryPublicKey(NamedTuple):
"""A tuple of auxiliary public key and owner information"""

owner_id: GUARDIAN_ID
"""
The unique identifier of the guardian
"""

sequence_order: int
"""
The sequence order of the auxiliary public key (usually the guardian's sequence order)
"""

key: PUBLIC_KEY
"""
A string representation of the Auxiliary public key.
It is up to the external `AuxiliaryEncrypt` function to know how to parse this value
"""


AuxiliaryEncrypt = Callable[[MESSAGE, PUBLIC_KEY], Optional[ENCRYPTED_MESSAGE]]
"""A callable type that represents the auxiliary encryption scheme."""

AuxiliaryDecrypt = Callable[[ENCRYPTED_MESSAGE, SECRET_KEY], Optional[MESSAGE]]
"""A callable type that represents the auxiliary decryption scheme."""
29 changes: 19 additions & 10 deletions src/electionguard/guardian.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
combine_election_public_keys,
CeremonyDetails,
CoefficientValidationSet,
default_auxiliary_decrypt,
default_auxiliary_encrypt,
ElectionJointKey,
ElectionKeyPair,
ElectionPartialKeyBackup,
Expand All @@ -35,12 +33,13 @@
generate_election_key_pair,
generate_election_partial_key_backup,
generate_election_partial_key_challenge,
generate_elgamal_auxiliary_key_pair,
generate_rsa_auxiliary_key_pair,
PublicKeySet,
verify_election_partial_key_backup,
verify_election_partial_key_challenge,
)
from .logs import log_warning
from .rsa import rsa_encrypt, rsa_decrypt
from .types import GUARDIAN_ID
from .utils import get_optional

Expand Down Expand Up @@ -179,7 +178,7 @@ def generate_auxiliary_key_pair(
self,
generate_auxiliary_key_pair: Callable[
[], AuxiliaryKeyPair
] = generate_elgamal_auxiliary_key_pair,
] = generate_rsa_auxiliary_key_pair,
) -> None:
"""
Generate auxiliary key pair
Expand Down Expand Up @@ -277,7 +276,7 @@ def guardian_election_public_keys(
return ReadOnlyDataStore(self._guardian_election_public_keys)

def generate_election_partial_key_backups(
self, encrypt: AuxiliaryEncrypt = default_auxiliary_encrypt
self, encrypt: AuxiliaryEncrypt = rsa_encrypt
) -> bool:
"""
Generate all election partial key backups based on existing public keys
Expand All @@ -292,6 +291,11 @@ def generate_election_partial_key_backups(
backup = generate_election_partial_key_backup(
self.object_id, self._election_keys.polynomial, auxiliary_key, encrypt
)
if backup is None:
log_warning(
f"guardian; {self.object_id} could not generate election partial key backups: failed to encrypt"
)
return False
self._backups_to_share.set(auxiliary_key.owner_id, backup)

return True
Expand Down Expand Up @@ -328,9 +332,7 @@ def all_election_partial_key_backups_received(self) -> bool:

# Verification
def verify_election_partial_key_backup(
self,
guardian_id: GUARDIAN_ID,
decrypt: AuxiliaryDecrypt = default_auxiliary_decrypt,
self, guardian_id: GUARDIAN_ID, decrypt: AuxiliaryDecrypt = rsa_decrypt,
) -> Optional[ElectionPartialKeyVerification]:
"""
Verify election partial key backup value is in polynomial
Expand Down Expand Up @@ -449,7 +451,7 @@ def compensate_decrypt(
elgamal: ElGamalCiphertext,
extended_base_hash: ElementModQ,
nonce_seed: ElementModQ = None,
decrypt: AuxiliaryDecrypt = default_auxiliary_decrypt,
decrypt: AuxiliaryDecrypt = rsa_decrypt,
) -> Optional[Tuple[ElementModP, ChaumPedersenProof]]:
"""
Compute a compensated partial decryption of an elgamal encryption
Expand All @@ -474,7 +476,14 @@ def compensate_decrypt(
)
return None

decrypted_value = decrypt(backup.encrypted_value, self._auxiliary_keys)
decrypted_value = decrypt(
backup.encrypted_value, self._auxiliary_keys.secret_key
)
if decrypted_value is None:
log_warning(
f"compensate decrypt guardian {self.object_id} failed decryption for {missing_guardian_id}"
)
return None
partial_secret_key = get_optional(int_to_q(int(decrypted_value)))

# 𝑀_{𝑖,l} = 𝐴^P𝑖_{l}
Expand Down
83 changes: 29 additions & 54 deletions src/electionguard/key_ceremony.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from dataclasses import dataclass
from typing import (
Callable,
List,
NamedTuple,
)
from typing import List, Optional, NamedTuple

from .auxiliary import (
AuxiliaryKeyPair,
AuxiliaryDecrypt,
AuxiliaryEncrypt,
AuxiliaryPublicKey,
)
from .data_store import DataStore
from .election_polynomial import (
compute_polynomial_coordinate,
Expand All @@ -18,9 +20,10 @@
elgamal_keypair_random,
)
from .group import int_to_q, rand_q, ElementModP, ElementModQ
from .rsa import rsa_keypair, rsa_decrypt, rsa_encrypt
from .schnorr import SchnorrProof, make_schnorr_proof
from .types import GUARDIAN_ID
from .serializable import Serializable
from .types import GUARDIAN_ID
from .utils import get_optional

ElectionJointKey = ElementModP
Expand All @@ -35,48 +38,6 @@ class CeremonyDetails(NamedTuple):
quorum: int


class AuxiliaryKeyPair(NamedTuple):
"""A tuple of a secret key and public key."""

secret_key: str
"""The secret or private key"""
public_key: str


class AuxiliaryPublicKey(NamedTuple):
"""A tuple of auxiliary public key and owner information"""

owner_id: GUARDIAN_ID
"""
The unique identifier of the guardian
"""

sequence_order: int
"""
The sequence order of the auxiliary public key (usually the guardian's sequence order)
"""

key: str
"""
A string representation of the Auxiliary public key.
It is up to the external `AuxiliaryEncrypt` function to know how to parse this value
"""


AuxiliaryEncrypt = Callable[[str, AuxiliaryPublicKey], str]
"""
A Genric callable type that represents an encryption/decryption scheme.
"""

# FIX_ME ISSUE #47: Default Auxiliary Encrypt is temporary placeholder
default_auxiliary_encrypt = lambda unencrypted, key: unencrypted

AuxiliaryDecrypt = Callable[[str, AuxiliaryKeyPair], str]

# FIX_ME ISSUE #47: Default Auxiliary Decrypt is temporary placeholder
default_auxiliary_decrypt = lambda encrypted, key_pair: encrypted


class ElectionKeyPair(NamedTuple):
"""A tuple of election key pair, proof and polynomial"""

Expand Down Expand Up @@ -187,6 +148,15 @@ def generate_elgamal_auxiliary_key_pair() -> AuxiliaryKeyPair:
)


def generate_rsa_auxiliary_key_pair() -> AuxiliaryKeyPair:
"""
Generate auxiliary key pair using RSA
:return: Auxiliary key pair
"""
rsa_key_pair = rsa_keypair()
return AuxiliaryKeyPair(rsa_key_pair.private_key, rsa_key_pair.public_key)


def generate_election_key_pair(
quorum: int, nonce: ElementModQ = None
) -> ElectionKeyPair:
Expand All @@ -207,8 +177,8 @@ def generate_election_partial_key_backup(
owner_id: GUARDIAN_ID,
polynomial: ElectionPolynomial,
auxiliary_public_key: AuxiliaryPublicKey,
encrypt: AuxiliaryEncrypt = default_auxiliary_encrypt,
) -> ElectionPartialKeyBackup:
encrypt: AuxiliaryEncrypt = rsa_encrypt,
) -> Optional[ElectionPartialKeyBackup]:
"""
Generate election partial key backup for sharing
:param owner_id: Owner of election key
Expand All @@ -220,7 +190,9 @@ def generate_election_partial_key_backup(
value = compute_polynomial_coordinate(
auxiliary_public_key.sequence_order, polynomial
)
encrypted_value = encrypt(str(value.to_int()), auxiliary_public_key)
encrypted_value = encrypt(str(value.to_int()), auxiliary_public_key.key)
if encrypted_value is None:
return None
return ElectionPartialKeyBackup(
owner_id,
auxiliary_public_key.owner_id,
Expand Down Expand Up @@ -253,7 +225,7 @@ def verify_election_partial_key_backup(
verifier_id: GUARDIAN_ID,
backup: ElectionPartialKeyBackup,
auxiliary_key_pair: AuxiliaryKeyPair,
decrypt: AuxiliaryDecrypt = default_auxiliary_decrypt,
decrypt: AuxiliaryDecrypt = rsa_decrypt,
) -> ElectionPartialKeyVerification:
"""
Verify election partial key backup contain point on owners polynomial
Expand All @@ -263,9 +235,12 @@ def verify_election_partial_key_backup(
:param decrypt: Decryption function using auxiliary key
"""

decrypted_value = decrypt(backup.encrypted_value, auxiliary_key_pair)
decrypted_value = decrypt(backup.encrypted_value, auxiliary_key_pair.secret_key)
if decrypted_value is None:
return ElectionPartialKeyVerification(
backup.owner_id, backup.designated_id, verifier_id, False
)
value = get_optional(int_to_q(int(decrypted_value)))

return ElectionPartialKeyVerification(
backup.owner_id,
backup.designated_id,
Expand Down
Loading

0 comments on commit 5bd36dc

Please sign in to comment.