# Hooking up with Fiat-Shamir Ben Smyth 2026-09-05 A zero-knowledge proof convinces us of some claim’s validity without revealing specifics. For example, you can prove possession of a private key without leaking any information about your key. Zero-knowledge proofs are typically derived by application of the Fiat-Shamir transformation to sigma protocols. Unfortunately, the transformation is commonly misapplied, introducing security vulnerabilities. Herein, I present a JavaScript framework for correct transformation. We start from a claim. For instance, I know a private key corresponding to a public key. A sigma protocol convinces us of a claim’s validity; we cannot be fooled by invalid claims: A prover inputs a public statement (e.g., a public key) and a private witness (e.g., a private key) to generate a commitment, the public statement and the commitment are input by a verifier to produce a challenge, that challenge is used by the prover to generate a response, and the verifier checks whether the response demonstrates validity of the statement (Listing [lst:sigma]). Sigma protocols are interactive; a prover sends a statement and a commitment to a verifier, the verifier replies with a challenge, and the prover supplies a response. The Fiat-Shamir transformation replaces the verifier’s challenge with a hash over the statement and the commitment (and, optionally, some message), reducing exchange of three messages to a single proof (Listing [lst:zkp]). The Fiat-Shamir transformation produces zero-knowledge proofs when the underlying sigma protocol guarantees anything derivable about a witness can be computed without interaction. As an exemplar application of our framework, let’s implement the sigma protocol by Chaum et al. for demonstrating knowledge of a discrete logarithm, which may be used to prove we know a private key corresponding to a public key (Listings [lst:DLog] & [lst:DLogB]). Securely implementing sigma protocols is as hard as implementing any comparable cryptographic primitive: Fiendishly difficult! (Just count the zero days.) I recommend the gold standard—*proven secure by design*—wherein security of sourcecode is shown to reduce to an unsolvable mathematical problem, rendering code secure assuming the math remains unsolvable. That’s a tall order; I’m unaware of any such mainstream system. As an initial step, let’s define a cyclic multiplicative group of prime-order using OpenSSL’s implementation of Diffie-Hellman (Listings [lst:group]–[lst:groupC]), simplifying reductions assuming OpenSSL’s Diffie-Hellman can be proven secure by design. As further exemplar applications of our framework, let’s implement sigma protocols by Chaum & Pedersen for demonstrating knowledge of equality between discrete logarithms (Listing [lst:DLogEq]) and Schoenmakers for disjunctive equality between logarithms (Listings [lst:DLogEqDisj] & [lst:DLogEqDisjB]), which may be used to prove correctness of partial decryptions and, respectively, to prove correct ciphertext construction, the latter giving way to non-malleable ElGamal encryption over booleans. Next steps: Provide reductions (preferably mechanised) from sourcecode to unsolvable mathematical problems, rendering implementations proven secure by design, assuming the math remains unsolvable. Building upon those reductions, demonstrate generality of techniques beyond zero-knowledge proofs by reducing code for non-malleable ElGamal to unsolvable problems. Furthermore, demonstrate generality at system level by reducing voting system sourcecode to the aforementioned unsolvable mathematical problems. In just a year, I found zero-days in TLS implementations used by Google/Android & Oracle/OpenJDK, OpenJS Foundation/Node.js’s Diffie-Hellman, and Swiss Post E-Voting, each annihilating security—clearing systems missettling trades, crypto looted, democracy stolen, could’ve happened. Zero-trust is the coming agenda; proven secure by design the future.
``` TypeScript import crypto from 'crypto'; type Statement = unknown; type Witness = unknown; type Commitment = unknown; type Challenge = unknown; type Response = unknown; export interface SigmaProtocol { statement: Statement; commit(): Commitment; challenge?(commitment: Commitment): Challenge; response(challenge: Challenge): Response; verify(commitment: Commitment, challenge: Challenge, response: Response): boolean; } type Proof = {commitment: Commitment, response: Response}; type Message = unknown; export class ZeroKnowledgeProof { private readonly sigma: SigmaProtocol; private readonly hash: string; constructor(sigma: SigmaProtocol, hash: string) { this.sigma = sigma; this.hash = hash; } public prove(message?: Message): Proof { const commitment = this.sigma.commit(); const challenge = this.getChallengeHash(this.sigma.statement, commitment, message); const response = this.sigma.response(challenge); return {commitment, response}; } public verify({commitment, response}: Proof, message?: Message): boolean { const challenge = this.getChallengeHash(this.sigma.statement, commitment, message); return this.sigma.verify(commitment, challenge, response); } private getChallengeHash(statement: Statement, commitment: Commitment, message?: Message): Buffer { const hash = crypto.createHash(this.hash); (message === undefined ? [statement, commitment] : [statement, commitment, message]).forEach(x => this.update(hash, x)); return hash.digest(); } protected update(hash: crypto.Hash, data: unknown) : void { if (typeof data === "string" || data instanceof Buffer) hash.update(data); else if (data !== null && Object.values(data as any).length > 0) Object.values(data as any).forEach(x => this.update(hash, x)); else throw Error(`ZeroKnowledgeProof's subclass must override method update for instances of type ${typeof data}`); } } ```
``` TypeScript import crypto from 'crypto'; type Statement = unknown; type Witness = unknown; type Commitment = unknown; type Challenge = unknown; type Response = unknown; export interface SigmaProtocol { statement: Statement; commit(): Commitment; challenge?(commitment: Commitment): Challenge; response(challenge: Challenge): Response; verify(commitment: Commitment, challenge: Challenge, response: Response): boolean; } type Proof = {commitment: Commitment, response: Response}; type Message = unknown; export class ZeroKnowledgeProof { private readonly sigma: SigmaProtocol; private readonly hash: string; constructor(sigma: SigmaProtocol, hash: string) { this.sigma = sigma; this.hash = hash; } public prove(message?: Message): Proof { const commitment = this.sigma.commit(); const challenge = this.getChallengeHash(this.sigma.statement, commitment, message); const response = this.sigma.response(challenge); return {commitment, response}; } public verify({commitment, response}: Proof, message?: Message): boolean { const challenge = this.getChallengeHash(this.sigma.statement, commitment, message); return this.sigma.verify(commitment, challenge, response); } private getChallengeHash(statement: Statement, commitment: Commitment, message?: Message): Buffer { const hash = crypto.createHash(this.hash); (message === undefined ? [statement, commitment] : [statement, commitment, message]).forEach(x => this.update(hash, x)); return hash.digest(); } protected update(hash: crypto.Hash, data: unknown) : void { if (typeof data === "string" || data instanceof Buffer) hash.update(data); else if (data !== null && Object.values(data as any).length > 0) Object.values(data as any).forEach(x => this.update(hash, x)); else throw Error(`ZeroKnowledgeProof's subclass must override method update for instances of type ${typeof data}`); } } ```
*\[tikzpicture diagram omitted\]* ``` TypeScript import crypto from 'crypto'; import { SigmaProtocol } from './ZKP'; import { Group } from './Group'; export type Statement = {g: bigint, p: bigint, h: bigint}; export type Witness = bigint; export type Commitment = bigint; export type Challenge = Buffer; export type Response = bigint; export class SigmaProtocolDLog extends Group implements SigmaProtocol { public readonly statement: Statement; private readonly x!: Witness; protected s!: bigint; constructor(statement: Statement, x?: Witness) { super(statement.p, statement.g); this.statement = statement; if (x) this.x = x; } public commit(): Commitment { if (!this.x) throw new Error("A witness must be instantiated to compute a proof"); this.s = this.random(); return this.g(this.s) % this.prime; } public response(c: Challenge): Response { if (!this.s) throw new Error("Method response must be called after method commit"); return (this.s + c.toBigint() * this.x) % this.order; } public verify(gs: Commitment, c: Challenge, r: Response): boolean { return this.g(r) === gs * this.exp(this.statement.h, c.toBigint()) % this.prime; } } import { ZeroKnowledgeProof as _ZeroKnowledgeProof } from './ZKP'; export class ZeroKnowledgeProof extends _ZeroKnowledgeProof { protected update(hash: crypto.Hash, x: unknown) : void { if (typeof x === "bigint") hash.update(x.toBuffer()); else super.update(hash, x); } } export class ZKPDLog extends ZeroKnowledgeProof { constructor(statement: Statement, hashAlgorithm: string, witness?: Witness) { super(new SigmaProtocolDLog(statement, witness), hashAlgorithm); } } if (require.main === module) { console.log("Running ZKPDLog.ts test script"); const dh = crypto.createDiffieHellman(1024); const p = dh.getPrime().toBigint(); const g = dh.getGenerator().toBigint(); for (let i = 0; i < 5; i++ ) { const group = new Group(p,g); const x = group.random(); const h = group.g(x); const statement = {g, p, h}; const prover = new ZKPDLog(statement, 'sha256', x); const verifier = new ZKPDLog(statement, 'sha256'); console.log(`Iteration ${i}, pass: ${verifier.verify(prover.prove())}`); } } ```
``` TypeScript import crypto from 'crypto'; import { SigmaProtocol } from './ZKP'; import { Group } from './Group'; export type Statement = {g: bigint, p: bigint, h: bigint}; export type Witness = bigint; export type Commitment = bigint; export type Challenge = Buffer; export type Response = bigint; export class SigmaProtocolDLog extends Group implements SigmaProtocol { public readonly statement: Statement; private readonly x!: Witness; protected s!: bigint; constructor(statement: Statement, x?: Witness) { super(statement.p, statement.g); this.statement = statement; if (x) this.x = x; } public commit(): Commitment { if (!this.x) throw new Error("A witness must be instantiated to compute a proof"); this.s = this.random(); return this.g(this.s) % this.prime; } public response(c: Challenge): Response { if (!this.s) throw new Error("Method response must be called after method commit"); return (this.s + c.toBigint() * this.x) % this.order; } public verify(gs: Commitment, c: Challenge, r: Response): boolean { return this.g(r) === gs * this.exp(this.statement.h, c.toBigint()) % this.prime; } } import { ZeroKnowledgeProof as _ZeroKnowledgeProof } from './ZKP'; export class ZeroKnowledgeProof extends _ZeroKnowledgeProof { protected update(hash: crypto.Hash, x: unknown) : void { if (typeof x === "bigint") hash.update(x.toBuffer()); else super.update(hash, x); } } export class ZKPDLog extends ZeroKnowledgeProof { constructor(statement: Statement, hashAlgorithm: string, witness?: Witness) { super(new SigmaProtocolDLog(statement, witness), hashAlgorithm); } } if (require.main === module) { console.log("Running ZKPDLog.ts test script"); const dh = crypto.createDiffieHellman(1024); const p = dh.getPrime().toBigint(); const g = dh.getGenerator().toBigint(); for (let i = 0; i < 5; i++ ) { const group = new Group(p,g); const x = group.random(); const h = group.g(x); const statement = {g, p, h}; const prover = new ZKPDLog(statement, 'sha256', x); const verifier = new ZKPDLog(statement, 'sha256'); console.log(`Iteration ${i}, pass: ${verifier.verify(prover.prove())}`); } } ```
``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ```
``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ``` ``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ``` ``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ```
``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ``` ``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ``` ``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ``` ``` TypeScript import crypto from 'crypto'; declare global { interface Buffer { toBigint(): bigint; } interface BigInt { toBuffer(): Buffer; } interface BigInt { mod(n: bigint): bigint; } } Buffer.prototype.toBigint = function(this: Buffer): bigint { return BigInt(`0x${this.toString('hex')}`); } BigInt.prototype.toBuffer = function(this: bigint): Buffer { /* RTFM: "Data truncation may occur when decoding strings that do not exclusively consist of an even number of hexadecimal characters," painful to debug... Source: https://github.com/nodejs/node/blob/main/doc/api/buffer.md */ let s = this.toString(16); if (s.length % 2 !== 0) s = "0" + s; return Buffer.from(s, 'hex'); } BigInt.prototype.mod = function(this: bigint, n: bigint): bigint { /* RTFM: "The remainder (%) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend," perplexing... Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder */ return ((this%n)+n)%n; } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ class _Group { public readonly _prime: Buffer; public readonly _generator: Buffer; private readonly dh: crypto.DiffieHellman; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: Buffer, generator: Buffer) { if (crypto.createDiffieHellman(prime, generator).verifyError !== 0) throw new Error("Not a prime, not a safe prime, generator unsuitable, or generator suitability cannot be checked"); this._prime = prime; this._generator = generator; this.dh = crypto.createDiffieHellman(prime, generator); } public exp(base: Buffer, exp: Buffer): Buffer { this.dh.setPrivateKey(exp); return this.dh.computeSecret(base); } public random(): Buffer { //Here be dragons: Use fresh Diffie-Hellman instance to avoid Node.js zero-day CVE-2023-30590; function generateKeys() "only generates a private //key if none has been set" (https://nodejs.org/en/blog/vulnerability/june-2023-security-releases) const dh = crypto.createDiffieHellman(this._prime, this._generator) dh.generateKeys(); return dh.getPrivateKey(); } } /* * Class representing a cyclic multiplicative (sub)group of prime-order */ export class Group extends _Group { public readonly prime: bigint; public readonly generator: bigint; public readonly order: bigint; /** * Creates the cyclic multiplicative (sub)group of prime-order ${(prime - 1)/2} * * @param {Buffer} prime - safe prime * @param {Buffer} generator - group generator * * @throws Error if there's an issue with either parameter */ constructor(prime: bigint, generator: bigint) { super(prime.toBuffer(), generator.toBuffer()); this.prime = prime; this.generator = generator; this.order = (prime - 1n)/2n; } public g(exp: bigint): bigint { return this.exp(this.generator, exp); } public exp(base: bigint, exp: bigint): bigint; public exp(base: Buffer, exp: Buffer): Buffer; public exp(base: any, exp: any): any { //DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(0n.toBuffer()) if (exp === 0n) return 1n; return super.exp(base.toBuffer(), exp.toBuffer()).toBigint(); } public random(): bigint; public random(): Buffer; public random(): any { //DiffieHellman.generateKeys() generates private keys of ${order}'s bit length rather than from the group return super.random().toBigint() % this.order; } /** * Inverts group element * * @param {bigint} x - element * * @returns inverted element * * @ensures inverted element is ${(this.order - (x % this.order)) % this.order} (recall g^x * g^q-x ≡ g^q ≡ 1 mod p) */ public inverse(x: bigint): bigint; public inverse(x: number): bigint; public inverse(x: any): bigint { if (typeof x !== 'bigint') x = BigInt(x); return (this.order - (x % this.order)) % this.order; //Equivalently, Fermat's (little) theorem could be applied. } public isMember(x: bigint): boolean { //For a cyclic (sub)group of prime order, every group element (except identity) is a generator; raising a group generator (or identity) to the //group's order produces one //Here be dragons: DiffieHellman.computeSecret(base) raises "RangeError: Invalid key type" with DiffieHellman.setPrivateKey(this.order.toBuffer()) try { return this.exp(x, this.order-1n) * x % this.prime === 1n; } catch (error) { return false; } //(Checking crypto.createDiffieHellman(this._prime, x.toBuffer()).verifyError !== 0 won't suffice; "don't worry if [x] is a generator or not: //since we are using safe primes, it will generate either an order-q [subgroup] or an order-2q group [either] is OK" (OpenSSL dh_gen.c), //see also https://florianjw.de/en/insecure_generators.html.) } } ```
*\[tikzpicture diagram omitted\]* ``` TypeScript import crypto from 'crypto'; import { SigmaProtocol } from './ZKP'; import { SigmaProtocolDLog, Statement as _Statement, Witness, Commitment as _Commitment, Challenge, Response } from './ZKPDLog'; type Statement = _Statement & {a: bigint, d: bigint}; type Commitment = {G: _Commitment, A: bigint}; class SigmaProtocolDLogEq extends SigmaProtocolDLog implements SigmaProtocol { public readonly statement: Statement; constructor(statement: Statement, x?: Witness) { super(statement as _Statement, x); this.statement = statement; } public commit(): T { return {G: super.commit(), A: this.exp(this.statement.a, this.s) % this.prime} as T; } public verify(commitment: Commitment|_Commitment|any, challenge: Challenge, response: Response): boolean { if (!commitment.G || !commitment.A) return false; return super.verify(commitment.G, challenge, response) && this.exp(this.statement.a, response) === commitment.A * this.exp(this.statement.d, challenge.toBigint()) % this.prime; } } import { ZeroKnowledgeProof } from './ZKPDLog'; export class ZKPDLogEq extends ZeroKnowledgeProof { constructor(statement: Statement, hashAlgorithm: string, witness?: Witness) { super(new SigmaProtocolDLogEq(statement, witness), hashAlgorithm); } } import { Group } from './Group'; if (require.main === module) { console.log("Running ZKPDLogEq.ts test script"); const dh = crypto.createDiffieHellman(1024); const p = dh.getPrime().toBigint(); const g = dh.getGenerator().toBigint(); for (let i = 0; i < 5; i++ ) { const group = new Group(p,g); const x = group.random(); const h = group.g(x); const r = group.random(); const a = group.g(r); const d = group.exp(a, x); const statement = {g, p, h, a, d}; const zkp = new ZKPDLogEq(statement, 'sha256', x); const prover = new ZKPDLogEq(statement, 'sha256', x); const verifier = new ZKPDLogEq(statement, 'sha256'); console.log(`Iteration ${i}, pass: ${verifier.verify(prover.prove())}`); } } ```
*\[tikzpicture diagram omitted\]* ``` TypeScript import crypto from 'crypto'; import { Group } from './Group'; import { SigmaProtocol } from './ZKP'; type Statement = {g: bigint, p: bigint, h: bigint, a: bigint, b: bigint}; type Witness = {r: bigint, m: 0|1}; type Commitment = { G: bigint; H: bigint } type Challenge = Buffer; type Response = {c: bigint; s: bigint}; export class SigmaProtocolDLogEqDisj extends Group implements SigmaProtocol { public readonly statement: Statement; private readonly witness!: Witness; private resp!: Response[]; private s!: bigint; constructor(statement: Statement, witness?: Witness) { super(statement.p, statement.g); this.statement = statement; if (witness) this.witness = witness; } public commit(): Commitment[] { if (!this.witness) throw new Error("A witness must be instantiated to compute a proof"); const commitment = new Array(2); //commitment for m this.s = this.random(); commitment[this.witness.m] = {G: this.g(this.s), H: this.exp(this.statement.h, this.s)}; //simulate proof (commitment & response) for bit m xor 1 const c = this.random(); const t = this.random(); const G = this.g(t) * this.exp(this.statement.a, this.inverse(c)) % this.prime; const H = this.exp(this.statement.h, t) * this.exp(this.statement.b * this.g(this.inverse(1^this.witness.m)) % this.prime, this.inverse(c)) % this.prime; this.resp = new Array(2); this.resp[1^this.witness.m] = {c, s: t}; commitment[1^this.witness.m] = {G, H}; return commitment as Commitment[]; } public response(challenge: Challenge): Response[] { if (!this.resp) throw new Error("Method response must be called after method commit"); this.resp[this.witness.m] = {} as Response; //Here be dragons: Hash function produces far fewer bits than random number generation (for sensible length primes), //apply function mod (rather than remainder operator) to negative bigint this.resp[this.witness.m].c = (challenge.toBigint() - this.resp[1^this.witness.m].c).mod(this.order); this.resp[this.witness.m].s = (this.s + this.witness.r * this.resp[this.witness.m].c) % this.order; return this.resp as Response[]; } public verify(commitment: Commitment[], challenge: Challenge, response: Response[]): boolean { if (!this.isMember(this.statement.a) || !this.isMember(this.statement.b)) return false; const proof = [0,1].map(i => ({G: commitment[i].G, H: commitment[i].H, c: response[i].c, s: response[i].s})).entries(); for (const [i, {G,H,c,s}] of proof) if (!this.isMember(G) || !this.isMember(H)) return false; else if ((this.g(s) % this.prime !== G * this.exp(this.statement.a,c) % this.prime) || (this.exp(this.statement.h, s) % this.prime !== H * this.exp(this.statement.b * this.g(this.inverse(i)) % this.prime, c) % this.prime)) return false; if (challenge.toBigint() % this.order !== response.reduce((sum, {c}) => sum + c, 0n) % this.order) return false; return true; } } import { ZeroKnowledgeProof } from './ZKPDLog'; export class ZKPDLogEqDisj extends ZeroKnowledgeProof { constructor(statement: Statement, hashAlgorithm: string, witness?: Witness) { super(new SigmaProtocolDLogEqDisj(statement, witness), hashAlgorithm); } } if (require.main === module) { console.log("Running ZKPDLogEqDisj.ts test script"); const dh = crypto.createDiffieHellman(1024); const p = dh.getPrime(); const g = dh.getGenerator(); for (let i = 0; i < 100; i++ ) { //Leverage ElGamal to produce cipertexts (a,b) such that a = g^r and b = h^r·g^m const e = new (require('/home/bas/votetech/demo/2023/ElGamal').ElGamal)(p, g); const {pk, sk} = e.keypair(); const r = e.random(); const m = Math.round(Math.random()) as 0|1; const {a,b} = e.encrypt(pk, m, r); const statement = {g: g.toBigint(), p: p.toBigint(), h: pk, a, b}; const witness = {r, m}; const prover = new ZKPDLogEqDisj(statement, 'sha256', witness); const verifier = new ZKPDLogEqDisj(statement, 'sha256'); console.log(`Iteration ${i}, pass: ${verifier.verify(prover.prove())}`); } } ```
``` TypeScript import crypto from 'crypto'; import { Group } from './Group'; import { SigmaProtocol } from './ZKP'; type Statement = {g: bigint, p: bigint, h: bigint, a: bigint, b: bigint}; type Witness = {r: bigint, m: 0|1}; type Commitment = { G: bigint; H: bigint } type Challenge = Buffer; type Response = {c: bigint; s: bigint}; export class SigmaProtocolDLogEqDisj extends Group implements SigmaProtocol { public readonly statement: Statement; private readonly witness!: Witness; private resp!: Response[]; private s!: bigint; constructor(statement: Statement, witness?: Witness) { super(statement.p, statement.g); this.statement = statement; if (witness) this.witness = witness; } public commit(): Commitment[] { if (!this.witness) throw new Error("A witness must be instantiated to compute a proof"); const commitment = new Array(2); //commitment for m this.s = this.random(); commitment[this.witness.m] = {G: this.g(this.s), H: this.exp(this.statement.h, this.s)}; //simulate proof (commitment & response) for bit m xor 1 const c = this.random(); const t = this.random(); const G = this.g(t) * this.exp(this.statement.a, this.inverse(c)) % this.prime; const H = this.exp(this.statement.h, t) * this.exp(this.statement.b * this.g(this.inverse(1^this.witness.m)) % this.prime, this.inverse(c)) % this.prime; this.resp = new Array(2); this.resp[1^this.witness.m] = {c, s: t}; commitment[1^this.witness.m] = {G, H}; return commitment as Commitment[]; } public response(challenge: Challenge): Response[] { if (!this.resp) throw new Error("Method response must be called after method commit"); this.resp[this.witness.m] = {} as Response; //Here be dragons: Hash function produces far fewer bits than random number generation (for sensible length primes), //apply function mod (rather than remainder operator) to negative bigint this.resp[this.witness.m].c = (challenge.toBigint() - this.resp[1^this.witness.m].c).mod(this.order); this.resp[this.witness.m].s = (this.s + this.witness.r * this.resp[this.witness.m].c) % this.order; return this.resp as Response[]; } public verify(commitment: Commitment[], challenge: Challenge, response: Response[]): boolean { if (!this.isMember(this.statement.a) || !this.isMember(this.statement.b)) return false; const proof = [0,1].map(i => ({G: commitment[i].G, H: commitment[i].H, c: response[i].c, s: response[i].s})).entries(); for (const [i, {G,H,c,s}] of proof) if (!this.isMember(G) || !this.isMember(H)) return false; else if ((this.g(s) % this.prime !== G * this.exp(this.statement.a,c) % this.prime) || (this.exp(this.statement.h, s) % this.prime !== H * this.exp(this.statement.b * this.g(this.inverse(i)) % this.prime, c) % this.prime)) return false; if (challenge.toBigint() % this.order !== response.reduce((sum, {c}) => sum + c, 0n) % this.order) return false; return true; } } import { ZeroKnowledgeProof } from './ZKPDLog'; export class ZKPDLogEqDisj extends ZeroKnowledgeProof { constructor(statement: Statement, hashAlgorithm: string, witness?: Witness) { super(new SigmaProtocolDLogEqDisj(statement, witness), hashAlgorithm); } } if (require.main === module) { console.log("Running ZKPDLogEqDisj.ts test script"); const dh = crypto.createDiffieHellman(1024); const p = dh.getPrime(); const g = dh.getGenerator(); for (let i = 0; i < 100; i++ ) { //Leverage ElGamal to produce cipertexts (a,b) such that a = g^r and b = h^r·g^m const e = new (require('/home/bas/votetech/demo/2023/ElGamal').ElGamal)(p, g); const {pk, sk} = e.keypair(); const r = e.random(); const m = Math.round(Math.random()) as 0|1; const {a,b} = e.encrypt(pk, m, r); const statement = {g: g.toBigint(), p: p.toBigint(), h: pk, a, b}; const witness = {r, m}; const prover = new ZKPDLogEqDisj(statement, 'sha256', witness); const verifier = new ZKPDLogEqDisj(statement, 'sha256'); console.log(`Iteration ${i}, pass: ${verifier.verify(prover.prove())}`); } } ```