Skip to content

Commit

Permalink
Merge pull request #215 from privacy-scaling-explorations/refactor/re…
Browse files Browse the repository at this point in the history
…strict-types

Restrict EdDSA private-key supported types
  • Loading branch information
cedoor committed Mar 19, 2024
2 parents fb61c04 + e545175 commit c90e57a
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 88 deletions.
2 changes: 1 addition & 1 deletion packages/circuits/tests/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const circomkit = new Circomkit({
* @param pubKey A public key generated using genPubKey()
* @returns The ECDH shared key.
*/
export const genEcdhSharedKey = (privKey: bigint, pubKey: [bigint, bigint]): Point<bigint> => {
export const genEcdhSharedKey = (privKey: Buffer | Uint8Array | string, pubKey: [bigint, bigint]): Point<bigint> => {
const secretScalar = deriveSecretScalar(privKey)

return mulPointEscalar(pubKey, secretScalar) as Point<bigint>
Expand Down
35 changes: 15 additions & 20 deletions packages/circuits/tests/ecdh.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WitnessTester } from "circomkit"
import { derivePublicKey, deriveSecretScalar } from "@zk-kit/eddsa-poseidon"
import { beBufferToBigInt, crypto } from "@zk-kit/utils"
import { crypto } from "@zk-kit/utils"
import { WitnessTester } from "circomkit"
import { circomkit, genEcdhSharedKey } from "./common"

describe("ECDH Shared Key derivation circuit", () => {
Expand All @@ -16,16 +16,14 @@ describe("ECDH Shared Key derivation circuit", () => {
it("should correctly compute an ECDH shared key", async () => {

Check warning on line 16 in packages/circuits/tests/ecdh.test.ts

View workflow job for this annotation

GitHub Actions / style

Test has no assertions
const privateKey1 = crypto.getRandomValues(32)
const privateKey2 = crypto.getRandomValues(32)
const bgPrivateKey1 = beBufferToBigInt(Buffer.from(privateKey1))
const bgPrivateKey2 = beBufferToBigInt(Buffer.from(privateKey2))

const publicKey2 = derivePublicKey(bgPrivateKey2)
const publicKey2 = derivePublicKey(privateKey2)

// generate a shared key between the first private key and the second public key
const ecdhSharedKey = genEcdhSharedKey(bgPrivateKey1, publicKey2)
const ecdhSharedKey = genEcdhSharedKey(privateKey1, publicKey2)

const circuitInputs = {
privateKey: deriveSecretScalar(bgPrivateKey1),
privateKey: deriveSecretScalar(privateKey1),
publicKey: publicKey2
}

Expand All @@ -35,22 +33,20 @@ describe("ECDH Shared Key derivation circuit", () => {
it("should generate the same shared key from the same keypairs", async () => {

Check warning on line 33 in packages/circuits/tests/ecdh.test.ts

View workflow job for this annotation

GitHub Actions / style

Test has no assertions
const privateKey1 = crypto.getRandomValues(32)
const privateKey2 = crypto.getRandomValues(32)
const bgPrivateKey1 = beBufferToBigInt(Buffer.from(privateKey1))
const bgPrivateKey2 = beBufferToBigInt(Buffer.from(privateKey2))
const publicKey1 = derivePublicKey(bgPrivateKey1)
const publicKey2 = derivePublicKey(bgPrivateKey2)
const publicKey1 = derivePublicKey(privateKey1)
const publicKey2 = derivePublicKey(privateKey2)

// generate a shared key between the first private key and the second public key
const ecdhSharedKey = genEcdhSharedKey(bgPrivateKey1, publicKey2)
const ecdhSharedKey2 = genEcdhSharedKey(bgPrivateKey2, publicKey1)
const ecdhSharedKey = genEcdhSharedKey(privateKey1, publicKey2)
const ecdhSharedKey2 = genEcdhSharedKey(privateKey2, publicKey1)

const circuitInputs = {
privateKey: deriveSecretScalar(bgPrivateKey1),
privateKey: deriveSecretScalar(privateKey1),
publicKey: publicKey2
}

const circuitInputs2 = {
privateKey: deriveSecretScalar(bgPrivateKey2),
privateKey: deriveSecretScalar(privateKey2),
publicKey: publicKey1
}

Expand All @@ -65,13 +61,12 @@ describe("ECDH Shared Key derivation circuit", () => {
})

it("should generate the same ECDH key consistently for the same inputs", async () => {

Check warning on line 63 in packages/circuits/tests/ecdh.test.ts

View workflow job for this annotation

GitHub Actions / style

Test has no assertions
const privateKey1 = deriveSecretScalar(Buffer.from(crypto.getRandomValues(32)))
const privateKey2 = crypto.getRandomValues(32)
const publicKey2 = derivePublicKey(beBufferToBigInt(Buffer.from(privateKey2)))
const secretScalar = deriveSecretScalar(crypto.getRandomValues(32))
const publicKey = derivePublicKey(crypto.getRandomValues(32))

const circuitInputs = {
privateKey: privateKey1,
publicKey: publicKey2
privateKey: secretScalar,
publicKey
}

// calculate first time witness and check contraints
Expand Down
6 changes: 3 additions & 3 deletions packages/circuits/tests/eddsa-proof.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { circomkit } from "./common"
describe("eddsa-proof", () => {
let circuit: WitnessTester<["secret", "scope"], ["commitment"]>

const secret = 3
const privateKey = Buffer.from("secret")
const scope = 2

const publicKey = derivePublicKey(secret)
const publicKey = derivePublicKey(privateKey)
const commitment = poseidon2(publicKey)

const INPUT = {
secret: deriveSecretScalar(secret),
secret: deriveSecretScalar(privateKey),
scope
}

Expand Down
19 changes: 8 additions & 11 deletions packages/circuits/tests/poseidon-cipher.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { WitnessTester } from "circomkit"
import { derivePublicKey } from "@zk-kit/eddsa-poseidon"
import { Nonce, PlainText, poseidonDecrypt, poseidonEncrypt, poseidonPerm } from "@zk-kit/poseidon-cipher"
import { beBufferToBigInt, crypto } from "@zk-kit/utils"
import { crypto } from "@zk-kit/utils"
import { WitnessTester } from "circomkit"
import { circomkit, genEcdhSharedKey } from "./common"

describe("poseidon-cipher", () => {
describe("poseidonDecrypt", () => {
let circuit: WitnessTester<["ciphertext", "nonce", "key"], ["decrypted"]>

const privateKey = crypto.getRandomValues(32)
const bgPrivateKey = beBufferToBigInt(Buffer.from(privateKey))
const publicKey = derivePublicKey(bgPrivateKey)
const encryptionKey = genEcdhSharedKey(bgPrivateKey, publicKey)
const publicKey = derivePublicKey(privateKey)
const encryptionKey = genEcdhSharedKey(privateKey, publicKey)

const nonce: Nonce = BigInt(5)

Expand Down Expand Up @@ -99,9 +98,8 @@ describe("poseidon-cipher", () => {
let circuit: WitnessTester<["ciphertext", "nonce", "key"], ["decrypted"]>

const privateKey = crypto.getRandomValues(32)
const bgPrivateKey = beBufferToBigInt(Buffer.from(privateKey))
const publicKey = derivePublicKey(bgPrivateKey)
const encryptionKey = genEcdhSharedKey(bgPrivateKey, publicKey)
const publicKey = derivePublicKey(privateKey)
const encryptionKey = genEcdhSharedKey(privateKey, publicKey)

const plainText: PlainText<bigint> = [BigInt(0), BigInt(1)]
const nonce: Nonce = BigInt(5)
Expand Down Expand Up @@ -157,9 +155,8 @@ describe("poseidon-cipher", () => {
let circuit: WitnessTester<["ciphertext", "nonce", "key"], ["decrypted"]>

const privateKey = crypto.getRandomValues(32)
const bgPrivateKey = beBufferToBigInt(Buffer.from(privateKey))
const publicKey = derivePublicKey(bgPrivateKey)
const encryptionKey = genEcdhSharedKey(bgPrivateKey, publicKey)
const publicKey = derivePublicKey(privateKey)
const encryptionKey = genEcdhSharedKey(privateKey, publicKey)

const plainText: PlainText<bigint> = [BigInt(0), BigInt(1)]
const nonce: Nonce = BigInt(5)
Expand Down
48 changes: 41 additions & 7 deletions packages/eddsa-poseidon/src/eddsa-poseidon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
} from "@zk-kit/baby-jubjub"
import type { BigNumberish } from "@zk-kit/utils"
import { crypto, requireBuffer } from "@zk-kit/utils"
import { bigNumberishToBigInt, leBigIntToBuffer, leBufferToBigInt } from "@zk-kit/utils/conversions"
import {
bigNumberishToBigInt,
leBigIntToBuffer,
leBufferToBigInt,
bufferToHexadecimal
} from "@zk-kit/utils/conversions"
import { requireBigNumberish } from "@zk-kit/utils/error-handlers"
import F1Field from "@zk-kit/utils/f1-field"
import * as scalar from "@zk-kit/utils/scalar"
Expand All @@ -22,16 +27,24 @@ import { hash as blake, checkMessage, checkPrivateKey, isPoint, isSignature, pru

/**
* Derives a secret scalar from a given EdDSA private key.
*
* This process involves hashing the private key with Blake1, pruning the resulting hash to retain the lower 32 bytes,
* and converting it into a little-endian integer. The use of the secret scalar streamlines the public key generation
* process by omitting steps 1, 2, and 3 as outlined in RFC 8032 section 5.1.5, enhancing circuit efficiency and simplicity.
* This method is crucial for fixed-base scalar multiplication operations within the correspondent cryptographic circuit.
* For detailed steps, see: {@link https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5}.
* For example usage in a circuit, see: {@link https://github.com/semaphore-protocol/semaphore/blob/2c144fc9e55b30ad09474aeafa763c4115338409/packages/circuits/semaphore.circom#L21}
*
* The private key must be an instance of Buffer, Uint8Array or a string. The input will be used to
* generate entropy and there is no limit in size.
* The string is used as a set of raw bytes (in UTF-8) and is typically used to pass passwords or secret messages.
* If you want to pass a bigint, a number or a hexadecimal, be sure to convert them to one of the supported types first.
* The 'conversions' module in @zk-kit/utils provides a set of functions that may be useful in case you need to convert types.
*
* @param privateKey The EdDSA private key for generating the associated public key.
* @returns The derived secret scalar to be used to calculate public key and optimized for circuit calculations.
*/
export function deriveSecretScalar(privateKey: BigNumberish): bigint {
export function deriveSecretScalar(privateKey: Buffer | Uint8Array | string): bigint {
// Convert the private key to buffer.
privateKey = checkPrivateKey(privateKey)

Expand All @@ -49,10 +62,17 @@ export function deriveSecretScalar(privateKey: BigNumberish): bigint {
* This function utilizes the Baby Jubjub elliptic curve for cryptographic operations.
* The private key should be securely stored and managed, and it should never be exposed
* or transmitted in an unsecured manner.
*
* The private key must be an instance of Buffer, Uint8Array or a string. The input will be used to
* generate entropy and there is no limit in size.
* The string is used as a set of raw bytes (in UTF-8) and is typically used to pass passwords or secret messages.
* If you want to pass a bigint, a number or a hexadecimal, be sure to convert them to one of the supported types first.
* The 'conversions' module in @zk-kit/utils provides a set of functions that may be useful in case you need to convert types.
*
* @param privateKey The private key used for generating the public key.
* @returns The derived public key.
*/
export function derivePublicKey(privateKey: BigNumberish): Point<bigint> {
export function derivePublicKey(privateKey: Buffer | Uint8Array | string): Point<bigint> {
const s = deriveSecretScalar(privateKey)

const publicKey = mulPointEscalar(Base8, s)
Expand All @@ -64,11 +84,18 @@ export function derivePublicKey(privateKey: BigNumberish): Point<bigint> {
/**
* Signs a message using the provided private key, employing Poseidon hashing and
* EdDSA with the Baby Jubjub elliptic curve.
*
* The private key must be an instance of Buffer, Uint8Array or a string. The input will be used to
* generate entropy and there is no limit in size.
* The string is used as a set of raw bytes (in UTF-8) and is typically used to pass passwords or secret messages.
* If you want to pass a bigint, a number or a hexadecimal, be sure to convert them to one of the supported types first.
* The 'conversions' module in @zk-kit/utils provides a set of functions that may be useful in case you need to convert types.
*
* @param privateKey The private key used to sign the message.
* @param message The message to be signed.
* @returns The signature object, containing properties relevant to EdDSA signatures, such as 'R8' and 'S' values.
*/
export function signMessage(privateKey: BigNumberish, message: BigNumberish): Signature<bigint> {
export function signMessage(privateKey: Buffer | Uint8Array | string, message: BigNumberish): Signature<bigint> {
// Convert the private key to buffer.
privateKey = checkPrivateKey(privateKey)

Expand Down Expand Up @@ -231,7 +258,7 @@ export function unpackSignature(packedSignature: Buffer): Signature<bigint> {
*/
export class EdDSAPoseidon {
// Private key for signing, stored securely.
privateKey: BigNumberish
privateKey: Buffer | Uint8Array | string
// The secret scalar derived from the private key to compute the public key.
secretScalar: bigint
// The public key corresponding to the private key.
Expand All @@ -241,10 +268,17 @@ export class EdDSAPoseidon {

/**
* Initializes a new instance, deriving necessary cryptographic parameters from the provided private key.
* If the private key is not passed as a parameter, a random 32-byte key is generated.
* If the private key is not passed as a parameter, a random 32-byte hexadecimal key is generated.
*
* The private key must be an instance of Buffer, Uint8Array or a string. The input will be used to
* generate entropy and there is no limit in size.
* The string is used as a set of raw bytes (in UTF-8) and is typically used to pass passwords or secret messages.
* If you want to pass a bigint, a number or a hexadecimal, be sure to convert them to one of the supported types first.
* The 'conversions' module in @zk-kit/utils provides a set of functions that may be useful in case you need to convert types.
*
* @param privateKey The private key used for signing and public key derivation.
*/
constructor(privateKey: BigNumberish = Buffer.from(crypto.getRandomValues(32))) {
constructor(privateKey: Buffer | Uint8Array | string = bufferToHexadecimal(crypto.getRandomValues(32))) {
this.privateKey = privateKey
this.secretScalar = deriveSecretScalar(privateKey)
this.publicKey = derivePublicKey(privateKey)
Expand Down
14 changes: 5 additions & 9 deletions packages/eddsa-poseidon/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Point } from "@zk-kit/baby-jubjub"
import type { BigNumberish } from "@zk-kit/utils"
import { bigNumberishToBigInt, bigNumberishToBuffer, bufferToBigInt } from "@zk-kit/utils/conversions"
import { bigNumberishToBigInt, bufferToBigInt } from "@zk-kit/utils/conversions"
import { requireTypes } from "@zk-kit/utils/error-handlers"
import { isArray, isBigNumberish, isObject, isBigNumber } from "@zk-kit/utils/type-checks"
import { isArray, isBigNumber, isBigNumberish, isObject } from "@zk-kit/utils/type-checks"
import { Buffer } from "buffer"
import { Blake512 } from "./blake"
import { Signature } from "./types"
Expand Down Expand Up @@ -50,14 +50,10 @@ export function isSignature(signature: Signature): boolean {
* @param privateKey The private key to check and convert.
* @returns The private key as a Buffer.
*/
export function checkPrivateKey(privateKey: BigNumberish): Buffer {
requireTypes(privateKey, "privateKey", ["bignumberish", "string"])
export function checkPrivateKey(privateKey: Buffer | Uint8Array | string): Buffer {
requireTypes(privateKey, "privateKey", ["Buffer", "Uint8Array", "string"])

if (isBigNumberish(privateKey)) {
return bigNumberishToBuffer(privateKey)
}

return Buffer.from(privateKey as string)
return Buffer.from(privateKey)
}

/**
Expand Down
38 changes: 8 additions & 30 deletions packages/eddsa-poseidon/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,7 @@ describe("EdDSAPoseidon", () => {
expect(publicKey[1]).toBe(circomlibPublicKey[1])
})

it("Should derive a public key from a private key (hexadecimal)", async () => {
const privateKey = "0x12"

const publicKey = derivePublicKey(privateKey)

const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.slice(2), "hex"))

expect(publicKey[0]).toBe(circomlibPublicKey[0])
expect(publicKey[1]).toBe(circomlibPublicKey[1])
})

it("Should derive a public key from a private key (buffer)", async () => {
it("Should derive a public key from a private key (Buffer)", async () => {
const privateKey = Buffer.from("secret")

const publicKey = derivePublicKey(privateKey)
Expand All @@ -63,34 +52,23 @@ describe("EdDSAPoseidon", () => {
expect(publicKey[1]).toBe(circomlibPublicKey[1])
})

it("Should derive a public key from a private key (bigint)", async () => {
const privateKey = BigInt(22)

const publicKey = derivePublicKey(privateKey)

const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.toString(16), "hex"))

expect(publicKey[0]).toBe(circomlibPublicKey[0])
expect(publicKey[1]).toBe(circomlibPublicKey[1])
})

it("Should derive a public key from a private key (number)", async () => {
const privateKey = 22
it("Should derive a public key from a private key (Uint8Array)", async () => {
const privateKey = new Uint8Array([3, 2])

const publicKey = derivePublicKey(privateKey)

const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey.toString(16), "hex"))
const circomlibPublicKey = eddsa.prv2pub(Buffer.from(privateKey))

expect(publicKey[0]).toBe(circomlibPublicKey[0])
expect(publicKey[1]).toBe(circomlibPublicKey[1])
})

it("Should throw an error if the secret type is not supported", async () => {
const privateKey = true
const privateKey = BigInt(32)

const fun = () => derivePublicKey(privateKey as any)

expect(fun).toThrow(`Parameter 'privateKey' is none of the following types: bignumberish, string`)
expect(fun).toThrow(`Parameter 'privateKey' is none of the following types: Buffer, Uint8Array, string`)
})

it("Should sign a message (bigint)", async () => {
Expand Down Expand Up @@ -407,8 +385,8 @@ describe("EdDSAPoseidon", () => {

const signature = eddsa.signMessage(message)

expect(eddsa.privateKey).toBeInstanceOf(Buffer)
expect(eddsa.privateKey).toHaveLength(32)
expect(typeof eddsa.privateKey).toBe("string")
expect(eddsa.privateKey).toHaveLength(64)
expect(eddsa.secretScalar).toBe(deriveSecretScalar(eddsa.privateKey))
expect(eddsa.packedPublicKey).toBe(packPublicKey(eddsa.publicKey))
expect(eddsa.verifySignature(message, signature)).toBeTruthy()
Expand Down
2 changes: 1 addition & 1 deletion packages/eddsa-proof/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { EddsaProof, SnarkArtifacts } from "./types"
* @returns The Poseidon zero-knowledge proof.
*/
export default async function generate(
privateKey: string | number | bigint | Buffer,
privateKey: Buffer | Uint8Array | string,
scope: BytesLike | Hexable | number | bigint,
snarkArtifacts?: SnarkArtifacts
): Promise<EddsaProof> {
Expand Down
2 changes: 1 addition & 1 deletion packages/eddsa-proof/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { EddsaProof } from "../src/types"
import verify from "../src/verify"

describe("EddsaProof", () => {
const privateKey = 2
const privateKey = Buffer.from("secret")
const scope = 1

let fullProof: EddsaProof
Expand Down
7 changes: 3 additions & 4 deletions packages/poseidon-cipher/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { derivePublicKey } from "@zk-kit/eddsa-poseidon"
import { beBufferToBigInt, crypto } from "@zk-kit/utils"
import { crypto } from "@zk-kit/utils"
import { poseidonDecrypt, poseidonDecryptWithoutCheck, poseidonEncrypt } from "../src/poseidonCipher"
import { Nonce, PlainText } from "../src/types"
import { genEcdhSharedKey } from "./utils"

describe("Poseidon Cipher", () => {
const privateKey = crypto.getRandomValues(32)
const bgPrivateKey = beBufferToBigInt(Buffer.from(privateKey))
const publicKey = derivePublicKey(bgPrivateKey)
const encryptionKey = genEcdhSharedKey(bgPrivateKey, publicKey)
const publicKey = derivePublicKey(privateKey)
const encryptionKey = genEcdhSharedKey(privateKey, publicKey)

const plainText: PlainText<bigint> = [BigInt(0), BigInt(1)]
const nonce: Nonce = BigInt(5)
Expand Down
Loading

0 comments on commit c90e57a

Please sign in to comment.