From e2dbca4dbc95804959247317f33059d248d6b17e Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 21 Jun 2023 14:40:55 +0200 Subject: [PATCH 1/8] start ecdsa section --- 0005-snarkyjs-ecdsa-sha.md | 42 +++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index d549216..04b7a67 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -105,7 +105,7 @@ Sha3_224.hash(xs); // .. ``` -### Alternative Approach +#### Alternative Approach Another possibility is to combine all hash functions, including Poseidon, under a shared namespace `Hash`. Developers will then be able to use these functions by calling `Hash.[hash_name].hash(xs)`. However, this would not be equivalent to the existing `Poseidon` API. @@ -156,7 +156,43 @@ Overall, exposing new gadgets and gates follow a strict pattern that has been us ## ECDSA -**TODO** this will be a seperate PR stacked on top of this one +Similar to SHA3 and Keccak, the gadget for ECDSA has been implemented by the crypto team and is available through exposing it in the bindings layer. However, designing and implementing a safe API for ECDSA is not as straight forward as it is for SHA3 and Keccak. + +The main verification step of ECDSA is implemented via a `verify` gadget in OCaml. + +```ocaml +let verify (type f) (module Circuit : Snark_intf.Run with type field = f) + (base_checks : f Foreign_field.External_checks.t) + (scalar_checks : f Foreign_field.External_checks.t) + (curve : f Curve_params.InCircuit.t) (pubkey : f Affine.t) + ?(use_precomputed_gen_doubles = true) ?(scalar_mul_bit_length = 0) + ?(doubles : f Affine.t array option) + (signature : + f Foreign_field.Element.Standard.t * f Foreign_field.Element.Standard.t ) + (msg_hash : f Foreign_field.Element.Standard.t) +``` + +The function takes the following arguments: + +- `base_check` := Context to track required base field external checks +- `scalar_checks` := Context to track required scalar field external checks +- `curve` := Elliptic curve parameters - for now, the goal is to make ECDSA over secp256k1 available. However, ECDSA accepts different curve parameters as well. +- `pubkey` := Public key of signer +- `doubles` := Optional powers of $2^i$ of the `pubkey`, $0 <= i < n$ where $n$ is `curve.order_bit_length` +- `signature` := ECDSA signature (r, s) s.t. r, s $\in [1, n)$ +- `msg_hash` := Message hash s.t. msg_hash \in Fn - this will be the output of `Keccak` + +However, it is important to mention that the OCaml API has been designed with the assumption in mind that some of the inputs already satisfy a set of preconditions. These preconditions will not be checked internally, but are a requirement. Additionally, verifying ECDSA signatures is an expensive task and requires a lot of constraints. By moving the precondition checks of the inputs outside of the actual verification step (requiring them as preconditions), it allows us to optimize constraints but also provides a bigger API surface to cover in order for us to provide a safe API developers can use. + +The input preconditions are: + +- `pubkey` is on the curve and not O (`Ec_group.is_on_curve` gadget) +- `pubkey` is in the subgroup (nP = O) (`Ec_group.check_subgroup` gadget) +- `pubkey` is bounds checked (`multi-range-check` gadgets) +- `r, s` $\in [1, n)$ (`signature_scalar_check` gadget) +- `msg_hash` $\in Fn$ (`bytes_to_foreign_field_element` gadget) + +Each of these preconditions requires expensive checks so its important to choose wisely when and how to check these inputs. Its important to provide a safe API as well as giving experienced developers enough space to optimize their applications by avoiding double constraining of inputs. **Evergreen, wide-sweeping Details** @@ -166,7 +202,7 @@ Overall, exposing new gadgets and gates follow a strict pattern that has been us [test-plan-and-functional-requirements]: #test-plan-and-functional-requirements -## SHA3/Keccak +### SHA3/Keccak In order to test the implementation of SHA3 and Keccak in SnarkyJS, we will follow the testing approach we already apply to other gadgets and gates. This includes testing the out-of- and in-snark variants using our testing framework, as well as adding a SHA3 and Keccak regression test. The regression tests will also include a set of predetermined digests to make sure that the algorithm doesn't unexpectedly change over time (similar to the tests implemented for the OCaml gadget). From a18143fb595f69a58d5df024ed72d6dc63cfb386 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 21 Jun 2023 15:00:02 +0200 Subject: [PATCH 2/8] some more ECDSA-related sections --- 0005-snarkyjs-ecdsa-sha.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 04b7a67..e36d1a7 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -209,18 +209,25 @@ This includes testing the out-of- and in-snark variants using our testing framew In addition to that, we should provide a dedicated integration test that handles SHA3/Keccak hashing within a smart contract (proving enabled). This will allow us to not only provide developers with an example, but also ensure that SHA3 and Keccak proofs can be generated. +### ECDSA + +TODO + ## Drawbacks [drawbacks]: #drawbacks Compared to Poseidon, hashing with SHA3 and Keccak is expensive. This should be made clear to the developer to avoid inefficient circuits. Additionally, it is important to educate developers of when to use SHA3/Keccak and when to use Poseidon. Additionally, the API should be secure. + +Especially in the case of ECDSA, verifying a signature is very expensive. It is important to provide both a safe API as well as avoiding double constraining of inputs. The developer needs to be educated that ECDSA is not the default signature scheme and should only be used in special cases and the API should reflect this difference. + Adding new primitives, especially cryptographic primitives, always includes risks such as the possibility of not constraining the algorithm and input enough to provide the developer with a safe API that is required to build secure applications. However, adding these primitives to SnarkyJS enables developers to explore a new range of important use cases. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -Keccak and SHA3 could not be exposed to SnarkyJS at all. However, this would essentially render these primitives useless since they were specifically designed to be used by developers with SnarkyJS. By adding these primitives, SnarkyJS will become an even more powerful zero-knowledge SDK that enables developers to explore a wide range of use cases. +ECDSA, Keccak and SHA3 could not be exposed to SnarkyJS at all. However, this would essentially render these primitives useless since they were specifically designed to be used by developers with SnarkyJS. By adding these primitives, SnarkyJS will become an even more powerful zero-knowledge SDK that enables developers to explore a wide range of use cases. Besides that, not adding these primitives would essentially block the ecosystem from interacting with other chains, mainly Ethereum. ## Prior art From d4336f9dfaf25f8bbc9e41cb6094f7ff5f5844f1 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 25 Jun 2023 16:37:36 +0200 Subject: [PATCH 3/8] fix conflicts --- 0005-snarkyjs-ecdsa-sha.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 2518b7f..0a4f24c 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -209,9 +209,10 @@ TODO Compared to Poseidon, hashing with SHA3 and Keccak is expensive. This should be made clear to the developer to avoid inefficient circuits. Additionally, it is important to educate developers of when to use SHA3/Keccak and when to use Poseidon. Additionally, the API should be secure. -Especially in the case of ECDSA, verifying a signature is very expensive. It is important to provide both a safe API as well as avoiding double constraining of inputs. The developer needs to be educated that ECDSA is not the default signature scheme and should only be used in special cases and the API should reflect this difference. It should be mentioned that developers should ideally use Poseidon for everything that does not explicitly require SHA3/Keccak (e.g. a Merkle Tree in SnarkyJS, checksums of Field elements and provable structures `Struct`, etc.) and only use SHA3/Keccak if it is really required (e.g. interacting with Ethereum, verifying Ethereum signatures, etc.). +Especially in the case of ECDSA, verifying a signature is very expensive. It is important to provide both a safe API as well as avoiding double constraining of inputs. The developer needs to be educated that ECDSA is not the default signature scheme and should only be used in special cases and the API should reflect this difference. + Adding new primitives, especially cryptographic primitives, always includes risks such as the possibility of not constraining the algorithm and input enough to provide the developer with a safe API that is required to build secure applications. However, adding these primitives to SnarkyJS enables developers to explore a new range of important use cases. ## Rationale and alternatives @@ -219,7 +220,6 @@ Adding new primitives, especially cryptographic primitives, always includes risk [rationale-and-alternatives]: #rationale-and-alternatives ECDSA, Keccak and SHA3 could not be exposed to SnarkyJS at all. However, this would essentially render these primitives useless since they were specifically designed to be used by developers with SnarkyJS. By adding these primitives, SnarkyJS will become an even more powerful zero-knowledge SDK that enables developers to explore a wide range of use cases. Besides that, not adding these primitives would essentially block the ecosystem from interacting with other chains, mainly Ethereum. -Keccak and SHA3 could not be exposed to SnarkyJS at all. However, this would essentially render these primitives useless since they were specifically designed to be used by developers with SnarkyJS. By adding these primitives, SnarkyJS will become an even more powerful zero-knowledge SDK that enables developers to explore a wide range of use cases. ## Prior art From b3c6b6073aa02d5b429c51c40572b4037679052f Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 26 Jun 2023 11:18:03 +0200 Subject: [PATCH 4/8] remove old tags --- 0005-snarkyjs-ecdsa-sha.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 0a4f24c..8e2fa99 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -192,7 +192,7 @@ Each of these preconditions requires expensive checks so its important to choose ## Test plan and functional requirements -[test-plan-and-functional-requirements]: #test-plan-and-functional-requirements +s ### SHA3/Keccak @@ -217,8 +217,6 @@ Adding new primitives, especially cryptographic primitives, always includes risk ## Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives - ECDSA, Keccak and SHA3 could not be exposed to SnarkyJS at all. However, this would essentially render these primitives useless since they were specifically designed to be used by developers with SnarkyJS. By adding these primitives, SnarkyJS will become an even more powerful zero-knowledge SDK that enables developers to explore a wide range of use cases. Besides that, not adding these primitives would essentially block the ecosystem from interacting with other chains, mainly Ethereum. ## Prior art From 7976f8ada4f9789fb84963700968ea3395979d06 Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 26 Jun 2023 11:23:53 +0200 Subject: [PATCH 5/8] typo --- 0005-snarkyjs-ecdsa-sha.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 8e2fa99..352ddc4 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -192,8 +192,6 @@ Each of these preconditions requires expensive checks so its important to choose ## Test plan and functional requirements -s - ### SHA3/Keccak In order to test the implementation of SHA3 and Keccak in SnarkyJS, we will follow the testing approach we already apply to other gadgets and gates. From ae4310549fde7834f445f8ec9c2277a12fcda3b8 Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 26 Jun 2023 12:08:50 +0200 Subject: [PATCH 6/8] add test section --- 0005-snarkyjs-ecdsa-sha.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 352ddc4..6ca1d77 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -186,9 +186,9 @@ The input preconditions are: Each of these preconditions requires expensive checks so its important to choose wisely when and how to check these inputs. Its important to provide a safe API as well as giving experienced developers enough space to optimize their applications by avoiding double constraining of inputs. -**Evergreen, wide-sweeping Details** +By building on top of the [`ForeignField`](https://github.com/o1-labs/snarkyjs/pull/985) implementation, we can leverage the modular approach taken by it to implement an ECDSA API which provides developers with a powerful modular interface. This approach allows us to potentially enable multiple versions of ECDSA with different curves, within the same API. We will also implement a `ForeignCurve` API which relies on the non-native elliptic curve primitives and gadgets introduce in the original [ECDAS PR](https://github.com/MinaProtocol/mina/pull/13279) so developers can not only profit from a modular ECDSA API, but can also access non-native elliptic curves directly. -**Ephemeral details** +TODO API preview ## Test plan and functional requirements @@ -201,7 +201,8 @@ In addition to that, we should provide a dedicated integration test that handles ### ECDSA -TODO +ECDSA testing will follow a similar approach to SHA3 and Keccak. We will utilize the regression test framework in SnarkyJS to make sure no unintended changes are introduced and backwards compatibility is guaranteed. Similar to the original ECDSA gadget tests, we will also test a set of pre-defined ECDSA signatures (e.g. Ethereum transaction signatures). +The implementation will also be tested in provable code (a smart contract) to make sure proofs can be generated correctly while also providing developers with an example of how to use ECDSA within smart contracts. ## Drawbacks From 8e7860ca9965bc92ed68b2b7ebb24ec81f827686 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 29 Jun 2023 11:52:14 +0200 Subject: [PATCH 7/8] foreign curve --- 0005-snarkyjs-ecdsa-sha.md | 52 +++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 6ca1d77..42dcd26 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -188,7 +188,57 @@ Each of these preconditions requires expensive checks so its important to choose By building on top of the [`ForeignField`](https://github.com/o1-labs/snarkyjs/pull/985) implementation, we can leverage the modular approach taken by it to implement an ECDSA API which provides developers with a powerful modular interface. This approach allows us to potentially enable multiple versions of ECDSA with different curves, within the same API. We will also implement a `ForeignCurve` API which relies on the non-native elliptic curve primitives and gadgets introduce in the original [ECDAS PR](https://github.com/MinaProtocol/mina/pull/13279) so developers can not only profit from a modular ECDSA API, but can also access non-native elliptic curves directly. -TODO API preview +Similar to [`ForeignField`](https://github.com/o1-labs/snarkyjs/pull/985), the API of `ForeignCurve` will make use of a modular approach. + +```ts +type CurveParams = { + /** + * Base field modulus + */ + modulus: bigint; + /** + * Scalar field modulus = group order + */ + order: bigint; + /** + * The `a` parameter in the curve equation y^2 = x^3 + ax + b + */ + a: bigint; + /** + * The `b` parameter in the curve equation y^2 = x^3 + ax + b + */ + b: bigint; + /** + * Generator point + */ + gen: AffineBigint; +}; +``` + +The `ForeignCurve` API will accept a set of `CurveParams`, which can then be used to create a `ForeignCurve` and serve as the foundation for ECDSA. + +```ts +function createForeignCurve(curve: CurveParams) { + class BaseField extends createForeignField(curve.modulus) {} + class ScalarField extends createForeignField(curve.order) {} + + class ForeignCurve implements Affine { + x: BaseField; + y: BaseField; + + constructor() {} + + add() {} + + static BaseField = BaseField; + static ScalarField = ScalarField; + } + + return ForeignCurve; +} +``` + +TODO ECDSA API preview ## Test plan and functional requirements From 63610b89406c8659dc877b3b590a7cf0dda69983 Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 3 Jul 2023 15:17:18 +0200 Subject: [PATCH 8/8] finish RFC --- 0005-snarkyjs-ecdsa-sha.md | 91 ++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 42dcd26..51b6bef 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -191,54 +191,87 @@ By building on top of the [`ForeignField`](https://github.com/o1-labs/snarkyjs/p Similar to [`ForeignField`](https://github.com/o1-labs/snarkyjs/pull/985), the API of `ForeignCurve` will make use of a modular approach. ```ts +// curve parameters to specify a foreign curve; secp256k1 and other curves will be provided to the developer type CurveParams = { - /** - * Base field modulus - */ + name: string; modulus: bigint; - /** - * Scalar field modulus = group order - */ order: bigint; - /** - * The `a` parameter in the curve equation y^2 = x^3 + ax + b - */ a: bigint; - /** - * The `b` parameter in the curve equation y^2 = x^3 + ax + b - */ b: bigint; - /** - * Generator point - */ gen: AffineBigint; }; ``` -The `ForeignCurve` API will accept a set of `CurveParams`, which can then be used to create a `ForeignCurve` and serve as the foundation for ECDSA. +The `ForeignCurve` API will accept a set of `CurveParams`, which can then be used to create a `ForeignCurve` and serve as the foundation for ECDSA. The function `createForeignCurve` returns a `ForeignCurve` class based on the specified parameters. ```ts -function createForeignCurve(curve: CurveParams) { - class BaseField extends createForeignField(curve.modulus) {} - class ScalarField extends createForeignField(curve.order) {} +function createForeignCurve(curve: CurveParams): ForeignCurve; +``` + +The returned class `ForeignCurve` will have a set of helper methods associated with it, most notably is the `initialize` method which will need to be called at least once per provable method before using the curve. This ensures the curve parameters are set in the circuit, however, this operation is relatively costly. + +In order to utilize a `ForeignCurve` and build an ECDSA signer with it, the developer can simply call the `createEcdsa` function (similarly to `createForeignCurve`) and specify the curve the developer wants to use. + +```ts +function createEcdsa(curve: CurveParams | ForeignCurveClass): EcdsaSignature; +``` + +The `EcdsaSignature` class can then be used to create ECDSA-signatures, which includes a set of helper methods: - class ForeignCurve implements Affine { - x: BaseField; - y: BaseField; +```ts +class EcdsaSignature extends Signature { + static Curve: ForeignCurve; - constructor() {} + // used to deserialize a signature + static from(sig: { r: Scalar | bigint; s: Scalar | bigint }): EcdsaSignature; - add() {} + // used to create a signature from the corresponding raw hex encoding + static fromHex(rawSignature: string): EcdsaSignature; - static BaseField = BaseField; - static ScalarField = ScalarField; - } + // verification of the signature + verify( + msgHash: Scalar | bigint, + publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } + ): void; - return ForeignCurve; + // check if the signature is valid + static check(signature: { r: Scalar; s: Scalar }): void; + + // dummy signature + static dummy: EcdsaSignature; } ``` -TODO ECDSA API preview +Developers will then be able to use the APIs as follows: + +```ts +import { + createEcdsa, + secp256k1Params, + createForeignCurve, + Provable, +} from "snarkyjs"; + +class Secp256k1 extends createForeignCurve(secp256k1Params) {} + +class EthSigner extends createEcdsa(Secp256k1) {} + +let publicKey = Secp256k1.from({ + x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n, + y: 44999051047832679156664607491606359183507784636787036192076848057884504239143n, +}); + +let signature = EthSigner.fromHex( + "0x82de9950cc5aac0dca7210cb4b77320ac9e844717d39b1781e9d941d920a12061da497b3c134f50b2fce514d66e20c5e43f9615f097395a5527041d14860a52f1b" +); + +let msgHash = + 0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn; + +Secp256k1.initialize(); + +signature.verify(msgHash, publicKey); +``` ## Test plan and functional requirements