Skip to content

Commit 3572f44

Browse files
cedoorartwyman
andauthored
fix(identity)!: update type of private key (#803)
* fix(identity)!: update type of private key The new types support buffers and text, so that there is less ambiguity on strings. * refactor(identity): use base64 as encoding for exported priv keys re #799 * Update packages/identity/src/index.ts Co-authored-by: Andrew Twyman <artwyman@users.noreply.github.com> * test(identity): add more tests re #799 --------- Co-authored-by: Andrew Twyman <artwyman@users.noreply.github.com>
1 parent 2830d3d commit 3572f44

File tree

5 files changed

+161
-62
lines changed

5 files changed

+161
-62
lines changed

packages/identity/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,28 @@ const { privateKey, publicKey, commitment } = new Identity()
8484
const identity = new Identity("your-private-key")
8585
```
8686

87+
\# **identity.export**(): _string_
88+
89+
```typescript
90+
import { Identity } from "@semaphore-protocol/identity"
91+
92+
const identity = new Identity()
93+
94+
const privateKey = identity.export()
95+
```
96+
97+
\# **identity.import**(privateKey: _string_): _Identity_
98+
99+
```typescript
100+
import { Identity } from "@semaphore-protocol/identity"
101+
102+
const identity = new Identity()
103+
104+
const privateKey = identity.export()
105+
106+
const identity2 = Identity.import(privateKey)
107+
```
108+
87109
\# **identity.signMessage**(message: _BigNumberish_): _Signature\<string>_
88110

89111
```typescript

packages/identity/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
},
4141
"dependencies": {
4242
"@zk-kit/baby-jubjub": "1.0.1",
43-
"@zk-kit/eddsa-poseidon": "1.0.1",
44-
"@zk-kit/utils": "1.0.0",
43+
"@zk-kit/eddsa-poseidon": "1.0.2",
44+
"@zk-kit/utils": "1.2.0",
4545
"poseidon-lite": "0.2.0"
4646
}
4747
}

packages/identity/src/index.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { Point } from "@zk-kit/baby-jubjub"
22
import { EdDSAPoseidon, Signature, signMessage, verifySignature } from "@zk-kit/eddsa-poseidon"
33
import type { BigNumberish } from "@zk-kit/utils"
4-
import { hexadecimalToBuffer } from "@zk-kit/utils/conversions"
5-
import { requireString } from "@zk-kit/utils/error-handlers"
6-
import { isHexadecimal } from "@zk-kit/utils/type-checks"
4+
import { base64ToBuffer, bufferToBase64, textToBase64 } from "@zk-kit/utils/conversions"
5+
import { isString } from "@zk-kit/utils/type-checks"
76
import { poseidon2 } from "poseidon-lite/poseidon2"
87

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

3028
/**
31-
* Initializes the class attributes based on a given private key, which must be a hexadecimal string or a text.
32-
* Hexadecimal strings must not start with '0x' or '0X'.
33-
* If the private key is not passed as a parameter, a random hexadecimal key will be generated.
29+
* Initializes the class attributes based on a given private key, which must be text or a buffer.
30+
* If the private key is not passed as a parameter, a random private key will be generated.
3431
* The EdDSAPoseidon class is used to generate the secret scalar and the public key.
3532
* Additionally, the constructor computes a commitment of the public key using a hash function (Poseidon).
3633
*
@@ -43,35 +40,20 @@ export class Identity {
4340
*
4441
* @param privateKey The private key used to derive the public key (hexadecimal or string).
4542
*/
46-
constructor(privateKey?: string) {
47-
let eddsa: EdDSAPoseidon
48-
49-
if (privateKey) {
50-
requireString(privateKey, "privateKey")
51-
52-
this._privateKey = privateKey
53-
54-
if (isHexadecimal(privateKey, false)) {
55-
eddsa = new EdDSAPoseidon(hexadecimalToBuffer(privateKey))
56-
} else {
57-
eddsa = new EdDSAPoseidon(privateKey)
58-
}
59-
} else {
60-
eddsa = new EdDSAPoseidon()
61-
62-
this._privateKey = eddsa.privateKey as string
63-
}
43+
constructor(privateKey?: string | Buffer | Uint8Array) {
44+
const eddsa = new EdDSAPoseidon(privateKey)
6445

46+
this._privateKey = eddsa.privateKey
6547
this._secretScalar = eddsa.secretScalar
6648
this._publicKey = eddsa.publicKey
6749
this._commitment = poseidon2(this._publicKey)
6850
}
6951

7052
/**
7153
* Returns the private key.
72-
* @returns The private key as a string (hexadecimal or text).
54+
* @returns The private key as a buffer or text.
7355
*/
74-
public get privateKey(): string {
56+
public get privateKey(): string | Buffer | Uint8Array {
7557
return this._privateKey
7658
}
7759

@@ -99,6 +81,28 @@ export class Identity {
9981
return this._commitment
10082
}
10183

84+
/**
85+
* Returns the private key encoded as a base64 string.
86+
* @returns The private key as a base64 string.
87+
*/
88+
public export(): string {
89+
if (isString(this._privateKey)) {
90+
return textToBase64(this._privateKey as string)
91+
}
92+
93+
return bufferToBase64(this.privateKey as Buffer | Uint8Array)
94+
}
95+
96+
/**
97+
* Returns a Semaphore identity based on a private key encoded as a base64 string.
98+
* The private key will be converted to a buffer, regardless of its original type.
99+
* @param privateKey The private key as a base64 string.
100+
* @returns The Semaphore identity.
101+
*/
102+
static import(privateKey: string): Identity {
103+
return new Identity(base64ToBuffer(privateKey))
104+
}
105+
102106
/**
103107
* Generates a signature for a given message using the private key.
104108
* This method demonstrates how to sign a message and could be used
@@ -112,11 +116,7 @@ export class Identity {
112116
* @returns A {@link https://zkkit.pse.dev/types/_zk_kit_eddsa_poseidon.Signature.html | Signature} object containing the signature components.
113117
*/
114118
public signMessage(message: BigNumberish): Signature<bigint> {
115-
const privateKey = isHexadecimal(this.privateKey, false)
116-
? hexadecimalToBuffer(this.privateKey)
117-
: this.privateKey
118-
119-
return signMessage(privateKey, message)
119+
return signMessage(this.privateKey, message)
120120
}
121121

122122
/**

packages/identity/tests/index.test.ts

Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,111 @@
1+
import { derivePublicKey, deriveSecretScalar } from "@zk-kit/eddsa-poseidon"
2+
import { poseidon2 } from "poseidon-lite"
13
import { Identity } from "../src"
24

35
describe("Identity", () => {
46
const privateKeyText = "secret"
5-
const privateKeyHexadecimal = "dd998334940df8931b76d899fdb189415f7ff4280599f03a7574725a166aad7d"
7+
const privateKeyBuffer = Buffer.from("another secret")
68

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

11-
expect(typeof identity.privateKey).toBe("string")
12-
expect(identity.privateKey).toHaveLength(64)
13-
expect(typeof identity.secretScalar).toBe("bigint")
14-
expect(identity.publicKey).toHaveLength(2)
15-
expect(typeof identity.commitment).toBe("bigint")
14+
expect(identity.privateKey).toHaveLength(32)
15+
expect(identity.secretScalar).toBe(deriveSecretScalar(privateKey))
16+
expect(identity.publicKey).toStrictEqual(derivePublicKey(privateKey))
17+
expect(identity.commitment).toBe(poseidon2(identity.publicKey))
1618
})
1719

18-
it("Should create deterministic identities from a secret text (private key)", () => {
20+
it("Should create a deterministic identity from a secret text (private key)", () => {
1921
const identity = new Identity(privateKeyText)
2022

21-
expect(typeof identity.privateKey).toBe("string")
22-
expect(typeof identity.secretScalar).toBe("bigint")
23-
expect(identity.publicKey).toHaveLength(2)
24-
expect(typeof identity.commitment).toBe("bigint")
23+
expect(identity.secretScalar).toBe(deriveSecretScalar(privateKeyText))
24+
expect(identity.publicKey).toStrictEqual(derivePublicKey(privateKeyText))
25+
expect(identity.commitment).toBe(poseidon2(identity.publicKey))
2526
})
2627

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

30-
expect(typeof identity.privateKey).toBe("string")
31-
expect(identity.privateKey).toHaveLength(64)
32-
expect(typeof identity.secretScalar).toBe("bigint")
33-
expect(identity.publicKey).toHaveLength(2)
34-
expect(typeof identity.commitment).toBe("bigint")
31+
expect(identity.secretScalar).toBe(deriveSecretScalar(privateKeyBuffer))
32+
expect(identity.publicKey).toStrictEqual(derivePublicKey(privateKeyBuffer))
33+
expect(identity.commitment).toBe(poseidon2(identity.publicKey))
34+
})
35+
36+
it("Should create the same identity if the private key is the same", () => {
37+
const identity = new Identity()
38+
39+
const identity2 = new Identity(identity.privateKey)
40+
41+
expect(identity.privateKey).toStrictEqual(identity2.privateKey)
42+
expect(identity.secretScalar).toBe(identity2.secretScalar)
43+
expect(identity.publicKey).toStrictEqual(identity2.publicKey)
44+
expect(identity.commitment).toBe(identity2.commitment)
3545
})
3646

3747
it("Should throw an error if the private key is not a string", () => {
3848
const fun = () => new Identity(32 as any)
3949

40-
expect(fun).toThrow("Parameter 'privateKey' is not a string, received type: number")
50+
expect(fun).toThrow("Parameter 'privateKey' is none of the following types: Buffer, Uint8Array, string")
51+
})
52+
})
53+
54+
describe("# export", () => {
55+
it("Should export an identity where the private key is a buffer", () => {
56+
const identity = new Identity(privateKeyBuffer)
57+
58+
const privateKey = identity.export()
59+
60+
expect(typeof privateKey).toBe("string")
61+
expect(Buffer.from(privateKey, "base64")).toStrictEqual(privateKeyBuffer)
62+
})
63+
64+
it("Should export an identity where the private key is text", () => {
65+
const identity = new Identity(privateKeyBuffer)
66+
67+
const privateKey = identity.export()
68+
69+
expect(typeof privateKey).toBe("string")
70+
expect(Buffer.from(privateKey, "base64")).toStrictEqual(privateKeyBuffer)
71+
})
72+
})
73+
74+
describe("# import", () => {
75+
it("Should import an identity with a private key of buffer type", () => {
76+
const identity = new Identity(privateKeyBuffer)
77+
const privateKey = identity.export()
78+
79+
const identity2 = Identity.import(privateKey)
80+
81+
expect(identity2.privateKey).toStrictEqual(identity.privateKey)
82+
expect(identity2.secretScalar).toBe(identity.secretScalar)
83+
expect(identity2.publicKey).toStrictEqual(identity.publicKey)
84+
expect(identity2.commitment).toBe(identity.commitment)
85+
})
86+
87+
it("Should import an identity with a private key of text type", () => {
88+
const identity = new Identity(privateKeyText)
89+
const privateKey = identity.export()
90+
91+
const identity2 = Identity.import(privateKey)
92+
93+
expect(identity2.privateKey).toStrictEqual(Buffer.from(identity.privateKey))
94+
expect(identity2.secretScalar).toBe(identity.secretScalar)
95+
expect(identity2.publicKey).toStrictEqual(identity.publicKey)
96+
expect(identity2.commitment).toBe(identity.commitment)
97+
})
98+
99+
it("Should import an identity generated from a random private key", () => {
100+
const identity = new Identity()
101+
const privateKey = identity.export()
102+
103+
const identity2 = Identity.import(privateKey)
104+
105+
expect(identity2.privateKey).toStrictEqual(Buffer.from(identity.privateKey))
106+
expect(identity2.secretScalar).toBe(identity.secretScalar)
107+
expect(identity2.publicKey).toStrictEqual(identity.publicKey)
108+
expect(identity2.commitment).toBe(identity.commitment)
41109
})
42110
})
43111

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

65133
it("Should verify a signature with hexadecimal private key", () => {
66-
const identity = new Identity(privateKeyHexadecimal)
134+
const identity = new Identity(privateKeyBuffer)
67135

68136
const signature = identity.signMessage("message")
69137

yarn.lock

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6394,8 +6394,8 @@ __metadata:
63946394
"@rollup/plugin-node-resolve": "npm:^15.2.3"
63956395
"@rollup/plugin-typescript": "npm:^11.1.6"
63966396
"@zk-kit/baby-jubjub": "npm:1.0.1"
6397-
"@zk-kit/eddsa-poseidon": "npm:1.0.1"
6398-
"@zk-kit/utils": "npm:1.0.0"
6397+
"@zk-kit/eddsa-poseidon": "npm:1.0.2"
6398+
"@zk-kit/utils": "npm:1.2.0"
63996399
poseidon-lite: "npm:0.2.0"
64006400
rimraf: "npm:^5.0.5"
64016401
rollup: "npm:^4.12.0"
@@ -8487,14 +8487,14 @@ __metadata:
84878487
languageName: node
84888488
linkType: hard
84898489

8490-
"@zk-kit/eddsa-poseidon@npm:1.0.1":
8491-
version: 1.0.1
8492-
resolution: "@zk-kit/eddsa-poseidon@npm:1.0.1"
8490+
"@zk-kit/eddsa-poseidon@npm:1.0.2":
8491+
version: 1.0.2
8492+
resolution: "@zk-kit/eddsa-poseidon@npm:1.0.2"
84938493
dependencies:
84948494
"@zk-kit/baby-jubjub": "npm:1.0.1"
84958495
"@zk-kit/utils": "npm:1.0.0"
84968496
buffer: "npm:6.0.3"
8497-
checksum: 10/337db4a73bd58680e462b6a82f95321406b3cffce7dce0413f7c857159798b8d85120750de5f5e4c7f2bcc102a02a5e3145302aeb9110a3b6172e6bd3bc29f43
8497+
checksum: 10/4b4e984a96c5dbc95a8cf36ceb8b3712e37291c1473d01b0e22c15a33311e9cf88c86175d878dacda1852cc905cd2074aaa76defeaed33490be3964ca7a53372
84988498
languageName: node
84998499
linkType: hard
85008500

@@ -8525,6 +8525,15 @@ __metadata:
85258525
languageName: node
85268526
linkType: hard
85278527

8528+
"@zk-kit/utils@npm:1.2.0":
8529+
version: 1.2.0
8530+
resolution: "@zk-kit/utils@npm:1.2.0"
8531+
dependencies:
8532+
buffer: "npm:^6.0.3"
8533+
checksum: 10/4c0b37d64b28a6cc33c901a0c59325b1fe9c31e6519eaefe4aa6028ae9cb85e97f047976942875face030a1835d5c955ea546d7dc4fcf9d35df79192ee2502f3
8534+
languageName: node
8535+
linkType: hard
8536+
85288537
"JSONStream@npm:1.3.2":
85298538
version: 1.3.2
85308539
resolution: "JSONStream@npm:1.3.2"

0 commit comments

Comments
 (0)