diff --git a/0005-snarkyjs-ecdsa-sha.md b/0005-snarkyjs-ecdsa-sha.md index 388e6e9..51b6bef 100644 --- a/0005-snarkyjs-ecdsa-sha.md +++ b/0005-snarkyjs-ecdsa-sha.md @@ -97,7 +97,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. @@ -148,31 +148,158 @@ 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. -**Evergreen, wide-sweeping Details** +The main verification step of ECDSA is implemented via a `verify` gadget in OCaml. -**Ephemeral details** +```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. + +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. + +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 = { + name: string; + modulus: bigint; + order: bigint; + a: bigint; + b: bigint; + 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 function `createForeignCurve` returns a `ForeignCurve` class based on the specified parameters. + +```ts +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: + +```ts +class EcdsaSignature extends Signature { + static Curve: ForeignCurve; + + // used to deserialize a signature + static from(sig: { r: Scalar | bigint; s: Scalar | bigint }): EcdsaSignature; + + // used to create a signature from the corresponding raw hex encoding + static fromHex(rawSignature: string): EcdsaSignature; + + // verification of the signature + verify( + msgHash: Scalar | bigint, + publicKey: Curve | { x: BaseField | bigint; y: BaseField | bigint } + ): void; + + // check if the signature is valid + static check(signature: { r: Scalar; s: Scalar }): void; + + // dummy signature + static dummy: EcdsaSignature; +} +``` + +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 -## 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). We will include a range of edge cases in the tests (e.g. empty input, zero, etc). 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 + +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 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. + 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 -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