Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7d908fd
initial part of ballot implementation
vivekpathak Oct 12, 2019
9c88c77
test case working
vivekpathak Oct 22, 2019
247e2ae
added spec for the ballot
vivekpathak Oct 23, 2019
b6746c2
added soft copy of ballot spec drawing
vivekpathak Oct 23, 2019
aadf4a9
include ballot sketch in readme.md
vivekpathak Oct 24, 2019
d918d31
update desc of protocol
vivekpathak Oct 24, 2019
f82b531
improve link
vivekpathak Oct 24, 2019
677fd0f
implementation of vote cryptographic content
vivekpathak Oct 24, 2019
647e196
remove warning
vivekpathak Oct 24, 2019
b71af37
prepare for full packet creation
vivekpathak Oct 24, 2019
88ac0ed
kept todo for later
vivekpathak Oct 24, 2019
e4c822a
add the cryptographic zero knowledge proof parameters
vivekpathak Nov 17, 2019
f3dcc86
compilation at zkp param computation
vivekpathak Nov 17, 2019
7b678d9
fix bug in d1 d2 calculation
vivekpathak Nov 17, 2019
102ea4e
fixed d1 d2 flow
vivekpathak Nov 17, 2019
1615bd3
validation of ballot
vivekpathak Nov 19, 2019
617b145
broken marked for fix
vivekpathak Nov 22, 2019
3c7a0f9
more to be fixed
vivekpathak Nov 22, 2019
6232d79
comment improvement and todos noted
vivekpathak Dec 1, 2019
700bc06
add todo
vivekpathak Oct 24, 2020
d6f77e5
moved to within class to match meaning of self generated vs sent params
vivekpathak Oct 24, 2020
b64cc45
trying to work through the test fail
vivekpathak Oct 24, 2020
6d2c035
note for future
vivekpathak Nov 1, 2020
edf0ea3
intellij using .gradle internally perhaps
vivekpathak Nov 1, 2020
74a2f7c
fewer bits in development
vivekpathak Nov 1, 2020
f8cede4
fix algebra bug and enforce bits end to end
vivekpathak Nov 1, 2020
1878cd8
more bug fixes
vivekpathak Nov 1, 2020
168a891
kept marker for debugging
vivekpathak Nov 1, 2020
c174c30
intermediate commit to save state of debug code that clears both the …
usaygij Nov 7, 2020
39790d2
intermediate commit#2 to save state of debug code that clears both th…
usaygij Nov 7, 2020
b8ae37b
intermediate commit#3 to save state of debug code that clears both th…
usaygij Nov 13, 2020
4bee88b
intermediate commit#4 removed most of the restrictions and made it cl…
usaygij Nov 13, 2020
10f035a
intermediate commit#5 removed all unnecessary restrictions and made i…
usaygij Nov 14, 2020
2d8265a
ignore temp files
vivekpathak Feb 20, 2021
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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,12 @@ project/plugins/project/

# adapted from https://www.gitignore.io/api/playframework,sbt,eclipse,intellij,scala,osx


.gradle/


# temp files

*.bak
*.backup
*.swp
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@

Pollster service
# Pollster service

Restful api sopporting privacy preserving verifiable
elections

# build
# How to Build and run

Build with the following:

sbt package

Run assembled jar file using java -jar

# Polls and Ballots
The polling mechanism uses el-gamal encryption along with non interactive
zero knowledge proofs in ballots to establish the following properties:

1. Ballots have encrypted ballots and have voter identity in the clear Everyone can see
and verify these ballots. The ballots in their clear form would indicate a yay or a nay vote
but the vote can not be deciphered from the encrypted ballot.

2. At end of the poll (no more votes can be cast) the service produces a total transcript of the
poll at which time, the following can be verified by anyone
a. The encrypted total is correct using modular arithmetic
b. The decryped value is the total number of positive votes

More details in the white paper (TBP). The mechanism is quite similar to that in the following
: https://www.win.tue.nl/%7Eberry/papers/euro97.pdf . Zero knowledge additions for 2b are similar
to the question asked here: https://crypto.stackexchange.com/questions/9997/perfect-zero-knowledge-for-the-schnorr-protocol

### Ballot sketch
![](./docs/ballot-spec.png "Ballot")
Binary file added docs/ballot-spec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
279 changes: 279 additions & 0 deletions src/main/scala/com/newlibertie/pollster/impl/Ballot.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
package com.newlibertie.pollster.impl

import java.math.BigInteger

import scala.collection.mutable.ListBuffer

/**
* Implementation of ballot
*
* A ballot object is empty but permanently associated with a specific poll and
* a specific voter upon construction
*
* The two ways to make it a valid ballot are as follows:
* 1. Using the cast(vote:Boolean) method to populate its internal fields, or
* 2. Instantiate it from pre-computed fields. In this scenario, the implementation
* will verify the ballot as can anyway be done using the verify() method.
*
* @param cp cryptographic parameters of the poll this ballot is about
* @param voter String identifier identifying the voter
*/
class Ballot(cp: CryptographicParameters, voter: String) {

final val isdbg = false
var x, y: BigInteger = _

var a1, b1, a2, b2: BigInteger = _

var d1, d2, r1, r2: BigInteger = _
var c_val: BigInteger = _

/**
* Cast a vote - populates all parameters to become a verifiable ballot
*
* @param vote boolean vote, true = yay, false = nay
*/
// TODO: Can be made to have only-once semantics (in that case there may
// TODO: also save the metadata - where this ballot was casted, what UTC ts,
// TODO: etc - that can serve other purposes when aggregated at scale)

def cast(vote: Boolean): Unit = {
// Voter votes m^0 or m^1
// Ballot is a tuple (x,y,a1,b1,a2,b2)
// x g^alpha,
// y h^alpha G OR h^alpha / G
// a1 g^r1 x^{d1} OR g ^ omega
// b1 h^r1 (yG)^{d1} OR h ^ omega
// a2 g ^ omega OR g ^ {r2} x ^ {d2}
// b2 h ^ omega OR h ^ {r2} (y/G)^{d2}

val alpha = if (isdbg)
new BigInteger("11774")
else
CryptographicParameters.random(CryptographicParameters.BITS).mod(cp.large_prime_p)
// this.c_val = if (isdbg)
// new BigInteger("257600")
// else
// CryptographicParameters.random(CryptographicParameters.BITS).mod(cp.large_prime_p)

// TODO : insecure, delete please
println(s"voter\t${this.voter}")
println(s"alpha\t$alpha")

this.x = cp.generator_g.modPow(alpha, cp.large_prime_p)
if (vote) { // is positive vote
this.y = cp.public_key_h.modPow(alpha, cp.large_prime_p).multiply(
cp.zkp_generator_G).mod(cp.large_prime_p)
this.r1 = if (isdbg)
new BigInteger("123456")
else
CryptographicParameters.random(CryptographicParameters.BITS).mod(cp.large_prime_p)
this.c_val = getC(this.r1)
val omega = if (isdbg)
new BigInteger("2281424433")
else
this.c_val.multiply(alpha) //.mod(cp.large_prime_p)
println(s"omega\t$omega")
this.a2 = cp.generator_g.modPow(omega, cp.large_prime_p)
this.b2 = cp.public_key_h.modPow(omega, cp.large_prime_p)
this.d1 = if (isdbg)
new BigInteger("63832")
else
CryptographicParameters.random(CryptographicParameters.BITS).mod(this.c_val) // TODO : adjust and check if "we will use SHA-512 for zkp" can work with c = d1 + d2
this.a1 = cp.generator_g.modPow(r1, cp.large_prime_p)
.multiply(x.modPow(d1, cp.large_prime_p))
.mod(cp.large_prime_p)
this.b1 = cp.public_key_h.modPow(r1, cp.large_prime_p)
.multiply(y.multiply(cp.zkp_generator_G).modPow(d1, cp.large_prime_p))
.mod(cp.large_prime_p)
this.d2 = this.c_val.subtract(this.d1)
this.r2 = omega.subtract(alpha.multiply(this.d2).mod(omega))
}
else { // vote is negative
this.y = cp.public_key_h.modPow(alpha, cp.large_prime_p).multiply(
cp.zkp_generator_G.modInverse(cp.large_prime_p)
).mod(cp.large_prime_p)
this.r2 = if (isdbg)
new BigInteger("123456")
else
CryptographicParameters.random(CryptographicParameters.BITS).mod(cp.large_prime_p)
this.c_val = getC(r2)
val omega = if (isdbg)
new BigInteger("2281424433")
else
this.c_val.multiply(alpha) //.mod(cp.large_prime_p)
println(s"omega\t$omega")
this.a1 = cp.generator_g.modPow(omega, cp.large_prime_p)
this.b1 = cp.public_key_h.modPow(omega, cp.large_prime_p)
this.d2 = if (isdbg)
new BigInteger("193768")
else
CryptographicParameters.random(CryptographicParameters.BITS).mod(this.c_val) // TODO : adjust and check if "we will use SHA-512 for zkp" can work with c = d1 + d2
this.a2 = cp.generator_g.modPow(r2, cp.large_prime_p)
.multiply(x.modPow(d2, cp.large_prime_p))
.mod(cp.large_prime_p)

val yByG = cp.zkp_generator_G.modInverse(cp.large_prime_p).multiply(y).mod(cp.large_prime_p)
val hr2 = cp.public_key_h.modPow(r2, cp.large_prime_p)
val yByGpowd2 = yByG.modPow(d2, cp.large_prime_p)
val hr2yByGpowd2 = hr2.multiply(yByGpowd2).mod(cp.large_prime_p)
this.b2 = hr2yByGpowd2
this.d1 = this.c_val.subtract(this.d2)
this.r1 = omega.subtract(alpha.multiply(this.d1).mod(omega))
}
println(s"large_prime_p\t${this.cp.large_prime_p}")
println(s"generator_g\t${this.cp.generator_g}")
println(s"private_key_s\t${this.cp.private_key_s}")
println(s"public_key_h\t${this.cp.public_key_h}")
println(s"zkp_generator_G\t${this.cp.zkp_generator_G}")
println(s"a1\t${this.a1}")
println(s"a2\t${this.a2}")
println(s"b1\t${this.b1}")
println(s"b2\t${this.b2}")
println(s"c_val\t${this.c_val}")
println(s"d1\t${this.d1}")
println(s"d2\t${this.d2}")
println(s"r1\t${this.r1}")
println(s"r2\t${this.r2}")
println(s"x\t${this.x}")
println(s"y\t${this.y}")
}

private def getC(r: BigInteger) = {
// take hash of <voter string, poll details>. the hash of this content is used to calculate the
// parameters d1 and d2 - which are used to produce the zero knowledge proof that the encrypted
// ballot is a valid ballot for the given poll
//
// this.voter is the name of the voter - eg: https://www.facebook.com/vpathak000
// the remaining parameters can be described as follows:
// h poll public key - which is also the identifier for the polls
// x some random numbers, refer to the white paper
//
val s = // the "formula" for the content hash is same as in the while paper : H(v_i||T)
s"""${this.voter}
|h=${this.cp.public_key_h}
|h=${this.cp.zkp_generator_G}
|x=${this.x}
|y=${this.y}
|a2=$r
|""".stripMargin
val shaBin = java.security.MessageDigest.getInstance("SHA-512").digest(s.getBytes("utf-8"))
println(s"string to hash $s -> ${new BigInteger(1, shaBin).toString}")
new BigInteger(1, shaBin).mod(cp.large_prime_p)
}

/**
* ZKP Verify integrity of the ballot
*/
def verify(_outBuffer: ListBuffer[String] = null): Boolean = {
// TODO : fix design-ish issue. why we would allowed
val outBuffer = if (_outBuffer == null)
new ListBuffer[String]()
else
_outBuffer
outBuffer +=
s"""C=${this.c_val}
|d1=${this.d1}
|d2=${this.d2}
|C = d1 + d2 ?\n
|""".stripMargin

try {
val c = this.c_val
val shouldBec = this.d1.add(this.d2)
if (!c.equals(shouldBec)) {
println(s"shouldBec\t$shouldBec")
return false
}

outBuffer +=
s"""a1=${this.a1}
|g=${cp.generator_g}
|r1=${this.r1}
|x=${this.x}
|d1=${this.d1}
|a1 = g^r1 . x ^ d1 ?\n
|""".stripMargin
val gpowr1 = cp.generator_g.modPow(r1, cp.large_prime_p)
val xpowd1 = x.modPow(d1, cp.large_prime_p)
val prod = gpowr1.multiply(xpowd1).mod(cp.large_prime_p)
println(s"gpowr1\t$gpowr1")
println(s"xpowd1\t$xpowd1")
println(s"prod\t$prod")
if (!prod.equals(a1)) {
println(s"a1 != prod")
return false
}
// if (!cp.generator_g.modPow(r1, cp.large_prime_p).multiply(x.modPow(d1, cp.large_prime_p)).mod(cp.large_prime_p).equals(a1)) {
// println(s"a1 != $a1")
// return false
// }

outBuffer +=
s"""b1=${this.b1}
|h=${cp.public_key_h}
|r1=${this.r1}
|y=${this.y}
|G=${cp.zkp_generator_G}
|d1=${this.d1}
|b1 = h^r1 (yG)^d1 ?\n
|""".stripMargin
val yG = y.multiply(cp.zkp_generator_G)
if (!cp.public_key_h.modPow(r1, cp.large_prime_p).multiply(
yG.modPow(d1, cp.large_prime_p)).mod(cp.large_prime_p).equals(b1)) {
println(s"b1 !=\t$b1")
return false
}

outBuffer +=
s"""a2=${this.a2}
|g=${cp.generator_g}
|r2=${this.r2}
|x=${this.x}
|d2=${this.d2}
|a2 = g^r2 x^d2 ?\n
|""".stripMargin
val gr2 = cp.generator_g.modPow(r2, cp.large_prime_p)
val xd2 = x.modPow(d2, cp.large_prime_p)
val shouldBea2 = gr2.multiply(xd2).mod(cp.large_prime_p)
println(s"gr2\t$gr2")
println(s"xd2\t$xd2")
println(s"shouldBea2\t$shouldBea2")
if (!shouldBea2.equals(a2)) {
println(s"shouldBea2 != a2")
return false // TODO : debug using algebra proof in voting protocol paper
}

outBuffer +=
s"""b2=${this.b2}
|h=${cp.public_key_h}
|r2=${this.r2}
|y=${this.y}
|G=${cp.zkp_generator_G}
|d2=${this.d2}
|b2 = h^r2 (y/G)^d2 ?\n
|""".stripMargin

val yByG = cp.zkp_generator_G.modInverse(cp.large_prime_p).multiply(y).mod(cp.large_prime_p)
val hr2 = cp.public_key_h.modPow(r2, cp.large_prime_p)
val yByGpowd2 = yByG.modPow(d2, cp.large_prime_p)
val hr2yByGpowd2 = hr2.multiply(yByGpowd2).mod(cp.large_prime_p)
println(s"yByG\t$yByG")
println(s"hr2\t$hr2")
println(s"yByGpowd2\t$yByGpowd2")
println(s"hr2yByGpowd2\t$hr2yByGpowd2")
if (!hr2yByGpowd2.equals(b2)) {
println(s"hr2yByGpowd2 != b2")
return false
}
//println(outBuffer.toString())
//println("done")
true
}
catch {
case ex: Throwable =>
println(s"Failed to verify because of exception ${ex.toString}")
false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package com.newlibertie.pollster.impl

import java.math.BigInteger

import com.newlibertie.pollster.impl.CryptographicParameters.{BITS, isdbg, rng}

object CryptographicParameters {
val BITS = 1000
final val isdbg = false
val BITS = 32 // 1000 // TODO : check for various values and then choose a production secure value
import java.security.SecureRandom;
private val rng = new SecureRandom()

def probablePrime() = BigInteger.probablePrime(BITS, rng)

def random() = new BigInteger(BITS, rng)
def random(bits:Int = BITS) = new BigInteger(bits, rng)

def apply(p:BigInteger, g:BigInteger, s:BigInteger) = new CryptographicParameters(p, g, s)
}
Expand All @@ -19,10 +22,14 @@ case class CryptographicParameters
(
large_prime_p:BigInteger = CryptographicParameters.probablePrime(),
generator_g:BigInteger = CryptographicParameters.random(),
private_key_s:BigInteger = CryptographicParameters.random()
private_key_s:BigInteger = CryptographicParameters.random(),
)
{
val public_key_h = generator_g.modPow(private_key_s, large_prime_p)
val zkp_generator_G:BigInteger = CryptographicParameters.random().mod(large_prime_p)
val public_key_h = if (isdbg)
new BigInteger("1048806755")
else
generator_g.modPow(private_key_s, large_prime_p)
}


6 changes: 3 additions & 3 deletions src/main/scala/com/newlibertie/pollster/impl/Poll.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ object Poll {
def apply(params: PollParameters): Poll = new Poll(params)

def apply(pollDetails:String): Poll = {
implicit val formats = DefaultFormats
implicit val formats: DefaultFormats.type = DefaultFormats
val jValue = parse(pollDetails)
val pollParameters = jValue.extract[PollParameters]
val pollJsonMap = jValue.values.asInstanceOf[Map[String, String]]
Expand All @@ -29,11 +29,11 @@ object Poll {
new Poll(pollParameters, cryptographicParameters)
}

def write(poll: Poll) = {
def write(poll: Poll): Any = {
DataAdapter.createPoll(poll)
}

def read() = {
def read(): Unit = {

}
}
Expand Down
Loading