2018-07-17 09:57:09 +01:00
function showDialogWithText ( titleTxt , bodyTxt ) {
var modalDialog = $ ( '#modalDialog' ) ;
var title = modalDialog . find ( '.modal-title' ) ;
var body = modalDialog . find ( '.modal-body' ) ;
title . text ( titleTxt ) ;
var p = document . createElement ( "p" ) ;
p . innerHTML = bodyTxt ;
body . empty ( ) ;
body . append ( p ) ;
modalDialog . modal ( 'show' ) ;
}
2018-07-13 17:19:35 +01:00
// This should stop people ticking more than the maximum permitted
function updateCheckboxInteractivity ( ) {
var inputs = $ ( "label input[type=checkbox]" ) ;
if ( selectedCount === MAX _SELECTIONS ) {
inputs . each ( function ( ) {
var input = $ ( this ) ;
if ( ! input . prop ( 'checked' ) ) {
input . prop ( 'disabled' , true ) ;
}
} ) ;
} else {
inputs . each ( function ( ) {
var input = $ ( this ) ;
if ( ! input . prop ( 'checked' ) ) {
input . prop ( 'disabled' , false ) ;
}
} ) ;
}
}
$ ( "label input[type=checkbox]" ) . change ( function ( ) {
// Increment the selectedCount counter if a box has been checked
if ( this . checked ) {
selectedCount += 1 ;
} else {
selectedCount -= 1 ;
// Just incase this falls below zero to avoid any nasty bugs
if ( selectedCount < 0 ) {
selectedCount = 0 ;
}
}
updateCheckboxInteractivity ( ) ;
} ) ;
function isVotingInputValid ( ) {
var valid = true ;
// First establish if the user's selection count is valid
if ( ! ( selectedCount >= MIN _SELECTIONS && selectedCount <= MAX _SELECTIONS ) ) {
valid = false ;
}
2018-07-16 10:16:06 +01:00
if ( selectedCount < MAX _SELECTIONS ) {
valid = false ;
}
2018-07-13 17:19:35 +01:00
// This will highlight when people haven't selected enough options
if ( ! valid ) {
2018-07-17 09:57:09 +01:00
let errText = "You've only selected " + selectedCount
2018-07-13 17:19:35 +01:00
+ " option(s). The minimum number you need to select is " + MIN _SELECTIONS
+ " and the maximum is " + MAX _SELECTIONS + ". Please go back and correct this." ;
2018-07-17 09:57:09 +01:00
let titleTxt = 'Voting Error' ;
2018-07-13 17:19:35 +01:00
2018-07-17 09:57:09 +01:00
showDialogWithText ( titleTxt , errText ) ;
2018-07-13 17:19:35 +01:00
return ;
}
return valid ;
}
2018-08-16 22:17:16 +01:00
var progressBar = document . getElementById ( "progress-bar" ) ;
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
$ ( '#gen-ballots-btn' ) . click ( function ( ) {
// Ensure that the user selections are valid
if ( isVotingInputValid ( ) ) {
// Hide the button
$ ( this ) . toggleClass ( 'hidden' ) ;
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
// Inject the description progress bar which can then be updated by the encrypt btn
$ ( '#progress-bar-description' ) . toggleClass ( 'hidden' ) ;
$ ( '#progress-bar-container' ) . toggleClass ( 'hidden' ) ;
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
setTimeout ( generateBallots , 25 ) ;
}
} ) ;
2018-07-13 17:19:35 +01:00
// Based on the user's vote in the current poll, this generates a ballot which
// does not leak information about how many options the user has selected
function generateBallot ( ) {
// Elliptic curve cryptography params used for encryption of encrypted vote
// fragments
var ctx = new CTX ( "BN254CX" ) ;
var n = new ctx . BIG ( ) ;
var g1 = new ctx . ECP ( ) ;
var g2 = new ctx . ECP2 ( ) ;
var parameter = $ ( '#event-param' ) . val ( ) ;
var tempParams = JSON . parse ( JSON . parse ( parameter ) . crypto ) ;
//copying the values
n . copy ( tempParams . n ) ;
g1 . copy ( tempParams . g1 ) ;
g2 . copy ( tempParams . g2 ) ;
var params = {
n : n ,
g1 : g1 ,
g2 : g2
} ;
var tempPK = JSON . parse ( $ ( '#comb_pk' ) . val ( ) ) ;
var pk = new ctx . ECP ( 0 ) ;
pk . copy ( tempPK . PK ) ;
// Collect together the unencrypted votes (which correspond to selected options)
var checkboxInputs = $ ( "label input[type=checkbox]" ) ;
var unencryptedVotes = [ ] ;
checkboxInputs . each ( function ( ) {
var checkbox = $ ( this ) ;
2018-07-17 09:57:09 +01:00
// Push the selected option values (ones that have been checked) to an array
2018-07-13 17:19:35 +01:00
if ( checkbox . prop ( 'checked' ) ) {
unencryptedVotes . push ( checkbox . val ( ) ) ;
}
2018-07-17 09:57:09 +01:00
} ) ;
// If there is a dif between the num selected and the max allowed, push blank votes to the array to pad this
// to prevent information leakage
if ( unencryptedVotes . length < MAX _SELECTIONS ) {
let blankVotesToPush = MAX _SELECTIONS - unencryptedVotes . length ;
for ( let i = 0 ; i < blankVotesToPush ; i ++ ) {
2018-07-13 17:19:35 +01:00
unencryptedVotes . push ( genBlankVote ( ) ) ;
}
2018-07-17 09:57:09 +01:00
}
2018-07-13 17:19:35 +01:00
// Encrypt all of the votes for this ballot
var encryptedVotes = [ ] ;
unencryptedVotes . forEach ( function ( unencryptedVote ) {
var encFragments = [ ] ;
// Encrypt each fragment of the unencrypted vote
unencryptedVote . split ( ',' ) . forEach ( function ( fragment ) {
var cipher = encrypt ( params , pk , parseInt ( fragment ) ) ;
2018-08-16 22:17:16 +01:00
// Store C1, C2 and r from the cipher in the fragment
2018-07-13 17:19:35 +01:00
var c1Bytes = [ ] ;
cipher . C1 . toBytes ( c1Bytes ) ;
var c2Bytes = [ ] ;
cipher . C2 . toBytes ( c2Bytes ) ;
2018-08-16 22:17:16 +01:00
var rBytes = [ ] ;
cipher . r . toBytes ( rBytes ) ;
2018-07-13 17:19:35 +01:00
encFragments . push ( {
C1 : c1Bytes . toString ( ) ,
2018-08-16 22:17:16 +01:00
C2 : c2Bytes . toString ( ) ,
r : rBytes . toString ( )
2018-07-13 17:19:35 +01:00
} ) ;
} ) ;
// Store all fragments in a single 'encrypted vote'
encryptedVotes . push ( {
fragments : encFragments
} ) ;
} ) ;
2018-07-17 09:57:09 +01:00
return {
2018-07-13 17:19:35 +01:00
encryptedVotes : encryptedVotes
} ;
}
2018-08-16 22:17:16 +01:00
// Generates a blank vote as a string using the binary encoding scheme
function genBlankVote ( ) {
var vote = "" ;
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
for ( var i = 0 ; i < OPTION _COUNT ; i ++ ) {
vote += "0" ;
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
if ( i !== ( OPTION _COUNT - 1 ) ) {
vote += "," ;
}
2018-07-16 10:16:06 +01:00
}
2018-08-16 22:17:16 +01:00
return vote ;
2018-07-13 17:19:35 +01:00
}
var CSRF = $ ( "input[name='csrfmiddlewaretoken']" ) . val ( ) ;
function csrfSafeMethod ( method ) {
// these HTTP methods do not require CSRF protection
return ( /^(GET|HEAD|OPTIONS|TRACE)$/ . test ( method ) ) ;
}
2018-07-17 09:57:09 +01:00
var bytestostring = function ( b ) {
var s = "" ;
var len = b . length ;
var ch ;
for ( var i = 0 ; i < len ; i ++ ) {
ch = b [ i ] ;
s += ( ( ch >>> 4 ) & 15 ) . toString ( 16 ) ;
s += ( ch & 15 ) . toString ( 16 ) ;
}
return s ;
} ;
2018-07-13 17:19:35 +01:00
2018-07-17 09:57:09 +01:00
var stringtobytes = function ( s ) {
var b = [ ] ;
for ( var i = 0 ; i < s . length ; i ++ ) {
b . push ( s . charCodeAt ( i ) ) ;
}
return b ;
} ;
function SHA256Hash ( bytes , toStr ) {
var ctx = new CTX ( ) ;
var R = [ ] ;
var H = new ctx . HASH256 ( ) ;
H . process _array ( bytes ) ;
R = H . hash ( ) ;
if ( R . length === 0 ) {
return null ;
}
2018-07-13 17:19:35 +01:00
2018-07-17 09:57:09 +01:00
if ( toStr ) {
// If toStr is true we return the stringified version of the bytes of the hash
return bytestostring ( R ) ;
} else {
// If toStr is false we return the bytes of the hash
return R ;
}
}
2018-08-16 22:17:16 +01:00
function generateBallots ( ) {
// Generate Ballot A and Ballot B to be displayed to the user
// This fn starts the process
var ballotA = generateBallot ( ) ;
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
// Update the progress bar once the generation has completed
progressBar . setAttribute ( "style" , "width: 50%;" ) ;
// This delay allows the execution thread to update the above CSS on the progress bar
setTimeout ( function ( ) {
var ballotB = generateBallot ( ) ;
progressBar . setAttribute ( "style" , "width: 100%;" ) ;
showFirstQRCode ( ballotA , ballotB ) ;
} , 150 ) ;
}
function showFirstQRCode ( ballotA , ballotB ) {
var ballots = new Array ( ballotA , ballotB ) ;
var ballotHashes = new Array ( 2 ) ;
2018-07-17 09:57:09 +01:00
2018-08-16 22:17:16 +01:00
// Hash both ballots and store
for ( let i = 0 ; i <= 1 ; i ++ )
ballotHashes [ i ] = SHA256Hash ( stringtobytes ( JSON . stringify ( ballots [ i ] ) ) , true ) ;
// With the ballots and their hashes generated, we can display the QR code of both hashes
2018-07-17 09:57:09 +01:00
var modalDialog = $ ( '#modalDialog' ) ;
2018-07-23 14:08:17 +01:00
var title = modalDialog . find ( '.modal-title' ) ;
var body = modalDialog . find ( '.modal-body' ) ;
2018-08-16 22:17:16 +01:00
var footer = modalDialog . find ( '.modal-footer' ) ;
2018-07-23 14:08:17 +01:00
2018-08-16 22:17:16 +01:00
body . empty ( ) ;
title . text ( 'Please Scan this QR Code' ) ;
2018-07-23 14:08:17 +01:00
var QRCodeImg = document . createElement ( 'img' ) ;
QRCodeImg . setAttribute ( 'class' , 'QR-code' ) ;
2018-08-16 22:17:16 +01:00
new QRCode ( QRCodeImg , ballotHashes [ 0 ] + ';' + ballotHashes [ 1 ] ) ;
2018-07-23 14:08:17 +01:00
// ----------------------------------------------
var hashGroupDiv = document . createElement ( 'div' ) ;
var br = document . createElement ( 'br' ) ;
hashGroupDiv . append ( br ) ;
2018-08-16 22:17:16 +01:00
var hashA = document . createElement ( "span" ) ;
hashA . innerHTML = "Hash A: " + ballotHashes [ 0 ] ;
hashGroupDiv . append ( hashA ) ;
var br2 = document . createElement ( 'br' ) ;
hashGroupDiv . append ( br2 ) ;
var hashB = document . createElement ( "span" ) ;
hashB . innerHTML = "Hash B: " + ballotHashes [ 1 ] ;
hashGroupDiv . append ( hashB ) ;
2018-07-23 14:08:17 +01:00
// -----------------------------------------------
2018-08-16 22:17:16 +01:00
body . append ( QRCodeImg ) ;
2018-07-23 14:08:17 +01:00
body . append ( hashGroupDiv ) ;
2018-08-16 22:17:16 +01:00
var closeButton = $ ( 'close-button' ) ;
closeButton . removeClass ( 'btn-success' ) ;
closeButton . addClass ( 'btn-danger' ) ;
closeButton . text ( "Close without submitting vote" ) ;
2018-07-23 14:08:17 +01:00
2018-08-16 22:17:16 +01:00
var nextButton = document . createElement ( 'button' ) ;
nextButton . setAttribute ( 'type' , 'button' ) ;
nextButton . setAttribute ( 'id' , 'next-button' ) ;
nextButton . setAttribute ( 'class' , 'btn btn-default' ) ;
nextButton . innerHTML = "Next" ;
2018-07-17 09:57:09 +01:00
2018-08-16 22:17:16 +01:00
footer . prepend ( nextButton ) ;
2018-07-17 09:57:09 +01:00
2018-07-23 14:08:17 +01:00
2018-08-16 22:17:16 +01:00
modalDialog . modal ( 'show' ) ;
2018-07-17 09:57:09 +01:00
2018-08-16 22:17:16 +01:00
$ ( '#next-button' ) . click ( function ( e ) {
showBallotChoiceDialog ( ballots ) ;
} ) ;
}
function showBallotChoiceDialog ( ballots ) {
// Display the ballot choice dialog
2018-07-17 09:57:09 +01:00
var modalDialog = $ ( '#modalDialog' ) ;
var title = modalDialog . find ( '.modal-title' ) ;
var body = modalDialog . find ( '.modal-body' ) ;
2018-08-16 22:17:16 +01:00
2018-07-17 09:57:09 +01:00
body . empty ( ) ;
title . text ( 'Please Select a Ballot' ) ;
2018-08-16 22:17:16 +01:00
// Generate the body of the dialog which consists of a button for A and for B
2018-07-17 09:57:09 +01:00
var choiceGroupDiv = document . createElement ( 'div' ) ;
choiceGroupDiv . setAttribute ( 'class' , 'choice-group' ) ;
var btnChoiceA = document . createElement ( 'a' ) ;
btnChoiceA . setAttribute ( 'id' , 'choice-A' ) ;
btnChoiceA . setAttribute ( 'class' , 'btn btn-sq btn-primary' ) ;
btnChoiceA . innerHTML = 'A' ;
choiceGroupDiv . append ( btnChoiceA ) ;
var btnChoiceB = document . createElement ( 'a' ) ;
btnChoiceB . setAttribute ( 'id' , 'choice-B' ) ;
btnChoiceB . setAttribute ( 'class' , 'btn btn-sq btn-warning choice' ) ;
btnChoiceB . innerHTML = 'B' ;
choiceGroupDiv . append ( btnChoiceB ) ;
body . append ( choiceGroupDiv ) ;
modalDialog . modal ( 'show' ) ;
// Register callback functions for the selection of either A or B
$ ( '#choice-A' ) . click ( function ( e ) {
2018-08-16 22:17:16 +01:00
sendBallotsToServer ( ballots [ 0 ] , ballots [ 1 ] ) ;
2018-07-17 09:57:09 +01:00
} ) ;
$ ( '#choice-B' ) . click ( function ( e ) {
2018-08-16 22:17:16 +01:00
sendBallotsToServer ( ballots [ 1 ] , ballots [ 0 ] ) ;
2018-07-17 09:57:09 +01:00
} ) ;
}
2018-08-16 22:17:16 +01:00
function sendBallotsToServer ( selection , alt ) {
$ . ajaxSetup ( {
beforeSend : function ( xhr , settings ) {
if ( ! csrfSafeMethod ( settings . type ) && ! this . crossDomain ) {
xhr . setRequestHeader ( "X-CSRFToken" , CSRF ) ;
}
}
} ) ;
// Elliptic curve cryptography params used for encryption of encrypted vote
// fragments
var ctx = new CTX ( "BN254CX" ) ;
var n = new ctx . BIG ( ) ;
var g1 = new ctx . ECP ( ) ;
var g2 = new ctx . ECP2 ( ) ;
var parameter = $ ( '#event-param' ) . val ( ) ;
var tempParams = JSON . parse ( JSON . parse ( parameter ) . crypto ) ;
2018-07-17 09:57:09 +01:00
2018-08-16 22:17:16 +01:00
//copying the values
n . copy ( tempParams . n ) ;
g1 . copy ( tempParams . g1 ) ;
g2 . copy ( tempParams . g2 ) ;
var params = {
n : n ,
g1 : g1 ,
g2 : g2
} ;
var tempPK = JSON . parse ( $ ( '#comb_pk' ) . val ( ) ) ;
var pk = new ctx . ECP ( 0 ) ;
pk . copy ( tempPK . PK ) ;
var voterID = window . location . search . slice ( 1 ) . split ( /=(.+)/ ) [ 1 ] ; //.slice(0, -2);
var eventID = window . location . href . split ( '/' ) [ 4 ] ;
var pollNum = $ ( '#poll-num' ) . text ( ) ;
var ballotID = encodeURIComponent ( btoa ( JSON . stringify ( { voterID : voterID , eventID : eventID , pollNum : pollNum } ) ) ) ;
var SK = "temporary" ;
var encAlt = sjcl . encrypt ( SK , JSON . stringify ( alt ) ) ;
selection = JSON . stringify ( selection ) ;
$ . ajax ( {
type : "POST" ,
url : window . location ,
data : { handle : ballotID , encBallot : encAlt , ballot : selection } ,
success : function ( ) {
onAfterBallotSend ( ballotID , SK ) ;
}
} ) ;
2018-07-13 17:19:35 +01:00
}
2018-08-16 22:17:16 +01:00
// Called once the ballot has been sent to the back-end and dialog has closed
function onAfterBallotSend ( ballotID , SK ) {
let titleText = 'Vote Successfully Received' ;
let bodyText = "Thank you for voting! Your secret key is '" + SK + "'. Make sure to scan this QR code with your phone before closing this window." ;
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
if ( POLL _NUM !== POLL _COUNT ) {
bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'." ;
}
2018-07-13 17:19:35 +01:00
2018-08-16 22:17:16 +01:00
// With one ballot selected, we can display a QR code of the ballot ID
var modalDialog = $ ( '#modalDialog' ) ;
var title = modalDialog . find ( '.modal-title' ) ;
var body = modalDialog . find ( '.modal-body' ) ;
title . text ( titleText ) ;
body . empty ( ) ;
var p = document . createElement ( "p" ) ;
p . innerHTML = bodyText ;
body . append ( p ) ;
// Generate the body of the dialog which displays the unselected ballot QR code
var QRCodeImg = document . createElement ( 'img' ) ;
QRCodeImg . setAttribute ( 'class' , 'QR-code' ) ;
new QRCode ( QRCodeImg , ballotID ) ;
body . append ( QRCodeImg ) ;
var closeButton = $ ( '#close-button' ) ;
closeButton . removeClass ( 'btn-danger' ) ;
closeButton . addClass ( 'btn-success' ) ;
closeButton . text ( "Close" ) ;
modalDialog . modal ( 'show' ) ;
2018-07-13 17:19:35 +01:00
}