Merge pull request #14 from appliedzkp/fix/nrln_removal

Fix/nrln removal
This commit is contained in:
Omar Desogus
2022-02-02 22:48:15 +01:00
committed by GitHub
8 changed files with 14 additions and 338 deletions

View File

@@ -1,7 +1,7 @@
import { SerializedIdentity } from "@zk-kit/types"
import { hexToBigint } from "bigint-conversion"
import { poseidon } from "circomlibjs"
import { Fq, genRandomNumber, sha256 } from "./utils"
import { genRandomNumber, sha256 } from "./utils"
// The strategy used to generate the ZK identity.
export enum Strategy {
@@ -10,12 +10,6 @@ export enum Strategy {
SERIALIZED // Identity parameters are passed from outside.
}
// The secret type is used for the identity commitment generation.
export enum SecretType {
GENERIC, // Generic secret, composed of identityNullifier and identityTrapdoor.
MULTIPART // Multipart secret, composed from multiple parts dependent on the spam threshold.
}
/**
* ZkIdentity is a class which can be used by protocols supported by the
* @zk-key/protocols package and it simplifies the management of
@@ -26,8 +20,6 @@ export default class ZkIdentity {
private _identityNullifier: bigint
private _secret: bigint[] = []
private _multipartSecret: bigint[] = []
private _defaultMultipartSecret: bigint[] = []
/**
* Initializes the class attributes based on the strategy passed as parameter.
@@ -40,8 +32,6 @@ export default class ZkIdentity {
this._identityTrapdoor = genRandomNumber()
this._identityNullifier = genRandomNumber()
this._secret = [this._identityNullifier, this._identityTrapdoor]
this._setMultipartSecret()
break
}
case Strategy.MESSAGE: {
@@ -58,8 +48,6 @@ export default class ZkIdentity {
this._identityTrapdoor = hexToBigint(sha256(`${messageHash}identity_trapdoor`))
this._identityNullifier = hexToBigint(sha256(`${messageHash}identity_nullifier`))
this._secret = [this._identityNullifier, this._identityTrapdoor]
this._setMultipartSecret()
break
}
case Strategy.SERIALIZED: {
@@ -78,19 +66,16 @@ export default class ZkIdentity {
if (
!("identityNullifier" in metadata) ||
!("identityTrapdoor" in metadata) ||
!("secret" in metadata) ||
!("multipartSecret" in metadata)
!("secret" in metadata)
) {
throw new Error("The serialized identity does not contain the right parameter")
}
const { identityNullifier, identityTrapdoor, secret, multipartSecret } = metadata
const { identityNullifier, identityTrapdoor, secret } = metadata
this._identityNullifier = hexToBigint(identityNullifier)
this._identityTrapdoor = hexToBigint(identityTrapdoor)
this._secret = secret.map((item) => hexToBigint(item))
this._multipartSecret = multipartSecret.map((item) => hexToBigint(item))
this._defaultMultipartSecret = this._multipartSecret.slice(0, 2)
break
}
@@ -99,21 +84,6 @@ export default class ZkIdentity {
}
}
/**
* Sets the multipart secret attribute.
*/
private _setMultipartSecret(): void {
const initialComponent = Fq.pow(this._identityTrapdoor, this._identityNullifier)
this._multipartSecret = [initialComponent]
for (let i = 1; i < 16; i += 1) {
this._multipartSecret.push(Fq.pow(initialComponent, BigInt(i + 1)))
}
this._defaultMultipartSecret = this._multipartSecret.slice(0, 2)
}
/**
* Returns the identity trapdoor.
* @returns The identity trapdoor.
@@ -138,15 +108,6 @@ export default class ZkIdentity {
return this._secret
}
/**
* Returns the default multipart secret or a portion of the multipart secret.
* @param secretParts The number of the secret parts.
* @returns The multipart secret.
*/
public getMultipartSecret(secretParts: number = 2): bigint[] {
return secretParts === 2 ? this._defaultMultipartSecret : this._multipartSecret.slice(0, secretParts)
}
/**
* Returns the Poseidon hash of the secret.
* @returns The hash of the secret.
@@ -155,32 +116,13 @@ export default class ZkIdentity {
return poseidon(this._secret)
}
/**
* Returns the Poseidon hash of the multipart secret.
* @param secretParts The number of the secret parts.
* @returns The hash of the multipart secret.
*/
public getMultipartSecretHash(secretParts: number = 2): bigint {
const multipartSecret = this.getMultipartSecret(secretParts)
return poseidon(multipartSecret)
}
/**
* Generates the identity commitment from the secret or the multipart secret.
* @param secretType The secret type for which to generate identity commitment
* @param secretParts The number of the secret parts.
* Generates the identity commitment from the secret.
* @returns identity commitment
*/
public genIdentityCommitment(secretType: SecretType = SecretType.GENERIC, secretParts: number = 2): bigint {
switch (secretType) {
case SecretType.GENERIC:
public genIdentityCommitment(): bigint {
return poseidon([this.getSecretHash()])
case SecretType.MULTIPART:
return poseidon([this.getMultipartSecretHash(secretParts)])
default:
throw new Error("The provided secret type is not supported")
}
}
/**
@@ -192,7 +134,6 @@ export default class ZkIdentity {
identityNullifier: this._identityNullifier.toString(16),
identityTrapdoor: this._identityTrapdoor.toString(16),
secret: this._secret.map((item) => item.toString(16)),
multipartSecret: this._multipartSecret.map((item) => item.toString(16))
}
return JSON.stringify(data)

View File

@@ -1,3 +1,3 @@
import ZkIdentity, { SecretType, Strategy } from "./identity"
import ZkIdentity, { Strategy } from "./identity"
export { ZkIdentity, Strategy, SecretType }
export { ZkIdentity, Strategy }

View File

@@ -1,4 +1,4 @@
import { SecretType, Strategy, ZkIdentity } from "../src"
import { Strategy, ZkIdentity } from "../src"
describe("ZK identity", () => {
describe("Create identity", () => {
@@ -32,35 +32,15 @@ describe("ZK identity", () => {
const trapdoor = identity.getTrapdoor()
const nullifier = identity.getNullifier()
const identitySecret = identity.getSecret()
const identityMultipartSecret = identity.getMultipartSecret(5)
expect(typeof identity).toBe("object")
expect(trapdoor).toBe(BigInt("58952291509798197436757858062402199043831251943841934828591473955215726495831"))
expect(nullifier).toBe(BigInt("44673097405870585416457571638073245190425597599743560105244308998175651589997"))
expect(identitySecret).toHaveLength(2)
expect(typeof identitySecret).toBe("object")
expect(identityMultipartSecret).toHaveLength(5)
expect(typeof identityMultipartSecret).toBe("object")
})
it("Should get the multipart secret hash", () => {
const identity = new ZkIdentity(Strategy.MESSAGE, "message")
const multipartSecretHash = identity.getMultipartSecretHash(2)
expect(multipartSecretHash).toBe(
BigInt("6297169125057226632028063090073601040720936421655236852498563322517193066452")
)
})
it("Should not generate identity commitment if the secret type is wrong", () => {
const identity = new ZkIdentity()
const fun = () => identity.genIdentityCommitment("wrong" as any)
expect(fun).toThrow("secret type is not supported")
})
it("Should generate an identity commitment from a generic secret", () => {
it("Should generate an identity commitment secret", () => {
const identity = new ZkIdentity(Strategy.MESSAGE, "message")
const identityCommitment = identity.genIdentityCommitment()
@@ -69,15 +49,6 @@ describe("ZK identity", () => {
)
})
it("Should generate an identity commitment from a multipart secret", () => {
const identity = new ZkIdentity(Strategy.MESSAGE, "message")
const identityCommitment = identity.genIdentityCommitment(SecretType.MULTIPART)
expect(identityCommitment).toBe(
BigInt("2814782719841798642796697732526373861365963990553246162063123378852339665110")
)
})
it("Should serialize an identity", () => {
const identity = new ZkIdentity()
const serialized = identity.serializeIdentity()

View File

@@ -1,13 +1,11 @@
import { FullProof, MerkleProof, SolidityProof } from "@zk-kit/types"
import RLN from "./rln"
import NRLN from "./nrln"
import Semaphore from "./semaphore"
import { generateMerkleProof, genExternalNullifier, genSignalHash } from "./utils"
export {
Semaphore,
RLN,
NRLN,
generateMerkleProof,
genExternalNullifier,
genSignalHash,

View File

@@ -1,114 +0,0 @@
import { MerkleProof, StrBigInt } from "@zk-kit/types"
import { poseidon } from "circomlibjs"
import { Fq, genSignalHash } from "./utils"
import ZkProtocol from "./zk-protocol"
export default class NRLN extends ZkProtocol {
/**
* Creates witness for RLN proof
* @param identitySecret identity secret
* @param merkleProof merkle proof that identity exists in RLN tree
* @param epoch epoch on which signal is broadcasted
* @param signal signal that is being broadcasted
* @param rlnIdentifier identifier used by each separate app, needed for more accurate spam filtering
* @param shouldHash should signal be hashed before broadcast
* @returns rln witness
*/
public static genWitness(
identitySecret: bigint[],
merkleProof: MerkleProof,
epoch: StrBigInt,
signal: string,
rlnIdentifier: bigint,
shouldHash = true
): any {
return {
identity_secret: identitySecret,
path_elements: merkleProof.siblings,
identity_path_index: merkleProof.pathIndices,
x: shouldHash ? genSignalHash(signal) : signal,
epoch,
rln_identifier: rlnIdentifier
}
}
/**
*
* @param identitySecret identity secret
* @param epoch epoch
* @param x singal hash
* @param limit number of messages per epoch allowed
* @param rlnIdentifier identifier used by each separate app, needed for more accurate spam filtering
* @returns
*/
public static calculateOutput(
identitySecret: bigint[],
epoch: bigint,
x: bigint,
limit: number,
rlnIdentifier: bigint
): bigint[] {
const a0 = poseidon(identitySecret)
const coeffs: bigint[] = []
let tmpX = x
coeffs.push(poseidon([identitySecret[0], epoch]))
let y: bigint = Fq.add(Fq.mul(coeffs[0], tmpX), a0)
for (let i = 1; i < limit; i += 1) {
tmpX = Fq.mul(x, tmpX)
coeffs.push(poseidon([identitySecret[i], epoch]))
y = Fq.add(y, Fq.mul(coeffs[i], tmpX))
}
coeffs.push(poseidon([rlnIdentifier]))
const nullifier: bigint = NRLN.genNullifier(coeffs)
return [y, nullifier]
}
/**
* Calculates slashing nullifier
* @param coeffs coeefitients from calculated polinomial
* @returns slashing nullifier
*/
public static genNullifier(coeffs: bigint[]): bigint {
return poseidon(coeffs)
}
/**
* When spam occurs, identity secret can be retrieved
* @param xs
* @param ys
* @returns identity secret
*/
public static retrieveSecret(xs: bigint[], ys: bigint[]): bigint {
if (xs.length !== ys.length) {
throw new Error("x and y arrays must be of same size")
}
const numOfPoints: number = xs.length
let f0 = BigInt(0)
for (let i = 0; i < numOfPoints; i += 1) {
let p = BigInt(1)
for (let j = 0; j < numOfPoints; j += 1) {
if (j !== i) {
p = Fq.mul(p, Fq.div(xs[j], Fq.sub(xs[j], xs[i])))
}
}
f0 = Fq.add(f0, Fq.mul(ys[i], p))
}
return f0
}
/**
*
* @returns unique identifier of the rln dapp
*/
public static genIdentifier(): bigint {
return Fq.random()
}
}

View File

@@ -1,119 +0,0 @@
import { SecretType, ZkIdentity } from "@zk-kit/identity"
import { poseidon } from "circomlibjs"
import { getCurveFromName } from "ffjavascript"
import * as fs from "fs"
import * as path from "path"
import { NRLN } from "../src"
import { generateMerkleProof, genExternalNullifier, genSignalHash } from "../src/utils"
describe("NRLN", () => {
const zkeyFiles = "./packages/protocols/zkeyFiles"
const identityCommitments: bigint[] = []
const SPAM_TRESHOLD = 3
let curve: any
beforeAll(async () => {
curve = await getCurveFromName("bn128")
const numberOfLeaves = 3
for (let i = 0; i < numberOfLeaves; i += 1) {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment(SecretType.MULTIPART)
identityCommitments.push(identityCommitment)
}
})
afterAll(async () => {
await curve.terminate()
})
describe("NRLN features", () => {
it("Should generate NRLN witness", () => {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment(SecretType.MULTIPART, SPAM_TRESHOLD)
const identitySecret = identity.getMultipartSecret(SPAM_TRESHOLD)
const leaves = Object.assign([], identityCommitments)
leaves.push(identityCommitment)
const signal = "hey hey"
const epoch = genExternalNullifier("test-epoch")
const rlnIdentifier = NRLN.genIdentifier()
const merkleProof = generateMerkleProof(15, BigInt(0), 2, leaves, identityCommitment)
const witness = NRLN.genWitness(identitySecret, merkleProof, epoch, signal, rlnIdentifier)
expect(typeof witness).toBe("object")
})
it("Should generate NRLN proof and verify it", async () => {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment(SecretType.MULTIPART, SPAM_TRESHOLD)
const identitySecret = identity.getMultipartSecret(SPAM_TRESHOLD)
const leaves = Object.assign([], identityCommitments)
leaves.push(identityCommitment)
const signal = "hey hey"
const signalHash = genSignalHash(signal)
const epoch = genExternalNullifier("test-epoch")
const rlnIdentifier = NRLN.genIdentifier()
const merkleProof = generateMerkleProof(15, BigInt(0), 2, leaves, identityCommitment)
const witness = NRLN.genWitness(identitySecret, merkleProof, epoch, signal, rlnIdentifier)
const [y, nullifier] = NRLN.calculateOutput(
identitySecret,
BigInt(epoch),
signalHash,
SPAM_TRESHOLD,
rlnIdentifier
)
const publicSignals = [y, merkleProof.root, nullifier, signalHash, epoch, rlnIdentifier]
const vkeyPath = path.join(zkeyFiles, "nrln", "verification_key.json")
const vKey = JSON.parse(fs.readFileSync(vkeyPath, "utf-8"))
const wasmFilePath = path.join(zkeyFiles, "nrln", "rln.wasm")
const finalZkeyPath = path.join(zkeyFiles, "nrln", "rln_final.zkey")
const fullProof = await NRLN.genProof(witness, wasmFilePath, finalZkeyPath)
const response = await NRLN.verifyProof(vKey, { proof: fullProof.proof, publicSignals })
expect(response).toBe(true)
}, 30000)
it("Should retrieve user secret after spaming", () => {
const identity = new ZkIdentity()
const identitySecret = identity.getMultipartSecret(SPAM_TRESHOLD)
const signal1 = "hey 1"
const signalHash1 = genSignalHash(signal1)
const signal2 = "hey 2"
const signalHash2 = genSignalHash(signal2)
const signal3 = "hey 3"
const signalHash3 = genSignalHash(signal3)
const signal4 = "hey 4"
const signalHash4 = genSignalHash(signal4)
const epoch = genExternalNullifier("test-epoch")
const rlnIdentifier = NRLN.genIdentifier()
const [y1] = NRLN.calculateOutput(identitySecret, BigInt(epoch), signalHash1, SPAM_TRESHOLD, rlnIdentifier)
const [y2] = NRLN.calculateOutput(identitySecret, BigInt(epoch), signalHash2, SPAM_TRESHOLD, rlnIdentifier)
const [y3] = NRLN.calculateOutput(identitySecret, BigInt(epoch), signalHash3, SPAM_TRESHOLD, rlnIdentifier)
const [y4] = NRLN.calculateOutput(identitySecret, BigInt(epoch), signalHash4, SPAM_TRESHOLD, rlnIdentifier)
const retrievedSecret = NRLN.retrieveSecret(
[signalHash1, signalHash2, signalHash3, signalHash4],
[y1, y2, y3, y4]
)
expect(retrievedSecret).toEqual(poseidon(identitySecret))
})
})
})

View File

@@ -1,4 +1,4 @@
import { SecretType, ZkIdentity } from "@zk-kit/identity"
import { ZkIdentity } from "@zk-kit/identity"
import { getCurveFromName } from "ffjavascript"
import * as fs from "fs"
import * as path from "path"
@@ -18,7 +18,7 @@ describe("RLN", () => {
for (let i = 0; i < numberOfLeaves; i += 1) {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment(SecretType.MULTIPART)
const identityCommitment = identity.genIdentityCommitment()
identityCommitments.push(identityCommitment)
}
@@ -32,7 +32,7 @@ describe("RLN", () => {
it("Should generate rln witness", () => {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment()
const secretHash = identity.getMultipartSecretHash()
const secretHash = identity.getSecretHash()
const leaves = Object.assign([], identityCommitments)
leaves.push(identityCommitment)
@@ -49,8 +49,8 @@ describe("RLN", () => {
it("Should generate rln proof and verify it", async () => {
const identity = new ZkIdentity()
const secretHash = identity.getMultipartSecretHash()
const identityCommitment = identity.genIdentityCommitment(SecretType.MULTIPART)
const secretHash = identity.getSecretHash()
const identityCommitment = identity.genIdentityCommitment()
const leaves = Object.assign([], identityCommitments)
leaves.push(identityCommitment)

View File

@@ -40,5 +40,4 @@ export declare type SerializedIdentity = {
identityNullifier: string
identityTrapdoor: string
secret: string[]
multipartSecret: string[]
}