zerok
Version:
Zero knowledge proof certification and validation for any datatype
131 lines (106 loc) • 4.01 kB
JavaScript
const crypto = require('crypto')
const paillier = require('paillier-js')
const bigInt = require('big-integer')
const rEncrypt = function ({ n, g }, message) {
const _n2 = n.pow(2)
let r
do {
r = bigInt.randBetween(2, n)
} while (r.leq(1))
return {r, cipher: g.modPow(bigInt(message), _n2).multiply(r.modPow(n, _n2)).mod(_n2)}
}
const getCoprime = (target) => {
const bits = Math.floor(Math.log2(target))
while (true) {
const lowerBound = bigInt(2).pow(bits-1).plus(1)
let possible = lowerBound.plus(bigInt.rand(bits)).or(1)
const result = bigInt(possible)
if (possible.gt(bigInt(2).pow(1024))) return result
while(target > 0) {
[possible, target] = [target, possible.mod(target)]
}
if (possible.eq(bigInt(1))) return result
}
}
const encryptWithProof = (publicKey, message, bits=512) => {
const {r: random, cipher} = rEncrypt(publicKey, message)
const om = getCoprime(publicKey.n)
const ap = om.modPow(publicKey.n, publicKey._n2)
let proof = {}
const gmk = publicKey.g.modPow(bigInt(message), publicKey._n2)
const uk = cipher.times(gmk.modInv(publicKey._n2)).mod(publicKey._n2)
if (message.toString() === message.toString()) {
proof[`a`] = ap.toString()
} else {
const zk = getCoprime(publicKey.n)
const ek = bigInt.randBetween(2, bigInt(2).pow(bits).subtract(1))
const zn = zk.modPow(publicKey.n, publicKey._n2)
const ue = uk.modPow(ek, publicKey._n2)
const ak = zn.times(ue.modInv(publicKey._n2)).mod(publicKey._n2)
proof[`a`] = ak.toString()
proof[`z`] = zk.toString()
proof[`e`] = ek.toString()
}
const hash = crypto.createHash('sha256').update(Object.values(proof).filter((_, i) => i % 3 === 0).join('')).digest('hex')
const esum = Object.values(proof).filter((_, i) => i % 3 === 2).reduce((acc, ek) => acc.plus(bigInt(ek)), bigInt(0))
const ep = bigInt(hash, 16).subtract(esum).mod(bigInt(2).pow(256))
const rep = random.modPow(ep, publicKey.n)
const zp = om.times(rep).mod(publicKey.n)
proof[`e`] = ep.toString()
proof[`z`] = zp.toString()
return {cipher: cipher.toString(), proof}
}
const verifyMessage = (publicKey, cipher, proof, validMessage) => {
const cipherBigInt = bigInt(cipher)
const hash = crypto.createHash('sha256').update(proof.a).digest('hex')
let gmk
if (publicKey.g instanceof bigInt) {
// you can safely call modPow
gmk = publicKey.g.modPow(validMessage, publicKey._n2)
} else {
// convert to bigInt first
publicKey.g = bigInt(publicKey.g);
}
gmk = publicKey.g.modPow(validMessage, publicKey._n2)
const uk = cipherBigInt.times(gmk.modInv(publicKey._n2)).mod(publicKey._n2)
const ek = bigInt(proof.e)
if (!bigInt(hash, 16).eq(ek)) {
return false
}
const zk = bigInt(proof.z)
const ak = bigInt(proof.a)
const zkn = zk.modPow(publicKey.n, publicKey._n2)
const uke = uk.modPow(ek, publicKey._n2)
const akue = ak.times(uke).mod(publicKey._n2)
return zkn.eq(akue)
}
function stringToBigInt(str) {
const buf = Buffer.from(str)
let hexString = buf.toString('hex')
if (hexString.length % 2 != 0) {
hexString = '0' + hexString // pad with a leading zero if necessary
}
return bigInt(hexString, 16)
}
module.exports = function(bits) {
const zerok = this
if(!bits) bits = 512
let {publicKey, privateKey} = paillier.generateRandomKeys(bits)
this.keypair = {publicKey, privateKey}
this.newKey = (bits) => {
if(!bits) bits = 512
let keypair = paillier.generateRandomKeys(bits)
publicKey = keypair.publicKey
privateKey = keypair.privateKey
this.keypair = {publicKey, privateKey}
}
this.proof = (message) => {
message = stringToBigInt(message.toString())
return encryptWithProof(publicKey, message, bits)
}
this.verify = (message, certificate, pubkey) => {
if(!pubkey) pubkey = publicKey
message = stringToBigInt(message.toString())
return verifyMessage(pubkey, certificate.cipher, certificate.proof, message)
}
}