PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-lib-mpc/src/tss/ecdsa
Просмотр файла: rangeproof.ts
/**
* Zero Knowledge Range Proofs as described in (Two-party generation of DSA signatures)[1].
* [1]: https://reitermk.github.io/papers/2004/IJIS.pdf
*/
import { createHash } from 'crypto';
import { BaseCurve } from '../../curves';
import { PublicKey } from 'paillier-bigint';
import { bitLength, randBetween } from 'bigint-crypto-utils';
import { modInv, modPow } from 'bigint-mod-arith';
import {
DeserializedNtilde,
DeserializedNtildeProof,
RSAModulus,
RangeProof,
RangeProofWithCheck,
DeserializedNtildeWithProofs,
} from './types';
import { bigIntFromBufferBE, bigIntToBufferBE, randomPositiveCoPrimeTo } from '../../util';
import { minModulusBitLength } from './index';
import { generateSafePrimes } from '../../safePrime';
// 128 as recommend by https://blog.verichains.io/p/vsa-2022-120-multichain-key-extraction.
const ITERATIONS = 128;
async function generateModulus(
openSSLBytes: Uint8Array,
bitlength = minModulusBitLength,
retry = 10
): Promise<RSAModulus> {
if (bitlength < minModulusBitLength) {
// https://www.keylength.com/en/6/
// eslint-disable-next-line no-console
console.warn('Generating a modulus with less than 3072 is not recommended!');
}
const bitlengthP = Math.floor(bitlength / 2);
const bitlengthQ = bitlength - bitlengthP;
for (let i = 0; i < retry; i++) {
const [p, q] = await generateSafePrimes([bitlengthP, bitlengthQ], openSSLBytes);
const n = p * q;
// For large bit lengths, the probability of generating a modulus with the wrong bit length is very low.
if (bitLength(n) !== bitlength) {
continue;
}
return { n, q1: (p - BigInt(1)) / BigInt(2), q2: (q - BigInt(1)) / BigInt(2) };
}
throw new Error(
`Unable to generate modulus with bit length of ${bitlength} after ${retry} tries. Please try again or reach out to support@bitgo.com`
);
}
/**
* Generate "challenge" values for range proofs.
* @param {number} bitlength The bit length of the modulus to generate. This should
* be the same as the bit length of the paillier public keys used for MtA.
* @returns {DeserializedNtilde} The generated Ntilde values.
*/
export async function generateNtilde(
openSSLBytes: Uint8Array,
bitlength = minModulusBitLength
): Promise<DeserializedNtildeWithProofs> {
const { n: ntilde, q1, q2 } = await generateModulus(openSSLBytes, bitlength);
const [f1, f2] = await Promise.all([randomPositiveCoPrimeTo(ntilde), randomPositiveCoPrimeTo(ntilde)]);
const h1 = modPow(f1, BigInt(2), ntilde);
const h2 = modPow(h1, f2, ntilde);
const beta = modInv(f2, q1 * q2);
const [h1wrtH2Proofs, h2wrtH1Proofs] = await Promise.all([
generateNtildeProof(
{
h1: h1,
h2: h2,
ntilde: ntilde,
},
f2,
q1,
q2
),
generateNtildeProof(
{
h1: h2,
h2: h1,
ntilde: ntilde,
},
beta,
q1,
q2
),
]);
return {
ntilde,
h1,
h2,
ntildeProof: {
h1WrtH2: {
alpha: h1wrtH2Proofs.alpha,
t: h1wrtH2Proofs.t,
},
h2WrtH1: {
alpha: h2wrtH1Proofs.alpha,
t: h2wrtH1Proofs.t,
},
},
};
}
/**
* Generate iterations of Ntilde, h1, h2 discrete log proofs.
* @param {DeserializedNtilde} ntilde Ntilde, h1, h2 to generate the proofs for.
* @param {bigint} x Either alpha or beta depending on whether it is a discrete log proof of
* h1 w.r.t h2 or h2 w.r.t h1.
* @param {bigint} q1 The Sophie Germain prime associated with the first safe prime p1 used to generate Ntilde.
* @param {bigint} q2 The Sophie Germain prime associated with the second safe prime p2 used to generate Ntilde.
* @returns {NtildeProof} The generated Ntilde Proofs.
*/
export async function generateNtildeProof(
ntilde: DeserializedNtilde,
x: bigint,
q1: bigint,
q2: bigint
): Promise<DeserializedNtildeProof> {
const q1MulQ2 = q1 * q2;
const a: bigint[] = [];
const alpha: bigint[] = [];
let msgToHash: Buffer = Buffer.concat([
bigIntToBufferBE(ntilde.h1),
bigIntToBufferBE(ntilde.h2),
bigIntToBufferBE(ntilde.ntilde),
]);
for (let i = 0; i < ITERATIONS; i++) {
a.push(randBetween(q1MulQ2));
alpha.push(modPow(ntilde.h1, a[i], ntilde.ntilde));
msgToHash = Buffer.concat([msgToHash, bigIntToBufferBE(alpha[i], Math.ceil(bitLength(ntilde.ntilde) / 8))]);
}
const simulatedResponse = createHash('sha256').update(msgToHash).digest();
const t: bigint[] = [];
for (let i = 0; i < ITERATIONS; i++) {
// Get the ith bit from a buffer of bytes.
const ithBit = (simulatedResponse[Math.floor(i / 8)] >> (7 - (i % 8))) & 1;
t.push((a[i] + ((BigInt(ithBit) * x) % q1MulQ2)) % q1MulQ2);
}
return { alpha, t };
}
/**
* Verify discrete log proofs of h1 and h2 mod Ntilde.
* @param {DeserializedNtilde} ntilde Ntilde, h1, h2 to generate the proofs for.
* @param {DeserializedNtildeProof} ntildeProof Ntilde Proofs
* @returns {boolean} true if proof is verified, false otherwise.
*/
export async function verifyNtildeProof(
ntilde: DeserializedNtilde,
ntildeProof: DeserializedNtildeProof
): Promise<boolean> {
const h1ModNtilde = ntilde.h1 % ntilde.ntilde;
const h2ModNtilde = ntilde.h2 % ntilde.ntilde;
if (h1ModNtilde === BigInt(0) || h2ModNtilde === BigInt(0)) {
return false;
}
if (h1ModNtilde === BigInt(1) || h2ModNtilde === BigInt(1)) {
return false;
}
if (h1ModNtilde === h2ModNtilde) {
return false;
}
if (
ntildeProof.alpha.length > 256 ||
ntildeProof.alpha.length !== ITERATIONS ||
ntildeProof.t.length !== ITERATIONS
) {
return false;
}
let msgToHash: Buffer = Buffer.concat([
bigIntToBufferBE(ntilde.h1),
bigIntToBufferBE(ntilde.h2),
bigIntToBufferBE(ntilde.ntilde),
]);
for (let i = 0; i < ntildeProof.alpha.length; i++) {
msgToHash = Buffer.concat([
msgToHash,
bigIntToBufferBE(ntildeProof.alpha[i], Math.ceil(bitLength(ntilde.ntilde) / 8)),
]);
}
const simulatedResponse = createHash('sha256').update(msgToHash).digest();
for (let i = 0; i < ntildeProof.alpha.length; i++) {
// Get the ith bit from a buffer of bytes.
const ithBit = (simulatedResponse[Math.floor(i / 8)] >> (7 - (i % 8))) & 1;
const h1PowTi = modPow(ntilde.h1, ntildeProof.t[i], ntilde.ntilde);
const h2PowCi = modPow(ntilde.h2, BigInt(ithBit), ntilde.ntilde);
const alphaMulh2PowCi = (ntildeProof.alpha[i] * h2PowCi) % ntilde.ntilde;
if (h1PowTi !== alphaMulh2PowCi) {
return false;
}
}
return true;
}
/**
* Generate a zero-knowledge range proof that an encrypted value is "small".
* @param {BaseCurve} curve An elliptic curve to use for group operations.
* @param {number} modulusBits The bit count of the prover's public key.
* @param {PublicKey} pk The prover's public key.
* @param {DeserializedNtilde} ntilde The verifier's Ntilde values.
* @param {bigint} c The ciphertext.
* @param {bigint} m The plaintext.
* @param {bigint} r The obfuscation value used to encrypt m.
* @returns {RangeProof} The generated proof.
*/
export async function prove(
curve: BaseCurve,
modulusBits: number,
pk: PublicKey,
ntilde: DeserializedNtilde,
c: bigint,
m: bigint,
r: bigint
): Promise<RangeProof> {
const modulusBytes = Math.floor((modulusBits + 7) / 8);
const q = curve.order();
const q3 = q ** BigInt(3);
const qntilde = q * ntilde.ntilde;
const q3ntilde = q3 * ntilde.ntilde;
const alpha = randBetween(q3);
const beta = await randomPositiveCoPrimeTo(pk.n);
const gamma = randBetween(q3ntilde);
const rho = randBetween(qntilde);
const z = (modPow(ntilde.h1, m, ntilde.ntilde) * modPow(ntilde.h2, rho, ntilde.ntilde)) % ntilde.ntilde;
const u = (modPow(pk.g, alpha, pk._n2) * modPow(beta, pk.n, pk._n2)) % pk._n2;
const w = (modPow(ntilde.h1, alpha, ntilde.ntilde) * modPow(ntilde.h2, gamma, ntilde.ntilde)) % ntilde.ntilde;
const hash = createHash('sha256');
hash.update('\x06\x00\x00\x00\x00\x00\x00\x00');
hash.update(bigIntToBufferBE(pk.n, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(pk.g, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(c, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(z, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(u, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(w, modulusBytes));
hash.update('$');
const e = bigIntFromBufferBE(hash.digest()) % q;
const s = (modPow(r, e, pk.n) * beta) % pk.n;
const s1 = e * m + alpha;
const s2 = e * rho + gamma;
return { z, u, w, s, s1, s2 };
}
/**
* Verify a zero-knowledge range proof that an encrypted value is "small".
* @param {BaseCurve} curve An elliptic curve to use for group operations.
* @param {number} modulusBits The bit count of the prover's public key.
* @param {PublicKey} pk The prover's public key.
* @param {DeserializedNtilde} ntilde The verifier's Ntilde values.
* @param {RangeProof} proof The range proof.
* @param {bigint} c The ciphertext.
* @returns {boolean} True if verification succeeds.
*/
export function verify(
curve: BaseCurve,
modulusBits: number,
pk: PublicKey,
ntilde: DeserializedNtilde,
proof: RangeProof,
c: bigint
): boolean {
if (proof.u === BigInt(0) || proof.s === BigInt(0)) {
return false;
}
const modulusBytes = Math.floor((modulusBits + 7) / 8);
const q = curve.order();
const q3 = q ** BigInt(3);
if (proof.s1 > q3) {
return false;
}
const hash = createHash('sha256');
hash.update('\x06\x00\x00\x00\x00\x00\x00\x00');
hash.update(bigIntToBufferBE(pk.n, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(pk.g, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(c, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.z, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.u, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.w, modulusBytes));
hash.update('$');
const e = bigIntFromBufferBE(hash.digest()) % q;
let products: bigint;
products = (modPow(pk.g, proof.s1, pk._n2) * modPow(proof.s, pk.n, pk._n2) * modPow(c, -e, pk._n2)) % pk._n2;
if (proof.u !== products) {
return false;
}
products =
(((modPow(ntilde.h1, proof.s1, ntilde.ntilde) * modPow(ntilde.h2, proof.s2, ntilde.ntilde)) % ntilde.ntilde) *
modPow(proof.z, -e, ntilde.ntilde)) %
ntilde.ntilde;
return proof.w === products;
}
/**
* Generate a zero-knowledge range proof that a homomorphically manipulated value is "small".
* @param {BaseCurve} curve An elliptic curve to use for group operations.
* @param {number} modulusBits The bit count of the prover's public key.
* @param {PublicKey} pk The prover's public key.
* @param {DeserializedNtilde} ntilde The verifier's Ntilde values.
* @param {bigint} c1 The original ciphertext.
* @param {bigint} c2 The manipulated ciphertext.
* @param {bigint} x The plaintext value multiplied by the original plaintext.
* @param {bigint} y The plaintext value that is added to x.
* @param {bigint} r The obfuscation value used to encrypt x.
* @param {bigint} X The curve's base point raised to x.
* @returns {RangeProofWithCheck} The generated proof.
*/
export async function proveWithCheck(
curve: BaseCurve,
modulusBits: number,
pk: PublicKey,
ntilde: DeserializedNtilde,
c1: bigint,
c2: bigint,
x: bigint,
y: bigint,
r: bigint,
X: bigint
): Promise<RangeProofWithCheck> {
const modulusBytes = Math.floor((modulusBits + 7) / 8);
const q = curve.order();
const q3 = q ** BigInt(3);
const q7 = q ** BigInt(7);
const qntilde = q * ntilde.ntilde;
const q3ntilde = q3 * ntilde.ntilde;
const alpha = randBetween(q3);
const rho = randBetween(qntilde);
const sigma = randBetween(qntilde);
const tau = randBetween(q3ntilde);
const rhoprm = randBetween(q3ntilde);
const beta = await randomPositiveCoPrimeTo(pk.n);
const gamma = randBetween(q7);
const u = curve.basePointMult(curve.scalarReduce(alpha));
const z = (modPow(ntilde.h1, x, ntilde.ntilde) * modPow(ntilde.h2, rho, ntilde.ntilde)) % ntilde.ntilde;
const zprm = (modPow(ntilde.h1, alpha, ntilde.ntilde) * modPow(ntilde.h2, rhoprm, ntilde.ntilde)) % ntilde.ntilde;
const t = (modPow(ntilde.h1, y, ntilde.ntilde) * modPow(ntilde.h2, sigma, ntilde.ntilde)) % ntilde.ntilde;
const v =
(((modPow(c1, alpha, pk._n2) * modPow(pk.g, gamma, pk._n2)) % pk._n2) * modPow(beta, pk.n, pk._n2)) % pk._n2;
const w = (modPow(ntilde.h1, gamma, ntilde.ntilde) * modPow(ntilde.h2, tau, ntilde.ntilde)) % ntilde.ntilde;
const hash = createHash('sha256');
hash.update('\x0d\x00\x00\x00\x00\x00\x00\x00');
hash.update(bigIntToBufferBE(pk.n, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(pk.g, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(X, 33));
hash.update('$');
hash.update(bigIntToBufferBE(c1, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(c2, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(u, 33));
hash.update('$');
hash.update(bigIntToBufferBE(z, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(zprm, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(t, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(v, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(w, modulusBytes));
hash.update('$');
const e = bigIntFromBufferBE(hash.digest()) % q;
const s = (modPow(r, e, pk.n) * beta) % pk.n;
const s1 = e * x + alpha;
const s2 = e * rho + rhoprm;
const t1 = e * y + gamma;
const t2 = e * sigma + tau;
return { z, zprm, t, v, w, s, s1, s2, t1, t2, u };
}
/**
* Verify a zero-knowledge range proof that a homomorphically manipulated value is "small".
* @param {BaseCurve} curve An elliptic curve to use for group operations.
* @param {number} modulusBits The bit count of the prover's public key.
* @param {PublicKey} pk The prover's public key.
* @param {DeserializedNtilde} ntilde The verifier's Ntilde values.
* @param {RangeProofWithCheck} proof The range proof.
* @param {bigint} c1 The original ciphertext.
* @param {bigint} c2 The manipulated ciphertext.
* @param {bigint} X The curve's base point raised to x.
* @returns {boolean} True if verification succeeds.
*/
export function verifyWithCheck(
curve: BaseCurve,
modulusBits: number,
pk: PublicKey,
ntilde: DeserializedNtilde,
proof: RangeProofWithCheck,
c1: bigint,
c2: bigint,
X: bigint
): boolean {
const modulusBytes = Math.floor((modulusBits + 7) / 8);
const q = curve.order();
const q3 = q ** BigInt(3);
const q7 = q ** BigInt(7);
if (proof.s1 > q3) {
return false;
}
if (proof.t1 > q7) {
return false;
}
const hash = createHash('sha256');
hash.update('\x0d\x00\x00\x00\x00\x00\x00\x00');
hash.update(bigIntToBufferBE(pk.n, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(pk.g, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(X, 33));
hash.update('$');
hash.update(bigIntToBufferBE(c1, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(c2, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.u, 33));
hash.update('$');
hash.update(bigIntToBufferBE(proof.z, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.zprm, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.t, modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.v, 2 * modulusBytes));
hash.update('$');
hash.update(bigIntToBufferBE(proof.w, modulusBytes));
hash.update('$');
const e = bigIntFromBufferBE(hash.digest()) % q;
const gS1 = curve.basePointMult(curve.scalarReduce(proof.s1));
const xEU = curve.pointAdd(curve.pointMultiply(X, e), proof.u);
if (gS1 !== xEU) {
return false;
}
let left, right;
const h1ExpS1 = modPow(ntilde.h1, proof.s1, ntilde.ntilde);
const h2ExpS2 = modPow(ntilde.h2, proof.s2, ntilde.ntilde);
left = (h1ExpS1 * h2ExpS2) % ntilde.ntilde;
const zExpE = modPow(proof.z, e, ntilde.ntilde);
right = (zExpE * proof.zprm) % ntilde.ntilde;
if (left !== right) {
return false;
}
const h1ExpT1 = modPow(ntilde.h1, proof.t1, ntilde.ntilde);
const h2ExpT2 = modPow(ntilde.h2, proof.t2, ntilde.ntilde);
left = (h1ExpT1 * h2ExpT2) % ntilde.ntilde;
const tExpE = modPow(proof.t, e, ntilde.ntilde);
right = (tExpE * proof.w) % ntilde.ntilde;
if (left !== right) {
return false;
}
const c1ExpS1 = modPow(c1, proof.s1, pk._n2);
const sExpN = modPow(proof.s, pk.n, pk._n2);
const gammaExpT1 = modPow(pk.g, proof.t1, pk._n2);
left = (((c1ExpS1 * sExpN) % pk._n2) * gammaExpT1) % pk._n2;
const c2ExpE = modPow(c2, e, pk._n2);
right = (c2ExpE * proof.v) % pk._n2;
return left === right;
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!