Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add endpoint for encrypting votes and generating corresponding proof. #14

Merged
merged 5 commits into from
Feb 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eth-contracts
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>3.2.0</version>
<version>3.2.0</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

import org.provotum.backend.communication.rest.message.deployment.BallotDeploymentRequest;
import org.provotum.backend.communication.rest.message.vote.VoteRequest;
import org.provotum.backend.config.EthereumConfiguration;
import org.provotum.backend.ethereum.accessor.BallotContractAccessor;
import org.provotum.backend.ethereum.config.BallotContractConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.web3j.crypto.Credentials;

import java.util.logging.Logger;

Expand All @@ -19,12 +17,10 @@ public class BallotController {
private static final String CONTEXT = "/ballot";

private BallotContractAccessor ballotContractAccessor;
private EthereumConfiguration ethereumConfiguration;

@Autowired
public BallotController(BallotContractAccessor ballotContractAccessor, EthereumConfiguration ethereumConfiguration) {
public BallotController(BallotContractAccessor ballotContractAccessor) {
this.ballotContractAccessor = ballotContractAccessor;
this.ethereumConfiguration = ethereumConfiguration;
}

@RequestMapping(value = CONTEXT + "/deploy", method = RequestMethod.POST)
Expand All @@ -42,21 +38,9 @@ public void deployBallot(@RequestBody BallotDeploymentRequest request) {
}

@RequestMapping(value = CONTEXT + "/{contractAddress}/vote", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.ACCEPTED)
@ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
public void vote(@PathVariable String contractAddress, @RequestBody VoteRequest voteRequest) {
logger.info("Received vote request");

// Credentials credentials = Credentials.create(privateKey, publicKey);
Credentials credentials = this.ethereumConfiguration.getVoterCredentials();

// TODO: remove this log statement!
logger.info("PublicKey: " + credentials.getEcKeyPair().getPublicKey() + ", PrivateKey: " + credentials.getEcKeyPair().getPrivateKey());

this.ballotContractAccessor.vote(
contractAddress,
voteRequest.getVote(),
credentials
);
logger.severe("Vote endpoint is deprecated. Not submitting vote to Ethereum.");
}

@RequestMapping(value = CONTEXT + "/{contractAddress}/open-vote", method = RequestMethod.POST)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.provotum.backend.communication.rest.controller;

import org.provotum.backend.communication.rest.message.vote.EncryptionRequest;
import org.provotum.backend.communication.rest.message.vote.EncryptionResponse;
import org.provotum.backend.security.CipherTextWrapper;
import org.provotum.backend.security.EncryptionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.logging.Logger;

@RestController
public class EncryptionController {

private static final Logger logger = Logger.getLogger(EncryptionController.class.getName());
private static final String CONTEXT = "/encryption";

private EncryptionManager encryptionManager;

@Autowired
public EncryptionController(EncryptionManager encryptionManager) {
this.encryptionManager = encryptionManager;
}

@RequestMapping(value = CONTEXT + "/generate", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public EncryptionResponse encryptAndGenerateProof(@RequestBody EncryptionRequest encryptionRequest) {
logger.info("Received request to encrypt and generate vote.");

CipherTextWrapper cipherTextWrapper = this.encryptionManager.encryptVoteAndGenerateProof(encryptionRequest.getVote());

return new EncryptionResponse(cipherTextWrapper.getCiphertext(), cipherTextWrapper.getProof());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.provotum.backend.communication.rest.message.vote;

import com.fasterxml.jackson.annotation.JsonProperty;

public class EncryptionRequest {

private int vote;

public EncryptionRequest(@JsonProperty("vote") int vote) {
this.vote = vote;
}

public int getVote() {
return vote;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.provotum.backend.communication.rest.message.vote;

import com.fasterxml.jackson.annotation.JsonProperty;

public class EncryptionResponse {

private String ciphertext;
private String proof;

public EncryptionResponse(@JsonProperty("ciphertext") String ciphertext, @JsonProperty("proof") String proof) {
this.ciphertext = ciphertext;
this.proof = proof;
}

public String getCiphertext() {
return ciphertext;
}

public String getProof() {
return proof;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

import java.math.BigInteger;

@Deprecated
public class VoteRequest extends ARequest {

private Credentials credentials;
private BigInteger vote;
private int vote;

public VoteRequest(@JsonProperty("credentials") Credentials credentials, @JsonProperty("vote") BigInteger vote) {
public VoteRequest(@JsonProperty("credentials") Credentials credentials, @JsonProperty("vote") int vote) {
this.credentials = credentials;
this.vote = vote;
}
Expand All @@ -20,7 +21,7 @@ public Credentials getCredentials() {
return credentials;
}

public BigInteger getVote() {
public int getVote() {
return vote;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.provotum.backend.config;

import org.bouncycastle.crypto.generators.ElGamalParametersGenerator;
import org.bouncycastle.crypto.params.ElGamalParameters;
import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
import org.bouncycastle.jce.spec.ElGamalParameterSpec;
import org.provotum.backend.ethereum.accessor.ZeroKnowledgeContractAccessor;
import org.provotum.security.elgamal.PrivateKey;
import org.provotum.security.elgamal.PublicKey;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGeneratorSpi;
import java.security.SecureRandom;
import java.util.logging.Logger;

@Configuration
@PropertySource("classpath:provotum-backend.properties")
public class SecurityConfiguration {

private static final Logger logger = Logger.getLogger(SecurityConfiguration.class.getName());

private PublicKey publicKey;
private PrivateKey privateKey;

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

public SecurityConfiguration() throws InvalidAlgorithmParameterException {
logger.info("Generating voting election keypair.");
ElGamalParametersGenerator generator = new ElGamalParametersGenerator();
generator.init(160, 20, new SecureRandom());
ElGamalParameters parameters = generator.generateParameters();

ElGamalParameterSpec elGamalParameterSpec = new ElGamalParameterSpec(parameters.getP(), parameters.getG());

KeyPairGeneratorSpi keyPairGeneratorSpi = new org.bouncycastle.jcajce.provider.asymmetric.elgamal.KeyPairGeneratorSpi();
keyPairGeneratorSpi.initialize(elGamalParameterSpec, new SecureRandom());

KeyPair keyPair = keyPairGeneratorSpi.generateKeyPair();

logger.info("Generated voting election keypair.");

ElGamalPublicKey pubKey = (ElGamalPublicKey) keyPair.getPublic();
ElGamalPrivateKey privKey = (ElGamalPrivateKey) keyPair.getPrivate();

this.publicKey = new PublicKey(pubKey);
this.privateKey = new PrivateKey(privKey);
}

public PublicKey getPublicKey() {
return this.publicKey;
}

public PrivateKey getPrivateKey() {
return this.privateKey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
import org.provotum.backend.ethereum.base.TransactionReceiptStatus;
import org.provotum.backend.ethereum.config.BallotContractConfig;
import org.provotum.backend.ethereum.wrappers.Ballot;
import org.provotum.backend.security.CipherTextWrapper;
import org.provotum.backend.security.EncryptionManager;
import org.provotum.security.arithmetic.ModInteger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.tuples.generated.Tuple2;
import org.web3j.tuples.generated.Tuple3;
import rx.Observer;
import rx.Scheduler;
import rx.schedulers.Schedulers;
Expand All @@ -42,15 +45,17 @@ public class BallotContractAccessor extends AContractAccessor<Ballot, BallotCont
private Web3j web3j;
private TopicPublisher topicPublisher;
private EthereumConfiguration ethereumConfiguration;
private EncryptionManager encryptionManager;

private Scheduler subscriptionScheduler;
private ExecutorService executorService;

@Autowired
public BallotContractAccessor(Web3j web3j, EthereumConfiguration ethereumConfiguration, TopicPublisher topicPublisher) {
public BallotContractAccessor(Web3j web3j, EthereumConfiguration ethereumConfiguration, TopicPublisher topicPublisher, EncryptionManager encryptionManager) {
this.web3j = web3j;
this.ethereumConfiguration = ethereumConfiguration;
this.topicPublisher = topicPublisher;
this.encryptionManager = encryptionManager;

ExecutorService executor = Executors.newCachedThreadPool();
this.subscriptionScheduler = Schedulers.from(executor);
Expand Down Expand Up @@ -252,44 +257,61 @@ public void closeVoting(String contractAddress) {
* @param contractAddress The address of the ballot.
* @param vote The vote.
*/
public void vote(String contractAddress, BigInteger vote, Credentials credentials) {
public void vote(String contractAddress, int vote, Credentials credentials) {
logger.info("Starting submitting vote in new thread.");

// starting execution in a new thread to avoid blocking.
this.executorService.submit(() -> {
logger.info("Submitting vote in new thread started.");
VoteResponse response;

try {
// TODO: we might need to subscribe again to vote events in the case when the ballot contract is not deployed but only referenced.
TransactionReceipt receipt = Ballot.load(
contractAddress,
this.web3j,
credentials,
Ballot.GAS_PRICE,
Ballot.GAS_LIMIT
).vote(vote).send();

// this field is only available from the Byzantium blocks on
if (null != receipt.getStatus() && ! TransactionReceiptStatus.SUCCESS.getValue().equals(receipt.getStatus())) {
logger.info("Failed to submit vote due to failed transaction. Transaction hash is " + receipt.getTransactionHash() + ". Logs are " + receipt.getLogsBloom());
response = new VoteResponse(Status.ERROR, "Failed to submit vote due to failed transaction.", receipt.getTransactionHash());
} else {
logger.info("Submitted vote. Transaction hash is " + receipt.getTransactionHash());
response = new VoteResponse(Status.SUCCESS, "Successfully submitted vote.", receipt.getTransactionHash());
logger.info("Submitting vote in new thread started.");
VoteResponse response;

logger.info("Starting to encrypt vote and generating the corresponding proof.");
CipherTextWrapper cipherTextWrapper = this.encryptionManager.encryptVoteAndGenerateProof(vote);
logger.info("Ciphertext and proof generated.");

logger.info("Submitting ciphertext to Ethereum.");
try {
// TODO: we might need to subscribe again to vote events in the case when the ballot contract is not deployed but only referenced.
TransactionReceipt receipt = Ballot.load(
contractAddress,
this.web3j,
credentials,
Ballot.GAS_PRICE,
Ballot.GAS_LIMIT
).vote(cipherTextWrapper.getCiphertext(), cipherTextWrapper.getProof()).send();

// this field is only available from the Byzantium blocks on
if (null != receipt.getStatus() && ! TransactionReceiptStatus.SUCCESS.getValue().equals(receipt.getStatus())) {
logger.info("Failed to submit vote due to failed transaction. Transaction hash is " + receipt.getTransactionHash() + ". Logs are " + receipt.getLogsBloom());
response = new VoteResponse(Status.ERROR, "Failed to submit vote due to failed transaction.", receipt.getTransactionHash());
} else {
logger.info("Submitted vote. Transaction hash is " + receipt.getTransactionHash());
response = new VoteResponse(Status.SUCCESS, "Successfully submitted vote.", receipt.getTransactionHash());
}
} catch (Exception e) {
logger.severe("Failed to submit vote on ballot contract at " + contractAddress);
e.printStackTrace();

response = new VoteResponse(Status.ERROR, "Submitting vote failed: " + e.getMessage(), null);
}

logger.info("Sending vote response to subscribers at topic " + TopicPublisher.EVENT_TOPIC);
this.topicPublisher.send(
TopicPublisher.VOTE_TOPIC,
response
);
} catch (Exception e) {
logger.severe("Failed to submit vote on ballot contract at " + contractAddress);
e.printStackTrace();

response = new VoteResponse(Status.ERROR, "Submitting vote failed: " + e.getMessage(), null);
VoteResponse response = new VoteResponse(Status.ERROR, "Submitting vote failed: " + e.getMessage(), null);
logger.info("Sending vote response to subscribers at topic " + TopicPublisher.EVENT_TOPIC);
this.topicPublisher.send(
TopicPublisher.VOTE_TOPIC,
response
);
}

logger.info("Sending vote response to subscribers at topic " + TopicPublisher.EVENT_TOPIC);
this.topicPublisher.send(
TopicPublisher.VOTE_TOPIC,
response
);
});
}

Expand Down Expand Up @@ -322,10 +344,17 @@ public void getResults(String contractAddress) {
Map<String, BigInteger> votes = new HashMap<>();
for (BigInteger i = BigInteger.ZERO; i.compareTo(totalVotes) < 0; i = i.add(BigInteger.ONE)) {
logger.info("Fetching vote at index " + i);
Tuple2<String, BigInteger> tuple = ballot.getVote(i).send();
Tuple3<String, String, String> tuple = ballot.getVote(i).send();
logger.info("Vote at index " + i + " fetched");

votes.put(tuple.getValue1(), tuple.getValue2());
CipherTextWrapper wrapper = new CipherTextWrapper(
tuple.getValue2(),
tuple.getValue3()
);

// TODO: vote should not be decrypted here!
ModInteger vote = this.encryptionManager.decrypt(wrapper);
votes.put(tuple.getValue1(), vote.asBigInteger());
}

response = new GetResultResponse(Status.SUCCESS, "Successfully fetched votes.", votes);
Expand Down
Loading