refactor: improve identity class methods

This commit is contained in:
cedoor
2022-01-19 22:34:28 +01:00
parent 793cf95161
commit 2c00e9bbff
5 changed files with 212 additions and 183 deletions

View File

@@ -1,62 +1,70 @@
import { Identity, SerializedIdentity } from "@zk-kit/types"
import * as bigintConversion from "bigint-conversion"
import * as ciromlibjs from "circomlibjs"
import { genIdentityFromMessage, genRandomIdentity } from "./strategies"
import { Fq } from "./utils"
const poseidonHash = (data: Array<bigint>): bigint => ciromlibjs.poseidon(data)
export enum Strategy {
RANDOM,
MESSAGE,
SERIALIZED
}
import { hexToBigint } from "bigint-conversion"
import { poseidon } from "circomlibjs"
import { genIdentityFromMessage, genRandomIdentity, Strategy } from "./strategies"
import { Fq, parseSerializedIdentity } from "./utils"
export enum SecretType {
GENERIC, // generic secret, composed of identityNullifier and identityTrapdoor
MULTIPART_SECRET // multipart secret, composed from multiple parts dependent on the spam threshold
GENERIC, // Generic secret, composed of identityNullifier and identityTrapdoor.
MULTIPART_SECRET // Multipart secret, composed from multiple parts dependent on the spam threshold.
}
class ZkIdentity {
private identityTrapdoor: bigint
private identityNullifier: bigint
private secret: Array<bigint> = []
private multipartSecret: Array<bigint> = []
export default class ZkIdentity {
private _identityTrapdoor: bigint
private _identityNullifier: bigint
private _secret: bigint[] = []
private _multipartSecret: bigint[] = []
/**
* Generates new ZkIdentity
* @param strategy strategy for identity generation
* @param metadata additional data needed to create identity for given strategy
* Generates new ZkIdentity.
* @param strategy strategy for identity generation.
* @param metadata additional data needed to create identity for given strategy.
* @returns
*/
constructor(strategy: Strategy = Strategy.RANDOM, metadata?: string | SerializedIdentity) {
if (strategy === Strategy.RANDOM) {
const { identityTrapdoor, identityNullifier } = genRandomIdentity()
this.identityTrapdoor = identityTrapdoor
this.identityNullifier = identityNullifier
this.genSecret()
this.genMultipartSecret()
} else if (strategy === Strategy.MESSAGE) {
const { identityTrapdoor, identityNullifier } = genIdentityFromMessage(metadata as string)
this.identityTrapdoor = identityTrapdoor
this.identityNullifier = identityNullifier
this.genSecret()
this.genMultipartSecret()
} else if (strategy === Strategy.SERIALIZED) {
const { identityNullifier, identityTrapdoor, secret, multipartSecret } = metadata as SerializedIdentity
this.identityNullifier = bigintConversion.hexToBigint(identityNullifier)
this.identityTrapdoor = bigintConversion.hexToBigint(identityTrapdoor)
this.secret = secret.map((item) => bigintConversion.hexToBigint(item))
this.multipartSecret = multipartSecret.map((item) => bigintConversion.hexToBigint(item))
} else throw new Error("provided strategy is not supported")
}
switch (strategy) {
case Strategy.RANDOM: {
const { identityTrapdoor, identityNullifier } = genRandomIdentity()
// Secret and identity generation
this._identityTrapdoor = identityTrapdoor
this._identityNullifier = identityNullifier
this._secret = [this._identityNullifier, this._identityTrapdoor]
this._genMultipartSecret()
/**
* Generate generic secret. To be used by Semaphore related apps.
*/
genSecret(): void {
this.secret = [this.identityNullifier, this.identityTrapdoor]
break
}
case Strategy.MESSAGE: {
const { identityTrapdoor, identityNullifier } = genIdentityFromMessage(metadata as string)
this._identityTrapdoor = identityTrapdoor
this._identityNullifier = identityNullifier
this._secret = [this._identityNullifier, this._identityTrapdoor]
this._genMultipartSecret()
break
}
case Strategy.SERIALIZED: {
if (!metadata) {
throw new Error("Metadata is not defined")
}
if (typeof metadata === "string") {
metadata = parseSerializedIdentity(metadata)
}
const { identityNullifier, identityTrapdoor, secret, multipartSecret } = metadata
this._identityNullifier = hexToBigint(identityNullifier)
this._identityTrapdoor = hexToBigint(identityTrapdoor)
this._secret = secret.map((item) => hexToBigint(item))
this._multipartSecret = multipartSecret.map((item) => hexToBigint(item))
break
}
default:
throw new Error("Provided strategy is not supported")
}
}
/**
@@ -64,99 +72,77 @@ class ZkIdentity {
* @param parts The number of parts that the secret should be composed of,
* corresponding to the spam threshold of the protocol
*/
genMultipartSecret(parts = 2): void {
private _genMultipartSecret(parts = 2): void {
if (parts < 2) throw new Error("Invalid number of parts")
const initialComponent = Fq.pow(this.identityTrapdoor, this.identityNullifier)
this.multipartSecret = [initialComponent]
const initialComponent = Fq.pow(this._identityTrapdoor, this._identityNullifier)
this._multipartSecret = [initialComponent]
for (let i = 1; i < parts; i += 1) {
this.multipartSecret.push(Fq.pow(initialComponent, BigInt(i + 1)))
this._multipartSecret.push(Fq.pow(initialComponent, BigInt(i + 1)))
}
}
/**
* Return the raw user identity, composed of identityNullifier and identityTrapdoor.
* @returns Identity
*/
public getIdentity(): Identity {
return {
identityNullifier: this._identityNullifier,
identityTrapdoor: this._identityTrapdoor
}
}
public getNullifier(): bigint {
return this._identityNullifier
}
public getSecret(): bigint[] {
return this._secret
}
public getMultipartSecret(): bigint[] {
return this._multipartSecret
}
public getSecretHash(): bigint {
return poseidon(this._secret)
}
public getMultipartSecretHash(): bigint {
return poseidon(this._multipartSecret)
}
/**
* Generate commitment from secret
* @param secretType The secret type for which to generate identity commitment
* @returns identity commitment
*/
genIdentityCommitment(secretType: SecretType = SecretType.GENERIC): bigint {
let secretHash = this.getSecretHash()
if (secretType === SecretType.MULTIPART_SECRET) {
secretHash = this.getMultipartSecretHash()
public genIdentityCommitment(secretType: SecretType = SecretType.GENERIC): bigint {
switch (secretType) {
case SecretType.GENERIC:
return poseidon([this.getSecretHash()])
case SecretType.MULTIPART_SECRET:
return poseidon([this.getMultipartSecretHash()])
default:
throw new Error("Provided secret type is not supported")
}
return poseidonHash([secretHash])
}
// Serialization
/**
* Serializes the `identityNullifier`, `identityTrapdoor` and `secret` from the identity
* @returns stringified serialized identity
*/
serializeIdentity(): string {
public serializeIdentity(): string {
const data: SerializedIdentity = {
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))
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)
}
/**
* Unserialize serialized identity
* @param serialisedIdentity
* @returns
*/
static genFromSerialized(serialisedIdentity: string): ZkIdentity {
const data = JSON.parse(serialisedIdentity)
if (
!("identityNullifier" in data) ||
!("identityTrapdoor" in data) ||
!("secret" in data) ||
!("multipartSecret" in data)
)
throw new Error("Wrong input identity")
return new ZkIdentity(Strategy.SERIALIZED, {
identityNullifier: data.identityNullifier,
identityTrapdoor: data.identityTrapdoor,
secret: data.secret,
multipartSecret: data.multipartSecret
})
}
// Getters
/**
* Return the raw user identity, composed of identityNullifier and identityTrapdoor.
* @returns Identity
*/
getIdentity(): Identity {
return {
identityNullifier: this.identityNullifier,
identityTrapdoor: this.identityTrapdoor
}
}
getNullifier(): bigint {
return this.identityNullifier
}
getSecret(): Array<bigint> {
return this.secret
}
getMultipartSecret(): Array<bigint> {
return this.multipartSecret
}
getSecretHash(): bigint {
return poseidonHash(this.secret)
}
getMultipartSecretHash(): bigint {
return poseidonHash(this.multipartSecret)
}
}
export default ZkIdentity

View File

@@ -1,4 +1,5 @@
import { Identity } from "@zk-kit/types"
import ZkIdentity, { Strategy, SecretType } from "./identity"
import ZkIdentity, { SecretType } from "./identity"
import { Strategy } from "./strategies"
export { ZkIdentity, Identity, Strategy, SecretType }

View File

@@ -1,39 +1,34 @@
import * as crypto from "crypto"
import * as bigintConversion from "bigint-conversion"
import { sha256 as _sha256 } from "js-sha256"
import { Identity } from "@zk-kit/types"
import { hexToBigint } from "bigint-conversion"
import { genRandomNumber, sha256 } from "./utils"
const genRandomNumber = (numBytes = 31): bigint => bigintConversion.bufToBigint(crypto.randomBytes(numBytes))
export enum Strategy {
RANDOM,
MESSAGE,
SERIALIZED
}
/**
*
* @returns Identity
* Generates a random identity.
* @returns Identity The generated identity.
*/
const genRandomIdentity = (): Identity => ({
identityNullifier: genRandomNumber(31),
identityTrapdoor: genRandomNumber(31)
})
/**
*
* @param metadata { signedMessage } from which to create identity
* @returns Identity
*/
const genIdentityFromMessage = (message: string): Identity => {
const sha256 = (message: string): string => {
const hash = _sha256.create()
hash.update(message)
return hash.hex()
}
const messageHash = sha256(message)
const identityNullifier = bigintConversion.hexToBigint(sha256(`${messageHash}identity_nullifier`))
const identityTrapdoor = bigintConversion.hexToBigint(sha256(`${messageHash}identity_trapdoor`))
export function genRandomIdentity(): Identity {
return {
identityTrapdoor,
identityNullifier
identityNullifier: genRandomNumber(),
identityTrapdoor: genRandomNumber()
}
}
export { genRandomIdentity, genIdentityFromMessage, genRandomNumber }
/**
* Generate an deterministic identity from an external message.
* @param message The message from which to create identity.
* @returns Identity The generated identity.
*/
export function genIdentityFromMessage(message: string): Identity {
const messageHash = sha256(message)
return {
identityNullifier: hexToBigint(sha256(`${messageHash}identity_nullifier`)),
identityTrapdoor: hexToBigint(sha256(`${messageHash}identity_trapdoor`))
}
}

View File

@@ -1,5 +1,51 @@
import { randomBytes } from "@ethersproject/random"
import { SerializedIdentity } from "@zk-kit/types"
import { bufToBigint } from "bigint-conversion"
import { ZqField } from "ffjavascript"
import { sha256 as _sha256 } from "js-sha256"
export const SNARK_FIELD_SIZE = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const SNARK_FIELD_SIZE = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617")
export const Fq = new ZqField(SNARK_FIELD_SIZE)
/**
* Returns an hexadecimal sha256 hash of the message passed as parameter.
* @param message The string to hash.
* @returns The hexadecimal hash of the message.
*/
export function sha256(message: string): string {
const hash = _sha256.create()
hash.update(message)
return hash.hex()
}
/**
* Generates a random big number.
* @param numberOfBytes The number of bytes of the number.
* @returns The generated random number.
*/
export function genRandomNumber(numberOfBytes = 31): bigint {
return bufToBigint(randomBytes(numberOfBytes))
}
/**
* Parses a string containing the serialized identity parameters.
* @param serializedIdentity The serialized identity string.
* @returns The serialized identity parameters.
*/
export function parseSerializedIdentity(serializedIdentity: string): SerializedIdentity {
const data = JSON.parse(serializedIdentity)
if (
!("identityNullifier" in data) ||
!("identityTrapdoor" in data) ||
!("secret" in data) ||
!("multipartSecret" in data)
) {
throw new Error("Wrong input identity")
}
return data
}

View File

@@ -1,58 +1,59 @@
import { Strategy, ZkIdentity } from "../src"
describe("Semaphore identity", () => {
describe("ZK identity", () => {
describe("Create identity", () => {
it("Should create a Semaphore identity", async () => {
const identity: ZkIdentity = new ZkIdentity()
it("Should not create a ZK identity if the strategy is wrong", () => {
const fun = () => new ZkIdentity("wrong" as any)
expect(typeof identity).toBe("object")
expect(fun).toThrow("strategy is not supported")
})
it("Should create a Semaphore identity with a message strategy", async () => {
const identity: ZkIdentity = new ZkIdentity(Strategy.MESSAGE, "message")
it("Should create a ZK identity", () => {
const identity = new ZkIdentity()
expect(typeof identity).toBe("object")
})
it("Should generate secret from identity", async () => {
const identity: ZkIdentity = new ZkIdentity()
identity.genSecret()
const identitySecret = identity.getSecret()
const identityMultipartSecret = identity.getMultipartSecret()
expect(identitySecret).toHaveLength(2)
expect(typeof identitySecret).toBe("object")
expect(identityMultipartSecret).toHaveLength(2)
expect(typeof identityMultipartSecret).toBe("object")
expect(typeof identity).toBe("object")
})
it("Should generate multipart secret", async () => {
const secretParts = 5
const identity: ZkIdentity = new ZkIdentity()
identity.genMultipartSecret(secretParts)
const identitySecret = identity.getMultipartSecret()
it("Should create a ZK identity with a message strategy", () => {
const identity = new ZkIdentity(Strategy.MESSAGE, "message")
expect(identitySecret).toHaveLength(5)
expect(typeof identitySecret).toBe("object")
expect(typeof identity).toBe("object")
})
it("Should generate identity commitment from identity", async () => {
const identity: ZkIdentity = new ZkIdentity()
const identityCommitment: bigint = identity.genIdentityCommitment()
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 identity commitment", () => {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment()
expect(typeof identityCommitment).toBe("bigint")
})
it("Should serialize identity", async () => {
const identity: ZkIdentity = new ZkIdentity()
const serialized: string = identity.serializeIdentity()
it("Should serialize an identity", () => {
const identity = new ZkIdentity()
const serialized = identity.serializeIdentity()
expect(typeof serialized).toBe("string")
})
it("Should unserialize identity", async () => {
const identity: ZkIdentity = new ZkIdentity()
const serialized: string = identity.serializeIdentity()
const unserialized: ZkIdentity = ZkIdentity.genFromSerialized(serialized)
it("Should unserialize an identity", () => {
const identity1 = new ZkIdentity()
const serialized = identity1.serializeIdentity()
const identity2 = new ZkIdentity(Strategy.SERIALIZED, serialized)
expect(unserialized).toStrictEqual(identity)
expect(identity2).toStrictEqual(identity1)
})
})
})