Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(identity)!: update type of private key #803

Merged
merged 4 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,28 @@ const { privateKey, publicKey, commitment } = new Identity()
const identity = new Identity("your-private-key")
```

\# **identity.export**(): _string_

```typescript
import { Identity } from "@semaphore-protocol/identity"

const identity = new Identity()

const privateKey = identity.export()
```

\# **identity.import**(privateKey: _string_): _Identity_

```typescript
import { Identity } from "@semaphore-protocol/identity"

const identity = new Identity()

const privateKey = identity.export()

const identity2 = Identity.import(privateKey)
```

\# **identity.signMessage**(message: _BigNumberish_): _Signature\<string>_

```typescript
Expand Down
4 changes: 2 additions & 2 deletions packages/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
},
"dependencies": {
"@zk-kit/baby-jubjub": "1.0.1",
"@zk-kit/eddsa-poseidon": "1.0.1",
"@zk-kit/utils": "1.0.0",
"@zk-kit/eddsa-poseidon": "1.0.2",
"@zk-kit/utils": "1.2.0",
"poseidon-lite": "0.2.0"
}
}
68 changes: 34 additions & 34 deletions packages/identity/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { Point } from "@zk-kit/baby-jubjub"
import { EdDSAPoseidon, Signature, signMessage, verifySignature } from "@zk-kit/eddsa-poseidon"
import type { BigNumberish } from "@zk-kit/utils"
import { hexadecimalToBuffer } from "@zk-kit/utils/conversions"
import { requireString } from "@zk-kit/utils/error-handlers"
import { isHexadecimal } from "@zk-kit/utils/type-checks"
import { base64ToBuffer, bufferToBase64, textToBase64 } from "@zk-kit/utils/conversions"
import { isString } from "@zk-kit/utils/type-checks"
import { poseidon2 } from "poseidon-lite/poseidon2"

/**
Expand All @@ -13,12 +12,11 @@ import { poseidon2 } from "poseidon-lite/poseidon2"
* and {@link https://www.poseidon-hash.info | Poseidon} for signatures.
* In addition, the commitment, i.e. the hash of the public key, is used to represent
* Semaphore identities in groups, adding an additional layer of privacy and security.
* The private key of the identity is stored as a hexadecimal string or text.
* The other attributes are stored as stringified bigint.
* The private key of the identity can be exported as a base64 string.
*/
export class Identity {
// The EdDSA private key, passed as a parameter or generated randomly.
private _privateKey: string
private _privateKey: string | Buffer | Uint8Array
// The secret scalar derived from the private key.
// It is used in circuits to derive the public key.
private _secretScalar: bigint
Expand All @@ -28,9 +26,8 @@ export class Identity {
private _commitment: bigint

/**
* Initializes the class attributes based on a given private key, which must be a hexadecimal string or a text.
* Hexadecimal strings must not start with '0x' or '0X'.
* If the private key is not passed as a parameter, a random hexadecimal key will be generated.
* Initializes the class attributes based on a given private key, which must be text or a buffer.
* If the private key is not passed as a parameter, a random private key will be generated.
* The EdDSAPoseidon class is used to generate the secret scalar and the public key.
* Additionally, the constructor computes a commitment of the public key using a hash function (Poseidon).
*
Expand All @@ -43,35 +40,20 @@ export class Identity {
*
* @param privateKey The private key used to derive the public key (hexadecimal or string).
*/
constructor(privateKey?: string) {
let eddsa: EdDSAPoseidon

if (privateKey) {
requireString(privateKey, "privateKey")

this._privateKey = privateKey

if (isHexadecimal(privateKey, false)) {
eddsa = new EdDSAPoseidon(hexadecimalToBuffer(privateKey))
} else {
eddsa = new EdDSAPoseidon(privateKey)
}
} else {
eddsa = new EdDSAPoseidon()

this._privateKey = eddsa.privateKey as string
}
constructor(privateKey?: string | Buffer | Uint8Array) {
const eddsa = new EdDSAPoseidon(privateKey)

this._privateKey = eddsa.privateKey
this._secretScalar = eddsa.secretScalar
this._publicKey = eddsa.publicKey
this._commitment = poseidon2(this._publicKey)
}

/**
* Returns the private key.
* @returns The private key as a string (hexadecimal or text).
* @returns The private key as a buffer or text.
*/
public get privateKey(): string {
public get privateKey(): string | Buffer | Uint8Array {
return this._privateKey
}

Expand Down Expand Up @@ -99,6 +81,28 @@ export class Identity {
return this._commitment
}

/**
* Returns the private key encoded as a base64 string.
* @returns The private key as a base64 string.
*/
public export(): string {
if (isString(this._privateKey)) {
return textToBase64(this._privateKey as string)
}

return bufferToBase64(this.privateKey as Buffer | Uint8Array)
}

/**
* Returns a Semaphore identity based on a private key encoded as a base64 string.
* The private key will be converted to a buffer, regardless of its original type.
cedoor marked this conversation as resolved.
Show resolved Hide resolved
* @param privateKey The private key as a base64 string.
* @returns The Semaphore identity.
*/
static import(privateKey: string): Identity {
return new Identity(base64ToBuffer(privateKey))
}

/**
* Generates a signature for a given message using the private key.
* This method demonstrates how to sign a message and could be used
Expand All @@ -112,11 +116,7 @@ export class Identity {
* @returns A {@link https://zkkit.pse.dev/types/_zk_kit_eddsa_poseidon.Signature.html | Signature} object containing the signature components.
*/
public signMessage(message: BigNumberish): Signature<bigint> {
const privateKey = isHexadecimal(this.privateKey, false)
? hexadecimalToBuffer(this.privateKey)
: this.privateKey

return signMessage(privateKey, message)
return signMessage(this.privateKey, message)
}

/**
Expand Down
108 changes: 88 additions & 20 deletions packages/identity/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,111 @@
import { derivePublicKey, deriveSecretScalar } from "@zk-kit/eddsa-poseidon"
import { poseidon2 } from "poseidon-lite"
import { Identity } from "../src"

describe("Identity", () => {
const privateKeyText = "secret"
const privateKeyHexadecimal = "dd998334940df8931b76d899fdb189415f7ff4280599f03a7574725a166aad7d"
const privateKeyBuffer = Buffer.from("another secret")

describe("# Identity", () => {
it("Should create an identity with a random secret (private key)", () => {
const identity = new Identity()
const privateKey = Buffer.from(identity.privateKey)

expect(typeof identity.privateKey).toBe("string")
expect(identity.privateKey).toHaveLength(64)
expect(typeof identity.secretScalar).toBe("bigint")
expect(identity.publicKey).toHaveLength(2)
expect(typeof identity.commitment).toBe("bigint")
expect(identity.privateKey).toHaveLength(32)
expect(identity.secretScalar).toBe(deriveSecretScalar(privateKey))
expect(identity.publicKey).toStrictEqual(derivePublicKey(privateKey))
expect(identity.commitment).toBe(poseidon2(identity.publicKey))
})

it("Should create deterministic identities from a secret text (private key)", () => {
it("Should create a deterministic identity from a secret text (private key)", () => {
cedoor marked this conversation as resolved.
Show resolved Hide resolved
const identity = new Identity(privateKeyText)

expect(typeof identity.privateKey).toBe("string")
expect(typeof identity.secretScalar).toBe("bigint")
expect(identity.publicKey).toHaveLength(2)
expect(typeof identity.commitment).toBe("bigint")
expect(identity.secretScalar).toBe(deriveSecretScalar(privateKeyText))
expect(identity.publicKey).toStrictEqual(derivePublicKey(privateKeyText))
expect(identity.commitment).toBe(poseidon2(identity.publicKey))
})

it("Should create deterministic identities from a secret hexadecimal (private key)", () => {
const identity = new Identity(privateKeyHexadecimal)
it("Should create a deterministic identity from a secret buffer (private key)", () => {
const identity = new Identity(privateKeyBuffer)

expect(typeof identity.privateKey).toBe("string")
expect(identity.privateKey).toHaveLength(64)
expect(typeof identity.secretScalar).toBe("bigint")
expect(identity.publicKey).toHaveLength(2)
expect(typeof identity.commitment).toBe("bigint")
expect(identity.secretScalar).toBe(deriveSecretScalar(privateKeyBuffer))
expect(identity.publicKey).toStrictEqual(derivePublicKey(privateKeyBuffer))
expect(identity.commitment).toBe(poseidon2(identity.publicKey))
})

it("Should create the same identity if the private key is the same", () => {
const identity = new Identity()

const identity2 = new Identity(identity.privateKey)

expect(identity.privateKey).toStrictEqual(identity2.privateKey)
expect(identity.secretScalar).toBe(identity2.secretScalar)
expect(identity.publicKey).toStrictEqual(identity2.publicKey)
expect(identity.commitment).toBe(identity2.commitment)
})

it("Should throw an error if the private key is not a string", () => {
cedoor marked this conversation as resolved.
Show resolved Hide resolved
const fun = () => new Identity(32 as any)

expect(fun).toThrow("Parameter 'privateKey' is not a string, received type: number")
expect(fun).toThrow("Parameter 'privateKey' is none of the following types: Buffer, Uint8Array, string")
})
})

describe("# export", () => {
it("Should export an identity where the private key is a buffer", () => {
const identity = new Identity(privateKeyBuffer)

const privateKey = identity.export()

expect(typeof privateKey).toBe("string")
expect(Buffer.from(privateKey, "base64")).toStrictEqual(privateKeyBuffer)
})

it("Should export an identity where the private key is text", () => {
const identity = new Identity(privateKeyBuffer)

const privateKey = identity.export()

expect(typeof privateKey).toBe("string")
expect(Buffer.from(privateKey, "base64")).toStrictEqual(privateKeyBuffer)
})
})

describe("# import", () => {
it("Should import an identity with a private key of buffer type", () => {
const identity = new Identity(privateKeyBuffer)
const privateKey = identity.export()

const identity2 = Identity.import(privateKey)

expect(identity2.privateKey).toStrictEqual(identity.privateKey)
expect(identity2.secretScalar).toBe(identity.secretScalar)
expect(identity2.publicKey).toStrictEqual(identity.publicKey)
expect(identity2.commitment).toBe(identity.commitment)
})

it("Should import an identity with a private key of text type", () => {
const identity = new Identity(privateKeyText)
const privateKey = identity.export()

const identity2 = Identity.import(privateKey)

expect(identity2.privateKey).toStrictEqual(Buffer.from(identity.privateKey))
expect(identity2.secretScalar).toBe(identity.secretScalar)
expect(identity2.publicKey).toStrictEqual(identity.publicKey)
expect(identity2.commitment).toBe(identity.commitment)
})

it("Should import an identity generated from a random private key", () => {
const identity = new Identity()
const privateKey = identity.export()

const identity2 = Identity.import(privateKey)

expect(identity2.privateKey).toStrictEqual(Buffer.from(identity.privateKey))
expect(identity2.secretScalar).toBe(identity.secretScalar)
expect(identity2.publicKey).toStrictEqual(identity.publicKey)
expect(identity2.commitment).toBe(identity.commitment)
})
})

Expand All @@ -63,7 +131,7 @@ describe("Identity", () => {
})

it("Should verify a signature with hexadecimal private key", () => {
const identity = new Identity(privateKeyHexadecimal)
const identity = new Identity(privateKeyBuffer)

const signature = identity.signMessage("message")

Expand Down
21 changes: 15 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6394,8 +6394,8 @@ __metadata:
"@rollup/plugin-node-resolve": "npm:^15.2.3"
"@rollup/plugin-typescript": "npm:^11.1.6"
"@zk-kit/baby-jubjub": "npm:1.0.1"
"@zk-kit/eddsa-poseidon": "npm:1.0.1"
"@zk-kit/utils": "npm:1.0.0"
"@zk-kit/eddsa-poseidon": "npm:1.0.2"
"@zk-kit/utils": "npm:1.2.0"
poseidon-lite: "npm:0.2.0"
rimraf: "npm:^5.0.5"
rollup: "npm:^4.12.0"
Expand Down Expand Up @@ -8487,14 +8487,14 @@ __metadata:
languageName: node
linkType: hard

"@zk-kit/eddsa-poseidon@npm:1.0.1":
version: 1.0.1
resolution: "@zk-kit/eddsa-poseidon@npm:1.0.1"
"@zk-kit/eddsa-poseidon@npm:1.0.2":
version: 1.0.2
resolution: "@zk-kit/eddsa-poseidon@npm:1.0.2"
dependencies:
"@zk-kit/baby-jubjub": "npm:1.0.1"
"@zk-kit/utils": "npm:1.0.0"
buffer: "npm:6.0.3"
checksum: 10/337db4a73bd58680e462b6a82f95321406b3cffce7dce0413f7c857159798b8d85120750de5f5e4c7f2bcc102a02a5e3145302aeb9110a3b6172e6bd3bc29f43
checksum: 10/4b4e984a96c5dbc95a8cf36ceb8b3712e37291c1473d01b0e22c15a33311e9cf88c86175d878dacda1852cc905cd2074aaa76defeaed33490be3964ca7a53372
languageName: node
linkType: hard

Expand Down Expand Up @@ -8525,6 +8525,15 @@ __metadata:
languageName: node
linkType: hard

"@zk-kit/utils@npm:1.2.0":
version: 1.2.0
resolution: "@zk-kit/utils@npm:1.2.0"
dependencies:
buffer: "npm:^6.0.3"
checksum: 10/4c0b37d64b28a6cc33c901a0c59325b1fe9c31e6519eaefe4aa6028ae9cb85e97f047976942875face030a1835d5c955ea546d7dc4fcf9d35df79192ee2502f3
languageName: node
linkType: hard

"JSONStream@npm:1.3.2":
version: 1.3.2
resolution: "JSONStream@npm:1.3.2"
Expand Down