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

feat: use reduction context for all operations #256

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
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 => {
meck93 marked this conversation as resolved.
Show resolved Hide resolved
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 => {
meck93 marked this conversation as resolved.
Show resolved Hide resolved
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 => {
meck93 marked this conversation as resolved.
Show resolved Hide resolved
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