Skip to content

Commit

Permalink
Including verifiable modes.
Browse files Browse the repository at this point in the history
  • Loading branch information
armfazh committed Mar 23, 2022
1 parent ad5c52e commit 198bbd4
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 105 deletions.
67 changes: 64 additions & 3 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Oprf,
SuiteID
} from './oprf.js'
import { Group, Scalar } from './group.js'
import { Elt, Group, Scalar, SerializedElt } from './group.js'

class baseClient extends Oprf {
constructor(mode: ModeID, suite: SuiteID) {
Expand All @@ -39,18 +39,79 @@ class baseClient extends Oprf {
return [finData, evalReq]
}

finalize(finData: FinalizeData, evaluation: Evaluation): Promise<Uint8Array> {
doFinalize(
finData: FinalizeData,
evaluation: Evaluation,
info = new Uint8Array(0)
): Promise<Uint8Array> {
const blindScalar = this.gg.deserializeScalar(finData.blind)
const blindScalarInv = this.gg.invScalar(blindScalar)
const Z = this.gg.deserialize(evaluation.element)
const N = Group.mul(blindScalarInv, Z)
const unblinded = this.gg.serialize(N)
return this.coreFinalize(finData.input, unblinded)
return this.coreFinalize(finData.input, unblinded, info)
}
}

export class OPRFClient extends baseClient {
constructor(suite: SuiteID) {
super(Oprf.Mode.OPRF, suite)
}
finalize(finData: FinalizeData, evaluation: Evaluation): Promise<Uint8Array> {
return super.doFinalize(finData, evaluation)
}
}

export class VOPRFClient extends baseClient {
constructor(suite: SuiteID, private readonly pubKeyServer: Uint8Array) {
super(Oprf.Mode.VOPRF, suite)
}

finalize(finData: FinalizeData, evaluation: Evaluation): Promise<Uint8Array> {
if (!evaluation.proof) {
throw new Error('no proof provided')
}
const pkS = this.gg.deserialize(new SerializedElt(this.pubKeyServer))
const Q = this.gg.deserialize(finData.evalReq.blinded)
const kQ = this.gg.deserialize(evaluation.element)
if (!evaluation.proof.verify([this.gg.generator(), pkS], [Q, kQ])) {
throw new Error('proof failed')
}

return super.doFinalize(finData, evaluation)
}
}

export class POPRFClient extends baseClient {
constructor(suite: SuiteID, private readonly pubKeyServer: Uint8Array) {
super(Oprf.Mode.POPRF, suite)
}

private async pointFromInfo(info: Uint8Array): Promise<Elt> {
const m = await this.scalarFromInfo(info)
const T = this.gg.mulBase(m)
const pkS = this.gg.deserialize(new SerializedElt(this.pubKeyServer))
const tw = Group.add(T, pkS)
if (tw.isIdentity) {
throw new Error('invalid info')
}
return tw
}

async finalize(
finData: FinalizeData,
evaluation: Evaluation,
info = new Uint8Array(0)
): Promise<Uint8Array> {
if (!evaluation.proof) {
throw new Error('no proof provided')
}
const tw = await this.pointFromInfo(info)
const Q = this.gg.deserialize(evaluation.element)
const kQ = this.gg.deserialize(finData.evalReq.blinded)
if (!evaluation.proof.verify([this.gg.generator(), tw], [Q, kQ])) {
throw new Error('proof failed')
}
return super.doFinalize(finData, evaluation, info)
}
}
34 changes: 18 additions & 16 deletions src/oprf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Licensed under the BSD-3-Clause license found in the LICENSE file or
// at https://opensource.org/licenses/BSD-3-Clause

import { Group, GroupID, SerializedElt, SerializedScalar } from './group.js'
import { Group, GroupID, Scalar, SerializedElt, SerializedScalar } from './group.js'
import { joinAll, to16bits } from './util.js'

import { DLEQProof } from './dleq.js'
Expand Down Expand Up @@ -96,28 +96,33 @@ export abstract class Oprf {

protected async coreFinalize(
input: Uint8Array,
unblindedElement: Uint8Array,
info?: Uint8Array
element: Uint8Array,
info: Uint8Array
): Promise<Uint8Array> {
let hasInfo: Uint8Array[] = []
if (this.mode === Oprf.Mode.POPRF) {
if (info) {
hasInfo = [to16bits(info.length), info]
} else {
hasInfo = [new Uint8Array(0)]
}
hasInfo = [to16bits(info.length), info]
}

const hashInput = joinAll([
to16bits(input.length),
input,
...hasInfo,
to16bits(unblindedElement.length),
unblindedElement,
to16bits(element.length),
element,
new TextEncoder().encode(Oprf.LABELS.FinalizeDST)
])
return new Uint8Array(await crypto.subtle.digest(this.hash, hashInput))
}

protected scalarFromInfo(info: Uint8Array): Promise<Scalar> {
if (info.length >= 1 << 16) {
throw new Error('invalid info length')
}
const te = new TextEncoder()
const framedInfo = joinAll([te.encode('Info'), to16bits(info.length), info])
return this.gg.hashToScalar(framedInfo, this.getDST(Oprf.LABELS.HashToScalarDST))
}
}

export class Blind extends SerializedScalar {
Expand All @@ -131,20 +136,17 @@ export class Evaluated extends SerializedElt {
}

export class Evaluation {
constructor(
public readonly element: Evaluated,
public readonly proof?: DLEQProof
) { }
constructor(public readonly element: Evaluated, public readonly proof?: DLEQProof) {}
}

export class EvaluationRequest {
constructor(public readonly blinded: Blinded) { }
constructor(public readonly blinded: Blinded) {}
}

export class FinalizeData {
constructor(
public readonly input: Uint8Array,
public readonly blind: Blind,
public readonly evalReq: EvaluationRequest
) { }
) {}
}
127 changes: 100 additions & 27 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
// at https://opensource.org/licenses/BSD-3-Clause

import { Blinded, Evaluated, Evaluation, EvaluationRequest, ModeID, Oprf, SuiteID } from './oprf.js'
import { Group, SerializedScalar } from './group.js'
import { Group, Scalar, SerializedScalar } from './group.js'

import { DLEQProver } from './dleq.js'
import { ctEqual } from './util.js'

class baseServer extends Oprf {
private privateKey: Uint8Array
protected privateKey: Uint8Array

public supportsWebCryptoOPRF = false

Expand All @@ -18,17 +19,17 @@ class baseServer extends Oprf {
this.privateKey = privateKey
}

evaluate(req: EvaluationRequest): Promise<Evaluation> {
protected doEvaluation(bl: Blinded, key: Uint8Array): Promise<Evaluated> {
if (this.supportsWebCryptoOPRF) {
return this.evaluateWebCrypto(req)
return this.evaluateWebCrypto(bl, key)
}
return Promise.resolve(this.evaluateSJCL(req))
return Promise.resolve(this.evaluateSJCL(bl, key))
}

private async evaluateWebCrypto(req: EvaluationRequest): Promise<Evaluation> {
const key = await crypto.subtle.importKey(
private async evaluateWebCrypto(bl: Blinded, key: Uint8Array): Promise<Evaluated> {
const crKey = await crypto.subtle.importKey(
'raw',
this.privateKey,
key,
{
name: 'OPRF',
namedCurve: this.gg.id
Expand All @@ -37,41 +38,113 @@ class baseServer extends Oprf {
['sign']
)
// webcrypto accepts only compressed points.
let compressed = Uint8Array.from(req.blinded)
if (req.blinded[0] === 0x04) {
const P = this.gg.deserialize(req.blinded)
let compressed = Uint8Array.from(bl)
if (bl[0] === 0x04) {
const P = this.gg.deserialize(bl)
compressed = Uint8Array.from(this.gg.serialize(P, true))
}
const evaluation = await crypto.subtle.sign('OPRF', key, compressed)
return new Evaluation(new Evaluated(evaluation))
return new Evaluated(await crypto.subtle.sign('OPRF', crKey, compressed))
}

private evaluateSJCL(req: EvaluationRequest): Evaluation {
const P = this.gg.deserialize(req.blinded)
const serSk = new SerializedScalar(this.privateKey)
const sk = this.gg.deserializeScalar(serSk)
private evaluateSJCL(bl: Blinded, key: Uint8Array): Evaluated {
const P = this.gg.deserialize(bl)
const sk = this.gg.deserializeScalar(new SerializedScalar(key))
const Z = Group.mul(sk, P)
return new Evaluation(new Evaluated(this.gg.serialize(Z)))
return new Evaluated(this.gg.serialize(Z))
}

async fullEvaluate(input: Uint8Array): Promise<Uint8Array> {
const dst = this.getDST(Oprf.LABELS.HashToGroupDST)
const P = await this.gg.hashToGroup(input, dst)
protected async secretFromInfo(info: Uint8Array): Promise<[Scalar, Scalar]> {
const m = await this.scalarFromInfo(info)
const skS = this.gg.deserializeScalar(new SerializedScalar(this.privateKey))
const t = this.gg.addScalar(m, skS)
if (this.gg.isScalarZero(t)) {
throw new Error('inverse of zero')
}
const tInv = this.gg.invScalar(t)
return [t, tInv]
}

protected async doFullEvaluate(
input: Uint8Array,
info = new Uint8Array(0)
): Promise<Uint8Array> {
let secret = this.privateKey
if (this.mode === Oprf.Mode.POPRF) {
const [, evalSecret] = await this.secretFromInfo(info)
secret = this.gg.serializeScalar(evalSecret)
}

const P = await this.gg.hashToGroup(input, this.getDST(Oprf.LABELS.HashToGroupDST))
if (this.gg.isIdentity(P)) {
throw new Error('InvalidInputError')
}
const issuedElement = new EvaluationRequest(new Blinded(this.gg.serialize(P)))
const evaluation = await this.evaluate(issuedElement)
return this.coreFinalize(input, evaluation.element)
const blinded = new Blinded(this.gg.serialize(P))
const evaluated = await this.doEvaluation(blinded, secret)
return this.coreFinalize(input, evaluated, info)
}
}

export class OPRFServer extends baseServer {
constructor(suite: SuiteID, privateKey: Uint8Array) {
super(Oprf.Mode.OPRF, suite, privateKey)
}

async evaluate(req: EvaluationRequest): Promise<Evaluation> {
return new Evaluation(await this.doEvaluation(req.blinded, this.privateKey))
}
async fullEvaluate(input: Uint8Array): Promise<Uint8Array> {
return this.doFullEvaluate(input)
}
async verifyFinalize(input: Uint8Array, output: Uint8Array): Promise<boolean> {
return ctEqual(output, await this.fullEvaluate(input))
return ctEqual(output, await this.doFullEvaluate(input))
}
}

export class OPRFServer extends baseServer {
export class VOPRFServer extends baseServer {
constructor(suite: SuiteID, privateKey: Uint8Array) {
super(Oprf.Mode.OPRF, suite, privateKey)
super(Oprf.Mode.VOPRF, suite, privateKey)
}
async evaluate(req: EvaluationRequest): Promise<Evaluation> {
const e = await this.doEvaluation(req.blinded, this.privateKey)
const prover = new DLEQProver({ gg: this.gg, hash: this.hash, dst: '' })
const skS = this.gg.deserializeScalar(new SerializedScalar(this.privateKey))
const pkS = this.gg.mulBase(skS)
const Q = this.gg.deserialize(req.blinded)
const kQ = this.gg.deserialize(e)
const proof = await prover.prove(skS, [this.gg.generator(), pkS], [Q, kQ])
return new Evaluation(e, proof)
}
async fullEvaluate(input: Uint8Array): Promise<Uint8Array> {
return this.doFullEvaluate(input)
}
async verifyFinalize(input: Uint8Array, output: Uint8Array): Promise<boolean> {
return ctEqual(output, await this.doFullEvaluate(input))
}
}

export class POPRFServer extends baseServer {
constructor(suite: SuiteID, privateKey: Uint8Array) {
super(Oprf.Mode.POPRF, suite, privateKey)
}
async evaluate(req: EvaluationRequest, info = new Uint8Array(0)): Promise<Evaluation> {
const [keyProof, evalSecret] = await this.secretFromInfo(info)
const secret = this.gg.serializeScalar(evalSecret)
const e = await this.doEvaluation(req.blinded, secret)
const prover = new DLEQProver({ gg: this.gg, hash: this.hash, dst: '' })
const kG = this.gg.mulBase(keyProof)
const Q = this.gg.deserialize(e)
const kQ = this.gg.deserialize(req.blinded)
const proof = await prover.prove(keyProof, [this.gg.generator(), kG], [Q, kQ])
return new Evaluation(e, proof)
}
async fullEvaluate(input: Uint8Array, info = new Uint8Array(0)): Promise<Uint8Array> {
return this.doFullEvaluate(input, info)
}
async verifyFinalize(
input: Uint8Array,
output: Uint8Array,
info = new Uint8Array(0)
): Promise<boolean> {
return ctEqual(output, await this.doFullEvaluate(input, info))
}
}
Loading

0 comments on commit 198bbd4

Please sign in to comment.