Skip to content

Commit

Permalink
feat: use reduction context for all operations
Browse files Browse the repository at this point in the history
  • Loading branch information
meck93 committed Aug 31, 2021
1 parent 7d82e60 commit 0ba589a
Show file tree
Hide file tree
Showing 7 changed files with 1,051 additions and 843 deletions.
1,636 changes: 853 additions & 783 deletions package-lock.json

Large diffs are not rendered by default.

34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,33 @@
"elliptic"
],
"dependencies": {
"bn.js": "^5.1.2",
"bn.js": "^5.2.0",
"elliptic": "6.5.4",
"hash.js": "^1.1.7",
"random": "^2.2.0",
"web3": "^1.2.8"
"web3": "^1.5.2"
},
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/elliptic": "^6.4.12",
"@types/chai": "^4.2.21",
"@types/elliptic": "^6.4.13",
"@types/mocha": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^3.1.0",
"@typescript-eslint/parser": "^3.1.0",
"chai": "^4.2.0",
"eslint": "^7.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-import-resolver-typescript": "^2.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-json": "^2.1.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.3",
"lint-staged": "^10.2.9",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"chai": "^4.3.4",
"eslint": "^7.32.0",
"eslint-config-prettier": "^6.15.0",
"eslint-import-resolver-typescript": "^2.4.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-json": "^2.1.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.4.1",
"lint-staged": "^10.5.4",
"mocha": "^7.2.0",
"nyc": "^15.1.0",
"prettier": "^2.0.5",
"prettier": "^2.3.2",
"ts-mocha": "^7.0.0",
"ts-node": "^8.10.2",
"typescript": "^3.9.5"
"typescript": "^3.9.10"
},
"nyc": {
"extension": [
Expand Down
56 changes: 44 additions & 12 deletions src/helper.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
import BN = require('bn.js')
import crypto = require('crypto')
import { GlobalHelper } from '.'

export const newBN = (n: number, base = 10): BN => new BN(n, base)
export const invmBN = (a: BN, modulus: BN): BN => a.invm(modulus)
export const addBN = (a: BN, b: BN, modulus: BN): BN => a.add(b).mod(modulus)
export const subBN = (a: BN, b: BN, modulus: BN): BN => a.sub(b).mod(modulus)
export const mulBN = (a: BN, b: BN, modulus: BN): BN => a.mul(b).mod(modulus)
export const divBN = (a: BN, b: BN, modulus: BN): BN => mulBN(a, invmBN(b, modulus), modulus)
export const powBN = (a: BN, b: BN, modulus: BN): BN => a.pow(b).mod(modulus)
export const invmBN = (a: BN, modulus: BN): BN => {
const reductionCtx = BN.red(modulus)
return a.toRed(reductionCtx).redInvm().fromRed()
}
export const addBN = (a: BN, b: BN, modulus: BN): BN => {
const reductionCtx = BN.red(modulus)
const aRed = a.toRed(reductionCtx)
const bRed = b.toRed(reductionCtx)
return aRed.redAdd(bRed).fromRed()
}
export const subBN = (a: BN, b: BN, modulus: BN): BN => {
const reductionCtx = BN.red(modulus)
const aRed = a.toRed(reductionCtx)
const bRed = b.toRed(reductionCtx)
return aRed.redSub(bRed).fromRed()
}
export const mulBN = (a: BN, b: BN, modulus: BN): BN => {
const reductionCtx = BN.red(modulus)
const aRed = a.toRed(reductionCtx)
const bRed = b.toRed(reductionCtx)
return aRed.redMul(bRed).fromRed()
}
export const divBN = (a: BN, b: BN, modulus: BN): BN => {
const reductionCtx = BN.red(modulus)
const aRed = a.toRed(reductionCtx)
const bRed = b.toRed(reductionCtx)
return bRed.redInvm().redMul(aRed).fromRed()
}
export const powBN = (a: BN, b: BN, modulus: BN): BN => {
const reductionCtx = BN.red(modulus)
const baseRed = a.toRed(reductionCtx)
return baseRed.redPow(b).fromRed()
}

// compute the required number of bytes to store a decimal
export const getByteSizeForDecimalNumber = (n: BN): BN => {
Expand All @@ -19,17 +47,21 @@ export const getByteSizeForDecimalNumber = (n: BN): BN => {

// get a secure random value x: 0 < x < n
export const getSecureRandomValue = (n: BN): BN => {
const ONE: BN = new BN(1, 10)
const UPPER_BOUND_RANDOM: BN = n.sub(ONE)
const BYTE_SIZE: BN = getByteSizeForDecimalNumber(n)
const ONE: BN = GlobalHelper.newBN(1, 10)
const NODE_RAND_UPPER_LIMIT: BN = GlobalHelper.newBN(4294967295 * 8, 10)
const UPPER_BOUND_RANDOM: BN = n.sub(ONE).gt(NODE_RAND_UPPER_LIMIT)
? NODE_RAND_UPPER_LIMIT
: n.sub(ONE)
const BYTE_SIZE: BN = getByteSizeForDecimalNumber(UPPER_BOUND_RANDOM)

let byteSize: number
try {
byteSize = BYTE_SIZE.toNumber()
byteSize = byteSize > 32 ? 4 : byteSize
} catch {
// https://www.ecma-international.org/ecma-262/5.1/#sec-8.5
// used for large numbers from EC
byteSize = 32
byteSize = 4
}

let randomBytes: Buffer = crypto.randomBytes(byteSize)
Expand Down Expand Up @@ -73,7 +105,7 @@ export const timingSafeEqualBN = (a: BN, b: BN): boolean => {
if (!BN.isBN(b)) {
throw new TypeError('Second argument must be of type: BN')
}
const a_ = new Buffer(a.toArray())
const b_ = new Buffer(b.toArray())
const a_ = Buffer.from(a.toArray())
const b_ = Buffer.from(b.toArray())
return timingSafeEqual(a_, b_)
}
4 changes: 2 additions & 2 deletions test/ff-elgamal/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ describe('ElGamal Finite Field E2E Test', () => {
)

expect(decryptedSum.toNumber()).to.equal(_result)
expect(summary.yes).to.equal(_votes.filter(v => v === 1).length)
expect(summary.no).to.equal(_votes.filter(v => v === 0).length)
expect(summary.yes).to.equal(_votes.filter((v) => v === 1).length)
expect(summary.no).to.equal(_votes.filter((v) => v === 0).length)
}

// voters: 3
Expand Down
3 changes: 1 addition & 2 deletions test/ff-elgamal/proofs/keyGeneration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ describe('ElGamal Finite Field NIZKP for Key Generation', () => {

// generate the public and private key share: H_, SK_
const share: FFelGamal.KeyPair = FFelGamal.SystemSetup.generateKeyPair(sp)

expect(share.h).to.eql(sp.g.pow(share.sk).mod(sp.p))
expect(share.h.eq(sp.g.pow(share.sk).mod(sp.p))).to.be.true

log && console.log('Key Parts')
log && console.log('h:\t', share.h.toString())
Expand Down
111 changes: 108 additions & 3 deletions test/ff-elgamal/voting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai'
import { FFelGamal } from '../../src/index'

describe('Finite Field ElGamal Voting', () => {
it('vote', () => {
it('vote (0, 1, 2 voters)', () => {
const vote = (_result: number, _votes: number[]): void => {
const [sp, { h: pk, sk }] = FFelGamal.SystemSetup.generateSystemParametersAndKeys(1319, 2)

Expand Down Expand Up @@ -30,8 +30,8 @@ describe('Finite Field ElGamal Voting', () => {
)

expect(result).to.equal(_result)
expect(summary.yes).to.equal(_votes.filter(v => v === 1).length)
expect(summary.no).to.equal(_votes.filter(v => v === 0).length)
expect(summary.yes).to.equal(_votes.filter((v) => v === 1).length)
expect(summary.no).to.equal(_votes.filter((v) => v === 0).length)
}

// voters: 0
Expand All @@ -49,6 +49,39 @@ describe('Finite Field ElGamal Voting', () => {
vote(1, [0, 1])
vote(1, [1, 0])
vote(2, [1, 1])
})

it('vote (3 voters)', () => {
const vote = (_result: number, _votes: number[]): void => {
const [sp, { h: pk, sk }] = FFelGamal.SystemSetup.generateSystemParametersAndKeys(1319, 2)

const log = false

const votes: FFelGamal.Cipher[] = []
for (const vote of _votes) {
vote === 1 && votes.push(FFelGamal.Voting.generateYesVote(sp, pk))
vote === 0 && votes.push(FFelGamal.Voting.generateNoVote(sp, pk))
}

const result = FFelGamal.Voting.tallyVotes(sp, sk, votes)
const summary = FFelGamal.Voting.getSummary(votes.length, result)
log &&
console.log(
_result,
_votes,
result,
'Total:',
summary.total,
'| Yes:',
summary.yes,
'| No:',
summary.no
)

expect(result).to.equal(_result)
expect(summary.yes).to.equal(_votes.filter((v) => v === 1).length)
expect(summary.no).to.equal(_votes.filter((v) => v === 0).length)
}

// voters: 3
// results: 2^3 = 8
Expand All @@ -60,6 +93,39 @@ describe('Finite Field ElGamal Voting', () => {
vote(2, [1, 0, 1])
vote(2, [1, 1, 0])
vote(3, [1, 1, 1])
})

it('vote (4 voters)', () => {
const vote = (_result: number, _votes: number[]): void => {
const [sp, { h: pk, sk }] = FFelGamal.SystemSetup.generateSystemParametersAndKeys(1319, 2)

const log = false

const votes: FFelGamal.Cipher[] = []
for (const vote of _votes) {
vote === 1 && votes.push(FFelGamal.Voting.generateYesVote(sp, pk))
vote === 0 && votes.push(FFelGamal.Voting.generateNoVote(sp, pk))
}

const result = FFelGamal.Voting.tallyVotes(sp, sk, votes)
const summary = FFelGamal.Voting.getSummary(votes.length, result)
log &&
console.log(
_result,
_votes,
result,
'Total:',
summary.total,
'| Yes:',
summary.yes,
'| No:',
summary.no
)

expect(result).to.equal(_result)
expect(summary.yes).to.equal(_votes.filter((v) => v === 1).length)
expect(summary.no).to.equal(_votes.filter((v) => v === 0).length)
}

// voters: 4
// results: 2^4 = 16
Expand All @@ -80,4 +146,43 @@ describe('Finite Field ElGamal Voting', () => {
vote(3, [1, 1, 1, 0])
vote(4, [1, 1, 1, 1])
})

it('larger vote (20 voters, 48-bit prime modulo)', () => {
const vote = (_result: number, _votes: number[]): void => {
const [sp, { h: pk, sk }] = FFelGamal.SystemSetup.generateSystemParametersAndKeys(
202178360940839,
4
)

const log = false

const votes: FFelGamal.Cipher[] = []
for (const vote of _votes) {
vote === 1 && votes.push(FFelGamal.Voting.generateYesVote(sp, pk))
vote === 0 && votes.push(FFelGamal.Voting.generateNoVote(sp, pk))
}

const result = FFelGamal.Voting.tallyVotes(sp, sk, votes)
const summary = FFelGamal.Voting.getSummary(votes.length, result)
log &&
console.log(
_result,
_votes,
result,
'Total:',
summary.total,
'| Yes:',
summary.yes,
'| No:',
summary.no
)

expect(result).to.equal(_result)
expect(summary.yes).to.equal(_votes.filter((v) => v === 1).length)
expect(summary.no).to.equal(_votes.filter((v) => v === 0).length)
}

// voters: 20
vote(10, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
})
})
50 changes: 26 additions & 24 deletions test/helper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@ describe('Global Helper Test', () => {

it('should invert BNs', () => {
const base = 10
const modulus = 4

// with moving to reduction context, only odd modulus >3 are valid
// see here: https://github.com/indutny/bn.js/issues/193#issuecomment-471473625
// should be no problem -> all primes are odd anyway
const modulus = 7

// inverse a mod modulus = c
const tests = [
{ a: 0, c: 0 }, // none
{ a: 0, c: 0 },
{ a: 1, c: 1 },
{ a: 2, c: 1 }, // none
{ a: 3, c: 3 },
{ a: 4, c: 0 }, // none
{ a: 5, c: 1 },
{ a: 6, c: 1 }, // none
{ a: 7, c: 3 },
{ a: 8, c: 0 }, // none
{ a: 9, c: 1 },
{ a: 2, c: 4 },
{ a: 3, c: 5 },
{ a: 4, c: 2 },
{ a: 5, c: 3 },
{ a: 6, c: 6 },
{ a: 7, c: 0 },
{ a: 8, c: 1 },
{ a: 9, c: 4 },
]

for (const test of tests) {
Expand All @@ -34,7 +38,8 @@ describe('Global Helper Test', () => {
const result = GlobalHelper.invmBN(a, new BN(modulus, base))

const log = false
log && console.log('a:', a.toNumber(), ', c:', c.toNumber(), 'res:', result.toNumber())
log &&
console.log(`a invMod modulus: ${a} mod 4`, ', c:', c.toNumber(), 'res:', result.toNumber())
expect(result.eq(c)).to.be.true
}
})
Expand Down Expand Up @@ -188,29 +193,26 @@ describe('Global Helper Test', () => {

it('should divide BNs', () => {
const base = 10
const modulus = 4
const modulus = 7

// a / b = c
const tests = [
{ a: 0, b: 0, c: 0 }, // none

{ a: 1, b: 0, c: 0 }, // none
{ a: 1, b: 1, c: 1 },

{ a: 2, b: 0, c: 0 }, // none
{ a: 2, b: 1, c: 2 },
{ a: 2, b: 2, c: 2 },

{ a: 2, b: 2, c: 1 },
{ a: 3, b: 0, c: 0 }, // none
{ a: 3, b: 1, c: 3 },
{ a: 3, b: 2, c: 3 },
{ a: 3, b: 2, c: 5 },
{ a: 3, b: 3, c: 1 },

{ a: 4, b: 0, c: 0 }, // none
{ a: 4, b: 1, c: 0 }, // none
{ a: 4, b: 2, c: 0 }, // none
{ a: 4, b: 3, c: 0 }, // none
{ a: 4, b: 4, c: 0 }, // none
{ a: 4, b: 0, c: 0 },
{ a: 4, b: 1, c: 4 },
{ a: 4, b: 2, c: 2 },
{ a: 4, b: 3, c: 6 },
{ a: 4, b: 4, c: 1 },
{ a: 8, b: 7, c: 0 },
]

for (const test of tests) {
Expand Down Expand Up @@ -332,7 +334,7 @@ describe('Global Helper Test', () => {
// a very long time to execute.
const filterOutliers = (array: number[]): number[] => {
const arrMean = mean(array)
return array.filter(value => value / arrMean < 50)
return array.filter((value) => value / arrMean < 50)
}

const safeEqualityCheck = (a: Buffer, b: Buffer, equalInputs: boolean): number => {
Expand Down

0 comments on commit 0ba589a

Please sign in to comment.