Skip to content

Commit

Permalink
Implementing dleq proofs.
Browse files Browse the repository at this point in the history
  • Loading branch information
armfazh committed Mar 18, 2022
1 parent 79c9183 commit 38942b7
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 0 deletions.
129 changes: 129 additions & 0 deletions src/dleq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) 2021 Cloudflare, Inc. and contributors.
// Copyright (c) 2021 Cloudflare, Inc.
// Licensed under the BSD-3-Clause license found in the LICENSE file or
// at https://opensource.org/licenses/BSD-3-Clause

import { Elt, Group, Scalar } from './group.js'
import { joinAll, to16bits } from './util.js'

export interface DLEQParams {
readonly gg: Group
readonly hash: string
readonly dst: string
}

const LABELS = {
Seed: 'Seed-',
Challenge: 'Challenge',
Composite: 'Composite',
HashToScalar: 'HashToScalar-'
} as const

async function computeComposites(
params: DLEQParams,
b: Elt,
cd: Array<[Elt, Elt]>,
key?: Scalar
): Promise<{ M: Elt; Z: Elt }> {
const te = new TextEncoder()
const Bm = params.gg.serialize(b)
const seedDST = te.encode(LABELS.Seed + params.dst)
const h1Input = joinAll([to16bits(Bm.length), Bm, to16bits(seedDST.length), seedDST])
const seed = new Uint8Array(await crypto.subtle.digest(params.hash, h1Input))

const compositeLabel = te.encode(LABELS.Composite)
const h2sDST = te.encode(LABELS.HashToScalar + params.dst)
let M = params.gg.identity()
let Z = params.gg.identity()
let i = 0
for (const [c, d] of cd) {
const Ci = params.gg.serialize(c)
const Di = params.gg.serialize(d)

const h2Input = joinAll([
to16bits(seed.length),
seed,
to16bits(i++),
to16bits(Ci.length),
Ci,
to16bits(Di.length),
Di,
compositeLabel
])
const di = await params.gg.hashToScalar(h2Input, h2sDST)
M = Group.add(M, Group.mul(di, c))

if (!key) {
Z = Group.add(Z, Group.mul(di, d))
}
}

if (key) {
Z = Group.mul(key, M)
}

return { M, Z }
}

function challenge(params: DLEQParams, points: [Elt, Elt, Elt, Elt, Elt]): Promise<Scalar> {
let h2Input = new Uint8Array()
for (const p of points) {
const P = params.gg.serialize(p)
h2Input = joinAll([h2Input, to16bits(P.length), P])
}
const te = new TextEncoder()
h2Input = joinAll([h2Input, te.encode(LABELS.Challenge)])
const h2sDST = te.encode(LABELS.HashToScalar + params.dst)
return params.gg.hashToScalar(h2Input, h2sDST)
}

export interface DLEQProof {
readonly params: DLEQParams
readonly c: Scalar
readonly s: Scalar
verify(p0: [Elt, Elt], p1: [Elt, Elt]): Promise<boolean>
verify_batch(p0: [Elt, Elt], p1: Array<[Elt, Elt]>): Promise<boolean>
}

class dleqProof implements DLEQProof {
constructor(
public readonly params: DLEQParams,
public readonly c: Scalar,
public readonly s: Scalar
) {}

verify(p0: [Elt, Elt], p1: [Elt, Elt]): Promise<boolean> {
return this.verify_batch(p0, [p1])
}

async verify_batch(p0: [Elt, Elt], p1s: Array<[Elt, Elt]>): Promise<boolean> {
const { M, Z } = await computeComposites(this.params, p0[1], p1s)
const t2 = Group.add(Group.mul(this.s, p0[0]), Group.mul(this.c, p0[1]))
const t3 = Group.add(Group.mul(this.s, M), Group.mul(this.c, Z))
const c = await challenge(this.params, [p0[1], M, Z, t2, t3])
return this.params.gg.equalScalar(this.c, c)
}
}

export class DLEQProver {
constructor(public readonly params: DLEQParams) {}

prove(k: Scalar, p0: [Elt, Elt], p1: [Elt, Elt], r?: Scalar): Promise<DLEQProof> {
return this.prove_batch(k, p0, [p1], r)
}

async prove_batch(
key: Scalar,
p0: [Elt, Elt],
p1s: Array<[Elt, Elt]>,
r?: Scalar
): Promise<DLEQProof> {
const rnd = r ? r : await this.params.gg.randomScalar()
const { M, Z } = await computeComposites(this.params, p0[1], p1s, key)
const t2 = Group.mul(rnd, p0[0])
const t3 = Group.mul(rnd, M)
const c = await challenge(this.params, [p0[1], M, Z, t2, t3])
const s = this.params.gg.subScalar(rnd, this.params.gg.mulScalar(c, key))
return new dleqProof(this.params, c, s)
}
}
21 changes: 21 additions & 0 deletions src/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,30 @@ export class Group {
return k as Scalar
}

equalScalar(a: Scalar, b: Scalar): boolean {
return a.equals(b)
}

addScalar(a: Scalar, b: Scalar): Scalar {
const c = a.add(b)
c.mod(this.curve.r)
c.normalize()
return c
}

subScalar(a: Scalar, b: Scalar): Scalar {
const c = a.sub(b).add(this.curve.r)
c.mod(this.curve.r)
c.normalize()
return c
}

mulScalar(a: Scalar, b: Scalar): Scalar {
const c = a.mulmod(b, this.curve.r)
c.normalize()
return c
}

invScalar(k: Scalar): Scalar {
return k.inverseMod(this.curve.r)
}
Expand All @@ -263,6 +280,10 @@ export class Group {
return k.equals(0)
}

static add(e: Elt, f: Elt): Elt {
return e.toJac().add(f).toAffine() as Elt
}

static mul(k: Scalar, e: Elt): Elt {
return e.mult(k) as Elt
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// at https://opensource.org/licenses/BSD-3-Clause

export * from './group.js'
export * from './dleq.js'
export * from './oprf.js'
export * from './client.js'
export * from './server.js'
Expand Down
62 changes: 62 additions & 0 deletions test/dleq.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2021 Cloudflare, Inc. and contributors.
// Copyright (c) 2021 Cloudflare, Inc.
// Licensed under the BSD-3-Clause license found in the LICENSE file or
// at https://opensource.org/licenses/BSD-3-Clause

import { DLEQParams, DLEQProof, DLEQProver, Elt, Group, GroupID, Scalar } from '../src/index.js'

describe.each([GroupID.P256, GroupID.P384, GroupID.P521])('DLEQ', (id: GroupID) => {
const gg = new Group(id)
const params: DLEQParams = { gg, hash: 'SHA-256', dst: 'domain-sep' }
const Peggy = new DLEQProver(params)

let k: Scalar
let P: Elt
let kP: Elt
let Q: Elt
let kQ: Elt
let proof: DLEQProof
let proofBatched: DLEQProof
let list: Array<[Elt, Elt]>

describe.each([...Array(5).keys()])(`${gg.id}`, (i: number) => {
beforeAll(async () => {
k = await gg.randomScalar()
P = gg.mulBase(await gg.randomScalar())
kP = Group.mul(k, P)
Q = gg.mulBase(await gg.randomScalar())
kQ = Group.mul(k, Q)
proof = await Peggy.prove(k, [P, kP], [Q, kQ])

list = new Array<[Elt, Elt]>()
for (let l = 0; l < 3; l++) {
const c = gg.mulBase(await gg.randomScalar())
const d = Group.mul(k, c)
list.push([c, d])
}
proofBatched = await Peggy.prove_batch(k, [P, kP], list)
})

it(`prove-single/${i}`, async () => {
expect(await proof.verify([P, kP], [Q, kQ])).toBe(true)
})

it(`prove-batch/${i}`, async () => {
expect(await proofBatched.verify_batch([P, kP], list)).toBe(true)
})

it(`invalid-arguments/${i}`, async () => {
expect(await proof.verify([kP, P], [Q, kQ])).toBe(false)
})

it(`invalid-proof/${i}`, async () => {
expect(await proof.verify([kP, P], [Q, kQ])).toBe(false)
})

it(`bad-key/${i}`, async () => {
const badKey = await gg.randomScalar()
const badProof: DLEQProof = await Peggy.prove(badKey, [P, kP], [Q, kQ])
expect(await badProof.verify([P, kP], [Q, kQ])).toBe(false)
})
})
})

0 comments on commit 38942b7

Please sign in to comment.