mirror of
https://github.com/siv-org/siv.git
synced 2026-01-10 10:57:59 -05:00
Add code to generate ZK Proof of Valid Decryption
This commit is contained in:
15
src/crypto/async-return-type.ts
Normal file
15
src/crypto/async-return-type.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// Based on https://www.jpwilliams.dev/how-to-unpack-the-return-type-of-a-promise-in-typescript
|
||||
|
||||
/** A generic type for extracting ReturnType from async functions */
|
||||
export type AsyncReturnType<T extends (...args: any[]) => any> =
|
||||
// if T matches this signature and returns a Promise, extract
|
||||
// U (the type of the resolved promise) and use that
|
||||
T extends (...args: any[]) => Promise<infer U>
|
||||
? U
|
||||
: // ...or if T matches this signature and returns anything else,
|
||||
// extract the return value U and use that
|
||||
T extends (...args: any[]) => infer U
|
||||
? U
|
||||
: // ...or if everything goes to hell, return a `never`
|
||||
never
|
||||
62
src/crypto/integer-from-seed.ts
Normal file
62
src/crypto/integer-from-seed.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// import { Crypto } from '@peculiar/webcrypto'
|
||||
|
||||
import { Big, big } from './types'
|
||||
|
||||
// const crypto = new Crypto()
|
||||
|
||||
/**
|
||||
* Deterministically generate a pseudorandom integer less than `max`, from a given `seed` string.
|
||||
*/
|
||||
export async function integer_from_seed(seed: string, max: Big): Promise<Big> {
|
||||
// This will hold our growing buffer of bytes
|
||||
let bytes = new ArrayBuffer(0)
|
||||
|
||||
// How many bytes will we need?
|
||||
const bits = max.bitLength()
|
||||
const bytesNeeded = Math.ceil(bits / 8)
|
||||
|
||||
// Because we're going to mod the result, we want to create significantly more bytes
|
||||
// than necessary, so that the overlap bias becomes insignificant
|
||||
const antibias_amount = 10
|
||||
|
||||
// 1. Get enough bits
|
||||
while (bytes.byteLength < bytesNeeded + antibias_amount) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const hash = await sha256(seed)
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
seed = [...new Uint8Array(hash)].join(',')
|
||||
bytes = appendBuffer(bytes, hash)
|
||||
}
|
||||
|
||||
// 2. Convert bits into a BigInteger
|
||||
const integer = big([...new Uint8Array(bytes)])
|
||||
|
||||
// 3. Make sure integer is not greater than max
|
||||
const result = integer.mod(max)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Example from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Basic_example
|
||||
async function sha256(message: string) {
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(message)
|
||||
const hash = await crypto.subtle.digest('SHA-256', data)
|
||||
return hash
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Uint8Array based on two different ArrayBuffers
|
||||
*
|
||||
* @param buffer1 The first buffer.
|
||||
* @param buffer2 The second buffer.
|
||||
* @return The new ArrayBuffer created out of the two.
|
||||
*
|
||||
* from: https://gist.github.com/72lions/4528834
|
||||
*/
|
||||
function appendBuffer(buffer1: ArrayBuffer, buffer2: ArrayBuffer) {
|
||||
const temporary = new Uint8Array(buffer1.byteLength + buffer2.byteLength)
|
||||
temporary.set(new Uint8Array(buffer1), 0)
|
||||
temporary.set(new Uint8Array(buffer2), buffer1.byteLength)
|
||||
return temporary.buffer
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
import { range } from 'lodash'
|
||||
|
||||
import { AsyncReturnType } from './async-return-type'
|
||||
import { integer_from_seed } from './integer-from-seed'
|
||||
import { moduloLambda } from './lagrange'
|
||||
import pickRandomInteger from './pick-random-integer'
|
||||
import { Big, big } from './types'
|
||||
@@ -103,24 +105,6 @@ export const partial_decrypt = (sealing_factor: Big, private_key_share: Big, { p
|
||||
= c[1]^a
|
||||
*/
|
||||
export const combine_partials = (partial_decrypts: Big[], { p, q }: Parameters) => {
|
||||
// var prod = big(1)
|
||||
// partial_decrypts.forEach((part, i) => {
|
||||
// let L = big(1)
|
||||
// partial_decrypts.forEach((_, ii) => {
|
||||
// let I = big(i + 1)
|
||||
// let II = big(ii + 1)
|
||||
// if (I.equals(II)) return
|
||||
// let num = big(0).subtract(II).mod(q)
|
||||
// let den = I.subtract(II).mod(q)
|
||||
|
||||
// // console.log(`I = ${I}, II = ${II}, num = ${num}, den = ${den}, q = ${q}`)
|
||||
|
||||
// L = L.multiply(num.multiply(den.modInverse(q)).mod(q)).mod(q)
|
||||
// })
|
||||
// prod = prod.multiply(part.modPow(L, p)).mod(p)
|
||||
// })
|
||||
// return prod
|
||||
|
||||
const indices = range(1, partial_decrypts.length + 1)
|
||||
|
||||
const indices_as_bigs = indices.map((index) => [big(index)])
|
||||
@@ -130,39 +114,87 @@ export const combine_partials = (partial_decrypts: Big[], { p, q }: Parameters)
|
||||
return product_bigs(partials_to_lambdas, p)
|
||||
}
|
||||
|
||||
/** Use Zero knowledge proof to prove
|
||||
log[c[1]](dⱼ) = log[g](hⱼ)
|
||||
/** Creates non-interactive Zero Knowledge proof of a valid partial decryption
|
||||
* by proving these two logs are equivalent:
|
||||
*
|
||||
* log(g^trustees_secret)/log(g) = log(partial_decryption)/log(unlock_factor)
|
||||
*
|
||||
* which are both equal to trustees_secret
|
||||
*
|
||||
* @param ciphertext_unlock c[0] of the encrypted ciphertext (g ^ randomizer)
|
||||
* @param trustees_secret
|
||||
* @param {g, p, q} - Safe prime parameters
|
||||
*
|
||||
* Based on Chaum-Pedersen ZK Proof of Discrete Logs
|
||||
*
|
||||
*/
|
||||
export async function generate_partial_decryption_proof(
|
||||
ciphertext_unlock: Big,
|
||||
trustees_secret: Big,
|
||||
{ g, p, q }: Parameters,
|
||||
) {
|
||||
// Prover picks a secret random integer less than q
|
||||
const secret_r = pickRandomInteger(q)
|
||||
|
||||
g = generator
|
||||
gs = g^s (s is the global secret)
|
||||
c[0] = unlock_factor of the encrypted (g ^ randomizer)
|
||||
t.part = partial for a given trustee
|
||||
t.s = secret for a given trustee
|
||||
const g_to_trustees_secret = g.modPow(trustees_secret, p)
|
||||
|
||||
log_proof(g, gs, c[0], t.part, t.s)
|
||||
*/
|
||||
// function log_proof(g, x, h, y, alpha) {
|
||||
// for (var i = 0; i < 100; i++) {
|
||||
// // prover creates:
|
||||
// let w = rand(big(1), q)
|
||||
// Calculates Verifier's deterministic random number (Fiat-Shamir technique):
|
||||
const public_r = await integer_from_seed(`${ciphertext_unlock} ${g_to_trustees_secret}`, q)
|
||||
|
||||
// // prover sends:
|
||||
// let gw = g.modPow(w, p)
|
||||
// let hw = h.modPow(w, p)
|
||||
// Prover creates and sends:
|
||||
const obfuscated_trustee_secret = secret_r.add(public_r.multiply(trustees_secret))
|
||||
|
||||
// // verifier creates and sends:
|
||||
// let c = rand(big(1), q)
|
||||
// Prover also shares commitment of their secret randomizer choice
|
||||
const g_to_secret_r = g.modPow(secret_r, p)
|
||||
const unlock_to_secret_r = ciphertext_unlock.modPow(secret_r, p)
|
||||
|
||||
// // prover creates and sends:
|
||||
// let r = w.add(c.multiply(alpha))
|
||||
return { g_to_secret_r, obfuscated_trustee_secret, unlock_to_secret_r }
|
||||
}
|
||||
|
||||
// // verifier checks:
|
||||
// var check = g.modPow(r, p).equals(gw.multiply(x.modPow(c, p)).mod(p))
|
||||
// && h.modPow(r, p).equals(hw.multiply(y.modPow(c, p)).mod(p))
|
||||
// if (!check) return false
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
/** Verifies a non-interactive Zero Knowledge proof of a valid partial decryption
|
||||
* by checking these two logs are equivalent:
|
||||
*
|
||||
* log(g^trustees_secret)/log(g) = log(partial_decryption)/log(unlock_factor)
|
||||
*
|
||||
* which are both equal to trustees_secret
|
||||
*
|
||||
* @param ciphertext_unlock c[0] of the encrypted ciphertext (g ^ randomizer)
|
||||
* @param trustees_secret
|
||||
* @param {g, p, q} - Safe prime parameters
|
||||
*
|
||||
* Based on Chaum-Pedersen ZK Proof of Discrete Logs
|
||||
*
|
||||
*/
|
||||
export async function verify_partial_decryption_proof(
|
||||
ciphertext_unlock: Big,
|
||||
g_to_trustees_secret: Big,
|
||||
partial_decryption: Big,
|
||||
{
|
||||
g_to_secret_r,
|
||||
obfuscated_trustee_secret,
|
||||
unlock_to_secret_r,
|
||||
}: AsyncReturnType<typeof generate_partial_decryption_proof>,
|
||||
{ g, p, q }: Parameters,
|
||||
) {
|
||||
// Recalculate deterministic verifier nonce
|
||||
const public_r = await integer_from_seed(`${ciphertext_unlock} ${g_to_trustees_secret}`, q)
|
||||
|
||||
// Verifier checks:
|
||||
// g ^ obfuscated_trustee_secret
|
||||
const left_side_1 = g.modPow(obfuscated_trustee_secret, p)
|
||||
// == g_to_secret_r * (g ^ trustee_secret ^ public_r)
|
||||
const right_side_1 = g_to_secret_r.multiply(g_to_trustees_secret.modPow(public_r, p)).mod(p)
|
||||
const check1 = left_side_1.equals(right_side_1)
|
||||
|
||||
// And Verifier checks:
|
||||
// ciphertext_unlock ^ obfuscated_trustee_secret
|
||||
const left_side_2 = ciphertext_unlock.modPow(obfuscated_trustee_secret, p)
|
||||
// == unlock_to_secret_r * (partial_decryption ^ public_r)
|
||||
const right_side_2 = unlock_to_secret_r.multiply(partial_decryption.modPow(public_r, p)).mod(p)
|
||||
const check2 = left_side_2.equals(right_side_2)
|
||||
|
||||
return check1 && check2
|
||||
}
|
||||
|
||||
/** Thereafter, compute
|
||||
m = c[2] / d
|
||||
|
||||
@@ -33,7 +33,7 @@ Big.prototype.lessThan = function (x: Big) {
|
||||
}
|
||||
|
||||
/** Smartly converts numbers or strings of numbers into Big */
|
||||
export function big(input: number | Big | string, radix = 10): Big {
|
||||
export function big(input: number | Big | string | number[], radix = 10): Big {
|
||||
// Is input already a Big?
|
||||
if (input instanceof Big) {
|
||||
return input
|
||||
@@ -48,6 +48,11 @@ export function big(input: number | Big | string, radix = 10): Big {
|
||||
return new Big(input, radix)
|
||||
}
|
||||
|
||||
// Can also pass in an array of bytes
|
||||
if (Array.isArray(input)) {
|
||||
return new Big(input)
|
||||
}
|
||||
|
||||
throw new TypeError(`${input} is not a number or string of an integer`)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
"jsx": "preserve",
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"exclude": ["node_modules", ".next", "out"],
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
|
||||
Reference in New Issue
Block a user