-
Notifications
You must be signed in to change notification settings - Fork 277
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(sdk-lib-mpc): add implementation of paillier-blum #3940
Changes from all commits
d7b4a64
c4315e8
b4f6afb
1f0d3ca
28e3fd5
820f19f
d0f701a
29bb06f
9fcf9cb
296af04
a618484
74cd01b
ed1bf0c
c0eda1f
4b20288
7e3836e
850e86a
e3ff77c
244d72e
519e200
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import * as bcu from 'bigint-crypto-utils'; | ||
import { PublicKey, PrivateKey, KeyPair } from 'paillier-bigint'; | ||
import { prove } from './paillierBlumProof'; | ||
import { DeserializedKeyPairWithPaillierBlumProof, RawPaillierKey } from './types'; | ||
|
||
// Implementation based on paillier-bigint's generateRandomKeys | ||
export async function generatePaillierKey(bitlength = 3072): Promise<RawPaillierKey> { | ||
let [p, q, n] = [BigInt(0), BigInt(0), BigInt(0)]; | ||
do { | ||
p = await bcu.prime(Math.floor(bitlength / 2) + 1); | ||
q = await bcu.prime(Math.floor(bitlength / 2)); | ||
n = p * q; | ||
} while (q === p || q % BigInt(4) !== BigInt(3) || p % BigInt(4) !== BigInt(3) || bcu.bitLength(n) !== bitlength); | ||
const lambda = (p - BigInt(1)) * (q - BigInt(1)); | ||
const mu = bcu.modInv(lambda, n); | ||
return { n, lambda, mu, p, q }; | ||
} | ||
|
||
export async function generatePaillierKeyWithProof( | ||
bitlength = 3072 | ||
): Promise<DeserializedKeyPairWithPaillierBlumProof> { | ||
const key = await generatePaillierKey(bitlength); | ||
const proof = await prove(key.p, key.q); | ||
return { ...key, ...proof }; | ||
} | ||
|
||
export function rawPaillierKeyToPaillierKey(n: bigint, lambda: bigint, mu: bigint, p: bigint, q: bigint): KeyPair { | ||
const g = n + BigInt(1); | ||
const publicKey = new PublicKey(n, g); | ||
const privateKey = new PrivateKey(lambda, mu, publicKey, p, q); | ||
return { publicKey, privateKey }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
export * as EcdsaTypes from './types'; | ||
export * as EcdsaRangeProof from './rangeproof'; | ||
export * as EcdsaPaillierProof from './paillierproof'; | ||
export * as EcdsaRangeProof from './rangeProof'; | ||
export * as EcdsaPaillierProof from './paillierProof'; | ||
export * as EcdsaZkVProof from './zkVProof'; | ||
|
||
export * as EcdsaNoSmallFactorsProof from './noSmallFactorsProof'; | ||
export * from './generatePaillierKey'; | ||
export const minModulusBitLength = 3072; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* Implementation of No Small Factors ($\Pi^\text{fac}). | ||
* https://eprint.iacr.org/2020/492.pdf Section B.4 | ||
*/ | ||
import { createHash } from 'crypto'; | ||
import { bitLength, randBetween } from 'bigint-crypto-utils'; | ||
import { modPow } from 'bigint-mod-arith'; | ||
import { bigIntFromBufferBE, bigIntToBufferBE } from '../../util'; | ||
import { DeserializedNoSmallFactorsProof } from './types'; | ||
|
||
const ORDER = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'); | ||
const ELL = BigInt(256); | ||
const EPSILON = BigInt(BigInt(2) * ELL); | ||
|
||
/** | ||
* Generate pseudo-random challenge value $e$ for $(N, w)$. | ||
* @param N - the prime number to verify is a product of two large primes. | ||
* @param w - a random number with the same bitLength as N, that satisfies the Jacobi of w is -1 wrt N. | ||
* @returns {bigint} - challenge value $e$. | ||
*/ | ||
function generateE(N: bigint, w: bigint): bigint { | ||
const digest = createHash('shake256', { outputLength: 1 + Math.floor((bitLength(ORDER) + 7) / 8) }) | ||
.update(bigIntToBufferBE(N)) | ||
.update('$') | ||
.update(bigIntToBufferBE(w)) | ||
.update('$') | ||
.digest(); | ||
const e = bigIntFromBufferBE(digest.subarray(1)) % ORDER; | ||
if (digest[0] & 1) { | ||
return -e; | ||
} | ||
return e; | ||
} | ||
|
||
/** | ||
* Calculate the closest integer square root of $n$. | ||
* @param n - the number to calculate the square root of. | ||
* @returns {bigint} - $n$'s closest integer square root. | ||
*/ | ||
function isqrt(n: bigint): bigint { | ||
if (n < BigInt(0)) { | ||
throw new Error(); | ||
} | ||
if (n < BigInt(2)) { | ||
return n; | ||
} | ||
function newtonIteration(n: bigint, x0: bigint) { | ||
const x1 = (n / x0 + x0) >> BigInt(1); | ||
if (x0 === x1 || x0 === x1 - BigInt(1)) { | ||
return x0; | ||
} | ||
return newtonIteration(n, x1); | ||
} | ||
return newtonIteration(n, BigInt(1)); | ||
} | ||
|
||
/** | ||
* Prove that $n0$ has no small factors, where $n0$ is the product of two large primes. | ||
* @param p - a large prime. | ||
* @param q - a large prime. | ||
* @param w - a random number with the same bitLength as $p * q$, that satisfies the Jacobi of w is -1 wrt $n0$. | ||
* @param nHat - a safe bi-prime, such as that returned from rangeProof.generateNTilde. | ||
* @param s - security parameters for $nHat$ such as the $h1$ value returned from rangeProof.generateNTilde. | ||
* @param t - security parameters for $nHat$ such as the $h2$ value returned from rangeProof.generateNTilde. | ||
* @returns proof that the product of $p * q$ has no small factors. | ||
*/ | ||
export function prove( | ||
p: bigint, | ||
q: bigint, | ||
w: bigint, | ||
nHat: bigint, | ||
s: bigint, | ||
t: bigint | ||
): DeserializedNoSmallFactorsProof { | ||
const n0 = p * q; | ||
const e = generateE(n0, w); | ||
const sqrtN0 = isqrt(n0); | ||
const alpha = randBetween(sqrtN0 << (ELL + EPSILON), -sqrtN0 << (ELL + EPSILON)); | ||
const beta = randBetween(sqrtN0 << (ELL + EPSILON), -sqrtN0 << (ELL + EPSILON)); | ||
const rho = randBetween((nHat * n0) << ELL, -(nHat * n0) << ELL); | ||
// Commit to p. | ||
const mu = randBetween(BigInt(1) << ELL, BigInt(-1) << ELL); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think mu should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
const P = (modPow(s, p, nHat) * modPow(t, mu, nHat)) % nHat; | ||
// Commit to q. | ||
const nu = randBetween(BigInt(1) << ELL, BigInt(-1) << ELL); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as mu There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
const Q = (modPow(s, q, nHat) * modPow(t, nu, nHat)) % nHat; | ||
// Commit to alpha. | ||
const x = randBetween(BigInt(1) << (ELL + EPSILON), BigInt(-1) << (ELL + EPSILON)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1 and -1 should be nHat and -nHat respectively. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
const A = (modPow(s, alpha, nHat) * modPow(t, x, nHat)) % nHat; | ||
// Commit to beta. | ||
const y = randBetween(BigInt(1) << (ELL + EPSILON), BigInt(-1) << (ELL + EPSILON)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1 and -1 should be nHat and -nHat respectively. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
const B = (modPow(s, beta, nHat) * modPow(t, y, nHat)) % nHat; | ||
// Commit to Q and alpha. | ||
const r = randBetween((nHat * n0) << (ELL + EPSILON), -(nHat * n0) << (ELL + EPSILON)); | ||
const T = (modPow(Q, alpha, nHat) * modPow(t, r, nHat)) % nHat; | ||
|
||
const rhoHat = rho - nu * p; | ||
const z1 = alpha + e * p; | ||
const z2 = beta + e * q; | ||
const w1 = x + e * mu; | ||
const w2 = y + e * nu; | ||
const v = r + e * rhoHat; | ||
|
||
return { P, Q, A, B, T, rho, z1, z2, w1, w2, v }; | ||
} | ||
|
||
/** | ||
* Verify that $n0$ is not the product of any small factors. | ||
* @param n0 - a modulus that is the product of $p$ and $q$. | ||
* @param w - a random number with the same bitLength as $n0$, that satisfies the Jacobi of w is -1 wrt $n0$. | ||
* @param nHat - a safe bi-prime, such as that returned from rangeProof.generateNTilde. | ||
* @param s - security parameters for $nHat$ such as the $h1$ value returned from rangeProof.generateNTilde. | ||
* @param t - security parameters for $nHat$ such as the $h2$ value returned from rangeProof.generateNTilde. | ||
* @param proof - a proof generated by noSmallFactors.prove. | ||
* @returns true if verification successful. | ||
*/ | ||
export function verify( | ||
n0: bigint, | ||
w: bigint, | ||
nHat: bigint, | ||
s: bigint, | ||
t: bigint, | ||
proof: DeserializedNoSmallFactorsProof | ||
): boolean { | ||
const { P, Q, A, B, T, rho, z1, z2, w1, w2, v } = proof; | ||
const e = generateE(n0, w); | ||
const sqrtN0 = isqrt(n0); | ||
const R = (modPow(s, n0, nHat) * modPow(t, rho, nHat)) % nHat; | ||
if ((modPow(s, z1, nHat) * modPow(t, w1, nHat)) % nHat !== (A * modPow(P, e, nHat)) % nHat) { | ||
throw new Error('Could not verify no small factors proof'); | ||
} | ||
if ((modPow(s, z2, nHat) * modPow(t, w2, nHat)) % nHat !== (B * modPow(Q, e, nHat)) % nHat) { | ||
throw new Error('Could not verify no small factors proof'); | ||
} | ||
if ((modPow(Q, z1, nHat) * modPow(t, v, nHat)) % nHat !== (T * modPow(R, e, nHat)) % nHat) { | ||
throw new Error('Could not verify no small factors proof'); | ||
} | ||
if (z1 < -sqrtN0 << (ELL + EPSILON) || z1 > sqrtN0 << (ELL + EPSILON)) { | ||
throw new Error('Could not verify no small factors proof'); | ||
} | ||
if (z2 < -sqrtN0 << (ELL + EPSILON) || z2 > sqrtN0 << (ELL + EPSILON)) { | ||
throw new Error('Could not verify no small factors proof'); | ||
} | ||
return true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { createHash } from 'crypto'; | ||
import { bitLength, randBits, isProbablyPrime } from 'bigint-crypto-utils'; | ||
import { gcd, modInv, modPow } from 'bigint-mod-arith'; | ||
import { bigIntFromBufferBE, bigIntToBufferBE } from '../../util'; | ||
import { DeserializedPaillierBlumProof } from './types'; | ||
|
||
// Security parameter. | ||
const m = 80; | ||
|
||
/** | ||
* Generate psuedo-random quadratic residue for (N, w, i). | ||
* @param N - the prime number to verify is a product of two large primes. | ||
* @param w - a random number with the same bitLength as N, that satisfies the Jacobi of w is -1 wrt N. | ||
* @returns {bigint[]} - set of challenges for N | ||
*/ | ||
function generateY(N, w): bigint[] { | ||
const NBuf = bigIntToBufferBE(N); | ||
const wBuf = bigIntToBufferBE(w, NBuf.length); | ||
let counter = 0; | ||
return Array(m) | ||
.fill(null) | ||
.map((_) => { | ||
while (true) { | ||
let h = bigIntFromBufferBE( | ||
// TypeScript doesn't like us using `outputLength` in the transform options, | ||
// but it is required for shake256. | ||
createHash('shake256', { outputLength: Math.floor((bitLength(N) + 7) / 8) }) | ||
.update(Buffer.from([counter++])) | ||
.update('$') | ||
.update(NBuf) | ||
.update('$') | ||
.update(wBuf) | ||
.update('$') | ||
.digest() | ||
); | ||
h = (h * h) % N; | ||
if (gcd(h, N) === BigInt(1)) { | ||
return h; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The sampling of y here deviates from https://eprint.iacr.org/2020/492.pdf section 4.3 and the non-interactive version at https://www.zkdocs.com/docs/zkdocs/zero-knowledge-protocols/product-primes/paillier_blum_modulus/#non-interactive-protocol Here it seems to be sampling y so that it will be the same as y' by implicitly making a = 0, b = 0 (and thus not including a, b in the proof). This will make only certain co-primes of N be picked as the challenge y, violating generateY() being a random oracle. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, for y' (and therefore y here) to be suitable so that its 4th root is well defined, it needs to be a quadratic residue modulo N. Here by making y = h^2 and y (mod N) = 1, y is indeed a quadratic residue N but only for the cases where remainder is 1. Other quadratic residues are missed where x^2 === y (mod N) is other values (e.g. 4, 9, etc.). This further limits the sampling domain of the challenge value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could refer to Fireblocks' implementation https://github.com/fireblocks/mpc-lib/blob/main/src/common/crypto/paillier/paillier_zkp.c#L648 Basically, due to p, q === 3 mod 4 and jacobi(w, N) === -1, it's guaranteed one of (0, 0), (0, 1), (1, 0), (1,1) for (a, b) will make y' a quadratic residue modulo N. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
} | ||
} | ||
}); | ||
} | ||
|
||
// https://en.wikipedia.org/wiki/Jacobi_symbol#Implementation_in_C++ | ||
function jacobi(a, n): bigint { | ||
// a/n is represented as (a,n) | ||
if (n <= BigInt(0)) { | ||
throw new Error('n must greater than 0'); | ||
} | ||
if (n % BigInt(2) !== BigInt(1)) { | ||
throw new Error('n must be odd'); | ||
} | ||
// step 1 | ||
a = a % n; | ||
let t = BigInt(1); | ||
let r; | ||
// step 3 | ||
while (a !== BigInt(0)) { | ||
// step 2 | ||
while (a % BigInt(2) === BigInt(0)) { | ||
a /= BigInt(2); | ||
r = n % BigInt(8); | ||
if (r === BigInt(3) || r === BigInt(5)) { | ||
t = -t; | ||
} | ||
} | ||
// step 4 | ||
r = n; | ||
n = a; | ||
a = r; | ||
if (a % BigInt(4) === BigInt(3) && n % BigInt(4) === BigInt(3)) { | ||
t = -t; | ||
} | ||
a = a % n; | ||
} | ||
if (n === BigInt(1)) { | ||
return t; | ||
} | ||
return BigInt(0); | ||
} | ||
|
||
/** | ||
* Prove that a modulus (p*q) is the product of two large safe primes (p and q). | ||
* @param {bigint} p The larger prime factor of the modulus | ||
* @param {bigint} q The smaller prime factor of the modulus. | ||
* @returns {DeserializedPaillierBlumProof} The proof that the modulus is the product of two large primes. | ||
*/ | ||
export async function prove(p: bigint, q: bigint): Promise<DeserializedPaillierBlumProof> { | ||
// Prover selects random w with Jacobi symbol 1 wrt N. | ||
const N = p * q; | ||
const l = (p - BigInt(1)) * (q - BigInt(1)); | ||
const d = modInv(N, l); | ||
let w; | ||
while (true) { | ||
w = bigIntFromBufferBE(Buffer.from(await randBits(bitLength(N)))); | ||
if (jacobi(w, N) === BigInt(-1)) { | ||
break; | ||
} | ||
} | ||
// This is calculating the inverse of the function y^4 mod N, | ||
// i.e.y ^ (1 / 4), where N = pq is a blum integer using HOC - Fact 2.160 | ||
// from cacr.uwaterloo.ca / hac / about / chap2.pdf | ||
// Prover generates y_i. | ||
const y = generateY(N, w); | ||
// Prover calculates z_i = y_i ^ d mod N | ||
const z = y.map((y_i) => modPow(y_i, d, N)); | ||
// Prover calculates x_i = y_i ^ 1/4 mod N using [HOC - Fact 2.160] | ||
const e = ((l + BigInt(4)) / BigInt(8)) ** BigInt(2); | ||
const x = y.map((y_i) => modPow(y_i, e, N)); | ||
return { w, x, z }; | ||
} | ||
|
||
/** | ||
* Verify that N is the product of two large primes. | ||
* @param {bigint} N The prime number being verified. | ||
* @param {DeserializedPaillierBlumProof} The proof to verify N is a product of two large primes. | ||
* @returns {boolean} True if N is a product of two large primes, and false otherwise. | ||
*/ | ||
export async function verify(N: bigint, { w, x, z }: DeserializedPaillierBlumProof): Promise<boolean> { | ||
// Verifier checks N > 1. | ||
if (N <= 1) { | ||
throw new Error('N must be greater than 1'); | ||
} | ||
// Verifier checks N is odd. | ||
if (N % BigInt(2) !== BigInt(1)) { | ||
throw new Error('N must be an odd number'); | ||
} | ||
// Verifier checks N is not prime. | ||
if (await isProbablyPrime(N, 24)) { | ||
throw new Error('N must be a composite number'); | ||
} | ||
// Verifier checks that the Jacobi symbol for w is 1 wrt N. | ||
if (jacobi(w, N) !== BigInt(-1)) { | ||
throw new Error('Jacobi symbol of w must be -1 wrt to N'); | ||
} | ||
// Verifier generates y_i. | ||
const y = generateY(N, w); | ||
for (let i = 0; i < m; i++) { | ||
// Verifier checks z_i ^ N mod N == y_i. | ||
if (modPow(z[i], N, N) !== y[i]) { | ||
throw new Error(`Paillier verification of z[${i}] failed`); | ||
} | ||
// Verifier checks x_i ^ 4 mod N == y_i. | ||
if (modPow(x[i], 4, N) !== y[i]) { | ||
throw new Error(`Paillier verification of x[${i}] failed`); | ||
} | ||
} | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will need to be optimized in a follow up.