2018-08-22 17:39:41 +01:00
var dialogOpen = false ;
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 ) ;
2018-08-22 17:39:41 +01:00
if ( ! dialogOpen ) {
modalDialog . modal ( 'toggle' ) ;
dialogOpen = true ;
}
2018-07-17 09:57:09 +01:00
}
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-08-22 17:39:41 +01:00
let errText = "You've only selected " + selectedCount ;
if ( selectedCount > 1 ) {
errText += " options." ;
} else {
errText = " You haven't selected any options." ;
}
errText += " The minimum number you need to select is " + MIN _SELECTIONS + " and the maximum is "
+ MAX _SELECTIONS + ". Please go back and correct this." ;
2018-07-13 17:19:35 +01:00
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 ;
}
// Generates a blank vote as a string using the binary encoding scheme
function genBlankVote ( ) {
var vote = "" ;
for ( var i = 0 ; i < OPTION _COUNT ; i ++ ) {
vote += "0" ;
if ( i !== ( OPTION _COUNT - 1 ) ) {
vote += "," ;
}
}
return vote ;
}
var progressBar = document . getElementById ( "progress-bar" ) ;
// 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 ) ) ;
// Store C1 and C2 from the cipher in the fragment
var c1Bytes = [ ] ;
cipher . C1 . toBytes ( c1Bytes ) ;
var c2Bytes = [ ] ;
cipher . C2 . toBytes ( c2Bytes ) ;
encFragments . push ( {
C1 : c1Bytes . toString ( ) ,
C2 : c2Bytes . toString ( )
} ) ;
} ) ;
// 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
} ;
}
$ ( '#gen-ballots-btn' ) . click ( function ( ) {
// Ensure that the user selections are valid
if ( isVotingInputValid ( ) ) {
// Hide the button
$ ( this ) . toggleClass ( 'hidden' ) ;
// 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-17 09:57:09 +01:00
setTimeout ( generateBallotsAndShowUsr , 25 ) ;
2018-07-13 17:19:35 +01:00
}
} ) ;
function voteSuccessfullyReceived ( ) {
2018-07-17 09:57:09 +01:00
let titleTxt = 'Vote Successfully Received' ;
let bodyText = "Thank you for voting!" ;
2018-07-13 17:19:35 +01:00
2018-07-16 10:16:06 +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-17 09:57:09 +01:00
showDialogWithText ( titleTxt , bodyText ) ;
2018-08-22 17:39:41 +01:00
// Update the dialog's btns
$ ( '#cancelVoteBtn' ) . addClass ( "hidden" ) ;
$ ( '#closeDialogBtn' ) . removeClass ( "hidden" ) ;
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-08-22 17:39:41 +01:00
function sendBallotToServer ( selection , ballot , ballotSelectionDialog ) {
// Use ajax to send the selected ballot to server
2018-07-13 17:19:35 +01:00
$ . ajaxSetup ( {
beforeSend : function ( xhr , settings ) {
if ( ! csrfSafeMethod ( settings . type ) && ! this . crossDomain ) {
xhr . setRequestHeader ( "X-CSRFToken" , CSRF ) ;
}
}
} ) ;
$ . ajax ( {
type : "POST" ,
url : window . location ,
2018-08-22 17:39:41 +01:00
data : JSON . stringify ( { selection : selection , ballot : ballot } ) ,
success : function ( ) {
2018-07-13 17:19:35 +01:00
voteSuccessfullyReceived ( ) ;
}
} ) ;
2018-08-22 17:39:41 +01:00
// Update the client to inform the user that the vote is being processed
showDialogWithText ( "Please Wait" , "Processing Vote. Please wait..." ) ;
2018-07-13 17:19:35 +01:00
}
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 ;
}
}
// FAO Ben: Called once the ballot has been sent to the back-end and dialog has closed
function onAfterBallotSend ( ) {
// TODO: FAO Ben: Implement QR func here.
2018-07-13 17:19:35 +01:00
// TODO: Currently, there is a dialog already implemented in the event_vote.html page which is
2018-07-17 09:57:09 +01:00
// TODO: used for voting error information but could be used to display the QR code using JS in
// TODO: a similar way that showBallotChoiceDialog does.
}
2018-07-13 17:19:35 +01:00
2018-08-22 17:39:41 +01:00
function processBallotSelection ( selection , selectedBallot , selectedBallotHash , dialog , successFn ) {
2018-07-17 09:57:09 +01:00
// Dispatch the ballot to the server
2018-08-22 17:39:41 +01:00
sendBallotToServer ( selection , selectedBallot , dialog ) ;
2018-07-17 09:57:09 +01:00
// Call the successfn currently with the selection hash but this may not be needed
2018-08-22 17:39:41 +01:00
successFn ( selectedBallotHash ) ;
2018-07-17 09:57:09 +01:00
}
function showBallotChoiceDialog ( ballotA , ballotB ) {
// Output hashes of the 2 ballots
const BALLOT _A _HASH = SHA256Hash ( stringtobytes ( JSON . stringify ( ballotA ) ) , true ) ;
const BALLOT _B _HASH = SHA256Hash ( stringtobytes ( JSON . stringify ( ballotB ) ) , true ) ;
// With the ballots and their hashes generated, we can display the ballot choice dialog
var modalDialog = $ ( '#modalDialog' ) ;
var title = modalDialog . find ( '.modal-title' ) ;
var body = modalDialog . find ( '.modal-body' ) ;
body . empty ( ) ;
title . text ( 'Please Select a Ballot' ) ;
// Generate the body of the dialog which consists of a button for A and for B as well as their hashes
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 ) ;
// ----------------------------------------------
var hashGroupDiv = document . createElement ( 'div' ) ;
var br = document . createElement ( 'br' ) ;
hashGroupDiv . append ( br ) ;
var hashA = document . createElement ( "span" ) ;
hashA . innerHTML = "Hash A: " + BALLOT _A _HASH ;
hashGroupDiv . append ( hashA ) ;
var br2 = document . createElement ( 'br' ) ;
hashGroupDiv . append ( br2 ) ;
var hashB = document . createElement ( "span" ) ;
hashB . innerHTML = "Hash B: " + BALLOT _B _HASH ;
hashGroupDiv . append ( hashB ) ;
// -----------------------------------------------
body . append ( choiceGroupDiv ) ;
body . append ( hashGroupDiv ) ;
2018-08-22 17:39:41 +01:00
// Prepare the appropriate dialog buttons
$ ( '#cancelVoteBtn' ) . removeClass ( "hidden" ) ;
$ ( '#closeDialogBtn' ) . removeClass ( "hidden" ) . addClass ( "hidden" ) ;
if ( ! dialogOpen ) {
modalDialog . modal ( 'toggle' ) ;
dialogOpen = true ;
}
2018-07-17 09:57:09 +01:00
// Register callback functions for the selection of either A or B
$ ( '#choice-A' ) . click ( function ( e ) {
2018-08-22 17:39:41 +01:00
processBallotSelection ( "A" , ballotA , BALLOT _A _HASH , modalDialog , onAfterBallotSend ) ;
2018-07-17 09:57:09 +01:00
} ) ;
$ ( '#choice-B' ) . click ( function ( e ) {
2018-08-22 17:39:41 +01:00
processBallotSelection ( "B" , ballotB , BALLOT _B _HASH , modalDialog , onAfterBallotSend ) ;
2018-07-17 09:57:09 +01:00
} ) ;
}
function generateBallotB ( ballotA ) {
var ballotB = generateBallot ( ) ;
progressBar . setAttribute ( "style" , "width: 100%;" ) ;
showBallotChoiceDialog ( ballotA , ballotB ) ;
2018-07-13 17:19:35 +01:00
}
function generateBallotsAndShowUsr ( ) {
// Generate Ballot A and Ballot B to be displayed to the user
// This fn starts the process
var ballotA = generateBallot ( ) ;
// 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 ( ) {
generateBallotB ( ballotA ) ;
2018-07-17 09:57:09 +01:00
} , 150 ) ;
2018-08-22 17:39:41 +01:00
}
$ ( '#modalDialog' ) . on ( 'hide.bs.modal' , function ( e ) {
var titleText = $ ( this ) . find ( '.modal-title' ) . text ( ) ;
if ( titleText . indexOf ( "Received" ) > - 1 ) {
// Update page to reflect the fact that a vote has taken place
location . reload ( ) ;
} else {
// Reset poll voting to allow user to vote again
progressBar . setAttribute ( "style" , "width: 0%;" ) ;
$ ( '#gen-ballots-btn' ) . toggleClass ( "hidden" ) ;
$ ( '#progress-bar-description' ) . toggleClass ( 'hidden' ) ;
$ ( '#progress-bar-container' ) . toggleClass ( 'hidden' ) ;
var inputs = $ ( "label input[type=checkbox]" ) ;
inputs . each ( function ( ) {
var input = $ ( this ) ;
input . prop ( 'checked' , false ) ;
input . prop ( 'disabled' , false ) ;
} ) ;
selectedCount = 0 ;
}
dialogOpen = false ;
} ) ;