mirror of
https://github.com/privacy-scaling-explorations/zk-kit.git
synced 2026-04-22 03:00:15 -04:00
refactor: improve identity class methods
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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`))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user