From 562df246e0a54d94331c6d2a20764acf648fc952 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Tue, 19 Dec 2023 15:00:38 -0300 Subject: [PATCH 01/20] Add crypto/v2 ADR --- docs/architecture/adr-071-cryptography-v2.md | 565 +++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 docs/architecture/adr-071-cryptography-v2.md diff --git a/docs/architecture/adr-071-cryptography-v2.md b/docs/architecture/adr-071-cryptography-v2.md new file mode 100644 index 000000000000..8310180498d1 --- /dev/null +++ b/docs/architecture/adr-071-cryptography-v2.md @@ -0,0 +1,565 @@ +# ADR 071: Cryptography v2 + +## Change log + +* Nov 1st 2023: Initial Draft (Zondax AG: @raynaudoe @bizk @juliantoledano @jleni @educlerici-zondax) + +## Status + +DRAFT + +## Abstract + +This ADR proposes a refactor of the crypto module to enhance modularity, re-usability, and maintainability, +while prioritizing developer experience and incorporating best security practices. +The proposal defines a clear division of scope for each component, cleaner interfaces, easier extension, +better test coverage and a single place of truth, allowing the developer to focus on what's important +while ensuring the secure handling of sensitive data throughout the module. + +## Introduction + +This ADR outlines the redesign and refactoring of the crypto package. The design establishes a clear decoupling via interfaces, extension points, and a much more modular design to allow developers to concentrate on application level aspects while ensuring the adequate handling of sensitive data. + +Special focus has been placed on the following key aspects: + +* modularity +* extensibility +* security +* maintainability +* developer experience + +The proposal determines a clear decoupling via interfaces, additional extension points, and a much more modular design to allow developers to application level aspects while ensuring the secure handling of sensitive data when applying this SDK. + +The enhancements in this proposal not only render the ["Keyring ADR"](https://github.com/cosmos/cosmos-sdk/issues/14940) obsolete, but also encompass its key aspects, replacing it with a more flexible and comprehensive approach. Furthermore, the gRPC service proposed in the Keyring ADR can be easily implemented as a specialized implementation of the "CryptoProvider" interface defined later in this ADR. This allows for the integration of HashiCorp-like [go-plugins](https://github.com/hashicorp/go-plugin) over gRPC, providing a robust and extensible solution for keyring functionality. + +Furthermore, the grpc service proposed in the Keyring ADR can be easily followed by creating an implementation of the "CryptoProvider" interface defined in this ADR. This allows for the integration of HashiCorp plugins over gRPC, providing a robust and extensible solution for keyring functionality. + +By deprecating the previous ADR and introducing these enhancements, the new ADR offers a more comprehensive and adaptable solution for cryptography and address management within the Cosmos SDK ecosystem. + +### Glossary + +1. **Interface**: In the context of this document, "interface" refers to Go's interface concept. + +2. **Module**: In this document, "module" refers to a Go module. The proposed ADR focuses on the Crypto module V2, which suggests the introduction of a new version of the Crypto module with updated features and improvements. + +3. **Package**: In the context of Go, a "package" refers to a unit of code organization. Each proposed architectural unit will be organized into packages for better reutilization and extension. + +## Context + +In order to fully understand the need for changes and improvements to the cryptographic package, it's crucial to consider the current state of affairs: + +* The Cosmos SDK currently lacks a comprehensive ADR for the cryptographic package. +* Type leakage outside the current crypto module pose backward compatibility and extensibility challenges. +* The demand for a more flexible and extensible approach to cryptography and address management is high. +* Architectural changes are necessary to resolve many of the currently open issues. +* There is a current tread towards modularity in the ICF stack (e.g. runtime modules) +* Security implications are a critical consideration during the redesign work. + +## Objectives + +The key objectives for the Cryptography v2 module are: + +Modular Design Philosophy + +* Establish a flexible and extensible foundation using interfaces to enable the seamless integration of various cryptographic methods and tools. + +* Restructure, Refactor, and Decouple: Update the cryptography codebase to ensure modularity and future adaptability. + +Documentation & Community Engagement + +* Cryptography v2 ADR: Draft a new Architecture Decision Record to guide and document the evolution of the module (this document). + +* Enhance documentation to ensure clarity, establish a good practices protocol and promote community engagement, providing a platform for feedback and collaborative growth. + +Backward Compatibility & Migration + +* Prioritize compatibility with previous module version to avoid disruptions for existing users. + +* Design and propose a suitable migration path, ensuring transitions are as seamless as possible. + +* Evaluate and decide on the relevance of existing systems and tools, incorporating or deprecating them based on their alignment with the module's new vision. + +Developer-Centric Approach + +* Prioritize clear, intuitive interfaces and best-practice design principles. +* Improve Developer Experience: Provide tools, samples, and best practices to foster an efficient and user-friendly development environment. + +Leverage Extensibility + +* Utilize the module's modular design to support a wide range of cryptographic tools, key types, and methods, ensuring adaptability for future technological advancements. +* Integrate support for advanced cryptographic features, ensuring the module's position at the forefront of cryptographic technologies. + +Quality Assurance + +* Enhanced Test Coverage: Improve testing methodologies to ensure the robustness and reliability of the module. +* Conduct an Audit: After implementation, perform a comprehensive audit to identify potential vulnerabilities and ensure the module's security and stability. + +## Technical Goals + +As technical goals, the aim is to create a robust, flexible, and future-proof cryptographic module. This is achieved through the following key points: + +Wide Hardware Device & Cloud-based HSM Interface Support: + +* Design a foundational interface for various hardware devices (Ledger, YubiKey, Thales, etc.) and cloud-based HSMs (Amazon, Azure) to cater to both current and future implementations. + +Plugin Architecture and Dependency Injection + +* Establish the architectural foundation for an extensible plugin system and integrate a dependency injection framework, ensuring modularity, testability, and third-party integrations. + +* Design an environment for plugin testing, ensuring developers can validate integrations without compromising system integrity. + +Interface considerations + +* Design should take into considerations support for Trusted Platform Module (TPM) 2.0 and similar devices to anticipate future enhancements. + +* Design should take into account the Cryptographic Token Interface Standard (PKCS#11) + +Increase cryptographic versatility + +* Support for a broad spectrum of cryptographic techniques +* Extend support for more hash functions (e.g. pedersen, argon2, Argon2d/I/id, Blake3, etc.) +* Extend support for more signature schemes (e.g. secp256r1, ed25519, ed448, sr25519, etc.) +* More advanced methods ( Post-Quantum Cryptography (PQC) methods +* Threshold signatures and encryption + +Community Engagement Infrastructure: + +* Structure the design with tools and documentation interfaces in mind, enabling a seamless future rollout of resources for developer engagement. + +## Proposed architecture + +### Introduction + +In the proposed architecture, each package is decoupled and isolated. Adding new implementations consist of implementing the required interfaces. + +```mermaid +classDiagram + +Keyring <|-- Wallet + +SecureStorage <|-- Keyring +SecureItem <|-- SecureStorage +CryptoProvider <|-- SecureItem + +Hasher <|-- CryptoProvider +Verifier <|-- CryptoProvider +Signer <|-- CryptoProvider +Cipher <|-- CryptoProvider +Generator <|-- CryptoProvider +``` + +### Crypto Provider + +*Crypto Providers* serve as a middleware responsible for managing the interaction with various instantiated cryptographic packages. It acts as a centralized controller, encapsulating the API of the crypto modules in a single location. +Through each Crypto provider, users can access functionality such as signing, verification, encryption, and hashing. + +By abstracting the underlying cryptographic functionality, *Crypto providers* enable a modular and extensible architecture. It allows users to easily switch between different cryptographic implementations without impacting the rest of the system. + +```go +type ProviderMetadata interface { + key string + value string +} + +type ICryptoProviderMetadata interface { + GetTypeUUID() TypeUUID + GetName() string + GetMetadata() []ProviderMetadata +} + +type ICryptoProviderBuilder interface { + ICryptoProviderMetadata + + FromSecureItem( item SecureItem ) (ICryptoProvider, error) + + FromRandomness( source IRandomnessSource ) (ICryptoProvider, error) + FromSeed( seed []byte ) (ICryptoProvider, error) + FromMnemonic( mnemonic string ) (ICryptoProvider error) + FromString( url string ) (ICryptoProvider error) +} + +type ICryptoProvider interface { + Proto.Message + ICryptoProviderMetadata + + GetKeys() (PubKey, PrivKey, error) + GetSigner() (ISigner, error) + GetVerifier() (IVerifier, error) + GetCipher() (ICipher, error) + GetHasher() (IHasher, error) +} +``` + +#### Signing + +Interface responsible for Signing a message and returning the generated Signature. + +```go +type ISigner interface { + Sign(Blob) (Signature, error) +} +``` + +#### Verifier + +Verifies if given a message belongs to a public key by validating against its respective signature. + +```go +type IVerifier interface { + Verify(Blob, Signature) (bool, error) +} +``` + +#### Cipher + +A cipher is an api for encryption and decryption of data. Given a message it should operate through a secret. + +```go +type ICipher interface { + Encrypt(message Blob) (encryptedMessage Blob, error) + Decrypt(encryptedMessage Blob) (message Blob, error) +} +``` + +#### Hasher + +This package contains the different hashing algorithms and conventions agreed on this matter. + +```go +type IHasher interface { + Hash(input Blob) Blob + CanHashIncrementally() bool +} +``` + +### StorageProvider + + +A *Secure Storage* represents a secure vault where one or more *Secure Items* can be stored. It serves as a centralized repository for securely storing sensitive data. To access a *Secure Item*, users must interact with the *Secure Storage*, which handles the retrieval and management of keys. +Different implementations of *Secure Storage* will be available to cater to various storage requirements: + +* FileSystem: This implementation stores the Secure Items in a designated folder within the file system. +* Memory: This implementation stores the Secure Items in memory, providing fast access but limited persistence. +* KMS: This implementation utilizes the Key Management System available on AWS, GCP, etc. +* others: 1password, OS-integrated secure storage (macOS, Linux, Windows, etc.) + +```go +type IStorageProvider interface { + List() []string + + Get(name string) (SecureItem, error) + Set(item SecureItem) error + Remove(name string) error +} +``` + +A *Secure Item* is a structured data object designed for storing any type of data within a *Secure Storage* instance. +In the context of this ADR, the **Blob** field of a Secure Item represents a "recipe" or blueprint for constructing the corresponding *Crypto Provider*. +The **Blob** can be encoded in any format and should contain all the necessary configuration information required to instantiate the specific cryptographic packages that compose the *Crypto Provider*. + +```go +type ISecureItemMetadata interface { + Type() TypeUUID // Relates to the corresponding provider + Name() string + ... +} + +type ISecureItem interface { + ISecureItemMetadata + + // Blob format/encoding will be dependant of the CryptoProvider implementation + Bytes() []byte +} +``` + +##### Keyring + +*Keyring* serves as a central hub for managing *Crypto Providers* and *Secure Storage* implementations. It provides methods to register *Crypto Providers* and *Secure Storage* implementations. +The **RegisterCryptoProvider** function allows users to register a Crypto Provider blueprint by providing its unique identifier and a builder function. Similarly, the **RegisterSecureStorage** function enables users to register a secure storage implementation by specifying a unique identifier and a builder function. + + +```go +type IKeyring interface { + RegisterCryptoProvider(typeUUID TypeUUID, builder CryptoProviderBuilder) + RegisterAndLoadStorageProvider(typeUUID TypeUUID, provider StorageProvider) + + ListStorageProviders() ([]IStorageProvider, error) + ListCryptoProviders() ([]ICryptoProvider, error) + + List() ([]SecureItemMetadata, error) + + GetCryptoProvider(ItemId) (CryptoProvider, error) +} +``` + +#### **Wallet** + +The Wallet interface contains the blockchain specific use cases of the crypto module. It also serves as an API for: + +* Signing and Verifying messages. +* Generating addresses out of keys + +Since wallet interacts with the user keys, it contains an instance of the Keyring, it is also where the blockchain specific logic should reside. + +Note: Each Wallet implementation should provide the logic to map addresses and ItemIds + +```go +type Wallet interface { + Init(Keyring) + GetSigner(address string) Signer + GetVerifier(address string) Verifier + Generate() string +} +``` + +#### Additional components + +##### **Blob** + +This is a wrapper for the widely used `[]byte` type that is used when handling binary data. Since crypto module handles sensitive information, the objective is to provide some extra security capabilities around such type as: + +* Zeroing values after a read operation. +* Proper data handling. + +These blob structures would be passed within components of the crypto module. For example: Signature information + +#### **Keys** + +A key object is responsible for containing the **BLOB** key information. Keys might not be passed through functions, and it is +suggested to interact through crypto providers to limit the exposure to vulnerabilities. + +```mermaid +classDiagram + PubKey <|-- PrivKey + PubKey : Address() string + PubKey : Key + + PrivKey : PubKey() PubKey + PrivKey : key +``` + +Base Key struct + +```go +type KeyStruct struct { + key Blob +} +``` + +Base key interface (common to private and public keys) + +```go +type BaseKey interface { + String() string + Bytes() Blob +} +``` + +##### PubKey + +```go +type PubKey interface { + BaseKey +} +``` + +##### PrivKey + +```go +type PrivKey interface { + BaseKey + Pubkey() PubKey //Generate a public key out of a private key +} +``` + +#### Signatures + +A signature consists of a message/hash signed by one or multiple private keys. The main objective is to authenticate a message signer through their public key. + +```go +type Signature struct { + data Blob +} +``` + +**Flow overview** + +***Initialization*** + +```mermaid +sequenceDiagram + participant Application + participant Wallet + participant Keyring + participant CryptoProvider + participant SecureStorage + + Application->> Wallet: New() + Wallet->>Keyring: New() + + loop CryptoProvider Registration + CryptoProvider->>CryptoProvider: GetUUID() + CryptoProvider->>CryptoProvider: GetBuilderFunction() + CryptoProvider->>Keyring: RegisterCryptoProvider(UUID, BuilderFunction) + end + + loop StorageSource Registration + SecureStorage->>Keyring: RegisterStorageSource() + Keyring->>SecureStorage: NewSecureStorage(SecureStorageSourceConfig) + SecureStorage->>Keyring: SecureStorage Instance + Keyring->>SecureStorage: List() + SecureStorage->>Keyring: SecureItemMetadata list + end + Keyring->>Wallet: Keyring Instance + + Wallet->>Wallet: Init(Keyring) + + + Wallet->>Application: Wallet Instance +``` + +***Signing and verifying a message*** + +```mermaid +sequenceDiagram + participant Application + participant Wallet + participant Keyring + participant CryptoProvider + participant Signer + participant Verifier + + Application->>Wallet: GetSigner(Address) + Wallet->>Keyring: GetCryptoProvider(ItemId) + Keyring->>SecureStorage: Get(ItemId.Uuid) + SecureStorage->>Keyring: SecureItem + Keyring->>Keyring: GetBuilderFunction(ItemId.Uuid) + Keyring->>CryptoProvider: Build(SecureItem) + CryptoProvider->>Wallet: CryptoProvider instance + Wallet->>CryptoProvider: GetSigner() + CryptoProvider->>Application: Signer instance + Application->>Signer: Sign() + Signer->>Application: Signed message + Application->>Wallet: GetVerifier(address) + Wallet->>CryptoProvider: GetVerifier() + CryptoProvider->>Wallet: Verifier instance + Application->>Verifier: Verify() + Verifier->>Application: true/false +``` + +## Alternatives + +The alternatives may vary in the way of distributing the packages, grouping them together as for example verify and signing in +one place. This will affect the granularity of the code, thus the reusability and modularity. We aim to balance between simplicity and +granularity. + +## Decision + +We will: + +* Refactor module structure as described above. +* Define types and interfaces as the code attached. +* Refactor existing code into new structure and interfaces. +* Implement Unit Tests to ensure no backward compatibility issues. + +## Consequences + +### Backwards Compatibility + +Some packages will need a medium to heavy refactor to be compatible with this ADR. +In short, packages using _Keyring_ (current SDK) will need to be adapted to use the new Keyring and CryptoProvider interfaces. +Other special cases where a refactor will be needed, are the ones that make use crypto components in isolation like the _PrivateKey_ and _PublicKey_ structs +to sign and verify transactions respectively. + +As first approach, the most affected packages are: +- crypto/types +- client/rpc +- client/tx +- client/keys +- types/tx/signing +- x/auth +- x/auth/client +- x/slashing +- simapp/simd + +### Positive + +* Single place of truth +* Easier to use interfaces +* Easier to extend +* Unit test for each crypto package +* Greater maintainability +* Incentivize addition of implementations instead of forks +* Decoupling behaviour from implementation +* Sanitization of code + +### Negative + +* It will involve an effort to adapt existing code. +* It will require attention to detail and audition. + +### Neutral + +* It will involve extensive testing. + +## Test Cases + +*The code will be unit tested to ensure a high code coverage +- There should be integration tests around Wallet, keyring and crypto providers. +- There should be benchmark tests for hashing, keyring, encryption, decryption, signing and verifying functions. + +## Further Discussions + +> While an ADR is in the DRAFT or PROPOSED stage, this section should contain a +> summary of issues to be solved in future iterations (usually referencing comments +> from a pull-request discussion). +> +> Later, this section can optionally list ideas or improvements the author or +> reviewers found during the analysis of this ADR. + + +## References + +* TPM 2.0 support: https://github.com/google/go-tpm +* Initial basic PKCS#11 + https://docs.oasis-open.org/pkcs11/pkcs11-base/v3.0/os/pkcs11-base-v3.0-os.pdf + https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-library.html +* https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-key-types.html +* https://solanacookbook.com/references/keypairs-and-wallets.html#how-to-generate-a-new-keypair +* https://www.nist.gov/news-events/news/2022/07/nist-announces-first-four-quantum-resistant-cryptographic-algorithms +* https://blog.cloudflare.com/nist-post-quantum-surprise/ +* https://pkg.go.dev/crypto#Hash +* https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html + +## Appendix + +### Tentative Primitive Building Blocks + +This is a tentative list of primitives that we might want to support. +This is not a final list or comprehensive, and it is subject to change. +Moreover, it is important to emphasize the purpose of this work allows extensibility so any other primitive can be added in the future. + +* digital signatures + * RSA (PSS) + * ECDSA (secp256r1, secp256k1, etc.) + * EdDSA (ed25519, ed448) + * SR25519 + * Schnorr + * Lattice-based (Dilithium) + * BLS (BLS12-381, 377?) + +* encryption + * AES (AES-GCM, AES-CCM) + * RSA (OAEP) + * salsa20 + * (x)chacha20 / (x)ChaCha20-Poly1305 (AEAD) + * Dilithium + * Ntru + +* Hashing + * sha2 / sha3 + * RIPEMD-160 + * blake2b,2s,3 + * Keccak-256 / shake256 + * bcrypt / scrypt / argon2, Argon2d/i/id + * Pedersen From 40dff54c48ab057d4e7d82230590f2b9f6dae3a1 Mon Sep 17 00:00:00 2001 From: educlerici-zondax <142804355+educlerici-zondax@users.noreply.github.com> Date: Tue, 26 Dec 2023 12:28:13 -0300 Subject: [PATCH 02/20] Add consequences text con ADR --- docs/architecture/adr-071-cryptography-v2.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/architecture/adr-071-cryptography-v2.md b/docs/architecture/adr-071-cryptography-v2.md index 8310180498d1..3c1854c67d55 100644 --- a/docs/architecture/adr-071-cryptography-v2.md +++ b/docs/architecture/adr-071-cryptography-v2.md @@ -462,7 +462,11 @@ We will: * Refactor existing code into new structure and interfaces. * Implement Unit Tests to ensure no backward compatibility issues. -## Consequences +## Consequences + +### Impact on the SDK codebase + +This ADR primarily impacts the client-side components of the Cosmos SDK. The consensus layer of the Cosmos SDK, which Tendermint Core largely handles, will not be directly affected by these changes. The cryptographic tools, such as hashers and verifiers, will still be available as external packages. These can be imported and utilized directly in the consensus code without the need for a CryptoProvider instance. This approach ensures that the consensus layer remains decoupled from the specific implementations of cryptographic operations. It maintains the integrity and stability of the consensus process while allowing for enhancements and extensions in the cryptographic functionalities. ### Backwards Compatibility From 0bceef5938af0fc174f6f01e6f25843ce689b069 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Wed, 24 Jan 2024 11:14:45 -0300 Subject: [PATCH 03/20] Update docs/architecture/adr-071-cryptography-v2.md Co-authored-by: Marko --- docs/architecture/adr-071-cryptography-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/adr-071-cryptography-v2.md b/docs/architecture/adr-071-cryptography-v2.md index 3c1854c67d55..28b96d843ff0 100644 --- a/docs/architecture/adr-071-cryptography-v2.md +++ b/docs/architecture/adr-071-cryptography-v2.md @@ -52,7 +52,7 @@ In order to fully understand the need for changes and improvements to the crypto * Type leakage outside the current crypto module pose backward compatibility and extensibility challenges. * The demand for a more flexible and extensible approach to cryptography and address management is high. * Architectural changes are necessary to resolve many of the currently open issues. -* There is a current tread towards modularity in the ICF stack (e.g. runtime modules) +* There is a current trend towards modularity in the Interchain stack (e.g. runtime modules) * Security implications are a critical consideration during the redesign work. ## Objectives From ef09a84b72b6ad0c357f66517ccc50171d9cd5b8 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Fri, 9 Feb 2024 08:46:03 -0300 Subject: [PATCH 04/20] Update cryptoV2 adr --- docs/architecture/adr-071-cryptography-v2.md | 160 +++++++++---------- 1 file changed, 77 insertions(+), 83 deletions(-) diff --git a/docs/architecture/adr-071-cryptography-v2.md b/docs/architecture/adr-071-cryptography-v2.md index 28b96d843ff0..757174a075d1 100644 --- a/docs/architecture/adr-071-cryptography-v2.md +++ b/docs/architecture/adr-071-cryptography-v2.md @@ -2,6 +2,7 @@ ## Change log +* Feb 8th 2024: Fixes and improvements (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax) * Nov 1st 2023: Initial Draft (Zondax AG: @raynaudoe @bizk @juliantoledano @jleni @educlerici-zondax) ## Status @@ -10,15 +11,16 @@ DRAFT ## Abstract -This ADR proposes a refactor of the crypto module to enhance modularity, re-usability, and maintainability, -while prioritizing developer experience and incorporating best security practices. +This ADR proposes the creation of a new crypto module (**cryptoV2** from now on) to enhance the modularity, re-usability, and maintainability +of all the crypto tools and algorithms available in the sdk while prioritizing developer experience and incorporating best security practices. The proposal defines a clear division of scope for each component, cleaner interfaces, easier extension, better test coverage and a single place of truth, allowing the developer to focus on what's important -while ensuring the secure handling of sensitive data throughout the module. +while ensuring the secure handling of sensitive data throughout the module. Lastly but not least allowing the ecosystem to +grow and implement new cryptographic methods and tools as easy as possible. ## Introduction -This ADR outlines the redesign and refactoring of the crypto package. The design establishes a clear decoupling via interfaces, extension points, and a much more modular design to allow developers to concentrate on application level aspects while ensuring the adequate handling of sensitive data. +This ADR outlines the redesign of the crypto package. The design establishes a clear decoupling via interfaces, extension points, and a much more modular design to allow developers to concentrate on application level aspects while ensuring the adequate handling of sensitive data. Special focus has been placed on the following key aspects: @@ -28,28 +30,24 @@ Special focus has been placed on the following key aspects: * maintainability * developer experience -The proposal determines a clear decoupling via interfaces, additional extension points, and a much more modular design to allow developers to application level aspects while ensuring the secure handling of sensitive data when applying this SDK. - The enhancements in this proposal not only render the ["Keyring ADR"](https://github.com/cosmos/cosmos-sdk/issues/14940) obsolete, but also encompass its key aspects, replacing it with a more flexible and comprehensive approach. Furthermore, the gRPC service proposed in the Keyring ADR can be easily implemented as a specialized implementation of the "CryptoProvider" interface defined later in this ADR. This allows for the integration of HashiCorp-like [go-plugins](https://github.com/hashicorp/go-plugin) over gRPC, providing a robust and extensible solution for keyring functionality. -Furthermore, the grpc service proposed in the Keyring ADR can be easily followed by creating an implementation of the "CryptoProvider" interface defined in this ADR. This allows for the integration of HashiCorp plugins over gRPC, providing a robust and extensible solution for keyring functionality. - -By deprecating the previous ADR and introducing these enhancements, the new ADR offers a more comprehensive and adaptable solution for cryptography and address management within the Cosmos SDK ecosystem. +Furthermore, the grpc service proposed in the Keyring ADR can be easily followed by creating an implementation of the "CryptoProvider" interface defined in this ADR. This allows, for example, the integration of HashiCorp plugins over gRPC, providing more flexible implementations. ### Glossary 1. **Interface**: In the context of this document, "interface" refers to Go's interface concept. -2. **Module**: In this document, "module" refers to a Go module. The proposed ADR focuses on the Crypto module V2, which suggests the introduction of a new version of the Crypto module with updated features and improvements. +2. **Module**: In this document, "module" refers to a Go module. -3. **Package**: In the context of Go, a "package" refers to a unit of code organization. Each proposed architectural unit will be organized into packages for better reutilization and extension. +3. **Package**: In the context of Go, a "package" refers to a unit of code organization. ## Context -In order to fully understand the need for changes and improvements to the cryptographic package, it's crucial to consider the current state of affairs: +In order to fully understand the need for changes and the proposed improvements, it's crucial to consider the current state of affairs: * The Cosmos SDK currently lacks a comprehensive ADR for the cryptographic package. -* Type leakage outside the current crypto module pose backward compatibility and extensibility challenges. +* Type leakage of specific crypto data types expose backward compatibility and extensibility challenges. * The demand for a more flexible and extensible approach to cryptography and address management is high. * Architectural changes are necessary to resolve many of the currently open issues. * There is a current trend towards modularity in the Interchain stack (e.g. runtime modules) @@ -57,7 +55,7 @@ In order to fully understand the need for changes and improvements to the crypto ## Objectives -The key objectives for the Cryptography v2 module are: +The key objectives for the CryptoV2 module are: Modular Design Philosophy @@ -155,6 +153,10 @@ Through each Crypto provider, users can access functionality such as signing, ve By abstracting the underlying cryptographic functionality, *Crypto providers* enable a modular and extensible architecture. It allows users to easily switch between different cryptographic implementations without impacting the rest of the system. +A `CryptoProvider` can be built from different sources, such as a `SecureItem`, a randomness source, a seed, a mnemonic, or a string. +Since the underlying representation of a `CryptoProvider` is a protobuf message, it can be easily serialized and stored in a safe manner. +The storing and retrieval is handled by a `SecureStorage` implementation which will be presented in the next section. + ```go type ProviderMetadata interface { key string @@ -192,7 +194,7 @@ type ICryptoProvider interface { #### Signing -Interface responsible for Signing a message and returning the generated Signature. +Interface responsible for signing a message and returning the generated signature. ```go type ISigner interface { @@ -232,19 +234,20 @@ type IHasher interface { } ``` -### StorageProvider - +### Secure Storage -A *Secure Storage* represents a secure vault where one or more *Secure Items* can be stored. It serves as a centralized repository for securely storing sensitive data. To access a *Secure Item*, users must interact with the *Secure Storage*, which handles the retrieval and management of keys. +A *Secure Storage* represents a secure vault where one or more *Secure Items* can be stored. As a parallelism, a `SecureStorage` instance can be compared to the current `Keyring Backend` interface, +but with the main difference that the stored items are abstracted as `SecureItem` instead of `Record`. This allows a fully decoupled relation between what is being stored (`SecureItem`) and where it is being stored (`SecureStorage`). +To access a *Secure Item*, users must interact with the *Secure Storage*, which handles the retrieval and management of keys. Different implementations of *Secure Storage* will be available to cater to various storage requirements: -* FileSystem: This implementation stores the Secure Items in a designated folder within the file system. -* Memory: This implementation stores the Secure Items in memory, providing fast access but limited persistence. +* FileSystem: This implementation stores the items in a designated folder within the file system. +* Memory: This implementation stores the items in memory, providing fast access but limited persistence (for testing purpose mainly). * KMS: This implementation utilizes the Key Management System available on AWS, GCP, etc. -* others: 1password, OS-integrated secure storage (macOS, Linux, Windows, etc.) +* others: 1password, OS-integrated secure storage (macOS, Linux, Windows) ```go -type IStorageProvider interface { +type ISecureStorage interface { List() []string Get(name string) (SecureItem, error) @@ -253,34 +256,35 @@ type IStorageProvider interface { } ``` +### Secure Item + A *Secure Item* is a structured data object designed for storing any type of data within a *Secure Storage* instance. -In the context of this ADR, the **Blob** field of a Secure Item represents a "recipe" or blueprint for constructing the corresponding *Crypto Provider*. -The **Blob** can be encoded in any format and should contain all the necessary configuration information required to instantiate the specific cryptographic packages that compose the *Crypto Provider*. +In the context of this ADR, the `Blob` field of a `SecureItem` represents the serialized form of a `CryptoProvider`. + +_Note:_ The encoder/decoder of the `Blob` field will be dependent on the `CryptoProvider` implementation, a `SecureItem` +has no knowledge of the underlying data structure. ```go type ISecureItemMetadata interface { Type() TypeUUID // Relates to the corresponding provider Name() string - ... } type ISecureItem interface { ISecureItemMetadata - - // Blob format/encoding will be dependant of the CryptoProvider implementation Bytes() []byte } ``` ##### Keyring -*Keyring* serves as a central hub for managing *Crypto Providers* and *Secure Storage* implementations. It provides methods to register *Crypto Providers* and *Secure Storage* implementations. -The **RegisterCryptoProvider** function allows users to register a Crypto Provider blueprint by providing its unique identifier and a builder function. Similarly, the **RegisterSecureStorage** function enables users to register a secure storage implementation by specifying a unique identifier and a builder function. - +The `Keyring` interface serves as a central hub for managing `CryptoProviders` and `SecureStorage` implementations. +The main difference with the current `Keyring` interface is that it adds the ability to register several `SecureStorage` (this is, several "storing backends") implementations and the +_values_ are abstracted as `SecureItem` instead of `Record`. ```go type IKeyring interface { - RegisterCryptoProvider(typeUUID TypeUUID, builder CryptoProviderBuilder) + RegisterCryptoProviderBuilder(typeUUID TypeUUID, builder CryptoProviderBuilder) RegisterAndLoadStorageProvider(typeUUID TypeUUID, provider StorageProvider) ListStorageProviders() ([]IStorageProvider, error) @@ -294,14 +298,13 @@ type IKeyring interface { #### **Wallet** -The Wallet interface contains the blockchain specific use cases of the crypto module. It also serves as an API for: +The `Wallet` interface contains the blockchain specifics logics for converting PubKeys into addresses. +It also serves as a convenient API for: -* Signing and Verifying messages. +* Signing and Verifying messages * Generating addresses out of keys -Since wallet interacts with the user keys, it contains an instance of the Keyring, it is also where the blockchain specific logic should reside. - -Note: Each Wallet implementation should provide the logic to map addresses and ItemIds +_Note:_ Each Wallet implementation should provide the logic to map addresses and PubKeys. ```go type Wallet interface { @@ -316,17 +319,16 @@ type Wallet interface { ##### **Blob** -This is a wrapper for the widely used `[]byte` type that is used when handling binary data. Since crypto module handles sensitive information, the objective is to provide some extra security capabilities around such type as: +This is a wrapper for the widely used `[]byte` type that is used when handling binary data. +Since handling sensitive information is a common task in crypto algorithms, the objective is to provide some extra security capabilities around such type as: * Zeroing values after a read operation. * Proper data handling. -These blob structures would be passed within components of the crypto module. For example: Signature information - -#### **Keys** +#### **Pub/Priv Key** -A key object is responsible for containing the **BLOB** key information. Keys might not be passed through functions, and it is -suggested to interact through crypto providers to limit the exposure to vulnerabilities. +PubKey and PrivKey are wrappers to store the corresponding keys bytes. +The corresponding implementations will have the proper security measures to handle the keys mentioned at the beginning of this document. ```mermaid classDiagram @@ -338,14 +340,6 @@ classDiagram PrivKey : key ``` -Base Key struct - -```go -type KeyStruct struct { - key Blob -} -``` - Base key interface (common to private and public keys) ```go @@ -372,16 +366,6 @@ type PrivKey interface { } ``` -#### Signatures - -A signature consists of a message/hash signed by one or multiple private keys. The main objective is to authenticate a message signer through their public key. - -```go -type Signature struct { - data Blob -} -``` - **Flow overview** ***Initialization*** @@ -457,34 +441,44 @@ granularity. We will: -* Refactor module structure as described above. +* Refactor the module structure as described above. * Define types and interfaces as the code attached. * Refactor existing code into new structure and interfaces. * Implement Unit Tests to ensure no backward compatibility issues. -## Consequences +## Consequences ### Impact on the SDK codebase -This ADR primarily impacts the client-side components of the Cosmos SDK. The consensus layer of the Cosmos SDK, which Tendermint Core largely handles, will not be directly affected by these changes. The cryptographic tools, such as hashers and verifiers, will still be available as external packages. These can be imported and utilized directly in the consensus code without the need for a CryptoProvider instance. This approach ensures that the consensus layer remains decoupled from the specific implementations of cryptographic operations. It maintains the integrity and stability of the consensus process while allowing for enhancements and extensions in the cryptographic functionalities. +We can divide the impact of this ADR into two main categories: state machine code and client related code. + +#### Client + +The major impact will be on the client side, where the current `Keyring` interface will be replaced by the new `Wallet` and `Keyring` interfaces. +This means that any piece of code that makes use of the `Record` struct will need to be adapted to use the new `CryptoProvider` instead. +This will aso affect a large number of unit tests that will need to be adapted/replaced with new ones. + +#### State Machine + +The impact on the state machine code will be minimal, the only module affected (at the time of writing this ADR) +is the `x/accounts` module, specifically the `Authenticate` function. This function will need to be adapted to use a `CryptoProvider` service to make use of the `Verifier` instance. +As a result, the account abstraction feature will benefit from all the new features and security improvements mentioned in this ADR. + + +_Note:_ All the cryptographic tools (hasher, verifier, signer, etc) will still be available as standalone packages that can be +imported and utilized directly without the need for a `CryptoProvider` instance. But, the `CryptoProvider` will be the recommended way to use them +since it provides a more secure way to handle sensitive dta, better modularity and the possibility to store configurations and metadata into the CryptoProvider +definition. + ### Backwards Compatibility -Some packages will need a medium to heavy refactor to be compatible with this ADR. -In short, packages using _Keyring_ (current SDK) will need to be adapted to use the new Keyring and CryptoProvider interfaces. -Other special cases where a refactor will be needed, are the ones that make use crypto components in isolation like the _PrivateKey_ and _PublicKey_ structs -to sign and verify transactions respectively. - -As first approach, the most affected packages are: -- crypto/types -- client/rpc -- client/tx -- client/keys -- types/tx/signing -- x/auth -- x/auth/client -- x/slashing -- simapp/simd +The proposed migration path is similar to what the cosmos-sdk has done in the past. To ensure a smooth transition, the following steps will be taken: +- Create a new package `cryptoV2` and implement this ADR. Create unit tests, documentation and examples. +- Deprecate the old crypto package. The old crypto package will still be usable, but it will be marked as deprecated and users can opt to use the new package. +- Migrate the codebase to use the new `CryptoV2` package and remove the old crypto package. + +_A more detailed migration path is provided in the corresponding document._ ### Positive @@ -509,7 +503,7 @@ As first approach, the most affected packages are: ## Test Cases *The code will be unit tested to ensure a high code coverage -- There should be integration tests around Wallet, keyring and crypto providers. +- There should be integration tests around Wallet, Keyring and CryptoProviders. - There should be benchmark tests for hashing, keyring, encryption, decryption, signing and verifying functions. ## Further Discussions @@ -539,8 +533,8 @@ As first approach, the most affected packages are: ### Tentative Primitive Building Blocks -This is a tentative list of primitives that we might want to support. -This is not a final list or comprehensive, and it is subject to change. +This is a tentative list of primitives that we might want to support. +This is not a final list or comprehensive, and it is subject to change. Moreover, it is important to emphasize the purpose of this work allows extensibility so any other primitive can be added in the future. * digital signatures @@ -562,8 +556,8 @@ Moreover, it is important to emphasize the purpose of this work allows extensibi * Hashing * sha2 / sha3 - * RIPEMD-160 + * RIPEMD-160 * blake2b,2s,3 * Keccak-256 / shake256 * bcrypt / scrypt / argon2, Argon2d/i/id - * Pedersen + * Pedersen \ No newline at end of file From 528b9947bb340de78e017e3fa3e46858ce4360fd Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Fri, 23 Feb 2024 12:11:47 -0300 Subject: [PATCH 05/20] Notes on StateMachine --- docs/architecture/adr-071-cryptography-v2.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/architecture/adr-071-cryptography-v2.md b/docs/architecture/adr-071-cryptography-v2.md index 757174a075d1..2c4c3e782755 100644 --- a/docs/architecture/adr-071-cryptography-v2.md +++ b/docs/architecture/adr-071-cryptography-v2.md @@ -460,14 +460,17 @@ This will aso affect a large number of unit tests that will need to be adapted/r #### State Machine -The impact on the state machine code will be minimal, the only module affected (at the time of writing this ADR) -is the `x/accounts` module, specifically the `Authenticate` function. This function will need to be adapted to use a `CryptoProvider` service to make use of the `Verifier` instance. -As a result, the account abstraction feature will benefit from all the new features and security improvements mentioned in this ADR. +The impact on the state machine code will be minimal, the modules affected (at the time of writing this ADR) +are the `x/accounts` module, specifically the `Authenticate` function and the `x/auth/ante` module. These function will need to be adapted to use a `CryptoProvider` service to make use of the `Verifier` instance. As a result, the account abstraction feature will benefit from all the new features and security improvements mentioned in this ADR. + +Worth mentioning that there's also the alternative of using `Verifier` instances in a standalone fashion (see note below). + +The specific way to adapt these modules will be deeply analyzed and decided at implementation time of this ADR. _Note:_ All the cryptographic tools (hasher, verifier, signer, etc) will still be available as standalone packages that can be imported and utilized directly without the need for a `CryptoProvider` instance. But, the `CryptoProvider` will be the recommended way to use them -since it provides a more secure way to handle sensitive dta, better modularity and the possibility to store configurations and metadata into the CryptoProvider +since it provides a more secure way to handle sensitive data, better modularity and the possibility to store configurations and metadata into the CryptoProvider definition. From 1d81f164ba8144c1a4c48c042e1064f58f365d84 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 19 Apr 2024 10:39:03 +0200 Subject: [PATCH 06/20] fix: typo --- docs/architecture/adr-071-cryptography-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/adr-071-cryptography-v2.md b/docs/architecture/adr-071-cryptography-v2.md index 2c4c3e782755..b95af85844b9 100644 --- a/docs/architecture/adr-071-cryptography-v2.md +++ b/docs/architecture/adr-071-cryptography-v2.md @@ -158,7 +158,7 @@ Since the underlying representation of a `CryptoProvider` is a protobuf message, The storing and retrieval is handled by a `SecureStorage` implementation which will be presented in the next section. ```go -type ProviderMetadata interface { +type ProviderMetadata struct { key string value string } From 604bb36fbaa8174c9f5c0da4d45dfc7a503f6f28 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Wed, 8 May 2024 09:22:47 -0300 Subject: [PATCH 07/20] Revamped ADR 071 --- .../adr-071-crypto-v2-multi-curve.md | 597 ++++++++++++++++++ docs/architecture/adr-071-cryptography-v2.md | 566 ----------------- 2 files changed, 597 insertions(+), 566 deletions(-) create mode 100644 docs/architecture/adr-071-crypto-v2-multi-curve.md delete mode 100644 docs/architecture/adr-071-cryptography-v2.md diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md new file mode 100644 index 000000000000..b8f7a6faf259 --- /dev/null +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -0,0 +1,597 @@ +# ADR 071: Cryptography v2- Multi-curve support + +## Change log + +* May 7th 2024: Initial Draft (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) + +## Status + +DRAFT + +## Abstract + +This ADR proposes the refactoring of the existing Keyring module to support multiple cryptographic curves for signing and verification processes. With this update, we aim to facilitate the integration of new cryptographic curves through clean and simple interfaces. Additionally, support for Hardware Security Modules (HSM) is introduced as a complementary enhancement in this redesign. + +This ADR also introduces the capability to support remote signers. This feature will enable the nodes to interact with cryptographic signers that are not locally present on the system where the main app is running. This is particularly useful for scenarios where keys are managed in secure, remote environments or when leveraging cloud-based cryptographic services. + + +## Introduction + +The introduction of multi-curve support in the cosmos-sdk cryptographic module offers significant advantages. By not being restricted to a single cryptographic curve, developers can choose the most appropriate curve based on security, performance, and compatibility requirements. This flexibility enhances the application's ability to adapt to evolving security standards and optimizes performance for specific use cases, helping to future-proofing the sdk's cryptographic capabilities. + + +Special focus has been placed on the following key aspects: + +* modularity +* extensibility +* security +* maintainability +* developer experience + +The enhancements in this proposal not only render the ["Keyring ADR"](https://github.com/cosmos/cosmos-sdk/issues/14940) obsolete, but also encompass its key aspects, replacing it with a more flexible and comprehensive approach. Furthermore, the gRPC service proposed in the Keyring ADR can be easily implemented as a specialized HSM. + + +### Glossary + +1. **Interface**: In the context of this document, "interface" refers to Go's interface. + +2. **Module**: In this document, "module" refers to a Go module. + +3. **Package**: In the context of Go, a "package" refers to a unit of code organization. + +## Context + +In order to fully understand the need for changes and the proposed improvements, it's crucial to consider the current state of affairs: + +* The Cosmos SDK currently lacks a comprehensive ADR for the cryptographic package. + +* If a blockchain project requires a cryptographic curve that is not supported by the current SDK, they are obligated to fork the SDK repository and make modifications. These modifications could potentially make the fork incompatible with future updates from the upstream SDK, complicating maintenance and integration. + +* Type leakage of specific crypto data types expose backward compatibility and extensibility challenges. + +* The demand for a more flexible and extensible approach to cryptography and address management is high. + +* Architectural changes are necessary to resolve many of the currently open issues related to new curves support. + +* There is a current trend towards modularity in the Interchain stack (e.g., runtime modules). + +* Security implications are a critical consideration during the redesign work. + +## Objectives + +The key objectives for this proposal are: + +Modular Design Philosophy + +* Establish a flexible and extensible foundation using interfaces to enable the seamless integration of various cryptographic curves. + +* Restructure, Refactor, and Decouple: Update the codebase to ensure modularity and future adaptability. + +Documentation & Community Engagement + +* Enhance documentation to ensure clarity, establish a good practices protocol and promote community engagement, providing a platform for feedback and collaborative growth. + +Backward Compatibility & Migration + +* Prioritize compatibility with previous version to avoid disruptions for existing users. + +* Design and propose a suitable migration path, ensuring transitions are as seamless as possible. + + +Developer-Centric Approach + +* Prioritize clear, intuitive interfaces and best-practice design principles. + + +Quality Assurance + +* Enhanced Test Coverage: Improve testing methodologies to ensure the robustness and reliability of the module. + +* Conduct an Audit: After implementation, perform a comprehensive audit to identify potential vulnerabilities and ensure the module's security and stability. + +## Technical Goals + +Multi-curve support: + +* Support for a wide range of cryptographic curves to be integrated seamlessly into the sdk in a modular way. + +Wide Hardware Device & Cloud-based HSM Interface Support: + +* Design a foundational interface for various hardware devices (Ledger, YubiKey, Thales, etc.) and cloud-based HSMs (Amazon, Azure) to support both current and future implementations. + +Testing: + +* Design an environment for testing, ensuring developers can validate integrations without compromising system integrity. + +New Keyring: + +* Design a new Keyring interface with modular backends injection system to support hardware devices and cloud-based HSMs. + + +## Proposed architecture + +### Introduction + +In this section, we will first introduce the concept of a `CryptoProvider`, which serves as the main API of the new cryptographic architecture. Following this, we will present the detailed components that make up the `CryptoProvider`. Lastly, we will introduce the storage and persistence layer, providing code snippets for each component to illustrate their implementation. + + +#### Crypto Provider + +Here we introduce the concept of `CryptoProvider`. This interface acts as a centralized controller, encapsulating the APIs for the **signing**, **verifying** and **hashing** functionalities. It acts as the main API with which the apps will interact with + +By abstracting the underlying cryptographic functionality, `CryptoProvider` enables a modular and extensible architecture, aka 'pluggable cryptography'. It allows users to easily switch between different cryptographic implementations without impacting the rest of the system. + +The `CryptoProvider` interface includes getters for essential cryptographic functionalities and metadata: + + +```go +// CryptoProvider aggregates the functionalities of signing, verifying, and hashing, and provides metadata. +type CryptoProvider interface { + // GetSigner returns an instance of Signer. + GetSigner() Signer + + // GetVerifier returns an instance of Verifier. + GetVerifier() Verifier + + // GetHasher returns an instance of Hasher. + GetHasher() Hasher + + // Metadata returns metadata for the crypto provider. + Metadata() ProviderMetadata +} +``` + +##### Components + +The components defined here are designed to act as wrappers around the underlying proper functions. This architecture ensures that the actual cryptographic operations such as signing, hashing, and verifying are delegated to the specialized functions, that are implementation dependant. These wrapper components facilitate a clean and modular approach by abstracting the complexity of direct cryptographic function calls. + +In all of the interface's methods, we add an *options* input parameter designed to provide a flexible and dynamic way to pass various options and configurations to the `Sign`, `Verify`, and `Hash` functions. This approach allows developers to customize these processes by including any necessary parameters that might be required by specific algorithms or operational contexts. However, this requires that a type assertion for each option be performed inside the function's implementation. + +###### Signer + +Interface responsible for signing a message and returning the generated signature. + + +```go +// Signer represents a general interface for signing messages. +type Signer interface { + // Sign takes a private key and a message as input and returns the digital signature. + Sign(pk PrivateKey, fullMsg []byte, options SignerOptions) (Signature, error) +} + +type SignerOpts = map[string]any +``` + +###### Verifier + +Verifies if given a message belongs to a public key by validating against its respective signature. + +```go +// Verifier represents a general interface for verifying signatures. +type Verifier interface { + // Verify checks the digital signature against the message and a public key to determine its validity. + Verify(signature Signature, msg []byte, pubKey PublicKey, options VerifierOptions) (bool, error) +} + +type VerifierOpts = map[string]any +``` + +###### Hasher + +This interface allows to have a specific hashing algorithm. + +```go +// Hasher represents a general interface for hashing data. +type Hasher interface { + // Hash takes an input byte array and returns the hashed output as a byte array. + Hash(input []byte, options HasherOpts) (output []byte, err error) +} + +type HasherOpts = map[string]any +``` + +###### Metadata + +The metadata allows uniquely identifying a `CryptoProvider` and also stores its configurations. + +```go +// ProviderMetadata holds metadata about the crypto provider. +type ProviderMetadata struct { + Name string + Version *semver.Version // Using semver type for versioning + Config map[string]any +} +``` + +###### Public Key + +```go +type PubKey interface { + Address() Address + Bytes() []byte + Equals(other PubKey) bool + Type() string +} +``` + +###### Private Key + +*Note*: For example, in hardware wallets, the `PrivKey` interface acts only as a *reference* to the real data. This is a design consideration and may be subject to change during implementation. + +Future enhancements could include additional security functions such as **zeroing** memory after private key usage to further enhance security measures. + + +```go +type PrivKey interface { + Bytes() []byte + PubKey() PubKey + Equals(other PrivKey) bool + Type() string +} +``` + +###### Signature + +```go +// Signature represents a general interface for a digital signature. +type Signature interface { + // Bytes returns the byte representation of the signature. + Bytes() []byte + + // Equals checks if two signatures are identical. + Equals(other Signature) bool +} +``` + +##### Storage and persistence + +The storage and persistence layer is tasked with storing a `CryptoProvider`. Specifically, this layer must: + +* Securely store the crypto provider's associated private key (only if stored locally, otherwise a reference to the private key will be stored instead). +* Store the `ProviderMetadata` struct. + +The purpose of this layer is to ensure that upon retrieval of the persisted data, we can access the provider's type, version, and specific configuration (which varies based on the provider type). This information will subsequently be utilized to initialize the appropriate factory, as detailed in the following section on the factory pattern. + +The storage proposal involves using a modified version of the [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) struct, which is already defined in Keyring/v1. Additionally, we propose utilizing the existing keyring backends (keychain, filesystem, memory, etc.) to store these Records in the same manner as the current Keyring/v1. + +*This approach will facilitate a smoother migration path from the current Keyring/v1 to the proposed architecture.* + +Below is the proposed protobuf message to be included in the modified `Record.proto` file + +```protobuf + +// cryptoprovider.proto + +syntax = "proto3"; + +package crypto; + +import "google/protobuf/any.proto"; + +// CryptoProvider holds all necessary information to instantiate and configure a CryptoProvider. +message CryptoProvider { + string type = 1; // Type of the crypto provider + string version = 2; // Version of the crypto provider + map config = 3; // Configuration data with byte array values + google.protobuf.Any privKey = 4; // Optional if key is stored locally +} +``` + + +type: +Specifies the type of the crypto provider. This field is used to identify and differentiate between various crypto provider implementations. + + +version: +Indicates the version of the crypto provider using semantic versioning. + + +configuration (map): +Contains serialized configuration data as key-value pairs, where the key is a string and the value is a byte array. + +privKey (google.protobuf.Any): +An optional field that can store a private key if it is managed locally. + + +The [record.proto](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) file will be modified to include the `CryptoProvider` message as an optional field as follows. + +```protobuf + +// record.proto + +message Record { + string name = 1; + google.protobuf.Any pub_key = 2; + + oneof item { + Local local = 3; + Ledger ledger = 4; + Multi multi = 5; + Offline offline = 6; + CryptoProvider crypto_provider = 7; + } + + message Local { + google.protobuf.Any priv_key = 1; + } + + message Ledger { + hd.v1.BIP44Params path = 1; + } + + message Multi {} + + message Offline {} +} +``` + +##### Creating and loading a `CryptoProvider` + +One of the main advantages of having an encapsulated entity for the defined cryptographic operations and tools is that it allows the use of different key pairs, which could be stored in various sources and/or be of different types, within the same codebase without necessitating changes to the rest of the app's code. + +To provide this functionality, we propose a *factory pattern* for the `CryptoProvider` interface and a *registry* for these builders. + +Below, we present the proposed interfaces and code snippets to illustrate the proposed architecture. + +```go +// CryptoProviderFactory is a factory interface for creating CryptoProviders. +// Must be implemented by each crypto provider. +type CryptoProviderFactory interface { + CreateFromRecord(*Record) (CryptoProvider, error) + CreateFromConfig(ProviderMetadata) (CryptoProvider, error) +} +``` + +**Note**: The mechanisms for storing and loading `records` will utilize the existing infrastructure from keyring/v1. + +**Example**: crypto provider factory and builder registry + +```go +// crypto/v2/providerFactory.go + +var providerFactories map[string]CryptoProviderFactory + +// RegisterCryptoProviderFactory is a function that registers a CryptoProviderFactory for a CryptoProvider. +func RegisterCryptoProviderFactory(provider CryptoProvider, factory CryptoProviderFactory) { + metadata := provider.Metadata() + id := generateProviderID(metadata) + providerFactories[id] = factory +} + +// CreateCryptoProviderFromRecordOrConfig creates a CryptoProvider based on the provided Record or ProviderMetadata. +// It enforces that exactly one of Record or ProviderMetadata must be provided. +func CreateCryptoProviderFromRecordOrConfig(id string, record *Record, config *ProviderMetadata) (CryptoProvider, error) { + factory, exists := providerFactories[id] + if !exists { + return nil, fmt.Errorf("no factory registered for id %s", id) + } + + // Validate input parameters + if record == nil && config == nil { + return nil, fmt.Errorf("both record and config cannot be nil") + } + if record != nil && config != nil { + return nil, fmt.Errorf("both record and config cannot be provided simultaneously") + } + + // Determine which factory method to call based on the input + if record != nil { + return factory.CreateFromRecord(record) + } + if config != nil { + return factory.CreateFromConfig(*config) + } + + // This line should never be reached due to the checks above + return nil, fmt.Errorf("unexpected error in CreateCryptoProviderFromRecordOrConfig") +} + +// generateProviderID is a function that generates a unique identifier for a CryptoProvider based on its metadata. +// This can be changed in the future to a more suitable function if needed. +func generateProviderID(metadata ProviderMetadata) string { + return fmt.Sprintf("%s-%s", metadata.Name, metadata.Version) +} +``` + +Example: Ledger HW implementation + +Below is an example implementation of how a Ledger hardware wallet `CryptoProvider` might implement the registration of its factory and how instantiation would work. + +```go +// crypto/v2/providers/ledger/factory.go + +type LedgerCryptoProviderFactory struct { + DevicePath string + // Any other necessary fields goes here +} + +func (f *LedgerCryptoProviderFactory) CreateFromRecord(record *Record) (CryptoProvider, error) { + // Extract necessary data from the record to initialize a LedgerCryptoProvider + if record == nil { + return nil, fmt.Errorf("record is nil") + } + + // Assuming the record contains necessary fields like devicePath + devicePath, ok := record.Config["devicePath"].(string) + if !ok { + return nil, fmt.Errorf("device path not found in record") + } + + // Initialize the LedgerCryptoProvider with the device path + return &LedgerCryptoProvider{DevicePath: devicePath}, nil +} + +// crypto/v2/examples/registerProvider.go + + +import ( + "crypto/v2/providers" + "log" +) + +func main() { + // Create an instance of the factory + ledgerFactory := &crypto.LedgerCryptoProviderFactory{} + + // Register the factory + crypto.RegisterCryptoProviderFactory("LedgerCryptoProvider", ledgerFactory) + + // Example of using a Record + record, err := keyring.GetRecord("ledgerDevice-0") + if err != nil { + log.Fatalf("Error fetching record from keyring: %s", err) + } + providerFromRecord, err := crypto.CreateCryptoProviderFromRecordOrConfig("LedgerCryptoProvider", record, nil) + if err != nil { + log.Fatalf("Error creating crypto provider from record: %s", err) + } + log.Printf("Provider from record created successfully: %+v", providerFromRecord.Metadata()) +} +``` + + +##### Keyring + +The new `Keyring` interface will serve as a central hub for managing and fetching `CryptoProviders`. To ensure a smoother migration path, the new Keyring will be backward compatible with the previous version. Since this will be the main API from which applications will obtain their `CryptoProvider` instances, the proposal is to extend the Keyring interface to include the methods: + + +```go +type Keyring interface { + // methods from Keyring/v1 + + // ListCryptoProviders returns a list of all the stored CryptoProvider metadata. + ListCryptoProviders() ([]ProviderMetadata, error) + + // GetCryptoProvider retrieves a specific CryptoProvider by its id. + GetCryptoProvider(id string) (CryptoProvider, error) +} +``` + +##### Especial use case: remote signers + +It's important to note that the `CryptoProvider` interface is versatile enough to be implemented as a remote signer. This capability allows for the integration of remote cryptographic operations, which can be particularly useful in distributed or cloud-based environments where local cryptographic resources are limited or need to be managed centrally. + +Here are a few of the services that can be leveraged: + +* AWS CloudHSM +* Azure Key Vault +* HashiCorp Vault +* Google Cloud KMS + +## Alternatives + +It is important to note that all the code presented in this document is not in its final form and could be subject to changes at the time of implementation. The examples and implementations discussed should be interpreted as alternatives, providing a conceptual framework rather than definitive solutions. This flexibility allows for adjustments based on further insights, technical evaluations, or changing requirements as development progresses. + +## Decision + +We will: + +* Refactor the module structure as described above. +* Define types and interfaces as the code attached. +* Refactor existing code into new structure and interfaces. +* Implement Unit Tests to ensure no backward compatibility issues. + +## Consequences + +### Impact on the SDK codebase + +We can divide the impact of this ADR into two main categories: state machine code and client related code. + +#### Client + +The major impact will be on the client side, where the current `Keyring` interface will be replaced by the new `Keyring` interface. +This means that any piece of code that makes use of the `Record` struct will need to be adapted to use the new `CryptoProvider` instead. +This will also affect a large number of unit tests that will need to be adapted/replaced with new ones. + +#### State Machine + +The impact on the state machine code will be minimal, the modules affected (at the time of writing this ADR) +are the `x/accounts` module, specifically the `Authenticate` function and the `x/auth/ante` module. These function will need to be adapted to use a `CryptoProvider` service to make use of the `Verifier` instance. + +Worth mentioning that there's also the alternative of using `Verifier` instances in a standalone fashion (see note below). + +The specific way to adapt these modules will be deeply analyzed and decided at implementation time of this ADR. + + +*Note*: All cryptographic tools (hashers, verifiers, signers, etc.) will continue to be available as standalone packages that can be imported and utilized directly without the need for a `CryptoProvider` instance. However, the `CryptoProvider` is the recommended method for using these tools as it offers a more secure way to handle sensitive data, enhanced modularity, and the ability to store configurations and metadata within the CryptoProvider definition. + + +### Backwards Compatibility + +The proposed migration path is similar to what the cosmos-sdk has done in the past. To ensure a smooth transition, the following steps will be taken: + +* Create a new package `cryptoV2` and implement this ADR. Create unit tests, documentation and examples. +* Deprecate the old crypto package. The old crypto package will still be usable, but it will be marked as deprecated and users can opt to use the new package. +* Migrate the codebase to use the new `CryptoV2` package and remove the old crypto package. + +_A more detailed migration path is provided in the corresponding document._ + +### Positive + +* Single place of truth +* Easier to use interfaces +* Easier to extend +* Unit test for each crypto package +* Greater maintainability +* Incentivize addition of implementations instead of forks +* Decoupling behavior from implementation +* Sanitization of code + +### Negative + +* It will involve an effort to adapt existing code. +* It will require attention to detail and audition. + +### Neutral + +* It will involve extensive testing. + +## Test Cases + +* The code will be unit tested to ensure a high code coverage +* There should be integration tests around Wallet, Keyring and CryptoProviders. +* There should be benchmark tests for hashing, keyring, encryption, decryption, signing and verifying functions. + +## Further Discussions + +> While an ADR is in the DRAFT or PROPOSED stage, this section should contain a +> summary of issues to be solved in future iterations (usually referencing comments +> from a pull-request discussion). +> +> Later, this section can optionally list ideas or improvements the author or +> reviewers found during the analysis of this ADR. + + +## References + +* TPM 2.0 support: https://github.com/google/go-tpm +* https://solanacookbook.com/references/keypairs-and-wallets.html#how-to-generate-a-new-keypair +* https://www.nist.gov/news-events/news/2022/07/nist-announces-first-four-quantum-resistant-cryptographic-algorithms +* https://blog.cloudflare.com/nist-post-quantum-surprise/ +* https://pkg.go.dev/crypto#Hash +* https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html + +## Appendix + +### Tentative Primitive Building Blocks + +This is a **tentative** list of primitives that we might want to support. +**This is not a final list or comprehensive, and it is subject to change.** +Moreover, it is important to emphasize the purpose of this work allows extensibility so any other primitive can be added in the future. + +* digital signatures + * RSA (PSS) + * ECDSA (secp256r1, secp256k1, etc.) + * EdDSA (ed25519, ed448) + * SR25519 + * Schnorr + * Lattice-based (Dilithium) + * BLS (BLS12-381, 377?) + +* Hashing + * sha2 / sha3 + * RIPEMD-160 + * blake2b,2s,3 + * Keccak-256 / shake256 + * bcrypt / scrypt / argon2, Argon2d/i/id + * Pedersen \ No newline at end of file diff --git a/docs/architecture/adr-071-cryptography-v2.md b/docs/architecture/adr-071-cryptography-v2.md deleted file mode 100644 index b95af85844b9..000000000000 --- a/docs/architecture/adr-071-cryptography-v2.md +++ /dev/null @@ -1,566 +0,0 @@ -# ADR 071: Cryptography v2 - -## Change log - -* Feb 8th 2024: Fixes and improvements (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax) -* Nov 1st 2023: Initial Draft (Zondax AG: @raynaudoe @bizk @juliantoledano @jleni @educlerici-zondax) - -## Status - -DRAFT - -## Abstract - -This ADR proposes the creation of a new crypto module (**cryptoV2** from now on) to enhance the modularity, re-usability, and maintainability -of all the crypto tools and algorithms available in the sdk while prioritizing developer experience and incorporating best security practices. -The proposal defines a clear division of scope for each component, cleaner interfaces, easier extension, -better test coverage and a single place of truth, allowing the developer to focus on what's important -while ensuring the secure handling of sensitive data throughout the module. Lastly but not least allowing the ecosystem to -grow and implement new cryptographic methods and tools as easy as possible. - -## Introduction - -This ADR outlines the redesign of the crypto package. The design establishes a clear decoupling via interfaces, extension points, and a much more modular design to allow developers to concentrate on application level aspects while ensuring the adequate handling of sensitive data. - -Special focus has been placed on the following key aspects: - -* modularity -* extensibility -* security -* maintainability -* developer experience - -The enhancements in this proposal not only render the ["Keyring ADR"](https://github.com/cosmos/cosmos-sdk/issues/14940) obsolete, but also encompass its key aspects, replacing it with a more flexible and comprehensive approach. Furthermore, the gRPC service proposed in the Keyring ADR can be easily implemented as a specialized implementation of the "CryptoProvider" interface defined later in this ADR. This allows for the integration of HashiCorp-like [go-plugins](https://github.com/hashicorp/go-plugin) over gRPC, providing a robust and extensible solution for keyring functionality. - -Furthermore, the grpc service proposed in the Keyring ADR can be easily followed by creating an implementation of the "CryptoProvider" interface defined in this ADR. This allows, for example, the integration of HashiCorp plugins over gRPC, providing more flexible implementations. - -### Glossary - -1. **Interface**: In the context of this document, "interface" refers to Go's interface concept. - -2. **Module**: In this document, "module" refers to a Go module. - -3. **Package**: In the context of Go, a "package" refers to a unit of code organization. - -## Context - -In order to fully understand the need for changes and the proposed improvements, it's crucial to consider the current state of affairs: - -* The Cosmos SDK currently lacks a comprehensive ADR for the cryptographic package. -* Type leakage of specific crypto data types expose backward compatibility and extensibility challenges. -* The demand for a more flexible and extensible approach to cryptography and address management is high. -* Architectural changes are necessary to resolve many of the currently open issues. -* There is a current trend towards modularity in the Interchain stack (e.g. runtime modules) -* Security implications are a critical consideration during the redesign work. - -## Objectives - -The key objectives for the CryptoV2 module are: - -Modular Design Philosophy - -* Establish a flexible and extensible foundation using interfaces to enable the seamless integration of various cryptographic methods and tools. - -* Restructure, Refactor, and Decouple: Update the cryptography codebase to ensure modularity and future adaptability. - -Documentation & Community Engagement - -* Cryptography v2 ADR: Draft a new Architecture Decision Record to guide and document the evolution of the module (this document). - -* Enhance documentation to ensure clarity, establish a good practices protocol and promote community engagement, providing a platform for feedback and collaborative growth. - -Backward Compatibility & Migration - -* Prioritize compatibility with previous module version to avoid disruptions for existing users. - -* Design and propose a suitable migration path, ensuring transitions are as seamless as possible. - -* Evaluate and decide on the relevance of existing systems and tools, incorporating or deprecating them based on their alignment with the module's new vision. - -Developer-Centric Approach - -* Prioritize clear, intuitive interfaces and best-practice design principles. -* Improve Developer Experience: Provide tools, samples, and best practices to foster an efficient and user-friendly development environment. - -Leverage Extensibility - -* Utilize the module's modular design to support a wide range of cryptographic tools, key types, and methods, ensuring adaptability for future technological advancements. -* Integrate support for advanced cryptographic features, ensuring the module's position at the forefront of cryptographic technologies. - -Quality Assurance - -* Enhanced Test Coverage: Improve testing methodologies to ensure the robustness and reliability of the module. -* Conduct an Audit: After implementation, perform a comprehensive audit to identify potential vulnerabilities and ensure the module's security and stability. - -## Technical Goals - -As technical goals, the aim is to create a robust, flexible, and future-proof cryptographic module. This is achieved through the following key points: - -Wide Hardware Device & Cloud-based HSM Interface Support: - -* Design a foundational interface for various hardware devices (Ledger, YubiKey, Thales, etc.) and cloud-based HSMs (Amazon, Azure) to cater to both current and future implementations. - -Plugin Architecture and Dependency Injection - -* Establish the architectural foundation for an extensible plugin system and integrate a dependency injection framework, ensuring modularity, testability, and third-party integrations. - -* Design an environment for plugin testing, ensuring developers can validate integrations without compromising system integrity. - -Interface considerations - -* Design should take into considerations support for Trusted Platform Module (TPM) 2.0 and similar devices to anticipate future enhancements. - -* Design should take into account the Cryptographic Token Interface Standard (PKCS#11) - -Increase cryptographic versatility - -* Support for a broad spectrum of cryptographic techniques -* Extend support for more hash functions (e.g. pedersen, argon2, Argon2d/I/id, Blake3, etc.) -* Extend support for more signature schemes (e.g. secp256r1, ed25519, ed448, sr25519, etc.) -* More advanced methods ( Post-Quantum Cryptography (PQC) methods -* Threshold signatures and encryption - -Community Engagement Infrastructure: - -* Structure the design with tools and documentation interfaces in mind, enabling a seamless future rollout of resources for developer engagement. - -## Proposed architecture - -### Introduction - -In the proposed architecture, each package is decoupled and isolated. Adding new implementations consist of implementing the required interfaces. - -```mermaid -classDiagram - -Keyring <|-- Wallet - -SecureStorage <|-- Keyring -SecureItem <|-- SecureStorage -CryptoProvider <|-- SecureItem - -Hasher <|-- CryptoProvider -Verifier <|-- CryptoProvider -Signer <|-- CryptoProvider -Cipher <|-- CryptoProvider -Generator <|-- CryptoProvider -``` - -### Crypto Provider - -*Crypto Providers* serve as a middleware responsible for managing the interaction with various instantiated cryptographic packages. It acts as a centralized controller, encapsulating the API of the crypto modules in a single location. -Through each Crypto provider, users can access functionality such as signing, verification, encryption, and hashing. - -By abstracting the underlying cryptographic functionality, *Crypto providers* enable a modular and extensible architecture. It allows users to easily switch between different cryptographic implementations without impacting the rest of the system. - -A `CryptoProvider` can be built from different sources, such as a `SecureItem`, a randomness source, a seed, a mnemonic, or a string. -Since the underlying representation of a `CryptoProvider` is a protobuf message, it can be easily serialized and stored in a safe manner. -The storing and retrieval is handled by a `SecureStorage` implementation which will be presented in the next section. - -```go -type ProviderMetadata struct { - key string - value string -} - -type ICryptoProviderMetadata interface { - GetTypeUUID() TypeUUID - GetName() string - GetMetadata() []ProviderMetadata -} - -type ICryptoProviderBuilder interface { - ICryptoProviderMetadata - - FromSecureItem( item SecureItem ) (ICryptoProvider, error) - - FromRandomness( source IRandomnessSource ) (ICryptoProvider, error) - FromSeed( seed []byte ) (ICryptoProvider, error) - FromMnemonic( mnemonic string ) (ICryptoProvider error) - FromString( url string ) (ICryptoProvider error) -} - -type ICryptoProvider interface { - Proto.Message - ICryptoProviderMetadata - - GetKeys() (PubKey, PrivKey, error) - GetSigner() (ISigner, error) - GetVerifier() (IVerifier, error) - GetCipher() (ICipher, error) - GetHasher() (IHasher, error) -} -``` - -#### Signing - -Interface responsible for signing a message and returning the generated signature. - -```go -type ISigner interface { - Sign(Blob) (Signature, error) -} -``` - -#### Verifier - -Verifies if given a message belongs to a public key by validating against its respective signature. - -```go -type IVerifier interface { - Verify(Blob, Signature) (bool, error) -} -``` - -#### Cipher - -A cipher is an api for encryption and decryption of data. Given a message it should operate through a secret. - -```go -type ICipher interface { - Encrypt(message Blob) (encryptedMessage Blob, error) - Decrypt(encryptedMessage Blob) (message Blob, error) -} -``` - -#### Hasher - -This package contains the different hashing algorithms and conventions agreed on this matter. - -```go -type IHasher interface { - Hash(input Blob) Blob - CanHashIncrementally() bool -} -``` - -### Secure Storage - -A *Secure Storage* represents a secure vault where one or more *Secure Items* can be stored. As a parallelism, a `SecureStorage` instance can be compared to the current `Keyring Backend` interface, -but with the main difference that the stored items are abstracted as `SecureItem` instead of `Record`. This allows a fully decoupled relation between what is being stored (`SecureItem`) and where it is being stored (`SecureStorage`). -To access a *Secure Item*, users must interact with the *Secure Storage*, which handles the retrieval and management of keys. -Different implementations of *Secure Storage* will be available to cater to various storage requirements: - -* FileSystem: This implementation stores the items in a designated folder within the file system. -* Memory: This implementation stores the items in memory, providing fast access but limited persistence (for testing purpose mainly). -* KMS: This implementation utilizes the Key Management System available on AWS, GCP, etc. -* others: 1password, OS-integrated secure storage (macOS, Linux, Windows) - -```go -type ISecureStorage interface { - List() []string - - Get(name string) (SecureItem, error) - Set(item SecureItem) error - Remove(name string) error -} -``` - -### Secure Item - -A *Secure Item* is a structured data object designed for storing any type of data within a *Secure Storage* instance. -In the context of this ADR, the `Blob` field of a `SecureItem` represents the serialized form of a `CryptoProvider`. - -_Note:_ The encoder/decoder of the `Blob` field will be dependent on the `CryptoProvider` implementation, a `SecureItem` -has no knowledge of the underlying data structure. - -```go -type ISecureItemMetadata interface { - Type() TypeUUID // Relates to the corresponding provider - Name() string -} - -type ISecureItem interface { - ISecureItemMetadata - Bytes() []byte -} -``` - -##### Keyring - -The `Keyring` interface serves as a central hub for managing `CryptoProviders` and `SecureStorage` implementations. -The main difference with the current `Keyring` interface is that it adds the ability to register several `SecureStorage` (this is, several "storing backends") implementations and the -_values_ are abstracted as `SecureItem` instead of `Record`. - -```go -type IKeyring interface { - RegisterCryptoProviderBuilder(typeUUID TypeUUID, builder CryptoProviderBuilder) - RegisterAndLoadStorageProvider(typeUUID TypeUUID, provider StorageProvider) - - ListStorageProviders() ([]IStorageProvider, error) - ListCryptoProviders() ([]ICryptoProvider, error) - - List() ([]SecureItemMetadata, error) - - GetCryptoProvider(ItemId) (CryptoProvider, error) -} -``` - -#### **Wallet** - -The `Wallet` interface contains the blockchain specifics logics for converting PubKeys into addresses. -It also serves as a convenient API for: - -* Signing and Verifying messages -* Generating addresses out of keys - -_Note:_ Each Wallet implementation should provide the logic to map addresses and PubKeys. - -```go -type Wallet interface { - Init(Keyring) - GetSigner(address string) Signer - GetVerifier(address string) Verifier - Generate() string -} -``` - -#### Additional components - -##### **Blob** - -This is a wrapper for the widely used `[]byte` type that is used when handling binary data. -Since handling sensitive information is a common task in crypto algorithms, the objective is to provide some extra security capabilities around such type as: - -* Zeroing values after a read operation. -* Proper data handling. - -#### **Pub/Priv Key** - -PubKey and PrivKey are wrappers to store the corresponding keys bytes. -The corresponding implementations will have the proper security measures to handle the keys mentioned at the beginning of this document. - -```mermaid -classDiagram - PubKey <|-- PrivKey - PubKey : Address() string - PubKey : Key - - PrivKey : PubKey() PubKey - PrivKey : key -``` - -Base key interface (common to private and public keys) - -```go -type BaseKey interface { - String() string - Bytes() Blob -} -``` - -##### PubKey - -```go -type PubKey interface { - BaseKey -} -``` - -##### PrivKey - -```go -type PrivKey interface { - BaseKey - Pubkey() PubKey //Generate a public key out of a private key -} -``` - -**Flow overview** - -***Initialization*** - -```mermaid -sequenceDiagram - participant Application - participant Wallet - participant Keyring - participant CryptoProvider - participant SecureStorage - - Application->> Wallet: New() - Wallet->>Keyring: New() - - loop CryptoProvider Registration - CryptoProvider->>CryptoProvider: GetUUID() - CryptoProvider->>CryptoProvider: GetBuilderFunction() - CryptoProvider->>Keyring: RegisterCryptoProvider(UUID, BuilderFunction) - end - - loop StorageSource Registration - SecureStorage->>Keyring: RegisterStorageSource() - Keyring->>SecureStorage: NewSecureStorage(SecureStorageSourceConfig) - SecureStorage->>Keyring: SecureStorage Instance - Keyring->>SecureStorage: List() - SecureStorage->>Keyring: SecureItemMetadata list - end - Keyring->>Wallet: Keyring Instance - - Wallet->>Wallet: Init(Keyring) - - - Wallet->>Application: Wallet Instance -``` - -***Signing and verifying a message*** - -```mermaid -sequenceDiagram - participant Application - participant Wallet - participant Keyring - participant CryptoProvider - participant Signer - participant Verifier - - Application->>Wallet: GetSigner(Address) - Wallet->>Keyring: GetCryptoProvider(ItemId) - Keyring->>SecureStorage: Get(ItemId.Uuid) - SecureStorage->>Keyring: SecureItem - Keyring->>Keyring: GetBuilderFunction(ItemId.Uuid) - Keyring->>CryptoProvider: Build(SecureItem) - CryptoProvider->>Wallet: CryptoProvider instance - Wallet->>CryptoProvider: GetSigner() - CryptoProvider->>Application: Signer instance - Application->>Signer: Sign() - Signer->>Application: Signed message - Application->>Wallet: GetVerifier(address) - Wallet->>CryptoProvider: GetVerifier() - CryptoProvider->>Wallet: Verifier instance - Application->>Verifier: Verify() - Verifier->>Application: true/false -``` - -## Alternatives - -The alternatives may vary in the way of distributing the packages, grouping them together as for example verify and signing in -one place. This will affect the granularity of the code, thus the reusability and modularity. We aim to balance between simplicity and -granularity. - -## Decision - -We will: - -* Refactor the module structure as described above. -* Define types and interfaces as the code attached. -* Refactor existing code into new structure and interfaces. -* Implement Unit Tests to ensure no backward compatibility issues. - -## Consequences - -### Impact on the SDK codebase - -We can divide the impact of this ADR into two main categories: state machine code and client related code. - -#### Client - -The major impact will be on the client side, where the current `Keyring` interface will be replaced by the new `Wallet` and `Keyring` interfaces. -This means that any piece of code that makes use of the `Record` struct will need to be adapted to use the new `CryptoProvider` instead. -This will aso affect a large number of unit tests that will need to be adapted/replaced with new ones. - -#### State Machine - -The impact on the state machine code will be minimal, the modules affected (at the time of writing this ADR) -are the `x/accounts` module, specifically the `Authenticate` function and the `x/auth/ante` module. These function will need to be adapted to use a `CryptoProvider` service to make use of the `Verifier` instance. As a result, the account abstraction feature will benefit from all the new features and security improvements mentioned in this ADR. - -Worth mentioning that there's also the alternative of using `Verifier` instances in a standalone fashion (see note below). - -The specific way to adapt these modules will be deeply analyzed and decided at implementation time of this ADR. - - -_Note:_ All the cryptographic tools (hasher, verifier, signer, etc) will still be available as standalone packages that can be -imported and utilized directly without the need for a `CryptoProvider` instance. But, the `CryptoProvider` will be the recommended way to use them -since it provides a more secure way to handle sensitive data, better modularity and the possibility to store configurations and metadata into the CryptoProvider -definition. - - -### Backwards Compatibility - -The proposed migration path is similar to what the cosmos-sdk has done in the past. To ensure a smooth transition, the following steps will be taken: -- Create a new package `cryptoV2` and implement this ADR. Create unit tests, documentation and examples. -- Deprecate the old crypto package. The old crypto package will still be usable, but it will be marked as deprecated and users can opt to use the new package. -- Migrate the codebase to use the new `CryptoV2` package and remove the old crypto package. - -_A more detailed migration path is provided in the corresponding document._ - -### Positive - -* Single place of truth -* Easier to use interfaces -* Easier to extend -* Unit test for each crypto package -* Greater maintainability -* Incentivize addition of implementations instead of forks -* Decoupling behaviour from implementation -* Sanitization of code - -### Negative - -* It will involve an effort to adapt existing code. -* It will require attention to detail and audition. - -### Neutral - -* It will involve extensive testing. - -## Test Cases - -*The code will be unit tested to ensure a high code coverage -- There should be integration tests around Wallet, Keyring and CryptoProviders. -- There should be benchmark tests for hashing, keyring, encryption, decryption, signing and verifying functions. - -## Further Discussions - -> While an ADR is in the DRAFT or PROPOSED stage, this section should contain a -> summary of issues to be solved in future iterations (usually referencing comments -> from a pull-request discussion). -> -> Later, this section can optionally list ideas or improvements the author or -> reviewers found during the analysis of this ADR. - - -## References - -* TPM 2.0 support: https://github.com/google/go-tpm -* Initial basic PKCS#11 - https://docs.oasis-open.org/pkcs11/pkcs11-base/v3.0/os/pkcs11-base-v3.0-os.pdf - https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-library.html -* https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-key-types.html -* https://solanacookbook.com/references/keypairs-and-wallets.html#how-to-generate-a-new-keypair -* https://www.nist.gov/news-events/news/2022/07/nist-announces-first-four-quantum-resistant-cryptographic-algorithms -* https://blog.cloudflare.com/nist-post-quantum-surprise/ -* https://pkg.go.dev/crypto#Hash -* https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html - -## Appendix - -### Tentative Primitive Building Blocks - -This is a tentative list of primitives that we might want to support. -This is not a final list or comprehensive, and it is subject to change. -Moreover, it is important to emphasize the purpose of this work allows extensibility so any other primitive can be added in the future. - -* digital signatures - * RSA (PSS) - * ECDSA (secp256r1, secp256k1, etc.) - * EdDSA (ed25519, ed448) - * SR25519 - * Schnorr - * Lattice-based (Dilithium) - * BLS (BLS12-381, 377?) - -* encryption - * AES (AES-GCM, AES-CCM) - * RSA (OAEP) - * salsa20 - * (x)chacha20 / (x)ChaCha20-Poly1305 (AEAD) - * Dilithium - * Ntru - -* Hashing - * sha2 / sha3 - * RIPEMD-160 - * blake2b,2s,3 - * Keccak-256 / shake256 - * bcrypt / scrypt / argon2, Argon2d/i/id - * Pedersen \ No newline at end of file From 0c09c4d1915c8815828cfa83048a4b4b294b9b46 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Thu, 6 Jun 2024 12:31:33 -0300 Subject: [PATCH 08/20] [WIP] Add Appendix for CometBFT implementation --- .../adr-071-crypto-v2-multi-curve.md | 197 +++++++++++++++++- 1 file changed, 193 insertions(+), 4 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index b8f7a6faf259..4c3aa15128d9 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -3,6 +3,7 @@ ## Change log * May 7th 2024: Initial Draft (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) +* June 4th 2024: Add CometBFT implementation proposal (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) ## Status @@ -14,9 +15,11 @@ This ADR proposes the refactoring of the existing Keyring module to support mult This ADR also introduces the capability to support remote signers. This feature will enable the nodes to interact with cryptographic signers that are not locally present on the system where the main app is running. This is particularly useful for scenarios where keys are managed in secure, remote environments or when leveraging cloud-based cryptographic services. +Additionally, the appendix will describe the implementation of this ADR within the [cometBFT](https://github.com/cometbft/cometbft) codebase, specifically focusing on the interactions with the [PrivateValidator](https://github.com/cometbft/cometbft/blob/68e5e1b4e3bd342a653a73091a1af7cc5e88b86b/types/priv_validator.go#L15) interface. ## Introduction + The introduction of multi-curve support in the cosmos-sdk cryptographic module offers significant advantages. By not being restricted to a single cryptographic curve, developers can choose the most appropriate curve based on security, performance, and compatibility requirements. This flexibility enhances the application's ability to adapt to evolving security standards and optimizes performance for specific use cases, helping to future-proofing the sdk's cryptographic capabilities. @@ -149,14 +152,16 @@ In all of the interface's methods, we add an *options* input parameter designed ###### Signer -Interface responsible for signing a message and returning the generated signature. - +Interface responsible for signing a message and returning the generated signature. +The `SignerOptions` map allows for flexible and dynamic configuration of the signing process. +This can include algorithm-specific parameters, security levels, or other contextual information +that might be necessary for the signing operation. ```go // Signer represents a general interface for signing messages. type Signer interface { - // Sign takes a private key and a message as input and returns the digital signature. - Sign(pk PrivateKey, fullMsg []byte, options SignerOptions) (Signature, error) + // Sign takes a message as input and returns the digital signature. + Sign(fullMsg []byte, options SignerOptions) (Signature, error) } type SignerOpts = map[string]any @@ -573,6 +578,190 @@ _A more detailed migration path is provided in the corresponding document._ ## Appendix + +### Implementation on CometBFT Codebase + +The implementation of this ADR to CometBFT will require a medium refactor in the codebase. Below we describe the proposed strategy to perform this implementation, this is: + +* Add a new `PrivValidator` implementation that uses `CryptoProvider` underneath. + +* Adding [Keyring](https://github.com/cosmos/cosmos-sdk/blob/main/crypto/keyring/keyring.go) and [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) for storing and loading providers. + +* New directories reorganization. + +* Use Keyring to load and instantiate validators when booting up a node. + + +#### Create a single implementation for `PrivValidator` + + +The current CometBFT codebase includes the following implementations of `PrivValidator`: + +* `FilePV`: Handles file-based private validators. +* `SignerClient`: Manages remote signing. +* `RetrySignerClient`: Provides retry mechanisms for remote signing. +* `MockPV`: Used exclusively for testing purposes. + +We propose introducing a new implementation, `CryptoProviderPV`, which will unify and replace all the above implementations. This single implementation will act as an abstraction layer for the `PrivValidator` implementations mentioned above. + +**Current:** + +```mermaid +classDiagram + class PrivValidator { + <> + } + + class FilePV { + } + + class SignerClientPV { + } + + class RetrySignerClientPV { + } + + class MockPV { + } + + PrivValidator <|.. FilePV + PrivValidator <|.. SignerClientPV + PrivValidator <|.. RetrySignerClientPV + PrivValidator <|.. MockPV +``` + + +**Proposed:** + +```mermaid +classDiagram + class PrivValidator { + <> + } + + class CryptoProvider { + <> + } + + class CryptoProviderPV { + } + + class FileCP { + } + + class SocketSignerCP { + } + + class RetrySocketSignerCP { + } + + class MockCP { + } + + PrivValidator <|.. CryptoProviderPV + CryptoProviderPV --> CryptoProvider + CryptoProvider <|.. FileCP + CryptoProvider <|.. SocketSignerCP + CryptoProvider <|.. RetrySocketSignerCP + CryptoProvider <|.. MockCP +``` + +For these new implementations, the current code for `File`, `SocketClient`, and `RetrySocketClient` will have to implement the `CryptoProvider` interface instead of the `PrivValidator` one. + +##### Code snippet for `CryptoProviderPV` + +As mentioned above, instead of having several implementations of `PrivValidator`, the proposal is to have only one that, by dependency injection, loads the corresponding `CryptoProvider` that offers the same functionality as the previous implementations of `PrivValidator`. + +Below is an example of how `CryptoProviderPV` would look like. Note that in this particular case, since the PrivateKey is managed inside the corresponding implementation, we're not passing that value to the signer. This is to avoid having to significantly change the code for `FilePV`. This is also valid for all implementations that manage their private keys in their own logic. + +```go +// CryptoProviderPV is the implementation of PrivValidator using CryptoProvider's methods +type CryptoProviderPV struct { + provider CryptoProvider + pubKey PubKey +} + +// NewCryptoProviderPV creates a new instance of CryptoProviderPV +func NewCryptoProviderPV(provider CryptoProvider, pk PubKey) (*CryptoProviderPV, error) { + return &CryptoProviderPV{provider: provider, pubKey: pubKey}, nil +} + +// GetPubKey returns the public key of the validator +func (pv *CryptoProviderPV) GetPubKey() (PubKey, error) { + return pv.pubKey, nil +} + +// SignVote signs a canonical representation of the vote. If signExtension is true, it also signs the vote extension. +func (pv *CryptoProviderPV) SignVote(chainID string, vote *Vote, signExtension bool) error { + signer := pv.provider.GetSigner() + + // code for getting voteBytes goes here + // voteBytes := ... + + // The underlying signer needs these parameters so we pass them through SignerOptions + options := SignerOptions{ + "chainID": chainID, + "vote": vote, + } + + sig, _ := signer.Sign(voteBytes, options) + vote.Signature = sig + return nil +} + +// SignProposal signs a canonical representation of the proposal +func (pv *CryptoProviderPV) SignProposal(chainID string, proposal *Proposal) error { + signer := pv.provider.GetSigner() + + // code for getting proposalBytes goes here + // proposalBytes := ... + + // The underlying signer needs these parameters so we pass them through SignerOptions + options := SignerOptions{ + "chainID": chainID, + "proposal": proposal, + } + + sig, _ := signer.Sign(proposalBytes, options) + proposal.Signature = sig + return nil +} + +// SignBytes signs an arbitrary array of bytes +func (pv *CryptoProviderPV) SignBytes(bytes []byte) ([]byte, error) { + signer := pv.provider.GetSigner() + return signer.Sign(bytes, SignerOptions{}) +} + +``` + + +#### Proposed directory structure + +Implementations of crypto providers (previously Privval implementations) should be in their own directory. + +``` +cometbft/ +├── privval/ +│ ├── providers/ +│ │ ├── file_provider.go +│ │ ├── signer_provider.go +│ │ ├── retry_signer_provider.go +│ │ ├── mock_provider.go +├── types/ +│ ├── priv_validator.go +``` + + +#### Loading and storing implementations +(CryptoProviderFactory, Keyring, Record) + + +#### Other considerations + +Need of using Keyring to store records which store providers info and config +Need to adjust the way a node loads the desired validator. Instead of replacing validators according to config, the proper cryptoValidator will be loaded by its id + ### Tentative Primitive Building Blocks This is a **tentative** list of primitives that we might want to support. From 1ceee0b0eb000d9a79afd4b02de244e035ac01ee Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Thu, 6 Jun 2024 12:43:41 -0300 Subject: [PATCH 09/20] [WIP] More details to CometBFT implementation --- .../adr-071-crypto-v2-multi-curve.md | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 4c3aa15128d9..0bb953b56058 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -735,6 +735,14 @@ func (pv *CryptoProviderPV) SignBytes(bytes []byte) ([]byte, error) { ``` +*Note:* Each provider (File, Socket, SignerClient, RetrySignerClient) will need to be modified to satisfy the `CryptoProvider` interface. + + +#### Loading and storing implementations + +Storing crypto providers and all data related to them (keys, special configurations) will be addressed in the same way as expressed for the cosmos-sdk, that is, by using the [Keyring](https://github.com/cosmos/cosmos-sdk/blob/439f2f9d5b5884bc9df4b58d702555330549a898/crypto/keyring/keyring.go#L58-L109) interface to abstract the storage backends. + +*Note:* this is subject to change since this approach will require decoupling the Keyring package from the cosmos-sdk to avoid having CometBFT import artifacts from that repository. #### Proposed directory structure @@ -752,15 +760,17 @@ cometbft/ │ ├── priv_validator.go ``` +#### Other considerations -#### Loading and storing implementations -(CryptoProviderFactory, Keyring, Record) +##### Config +//TODO +(config struct needs to store the CryptoProvider ID which the app will load) -#### Other considerations +##### Node startup -Need of using Keyring to store records which store providers info and config -Need to adjust the way a node loads the desired validator. Instead of replacing validators according to config, the proper cryptoValidator will be loaded by its id +//TODO +(mention that the startup logic will need to change for the better: validator would only be matter of getting its ID from the config and then load it from the Keyring) ### Tentative Primitive Building Blocks From 383c763b81b54bfa0b808f77f6cdb3dd19fc6a7a Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Wed, 12 Jun 2024 21:05:43 -0300 Subject: [PATCH 10/20] Fixes --- .../adr-071-crypto-v2-multi-curve.md | 102 +++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 0bb953b56058..9e06c8f78d6f 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -11,11 +11,11 @@ DRAFT ## Abstract -This ADR proposes the refactoring of the existing Keyring module to support multiple cryptographic curves for signing and verification processes. With this update, we aim to facilitate the integration of new cryptographic curves through clean and simple interfaces. Additionally, support for Hardware Security Modules (HSM) is introduced as a complementary enhancement in this redesign. +This ADR proposes the refactoring of the existing `Keyring` and `cosmos-sdk/crypto` code to support multiple cryptographic curves for signing and verification processes. With this update, we aim to facilitate the integration of new cryptographic curves through clean and simple interfaces. Additionally, support for Hardware Security Modules (HSM) is introduced as a complementary enhancement in this redesign. This ADR also introduces the capability to support remote signers. This feature will enable the nodes to interact with cryptographic signers that are not locally present on the system where the main app is running. This is particularly useful for scenarios where keys are managed in secure, remote environments or when leveraging cloud-based cryptographic services. -Additionally, the appendix will describe the implementation of this ADR within the [cometBFT](https://github.com/cometbft/cometbft) codebase, specifically focusing on the interactions with the [PrivateValidator](https://github.com/cometbft/cometbft/blob/68e5e1b4e3bd342a653a73091a1af7cc5e88b86b/types/priv_validator.go#L15) interface. +Additionally, the appendix will describe the implementation of this ADR within the [cometBFT](https://github.com/cometbft/cometbft) codebase, specifically focusing on the interactions with the [PrivateValidator](https://github.com/cometbft/cometbft/blob/68e5e1b4e3bd342a653a73091a1af7cc5e88b86b/types/priv_validator.go#L15) interface. See the [Appendix](#appendix) for more details on CometBFT integration. ## Introduction @@ -31,7 +31,9 @@ Special focus has been placed on the following key aspects: * maintainability * developer experience -The enhancements in this proposal not only render the ["Keyring ADR"](https://github.com/cosmos/cosmos-sdk/issues/14940) obsolete, but also encompass its key aspects, replacing it with a more flexible and comprehensive approach. Furthermore, the gRPC service proposed in the Keyring ADR can be easily implemented as a specialized HSM. +The enhancements in this proposal not only render the ["Keyring ADR"](https://github.com/cosmos/cosmos-sdk/issues/14940) obsolete, but also encompass its key aspects, replacing it with a more flexible and comprehensive approach. Furthermore, the gRPC service proposed in the mentioned ADR can be easily implemented as a specialized `CryptoProvider`. + +We'll introduce the concept of `CryptoProvider` in the following sections. ### Glossary @@ -48,7 +50,7 @@ In order to fully understand the need for changes and the proposed improvements, * The Cosmos SDK currently lacks a comprehensive ADR for the cryptographic package. -* If a blockchain project requires a cryptographic curve that is not supported by the current SDK, they are obligated to fork the SDK repository and make modifications. These modifications could potentially make the fork incompatible with future updates from the upstream SDK, complicating maintenance and integration. +* If a blockchain project requires a cryptographic curve that is not supported by the current SDK, the most likely scenario is that they will need to fork the SDK repository and make modifications. These modifications could potentially make the fork incompatible with future updates from the upstream SDK, complicating maintenance and integration. * Type leakage of specific crypto data types expose backward compatibility and extensibility challenges. @@ -108,23 +110,23 @@ Testing: New Keyring: -* Design a new Keyring interface with modular backends injection system to support hardware devices and cloud-based HSMs. +* Design a new Keyring interface with modular backends injection system to support hardware devices and cloud-based HSMs. This feature is optional and tied to complexity; if it proves too complex, it will be deferred to a future release as an enhancement. ## Proposed architecture ### Introduction -In this section, we will first introduce the concept of a `CryptoProvider`, which serves as the main API of the new cryptographic architecture. Following this, we will present the detailed components that make up the `CryptoProvider`. Lastly, we will introduce the storage and persistence layer, providing code snippets for each component to illustrate their implementation. +In this section, we will first introduce the concept of a `CryptoProvider`, which serves as the main API. Following this, we will present the detailed components that make up the `CryptoProvider`. Lastly, we will introduce the storage and persistence layer, providing code snippets for each component to illustrate their implementation. #### Crypto Provider -Here we introduce the concept of `CryptoProvider`. This interface acts as a centralized controller, encapsulating the APIs for the **signing**, **verifying** and **hashing** functionalities. It acts as the main API with which the apps will interact with +This **interface** acts as a centralized controller, encapsulating the APIs for the **signing**, **verifying** and **hashing** functionalities. It acts as the main API with which the apps will interact with -By abstracting the underlying cryptographic functionality, `CryptoProvider` enables a modular and extensible architecture, aka 'pluggable cryptography'. It allows users to easily switch between different cryptographic implementations without impacting the rest of the system. +By abstracting the underlying cryptographic functionalities, `CryptoProvider` enables a modular and extensible architecture, aka 'pluggable cryptography'. It allows users to easily switch between different cryptographic implementations without impacting the rest of the system. -The `CryptoProvider` interface includes getters for essential cryptographic functionalities and metadata: +The `CryptoProvider` interface includes getters for essential cryptographic functionalities and its metadata: ```go @@ -146,9 +148,9 @@ type CryptoProvider interface { ##### Components -The components defined here are designed to act as wrappers around the underlying proper functions. This architecture ensures that the actual cryptographic operations such as signing, hashing, and verifying are delegated to the specialized functions, that are implementation dependant. These wrapper components facilitate a clean and modular approach by abstracting the complexity of direct cryptographic function calls. +The components defined here are designed to act as *wrappers* around the underlying proper functions. This architecture ensures that the actual cryptographic operations such as signing, hashing, and verifying are delegated to the specialized functions, that are implementation dependant. These wrapper components facilitate a clean and modular approach by abstracting the complexity of direct cryptographic function calls. -In all of the interface's methods, we add an *options* input parameter designed to provide a flexible and dynamic way to pass various options and configurations to the `Sign`, `Verify`, and `Hash` functions. This approach allows developers to customize these processes by including any necessary parameters that might be required by specific algorithms or operational contexts. However, this requires that a type assertion for each option be performed inside the function's implementation. +In all of the interface's methods, we add an *options* input parameter of type `map[string]any`, designed to provide a flexible and dynamic way to pass various options and configurations to the `Sign`, `Verify`, and `Hash` functions. This approach allows developers to customize these processes by including any necessary parameters that might be required by specific algorithms or operational contexts. However, this requires that a type assertion for each option be performed inside the function's implementation. ###### Signer @@ -160,13 +162,26 @@ that might be necessary for the signing operation. ```go // Signer represents a general interface for signing messages. type Signer interface { - // Sign takes a message as input and returns the digital signature. - Sign(fullMsg []byte, options SignerOptions) (Signature, error) + // Sign takes a signDoc as input and returns the digital signature. + Sign(signDoc []byte, options SignerOptions) (Signature, error) } type SignerOpts = map[string]any ``` +###### Signature + +```go +// Signature represents a general interface for a digital signature. +type Signature interface { + // Bytes returns the byte representation of the signature. + Bytes() []byte + + // Equals checks if two signatures are identical. + Equals(other Signature) bool +} +``` + ###### Verifier Verifies if given a message belongs to a public key by validating against its respective signature. @@ -175,7 +190,7 @@ Verifies if given a message belongs to a public key by validating against its re // Verifier represents a general interface for verifying signatures. type Verifier interface { // Verify checks the digital signature against the message and a public key to determine its validity. - Verify(signature Signature, msg []byte, pubKey PublicKey, options VerifierOptions) (bool, error) + Verify(signature Signature, signDoc []byte, pubKey PublicKey, options VerifierOptions) (bool, error) } type VerifierOpts = map[string]any @@ -203,6 +218,7 @@ The metadata allows uniquely identifying a `CryptoProvider` and also stores its // ProviderMetadata holds metadata about the crypto provider. type ProviderMetadata struct { Name string + Type string Version *semver.Version // Using semver type for versioning Config map[string]any } @@ -210,9 +226,10 @@ type ProviderMetadata struct { ###### Public Key +*Note:* Here we decoupled the `Address` type from its corresponding `PubKey`. The corresponding codec step is proposed to be abstracted out from the CryptoProvider layer. + ```go type PubKey interface { - Address() Address Bytes() []byte Equals(other PubKey) bool Type() string @@ -235,31 +252,18 @@ type PrivKey interface { } ``` -###### Signature - -```go -// Signature represents a general interface for a digital signature. -type Signature interface { - // Bytes returns the byte representation of the signature. - Bytes() []byte - - // Equals checks if two signatures are identical. - Equals(other Signature) bool -} -``` - ##### Storage and persistence -The storage and persistence layer is tasked with storing a `CryptoProvider`. Specifically, this layer must: +The storage and persistence layer is tasked with storing a `CryptoProvider`s. Specifically, this layer must: * Securely store the crypto provider's associated private key (only if stored locally, otherwise a reference to the private key will be stored instead). -* Store the `ProviderMetadata` struct. +* Store the `ProviderMetadata` struct which contains the data that distinguishes that provider. The purpose of this layer is to ensure that upon retrieval of the persisted data, we can access the provider's type, version, and specific configuration (which varies based on the provider type). This information will subsequently be utilized to initialize the appropriate factory, as detailed in the following section on the factory pattern. -The storage proposal involves using a modified version of the [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) struct, which is already defined in Keyring/v1. Additionally, we propose utilizing the existing keyring backends (keychain, filesystem, memory, etc.) to store these Records in the same manner as the current Keyring/v1. +The storage proposal involves using a modified version of the [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) struct, which is already defined in **Keyring/v1**. Additionally, we propose utilizing the existing keyring backends (keychain, filesystem, memory, etc.) to store these `Record`s in the same manner as the current **Keyring/v1**. -*This approach will facilitate a smoother migration path from the current Keyring/v1 to the proposed architecture.* +*Note: This approach will facilitate a smoother migration path from the current Keyring/v1 to the proposed architecture.* Below is the proposed protobuf message to be included in the modified `Record.proto` file @@ -275,22 +279,26 @@ import "google/protobuf/any.proto"; // CryptoProvider holds all necessary information to instantiate and configure a CryptoProvider. message CryptoProvider { - string type = 1; // Type of the crypto provider - string version = 2; // Version of the crypto provider - map config = 3; // Configuration data with byte array values - google.protobuf.Any privKey = 4; // Optional if key is stored locally + string name = 1; // (unique) name of the crypto provider. + google.protobuf.Any pub_key = 2; + string type = 3; // Type of the crypto provider + string version = 4; // Version (semver format) + map config = 5; // Configuration data with byte array values + google.protobuf.Any privKey = 6; // Optional if key is stored locally } ``` +name: +Specifies the unique name of the crypto provider. This name is used to identify and reference the specific crypto provider instance. +pub_key (google.protobuf.Any): +Holds the public key associated with the crypto provider. type: -Specifies the type of the crypto provider. This field is used to identify and differentiate between various crypto provider implementations. - +Specifies the type of the crypto provider. This field is used to identify and differentiate between various crypto provider implementations. Examples: `ledger`, `AWSCloudHSM`, `local-secp256k1` version: Indicates the version of the crypto provider using semantic versioning. - configuration (map): Contains serialized configuration data as key-value pairs, where the key is a string and the value is a byte array. @@ -313,7 +321,7 @@ message Record { Ledger ledger = 4; Multi multi = 5; Offline offline = 6; - CryptoProvider crypto_provider = 7; + CryptoProvider crypto_provider = 7; // <- New } message Local { @@ -332,9 +340,7 @@ message Record { ##### Creating and loading a `CryptoProvider` -One of the main advantages of having an encapsulated entity for the defined cryptographic operations and tools is that it allows the use of different key pairs, which could be stored in various sources and/or be of different types, within the same codebase without necessitating changes to the rest of the app's code. - -To provide this functionality, we propose a *factory pattern* for the `CryptoProvider` interface and a *registry* for these builders. +For creating providers, we propose a *factory pattern* and a *registry* for these builders. Below, we present the proposed interfaces and code snippets to illustrate the proposed architecture. @@ -347,9 +353,9 @@ type CryptoProviderFactory interface { } ``` -**Note**: The mechanisms for storing and loading `records` will utilize the existing infrastructure from keyring/v1. +**Note**: The mechanisms for storing and loading `records` will utilize the existing infrastructure from **Keyring/v1**. -**Example**: crypto provider factory and builder registry +**Code snippet**: provider **factory** and builder **registry** ```go // crypto/v2/providerFactory.go @@ -394,11 +400,11 @@ func CreateCryptoProviderFromRecordOrConfig(id string, record *Record, config *P // generateProviderID is a function that generates a unique identifier for a CryptoProvider based on its metadata. // This can be changed in the future to a more suitable function if needed. func generateProviderID(metadata ProviderMetadata) string { - return fmt.Sprintf("%s-%s", metadata.Name, metadata.Version) + return fmt.Sprintf("%s-%s", metadata.Type, metadata.Version) } ``` -Example: Ledger HW implementation +**Example**: Ledger HW implementation Below is an example implementation of how a Ledger hardware wallet `CryptoProvider` might implement the registration of its factory and how instantiation would work. @@ -793,4 +799,4 @@ Moreover, it is important to emphasize the purpose of this work allows extensibi * blake2b,2s,3 * Keccak-256 / shake256 * bcrypt / scrypt / argon2, Argon2d/i/id - * Pedersen \ No newline at end of file + * Pedersen From 2b1d33a3249bdb181ce234211636f0806cd05d47 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Thu, 13 Jun 2024 10:37:36 -0300 Subject: [PATCH 11/20] More fixes --- .../adr-071-crypto-v2-multi-curve.md | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 9e06c8f78d6f..17f227c95e89 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -423,7 +423,7 @@ func (f *LedgerCryptoProviderFactory) CreateFromRecord(record *Record) (CryptoPr } // Assuming the record contains necessary fields like devicePath - devicePath, ok := record.Config["devicePath"].(string) + devicePath, ok := record.CryptoProvider.Config["devicePath"].(string) if !ok { return nil, fmt.Errorf("device path not found in record") } @@ -452,12 +452,15 @@ func main() { if err != nil { log.Fatalf("Error fetching record from keyring: %s", err) } - providerFromRecord, err := crypto.CreateCryptoProviderFromRecordOrConfig("LedgerCryptoProvider", record, nil) + ledgerProvider, err := crypto.CreateCryptoProviderFromRecordOrConfig("LedgerCryptoProvider", record, nil) if err != nil { log.Fatalf("Error creating crypto provider from record: %s", err) } - log.Printf("Provider from record created successfully: %+v", providerFromRecord.Metadata()) + log.Printf("Provider from record created successfully: %+v", ledgerProvider.Metadata()) + + // ledgerProvider CryptoProvider ready to use } + ``` @@ -467,7 +470,7 @@ The new `Keyring` interface will serve as a central hub for managing and fetchin ```go -type Keyring interface { +type KeyringV2 interface { // methods from Keyring/v1 // ListCryptoProviders returns a list of all the stored CryptoProvider metadata. @@ -478,6 +481,8 @@ type Keyring interface { } ``` +*Note*: Methods to obtain a provider from a public key or other means that make it easier to load the desired provider can be added. + ##### Especial use case: remote signers It's important to note that the `CryptoProvider` interface is versatile enough to be implemented as a remote signer. This capability allows for the integration of remote cryptographic operations, which can be particularly useful in distributed or cloud-based environments where local cryptographic resources are limited or need to be managed centrally. @@ -501,6 +506,7 @@ We will: * Define types and interfaces as the code attached. * Refactor existing code into new structure and interfaces. * Implement Unit Tests to ensure no backward compatibility issues. +* All code will be uploaded to the already existing [cosmos/crypto](https://github.com/cosmos/crypto) repo. Once a first stable release is ready, the projects that decide to adopt this package will be ready to clean up their internal crypto-related code to avoid code duplication and cluttering. ## Consequences @@ -510,9 +516,8 @@ We can divide the impact of this ADR into two main categories: state machine cod #### Client -The major impact will be on the client side, where the current `Keyring` interface will be replaced by the new `Keyring` interface. -This means that any piece of code that makes use of the `Record` struct will need to be adapted to use the new `CryptoProvider` instead. -This will also affect a large number of unit tests that will need to be adapted/replaced with new ones. +The major impact will be on the client side, where the current `Keyring` interface will be replaced by the new `KeyringV2` interface. At first, the impact will be low since `CryptoProvider` is an optional field in the `Record` message, so there's no mandatory requirement for migrating to this new concept right away. This allows a progressive transition where the risks of breaking changes or regressions are minimized. + #### State Machine @@ -524,16 +529,16 @@ Worth mentioning that there's also the alternative of using `Verifier` instances The specific way to adapt these modules will be deeply analyzed and decided at implementation time of this ADR. -*Note*: All cryptographic tools (hashers, verifiers, signers, etc.) will continue to be available as standalone packages that can be imported and utilized directly without the need for a `CryptoProvider` instance. However, the `CryptoProvider` is the recommended method for using these tools as it offers a more secure way to handle sensitive data, enhanced modularity, and the ability to store configurations and metadata within the CryptoProvider definition. +*Note*: All cryptographic tools (hashers, verifiers, signers, etc.) will continue to be available as standalone packages that can be imported and utilized directly without the need for a `CryptoProvider` instance. However, the `CryptoProvider` is the recommended method for using these tools as it offers a more secure way to handle sensitive data, enhanced modularity, and the ability to store configurations and metadata within the `CryptoProvider` definition. ### Backwards Compatibility The proposed migration path is similar to what the cosmos-sdk has done in the past. To ensure a smooth transition, the following steps will be taken: -* Create a new package `cryptoV2` and implement this ADR. Create unit tests, documentation and examples. +* Develop all new code in [cosmos/crypto](https://github.com/cosmos/crypto) repo. Create unit tests, documentation and examples. * Deprecate the old crypto package. The old crypto package will still be usable, but it will be marked as deprecated and users can opt to use the new package. -* Migrate the codebase to use the new `CryptoV2` package and remove the old crypto package. +* Migrate the codebase to use the new cosmos/crypto package and remove the old crypto one. _A more detailed migration path is provided in the corresponding document._ @@ -560,8 +565,7 @@ _A more detailed migration path is provided in the corresponding document._ ## Test Cases * The code will be unit tested to ensure a high code coverage -* There should be integration tests around Wallet, Keyring and CryptoProviders. -* There should be benchmark tests for hashing, keyring, encryption, decryption, signing and verifying functions. +* There should be integration tests around Keyring and CryptoProviders. ## Further Discussions From ae1188ce9e1808fda82e01e83158cb240f8ffe9d Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Thu, 13 Jun 2024 15:59:28 -0300 Subject: [PATCH 12/20] More fixes --- .../adr-071-crypto-v2-multi-curve.md | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 17f227c95e89..99fb33e14621 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -267,6 +267,8 @@ The storage proposal involves using a modified version of the [Record](https://g Below is the proposed protobuf message to be included in the modified `Record.proto` file +###### Protobuf message structure + ```protobuf // cryptoprovider.proto @@ -591,15 +593,15 @@ _A more detailed migration path is provided in the corresponding document._ ### Implementation on CometBFT Codebase -The implementation of this ADR to CometBFT will require a medium refactor in the codebase. Below we describe the proposed strategy to perform this implementation, this is: +The implementation of this ADR in CometBFT will require a medium-level refactor of the codebase. Below we describe the proposed strategy to perform this implementation, which is: -* Add a new `PrivValidator` implementation that uses `CryptoProvider` underneath. +* Add a new `PrivValidator` implementation that uses the `CryptoProvider` interface underneath. * Adding [Keyring](https://github.com/cosmos/cosmos-sdk/blob/main/crypto/keyring/keyring.go) and [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) for storing and loading providers. * New directories reorganization. -* Use Keyring to load and instantiate validators when booting up a node. +* Use **Keyring** to load and instantiate validators when booting up a node. #### Create a single implementation for `PrivValidator` @@ -750,13 +752,14 @@ func (pv *CryptoProviderPV) SignBytes(bytes []byte) ([]byte, error) { #### Loading and storing implementations -Storing crypto providers and all data related to them (keys, special configurations) will be addressed in the same way as expressed for the cosmos-sdk, that is, by using the [Keyring](https://github.com/cosmos/cosmos-sdk/blob/439f2f9d5b5884bc9df4b58d702555330549a898/crypto/keyring/keyring.go#L58-L109) interface to abstract the storage backends. +Alternatives: + +* Low impact / minimal dependencies: The corresponding CryptoProvider [Protobuf message](#protobuf-message-structure) can be directly stored on disk in a dedicated directory in an encoding format of choice (text, JSON). -*Note:* this is subject to change since this approach will require decoupling the Keyring package from the cosmos-sdk to avoid having CometBFT import artifacts from that repository. +* Greater impact / better security: Use cosmos-sdk's [Keyring](https://github.com/cosmos/cosmos-sdk/blob/439f2f9d5b5884bc9df4b58d702555330549a898/crypto/keyring/keyring). to manage CryptoProviders along with its private keys. This specifically applies to the `FilePV` implementation, which could store its private keys in Keyring instead of a file in the filesystem. This approach will require decoupling the Keyring package from the cosmos-sdk to avoid having CometBFT import artifacts from that repository. -#### Proposed directory structure -Implementations of crypto providers (previously Privval implementations) should be in their own directory. +Implementations of crypto providers (previously `Privval` implementations) should be in their own directory: ``` cometbft/ @@ -772,15 +775,10 @@ cometbft/ #### Other considerations -##### Config - -//TODO -(config struct needs to store the CryptoProvider ID which the app will load) - ##### Node startup -//TODO -(mention that the startup logic will need to change for the better: validator would only be matter of getting its ID from the config and then load it from the Keyring) +Node's configuration file should include the name of the `CryptoProvider` to be loaded at startup. Also, the startup logic will need to be changed from creating a validator to loading a `CryptoProvider` + ### Tentative Primitive Building Blocks From e583d9f6e8005f2aadefda6485f5c78bf86a9d7f Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Thu, 13 Jun 2024 16:00:24 -0300 Subject: [PATCH 13/20] Fix date --- docs/architecture/adr-071-crypto-v2-multi-curve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 99fb33e14621..dafe7b22bb74 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -3,7 +3,7 @@ ## Change log * May 7th 2024: Initial Draft (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) -* June 4th 2024: Add CometBFT implementation proposal (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) +* June 13th 2024: Add CometBFT implementation proposal (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) ## Status From a37c3e97f5cbdeb30132b10c2d6bb94eca95c053 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Mon, 24 Jun 2024 09:17:12 -0300 Subject: [PATCH 14/20] Minor var renamings --- .../adr-071-crypto-v2-multi-curve.md | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index dafe7b22bb74..48fb260007e4 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -155,7 +155,7 @@ In all of the interface's methods, we add an *options* input parameter of type ` ###### Signer Interface responsible for signing a message and returning the generated signature. -The `SignerOptions` map allows for flexible and dynamic configuration of the signing process. +The `SignerOpts` map allows for flexible and dynamic configuration of the signing process. This can include algorithm-specific parameters, security levels, or other contextual information that might be necessary for the signing operation. @@ -163,7 +163,7 @@ that might be necessary for the signing operation. // Signer represents a general interface for signing messages. type Signer interface { // Sign takes a signDoc as input and returns the digital signature. - Sign(signDoc []byte, options SignerOptions) (Signature, error) + Sign(signDoc []byte, options SignerOpts) (Signature, error) } type SignerOpts = map[string]any @@ -190,7 +190,7 @@ Verifies if given a message belongs to a public key by validating against its re // Verifier represents a general interface for verifying signatures. type Verifier interface { // Verify checks the digital signature against the message and a public key to determine its validity. - Verify(signature Signature, signDoc []byte, pubKey PublicKey, options VerifierOptions) (bool, error) + Verify(signature Signature, signDoc []byte, pubKey PublicKey, options VerifierOpts) (bool, error) } type VerifierOpts = map[string]any @@ -365,10 +365,11 @@ type CryptoProviderFactory interface { var providerFactories map[string]CryptoProviderFactory // RegisterCryptoProviderFactory is a function that registers a CryptoProviderFactory for a CryptoProvider. -func RegisterCryptoProviderFactory(provider CryptoProvider, factory CryptoProviderFactory) { +func RegisterCryptoProviderFactory(provider CryptoProvider, factory CryptoProviderFactory) string { metadata := provider.Metadata() id := generateProviderID(metadata) providerFactories[id] = factory + return id } // CreateCryptoProviderFromRecordOrConfig creates a CryptoProvider based on the provided Record or ProviderMetadata. @@ -447,14 +448,14 @@ func main() { ledgerFactory := &crypto.LedgerCryptoProviderFactory{} // Register the factory - crypto.RegisterCryptoProviderFactory("LedgerCryptoProvider", ledgerFactory) + id := crypto.RegisterCryptoProviderFactory("LedgerCryptoProvider", ledgerFactory) // Example of using a Record record, err := keyring.GetRecord("ledgerDevice-0") if err != nil { log.Fatalf("Error fetching record from keyring: %s", err) } - ledgerProvider, err := crypto.CreateCryptoProviderFromRecordOrConfig("LedgerCryptoProvider", record, nil) + ledgerProvider, err := crypto.CreateCryptoProviderFromRecordOrConfig(id, record, nil) if err != nil { log.Fatalf("Error creating crypto provider from record: %s", err) } @@ -710,8 +711,8 @@ func (pv *CryptoProviderPV) SignVote(chainID string, vote *Vote, signExtension b // code for getting voteBytes goes here // voteBytes := ... - // The underlying signer needs these parameters so we pass them through SignerOptions - options := SignerOptions{ + // The underlying signer needs these parameters so we pass them through SignerOpts + options := SignerOpts{ "chainID": chainID, "vote": vote, } @@ -728,8 +729,8 @@ func (pv *CryptoProviderPV) SignProposal(chainID string, proposal *Proposal) err // code for getting proposalBytes goes here // proposalBytes := ... - // The underlying signer needs these parameters so we pass them through SignerOptions - options := SignerOptions{ + // The underlying signer needs these parameters so we pass them through SignerOpts + options := SignerOpts{ "chainID": chainID, "proposal": proposal, } @@ -742,7 +743,7 @@ func (pv *CryptoProviderPV) SignProposal(chainID string, proposal *Proposal) err // SignBytes signs an arbitrary array of bytes func (pv *CryptoProviderPV) SignBytes(bytes []byte) ([]byte, error) { signer := pv.provider.GetSigner() - return signer.Sign(bytes, SignerOptions{}) + return signer.Sign(bytes, SignerOpts{}) } ``` From 98b8f8c33f7bb9fad10a8ded7816092d3513626b Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Mon, 24 Jun 2024 09:22:01 -0300 Subject: [PATCH 15/20] Fix type name --- docs/architecture/adr-071-crypto-v2-multi-curve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 48fb260007e4..50746b38b470 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -190,7 +190,7 @@ Verifies if given a message belongs to a public key by validating against its re // Verifier represents a general interface for verifying signatures. type Verifier interface { // Verify checks the digital signature against the message and a public key to determine its validity. - Verify(signature Signature, signDoc []byte, pubKey PublicKey, options VerifierOpts) (bool, error) + Verify(signature Signature, signDoc []byte, pubKey PubKey, options VerifierOpts) (bool, error) } type VerifierOpts = map[string]any From 0a65b9b1e92cdf4b55651c21428da474419127f6 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Mon, 24 Jun 2024 09:41:27 -0300 Subject: [PATCH 16/20] Fix grammar --- docs/architecture/adr-071-crypto-v2-multi-curve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 50746b38b470..101787832f07 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -525,7 +525,7 @@ The major impact will be on the client side, where the current `Keyring` interfa #### State Machine The impact on the state machine code will be minimal, the modules affected (at the time of writing this ADR) -are the `x/accounts` module, specifically the `Authenticate` function and the `x/auth/ante` module. These function will need to be adapted to use a `CryptoProvider` service to make use of the `Verifier` instance. +are the `x/accounts` module, specifically the `Authenticate` function and the `x/auth/ante` module. This function will need to be adapted to use a `CryptoProvider` service to make use of the `Verifier` instance. Worth mentioning that there's also the alternative of using `Verifier` instances in a standalone fashion (see note below). From 7c70dbbd1f859eded045ded0e46a4ff6011f2b2a Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Mon, 24 Jun 2024 09:44:08 -0300 Subject: [PATCH 17/20] Clarify that Keyring and Record are optional for CometBFT's implementation --- docs/architecture/adr-071-crypto-v2-multi-curve.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 101787832f07..a49f62b9281c 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -598,11 +598,11 @@ The implementation of this ADR in CometBFT will require a medium-level refactor * Add a new `PrivValidator` implementation that uses the `CryptoProvider` interface underneath. -* Adding [Keyring](https://github.com/cosmos/cosmos-sdk/blob/main/crypto/keyring/keyring.go) and [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) for storing and loading providers. +* **[Optional]** Adding [Keyring](https://github.com/cosmos/cosmos-sdk/blob/main/crypto/keyring/keyring.go) and [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) for storing and loading providers. * New directories reorganization. -* Use **Keyring** to load and instantiate validators when booting up a node. +* **[Optional]** Use `Keyring` to load and instantiate validators when booting up a node. #### Create a single implementation for `PrivValidator` From c3c3eca58dc954c48b5725e4f7c840284fe80228 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Mon, 24 Jun 2024 10:03:45 -0300 Subject: [PATCH 18/20] Add comment on directory reorganization --- docs/architecture/adr-071-crypto-v2-multi-curve.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index a49f62b9281c..ee7995d450be 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -600,7 +600,9 @@ The implementation of this ADR in CometBFT will require a medium-level refactor * **[Optional]** Adding [Keyring](https://github.com/cosmos/cosmos-sdk/blob/main/crypto/keyring/keyring.go) and [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) for storing and loading providers. -* New directories reorganization. +* New directories reorganization: +Implementations of crypto providers (previously `Privval` implementations) should be in their own directory. See the [Directories reorganization](#directories-reorganization) section for more details. + * **[Optional]** Use `Keyring` to load and instantiate validators when booting up a node. @@ -760,6 +762,8 @@ Alternatives: * Greater impact / better security: Use cosmos-sdk's [Keyring](https://github.com/cosmos/cosmos-sdk/blob/439f2f9d5b5884bc9df4b58d702555330549a898/crypto/keyring/keyring). to manage CryptoProviders along with its private keys. This specifically applies to the `FilePV` implementation, which could store its private keys in Keyring instead of a file in the filesystem. This approach will require decoupling the Keyring package from the cosmos-sdk to avoid having CometBFT import artifacts from that repository. +#### Directories reorganization + Implementations of crypto providers (previously `Privval` implementations) should be in their own directory: ``` From 536771779635f7c6b157a52a5da9c79a24f634f4 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Mon, 24 Jun 2024 16:12:25 -0300 Subject: [PATCH 19/20] Several code snippet fixes --- .../adr-071-crypto-v2-multi-curve.md | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index ee7995d450be..80708017d06f 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -352,6 +352,7 @@ Below, we present the proposed interfaces and code snippets to illustrate the pr type CryptoProviderFactory interface { CreateFromRecord(*Record) (CryptoProvider, error) CreateFromConfig(ProviderMetadata) (CryptoProvider, error) + Type() string } ``` @@ -362,24 +363,28 @@ type CryptoProviderFactory interface { ```go // crypto/v2/providerFactory.go -var providerFactories map[string]CryptoProviderFactory - -// RegisterCryptoProviderFactory is a function that registers a CryptoProviderFactory for a CryptoProvider. -func RegisterCryptoProviderFactory(provider CryptoProvider, factory CryptoProviderFactory) string { - metadata := provider.Metadata() - id := generateProviderID(metadata) - providerFactories[id] = factory - return id +type Factory struct { + providerFactories map[string]CryptoProviderFactory } -// CreateCryptoProviderFromRecordOrConfig creates a CryptoProvider based on the provided Record or ProviderMetadata. -// It enforces that exactly one of Record or ProviderMetadata must be provided. -func CreateCryptoProviderFromRecordOrConfig(id string, record *Record, config *ProviderMetadata) (CryptoProvider, error) { - factory, exists := providerFactories[id] - if !exists { - return nil, fmt.Errorf("no factory registered for id %s", id) +// NewFactory creates a new Factory instance and initializes the providerFactories map. +func NewFactory() *Factory { + return &Factory{ + providerFactories: make(map[string]CryptoProviderFactory), } +} + +// RegisterCryptoProviderFactory is a function that registers a CryptoProviderFactory for its corresponding type. +func (f *Factory) RegisterCryptoProviderFactory(factory CryptoProviderFactory) string { + providerType := factory.Type() + f.providerFactories[providerType] = factory + return providerType +} + + +// CreateCryptoProviderFromRecordOrConfig creates a CryptoProvider based on the provided Record or ProviderMetadata. +func (f *Factory) CreateCryptoProviderFromRecordOrConfig(record *Record, config *ProviderMetadata) (CryptoProvider, error) { // Validate input parameters if record == nil && config == nil { return nil, fmt.Errorf("both record and config cannot be nil") @@ -390,21 +395,25 @@ func CreateCryptoProviderFromRecordOrConfig(id string, record *Record, config *P // Determine which factory method to call based on the input if record != nil { + factory, exists := f.providerFactories[record.CryptoProvider.Type()] + if !exists { + return nil, fmt.Errorf("no factory registered for provider type %s", record.CryptoProvider.Type()) + } + return factory.CreateFromRecord(record) } + if config != nil { + factory, exists := f.providerFactories[config.Type] + if !exists { + return nil, fmt.Errorf("no factory registered for provider type %s", config.Type) + } return factory.CreateFromConfig(*config) } // This line should never be reached due to the checks above return nil, fmt.Errorf("unexpected error in CreateCryptoProviderFromRecordOrConfig") } - -// generateProviderID is a function that generates a unique identifier for a CryptoProvider based on its metadata. -// This can be changed in the future to a more suitable function if needed. -func generateProviderID(metadata ProviderMetadata) string { - return fmt.Sprintf("%s-%s", metadata.Type, metadata.Version) -} ``` **Example**: Ledger HW implementation @@ -414,6 +423,8 @@ Below is an example implementation of how a Ledger hardware wallet `CryptoProvid ```go // crypto/v2/providers/ledger/factory.go +const FACTORY_TYPE = "Ledger" + type LedgerCryptoProviderFactory struct { DevicePath string // Any other necessary fields goes here @@ -435,6 +446,11 @@ func (f *LedgerCryptoProviderFactory) CreateFromRecord(record *Record) (CryptoPr return &LedgerCryptoProvider{DevicePath: devicePath}, nil } +func (f *LedgerCryptoProviderFactory) Type() string { + return FACTORY_TYPE +} + + // crypto/v2/examples/registerProvider.go @@ -444,21 +460,27 @@ import ( ) func main() { - // Create an instance of the factory - ledgerFactory := &crypto.LedgerCryptoProviderFactory{} + // Initialize a new factory instance + factory := NewFactory() + + + // Create an instance of the ledger factory + ledgerFactory := &ledger.LedgerCryptoProviderFactory{} // Register the factory - id := crypto.RegisterCryptoProviderFactory("LedgerCryptoProvider", ledgerFactory) + factory.RegisterCryptoProviderFactory(ledgerFactory) // Example of using a Record record, err := keyring.GetRecord("ledgerDevice-0") if err != nil { log.Fatalf("Error fetching record from keyring: %s", err) } - ledgerProvider, err := crypto.CreateCryptoProviderFromRecordOrConfig(id, record, nil) + + ledgerProvider, err := factory.CreateCryptoProviderFromRecordOrConfig(record, nil) if err != nil { log.Fatalf("Error creating crypto provider from record: %s", err) } + log.Printf("Provider from record created successfully: %+v", ledgerProvider.Metadata()) // ledgerProvider CryptoProvider ready to use From 7dfcc328dfb17f77cf8d4698d3af499a81cc7348 Mon Sep 17 00:00:00 2001 From: Ezequiel Raynaudo Date: Tue, 2 Jul 2024 12:03:03 -0300 Subject: [PATCH 20/20] Update ADR --- .../adr-071-crypto-v2-multi-curve.md | 624 +----------------- 1 file changed, 16 insertions(+), 608 deletions(-) diff --git a/docs/architecture/adr-071-crypto-v2-multi-curve.md b/docs/architecture/adr-071-crypto-v2-multi-curve.md index 80708017d06f..c2ae96bd297f 100644 --- a/docs/architecture/adr-071-crypto-v2-multi-curve.md +++ b/docs/architecture/adr-071-crypto-v2-multi-curve.md @@ -4,6 +4,7 @@ * May 7th 2024: Initial Draft (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) * June 13th 2024: Add CometBFT implementation proposal (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) +* July 2nd 2024: Split ADR proposal, add link to ADR in cosmos/crypto (Zondax AG: @raynaudoe @juliantoledano @jleni @educlerici-zondax @lucaslopezf) ## Status @@ -11,30 +12,17 @@ DRAFT ## Abstract -This ADR proposes the refactoring of the existing `Keyring` and `cosmos-sdk/crypto` code to support multiple cryptographic curves for signing and verification processes. With this update, we aim to facilitate the integration of new cryptographic curves through clean and simple interfaces. Additionally, support for Hardware Security Modules (HSM) is introduced as a complementary enhancement in this redesign. +This ADR proposes the refactoring of the existing `Keyring` and `cosmos-sdk/crypto` code to implement [ADR-001-CryptoProviders](https://github.com/cosmos/crypto/blob/main/docs/architecture/adr-001-crypto-provider.md). -This ADR also introduces the capability to support remote signers. This feature will enable the nodes to interact with cryptographic signers that are not locally present on the system where the main app is running. This is particularly useful for scenarios where keys are managed in secure, remote environments or when leveraging cloud-based cryptographic services. - -Additionally, the appendix will describe the implementation of this ADR within the [cometBFT](https://github.com/cometbft/cometbft) codebase, specifically focusing on the interactions with the [PrivateValidator](https://github.com/cometbft/cometbft/blob/68e5e1b4e3bd342a653a73091a1af7cc5e88b86b/types/priv_validator.go#L15) interface. See the [Appendix](#appendix) for more details on CometBFT integration. +For in-depth details of the `CryptoProviders` and their design please refer to ADR mentioned above. ## Introduction +The introduction of multi-curve support in the cosmos-sdk cryptographic package offers significant advantages. By not being restricted to a single cryptographic curve, developers can choose the most appropriate curve based on security, performance, and compatibility requirements. This flexibility enhances the application's ability to adapt to evolving security standards and optimizes performance for specific use cases, helping to future-proofing the sdk's cryptographic capabilities. -The introduction of multi-curve support in the cosmos-sdk cryptographic module offers significant advantages. By not being restricted to a single cryptographic curve, developers can choose the most appropriate curve based on security, performance, and compatibility requirements. This flexibility enhances the application's ability to adapt to evolving security standards and optimizes performance for specific use cases, helping to future-proofing the sdk's cryptographic capabilities. - - -Special focus has been placed on the following key aspects: - -* modularity -* extensibility -* security -* maintainability -* developer experience The enhancements in this proposal not only render the ["Keyring ADR"](https://github.com/cosmos/cosmos-sdk/issues/14940) obsolete, but also encompass its key aspects, replacing it with a more flexible and comprehensive approach. Furthermore, the gRPC service proposed in the mentioned ADR can be easily implemented as a specialized `CryptoProvider`. -We'll introduce the concept of `CryptoProvider` in the following sections. - ### Glossary @@ -66,198 +54,37 @@ In order to fully understand the need for changes and the proposed improvements, The key objectives for this proposal are: -Modular Design Philosophy - -* Establish a flexible and extensible foundation using interfaces to enable the seamless integration of various cryptographic curves. - -* Restructure, Refactor, and Decouple: Update the codebase to ensure modularity and future adaptability. - -Documentation & Community Engagement - -* Enhance documentation to ensure clarity, establish a good practices protocol and promote community engagement, providing a platform for feedback and collaborative growth. - -Backward Compatibility & Migration - -* Prioritize compatibility with previous version to avoid disruptions for existing users. - -* Design and propose a suitable migration path, ensuring transitions are as seamless as possible. - +* Leverage `CryptoProviders`: Utilize them as APIs for cryptographic tools, ensuring modularity, flexibility, and ease of integration. Developer-Centric Approach * Prioritize clear, intuitive interfaces and best-practice design principles. - Quality Assurance * Enhanced Test Coverage: Improve testing methodologies to ensure the robustness and reliability of the module. -* Conduct an Audit: After implementation, perform a comprehensive audit to identify potential vulnerabilities and ensure the module's security and stability. - ## Technical Goals -Multi-curve support: - -* Support for a wide range of cryptographic curves to be integrated seamlessly into the sdk in a modular way. - -Wide Hardware Device & Cloud-based HSM Interface Support: - -* Design a foundational interface for various hardware devices (Ledger, YubiKey, Thales, etc.) and cloud-based HSMs (Amazon, Azure) to support both current and future implementations. - -Testing: - -* Design an environment for testing, ensuring developers can validate integrations without compromising system integrity. - New Keyring: -* Design a new Keyring interface with modular backends injection system to support hardware devices and cloud-based HSMs. This feature is optional and tied to complexity; if it proves too complex, it will be deferred to a future release as an enhancement. +* Design a new `Keyring` interface with modular backends injection system to support hardware devices and cloud-based HSMs. This feature is optional and tied to complexity; if it proves too complex, it will be deferred to a future release as an enhancement. ## Proposed architecture -### Introduction - -In this section, we will first introduce the concept of a `CryptoProvider`, which serves as the main API. Following this, we will present the detailed components that make up the `CryptoProvider`. Lastly, we will introduce the storage and persistence layer, providing code snippets for each component to illustrate their implementation. - - -#### Crypto Provider - -This **interface** acts as a centralized controller, encapsulating the APIs for the **signing**, **verifying** and **hashing** functionalities. It acts as the main API with which the apps will interact with - -By abstracting the underlying cryptographic functionalities, `CryptoProvider` enables a modular and extensible architecture, aka 'pluggable cryptography'. It allows users to easily switch between different cryptographic implementations without impacting the rest of the system. - -The `CryptoProvider` interface includes getters for essential cryptographic functionalities and its metadata: - -```go -// CryptoProvider aggregates the functionalities of signing, verifying, and hashing, and provides metadata. -type CryptoProvider interface { - // GetSigner returns an instance of Signer. - GetSigner() Signer - - // GetVerifier returns an instance of Verifier. - GetVerifier() Verifier - - // GetHasher returns an instance of Hasher. - GetHasher() Hasher - - // Metadata returns metadata for the crypto provider. - Metadata() ProviderMetadata -} -``` - -##### Components - -The components defined here are designed to act as *wrappers* around the underlying proper functions. This architecture ensures that the actual cryptographic operations such as signing, hashing, and verifying are delegated to the specialized functions, that are implementation dependant. These wrapper components facilitate a clean and modular approach by abstracting the complexity of direct cryptographic function calls. - -In all of the interface's methods, we add an *options* input parameter of type `map[string]any`, designed to provide a flexible and dynamic way to pass various options and configurations to the `Sign`, `Verify`, and `Hash` functions. This approach allows developers to customize these processes by including any necessary parameters that might be required by specific algorithms or operational contexts. However, this requires that a type assertion for each option be performed inside the function's implementation. - -###### Signer - -Interface responsible for signing a message and returning the generated signature. -The `SignerOpts` map allows for flexible and dynamic configuration of the signing process. -This can include algorithm-specific parameters, security levels, or other contextual information -that might be necessary for the signing operation. - -```go -// Signer represents a general interface for signing messages. -type Signer interface { - // Sign takes a signDoc as input and returns the digital signature. - Sign(signDoc []byte, options SignerOpts) (Signature, error) -} +### Components -type SignerOpts = map[string]any -``` - -###### Signature - -```go -// Signature represents a general interface for a digital signature. -type Signature interface { - // Bytes returns the byte representation of the signature. - Bytes() []byte - - // Equals checks if two signatures are identical. - Equals(other Signature) bool -} -``` - -###### Verifier - -Verifies if given a message belongs to a public key by validating against its respective signature. - -```go -// Verifier represents a general interface for verifying signatures. -type Verifier interface { - // Verify checks the digital signature against the message and a public key to determine its validity. - Verify(signature Signature, signDoc []byte, pubKey PubKey, options VerifierOpts) (bool, error) -} - -type VerifierOpts = map[string]any -``` +The main components to be used will be the same as those found in the [ADR-001](https://github.com/cosmos/crypto/blob/main/docs/architecture/adr-001-crypto-provider.md#components). -###### Hasher -This interface allows to have a specific hashing algorithm. - -```go -// Hasher represents a general interface for hashing data. -type Hasher interface { - // Hash takes an input byte array and returns the hashed output as a byte array. - Hash(input []byte, options HasherOpts) (output []byte, err error) -} - -type HasherOpts = map[string]any -``` - -###### Metadata - -The metadata allows uniquely identifying a `CryptoProvider` and also stores its configurations. - -```go -// ProviderMetadata holds metadata about the crypto provider. -type ProviderMetadata struct { - Name string - Type string - Version *semver.Version // Using semver type for versioning - Config map[string]any -} -``` - -###### Public Key - -*Note:* Here we decoupled the `Address` type from its corresponding `PubKey`. The corresponding codec step is proposed to be abstracted out from the CryptoProvider layer. - -```go -type PubKey interface { - Bytes() []byte - Equals(other PubKey) bool - Type() string -} -``` - -###### Private Key - -*Note*: For example, in hardware wallets, the `PrivKey` interface acts only as a *reference* to the real data. This is a design consideration and may be subject to change during implementation. - -Future enhancements could include additional security functions such as **zeroing** memory after private key usage to further enhance security measures. - - -```go -type PrivKey interface { - Bytes() []byte - PubKey() PubKey - Equals(other PrivKey) bool - Type() string -} -``` - -##### Storage and persistence +#### Storage and persistence The storage and persistence layer is tasked with storing a `CryptoProvider`s. Specifically, this layer must: * Securely store the crypto provider's associated private key (only if stored locally, otherwise a reference to the private key will be stored instead). -* Store the `ProviderMetadata` struct which contains the data that distinguishes that provider. +* Store the [`ProviderMetadata`](https://github.com/cosmos/crypto/blob/main/docs/architecture/adr-001-crypto-provider.md#metadata) struct which contains the data that distinguishes that provider. The purpose of this layer is to ensure that upon retrieval of the persisted data, we can access the provider's type, version, and specific configuration (which varies based on the provider type). This information will subsequently be utilized to initialize the appropriate factory, as detailed in the following section on the factory pattern. @@ -267,45 +94,7 @@ The storage proposal involves using a modified version of the [Record](https://g Below is the proposed protobuf message to be included in the modified `Record.proto` file -###### Protobuf message structure - -```protobuf - -// cryptoprovider.proto - -syntax = "proto3"; - -package crypto; - -import "google/protobuf/any.proto"; - -// CryptoProvider holds all necessary information to instantiate and configure a CryptoProvider. -message CryptoProvider { - string name = 1; // (unique) name of the crypto provider. - google.protobuf.Any pub_key = 2; - string type = 3; // Type of the crypto provider - string version = 4; // Version (semver format) - map config = 5; // Configuration data with byte array values - google.protobuf.Any privKey = 6; // Optional if key is stored locally -} -``` -name: -Specifies the unique name of the crypto provider. This name is used to identify and reference the specific crypto provider instance. - -pub_key (google.protobuf.Any): -Holds the public key associated with the crypto provider. - -type: -Specifies the type of the crypto provider. This field is used to identify and differentiate between various crypto provider implementations. Examples: `ledger`, `AWSCloudHSM`, `local-secp256k1` - -version: -Indicates the version of the crypto provider using semantic versioning. - -configuration (map): -Contains serialized configuration data as key-value pairs, where the key is a string and the value is a byte array. - -privKey (google.protobuf.Any): -An optional field that can store a private key if it is managed locally. +##### Protobuf message structure The [record.proto](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) file will be modified to include the `CryptoProvider` message as an optional field as follows. @@ -342,151 +131,8 @@ message Record { ##### Creating and loading a `CryptoProvider` -For creating providers, we propose a *factory pattern* and a *registry* for these builders. - -Below, we present the proposed interfaces and code snippets to illustrate the proposed architecture. - -```go -// CryptoProviderFactory is a factory interface for creating CryptoProviders. -// Must be implemented by each crypto provider. -type CryptoProviderFactory interface { - CreateFromRecord(*Record) (CryptoProvider, error) - CreateFromConfig(ProviderMetadata) (CryptoProvider, error) - Type() string -} -``` - -**Note**: The mechanisms for storing and loading `records` will utilize the existing infrastructure from **Keyring/v1**. - -**Code snippet**: provider **factory** and builder **registry** - -```go -// crypto/v2/providerFactory.go - -type Factory struct { - providerFactories map[string]CryptoProviderFactory -} - -// NewFactory creates a new Factory instance and initializes the providerFactories map. -func NewFactory() *Factory { - return &Factory{ - providerFactories: make(map[string]CryptoProviderFactory), - } -} - - -// RegisterCryptoProviderFactory is a function that registers a CryptoProviderFactory for its corresponding type. -func (f *Factory) RegisterCryptoProviderFactory(factory CryptoProviderFactory) string { - providerType := factory.Type() - f.providerFactories[providerType] = factory - return providerType -} - - -// CreateCryptoProviderFromRecordOrConfig creates a CryptoProvider based on the provided Record or ProviderMetadata. -func (f *Factory) CreateCryptoProviderFromRecordOrConfig(record *Record, config *ProviderMetadata) (CryptoProvider, error) { - // Validate input parameters - if record == nil && config == nil { - return nil, fmt.Errorf("both record and config cannot be nil") - } - if record != nil && config != nil { - return nil, fmt.Errorf("both record and config cannot be provided simultaneously") - } - - // Determine which factory method to call based on the input - if record != nil { - factory, exists := f.providerFactories[record.CryptoProvider.Type()] - if !exists { - return nil, fmt.Errorf("no factory registered for provider type %s", record.CryptoProvider.Type()) - } - - return factory.CreateFromRecord(record) - } - - if config != nil { - factory, exists := f.providerFactories[config.Type] - if !exists { - return nil, fmt.Errorf("no factory registered for provider type %s", config.Type) - } - return factory.CreateFromConfig(*config) - } - - // This line should never be reached due to the checks above - return nil, fmt.Errorf("unexpected error in CreateCryptoProviderFromRecordOrConfig") -} -``` - -**Example**: Ledger HW implementation - -Below is an example implementation of how a Ledger hardware wallet `CryptoProvider` might implement the registration of its factory and how instantiation would work. - -```go -// crypto/v2/providers/ledger/factory.go - -const FACTORY_TYPE = "Ledger" - -type LedgerCryptoProviderFactory struct { - DevicePath string - // Any other necessary fields goes here -} - -func (f *LedgerCryptoProviderFactory) CreateFromRecord(record *Record) (CryptoProvider, error) { - // Extract necessary data from the record to initialize a LedgerCryptoProvider - if record == nil { - return nil, fmt.Errorf("record is nil") - } - - // Assuming the record contains necessary fields like devicePath - devicePath, ok := record.CryptoProvider.Config["devicePath"].(string) - if !ok { - return nil, fmt.Errorf("device path not found in record") - } - - // Initialize the LedgerCryptoProvider with the device path - return &LedgerCryptoProvider{DevicePath: devicePath}, nil -} - -func (f *LedgerCryptoProviderFactory) Type() string { - return FACTORY_TYPE -} - - -// crypto/v2/examples/registerProvider.go - - -import ( - "crypto/v2/providers" - "log" -) - -func main() { - // Initialize a new factory instance - factory := NewFactory() - - - // Create an instance of the ledger factory - ledgerFactory := &ledger.LedgerCryptoProviderFactory{} - - // Register the factory - factory.RegisterCryptoProviderFactory(ledgerFactory) - - // Example of using a Record - record, err := keyring.GetRecord("ledgerDevice-0") - if err != nil { - log.Fatalf("Error fetching record from keyring: %s", err) - } - - ledgerProvider, err := factory.CreateCryptoProviderFromRecordOrConfig(record, nil) - if err != nil { - log.Fatalf("Error creating crypto provider from record: %s", err) - } - - log.Printf("Provider from record created successfully: %+v", ledgerProvider.Metadata()) - - // ledgerProvider CryptoProvider ready to use -} - -``` +For creating providers, we propose a *factory pattern* and a *registry* for these builders. Examples of these +patterns can be found [here](https://github.com/cosmos/crypto/blob/main/docs/architecture/adr-001-crypto-provider.md#illustrative-code-snippets) ##### Keyring @@ -512,12 +158,6 @@ type KeyringV2 interface { It's important to note that the `CryptoProvider` interface is versatile enough to be implemented as a remote signer. This capability allows for the integration of remote cryptographic operations, which can be particularly useful in distributed or cloud-based environments where local cryptographic resources are limited or need to be managed centrally. -Here are a few of the services that can be leveraged: - -* AWS CloudHSM -* Azure Key Vault -* HashiCorp Vault -* Google Cloud KMS ## Alternatives @@ -527,11 +167,11 @@ It is important to note that all the code presented in this document is not in i We will: +* Leverage crypto providers * Refactor the module structure as described above. * Define types and interfaces as the code attached. * Refactor existing code into new structure and interfaces. * Implement Unit Tests to ensure no backward compatibility issues. -* All code will be uploaded to the already existing [cosmos/crypto](https://github.com/cosmos/crypto) repo. Once a first stable release is ready, the projects that decide to adopt this package will be ready to clean up their internal crypto-related code to avoid code duplication and cluttering. ## Consequences @@ -561,12 +201,11 @@ The specific way to adapt these modules will be deeply analyzed and decided at i The proposed migration path is similar to what the cosmos-sdk has done in the past. To ensure a smooth transition, the following steps will be taken: -* Develop all new code in [cosmos/crypto](https://github.com/cosmos/crypto) repo. Create unit tests, documentation and examples. +Once ADR-001 is implemented with a stable release: + * Deprecate the old crypto package. The old crypto package will still be usable, but it will be marked as deprecated and users can opt to use the new package. * Migrate the codebase to use the new cosmos/crypto package and remove the old crypto one. -_A more detailed migration path is provided in the corresponding document._ - ### Positive * Single place of truth @@ -592,240 +231,9 @@ _A more detailed migration path is provided in the corresponding document._ * The code will be unit tested to ensure a high code coverage * There should be integration tests around Keyring and CryptoProviders. -## Further Discussions - > While an ADR is in the DRAFT or PROPOSED stage, this section should contain a > summary of issues to be solved in future iterations (usually referencing comments > from a pull-request discussion). > > Later, this section can optionally list ideas or improvements the author or > reviewers found during the analysis of this ADR. - - -## References - -* TPM 2.0 support: https://github.com/google/go-tpm -* https://solanacookbook.com/references/keypairs-and-wallets.html#how-to-generate-a-new-keypair -* https://www.nist.gov/news-events/news/2022/07/nist-announces-first-four-quantum-resistant-cryptographic-algorithms -* https://blog.cloudflare.com/nist-post-quantum-surprise/ -* https://pkg.go.dev/crypto#Hash -* https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html - -## Appendix - - -### Implementation on CometBFT Codebase - -The implementation of this ADR in CometBFT will require a medium-level refactor of the codebase. Below we describe the proposed strategy to perform this implementation, which is: - -* Add a new `PrivValidator` implementation that uses the `CryptoProvider` interface underneath. - -* **[Optional]** Adding [Keyring](https://github.com/cosmos/cosmos-sdk/blob/main/crypto/keyring/keyring.go) and [Record](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/crypto/keyring/v1/record.proto) for storing and loading providers. - -* New directories reorganization: -Implementations of crypto providers (previously `Privval` implementations) should be in their own directory. See the [Directories reorganization](#directories-reorganization) section for more details. - - -* **[Optional]** Use `Keyring` to load and instantiate validators when booting up a node. - - -#### Create a single implementation for `PrivValidator` - - -The current CometBFT codebase includes the following implementations of `PrivValidator`: - -* `FilePV`: Handles file-based private validators. -* `SignerClient`: Manages remote signing. -* `RetrySignerClient`: Provides retry mechanisms for remote signing. -* `MockPV`: Used exclusively for testing purposes. - -We propose introducing a new implementation, `CryptoProviderPV`, which will unify and replace all the above implementations. This single implementation will act as an abstraction layer for the `PrivValidator` implementations mentioned above. - -**Current:** - -```mermaid -classDiagram - class PrivValidator { - <> - } - - class FilePV { - } - - class SignerClientPV { - } - - class RetrySignerClientPV { - } - - class MockPV { - } - - PrivValidator <|.. FilePV - PrivValidator <|.. SignerClientPV - PrivValidator <|.. RetrySignerClientPV - PrivValidator <|.. MockPV -``` - - -**Proposed:** - -```mermaid -classDiagram - class PrivValidator { - <> - } - - class CryptoProvider { - <> - } - - class CryptoProviderPV { - } - - class FileCP { - } - - class SocketSignerCP { - } - - class RetrySocketSignerCP { - } - - class MockCP { - } - - PrivValidator <|.. CryptoProviderPV - CryptoProviderPV --> CryptoProvider - CryptoProvider <|.. FileCP - CryptoProvider <|.. SocketSignerCP - CryptoProvider <|.. RetrySocketSignerCP - CryptoProvider <|.. MockCP -``` - -For these new implementations, the current code for `File`, `SocketClient`, and `RetrySocketClient` will have to implement the `CryptoProvider` interface instead of the `PrivValidator` one. - -##### Code snippet for `CryptoProviderPV` - -As mentioned above, instead of having several implementations of `PrivValidator`, the proposal is to have only one that, by dependency injection, loads the corresponding `CryptoProvider` that offers the same functionality as the previous implementations of `PrivValidator`. - -Below is an example of how `CryptoProviderPV` would look like. Note that in this particular case, since the PrivateKey is managed inside the corresponding implementation, we're not passing that value to the signer. This is to avoid having to significantly change the code for `FilePV`. This is also valid for all implementations that manage their private keys in their own logic. - -```go -// CryptoProviderPV is the implementation of PrivValidator using CryptoProvider's methods -type CryptoProviderPV struct { - provider CryptoProvider - pubKey PubKey -} - -// NewCryptoProviderPV creates a new instance of CryptoProviderPV -func NewCryptoProviderPV(provider CryptoProvider, pk PubKey) (*CryptoProviderPV, error) { - return &CryptoProviderPV{provider: provider, pubKey: pubKey}, nil -} - -// GetPubKey returns the public key of the validator -func (pv *CryptoProviderPV) GetPubKey() (PubKey, error) { - return pv.pubKey, nil -} - -// SignVote signs a canonical representation of the vote. If signExtension is true, it also signs the vote extension. -func (pv *CryptoProviderPV) SignVote(chainID string, vote *Vote, signExtension bool) error { - signer := pv.provider.GetSigner() - - // code for getting voteBytes goes here - // voteBytes := ... - - // The underlying signer needs these parameters so we pass them through SignerOpts - options := SignerOpts{ - "chainID": chainID, - "vote": vote, - } - - sig, _ := signer.Sign(voteBytes, options) - vote.Signature = sig - return nil -} - -// SignProposal signs a canonical representation of the proposal -func (pv *CryptoProviderPV) SignProposal(chainID string, proposal *Proposal) error { - signer := pv.provider.GetSigner() - - // code for getting proposalBytes goes here - // proposalBytes := ... - - // The underlying signer needs these parameters so we pass them through SignerOpts - options := SignerOpts{ - "chainID": chainID, - "proposal": proposal, - } - - sig, _ := signer.Sign(proposalBytes, options) - proposal.Signature = sig - return nil -} - -// SignBytes signs an arbitrary array of bytes -func (pv *CryptoProviderPV) SignBytes(bytes []byte) ([]byte, error) { - signer := pv.provider.GetSigner() - return signer.Sign(bytes, SignerOpts{}) -} - -``` - -*Note:* Each provider (File, Socket, SignerClient, RetrySignerClient) will need to be modified to satisfy the `CryptoProvider` interface. - - -#### Loading and storing implementations - -Alternatives: - -* Low impact / minimal dependencies: The corresponding CryptoProvider [Protobuf message](#protobuf-message-structure) can be directly stored on disk in a dedicated directory in an encoding format of choice (text, JSON). - -* Greater impact / better security: Use cosmos-sdk's [Keyring](https://github.com/cosmos/cosmos-sdk/blob/439f2f9d5b5884bc9df4b58d702555330549a898/crypto/keyring/keyring). to manage CryptoProviders along with its private keys. This specifically applies to the `FilePV` implementation, which could store its private keys in Keyring instead of a file in the filesystem. This approach will require decoupling the Keyring package from the cosmos-sdk to avoid having CometBFT import artifacts from that repository. - - -#### Directories reorganization - -Implementations of crypto providers (previously `Privval` implementations) should be in their own directory: - -``` -cometbft/ -├── privval/ -│ ├── providers/ -│ │ ├── file_provider.go -│ │ ├── signer_provider.go -│ │ ├── retry_signer_provider.go -│ │ ├── mock_provider.go -├── types/ -│ ├── priv_validator.go -``` - -#### Other considerations - -##### Node startup - -Node's configuration file should include the name of the `CryptoProvider` to be loaded at startup. Also, the startup logic will need to be changed from creating a validator to loading a `CryptoProvider` - - -### Tentative Primitive Building Blocks - -This is a **tentative** list of primitives that we might want to support. -**This is not a final list or comprehensive, and it is subject to change.** -Moreover, it is important to emphasize the purpose of this work allows extensibility so any other primitive can be added in the future. - -* digital signatures - * RSA (PSS) - * ECDSA (secp256r1, secp256k1, etc.) - * EdDSA (ed25519, ed448) - * SR25519 - * Schnorr - * Lattice-based (Dilithium) - * BLS (BLS12-381, 377?) - -* Hashing - * sha2 / sha3 - * RIPEMD-160 - * blake2b,2s,3 - * Keccak-256 / shake256 - * bcrypt / scrypt / argon2, Argon2d/i/id - * Pedersen