diff --git a/sdk-wallet/README.md b/sdk-wallet/README.md index 63c05a2..c5a3e13 100644 --- a/sdk-wallet/README.md +++ b/sdk-wallet/README.md @@ -1,13 +1,36 @@ -## Keystores +## Package `controllers` -Components in `sdk-wallet/keystore`, even if they do share a common purpose - storing secret keys - are not meant to be used interchangeably. Generally speaking, they do not (are not required to) share a common interface. If needed (for exotic use-cases), client code can design customized adapters over these components in order to unify their interfaces (this is not covered by specs). +This package holds the most high-level components of **sdk-wallet**, the ones generally used to sign transactions and messages. -**Implementation detail:** an instance of `EncryptedKeystore` (which is a wrapper over the well-known JSON wallet) holds decrypted data within its state. +```mermaid +classDiagram +IWalletProvider <|-- LedgerWalletController : implements +IWalletProvider <|-- SecretKeyWalletController : implements +IWalletProvider <|-- MnemonicWalletController : implements +<> IWalletProvider +IWalletProvider : get_address(address_index) +IWalletProvider : get_current_address() +IWalletProvider : select_current_address(address_index) +IWalletProvider : sign_auth_token(auth_token) +IWalletProvider : sign_transaction(serialized_transaction) +IWalletProvider : sign_message(serialized_message) +``` -**Design detail:** components in `sdk-wallet/keystore` should not depend on `Address` within their public interface (though they are allowed to depend on it within their implementation). For example, the `export` functionality of `EncryptedKeystore` requires the functionality provided by `Address` (conversion from public key bytes to bech32 representation). +## Package `crypto` + +This package must be agnostic to all the other ones. Though, for languages that do no support **structural typing** (e.g. C#), the package is allowed to depend on the interfaces `ISecretKey` and `IPublicKey` (which are externally defined). In all other cases, the package must be completely self-contained and independent. + +## Package `keystores` + +TBD + +**Implementation detail:** an instance of `EncryptedKeystoreWithMnemonic` or `EncryptedKeystoreWithSecretKey` (wrappers over the well-known JSON wallet) hold decrypted data within their state. + +**Design detail:** components in `sdk-wallet/keystore` should not reference `Address` within their public interface (though they are allowed to depend on it within their implementation). + +### Useful links -### References: - https://github.com/ethereumjs/keythereum - - https://github.com/ethereumjs/ethereumjs-wallet/blob/master/docs/classes/wallet.md - - https://github.com/multiversx/mx-sdk-js-wallet/blob/main/src/userWallet.ts + - https://github.com/ethereumjs/ethereumjs-wallet/blob/v1.0.2/docs/classes/wallet.md + - https://github.com/multiversx/mx-sdk-js-wallet/blob/v4.2.1/src/userWallet.ts - https://github.com/multiversx/mx-sdk-py-wallet/blob/main/multiversx_sdk_wallet/user_wallet.py diff --git a/sdk-wallet/controllers/ledger_wallet_controller.md b/sdk-wallet/controllers/ledger_wallet_controller.md new file mode 100644 index 0000000..3bfcaf4 --- /dev/null +++ b/sdk-wallet/controllers/ledger_wallet_controller.md @@ -0,0 +1,35 @@ +## LedgerWalletController + +Note: though the Ledger device supports `account_index`, as well, this is not used in the context of MultiversX. For all purposes, `account_index` is `0`, while `address_index` is allowed to vary. + +References (not implementation suggestions): +- https://github.com/multiversx/mx-sdk-js-hw-provider/blob/v6.4.0/src/ledgerApp.ts +- https://github.com/multiversx/mx-sdk-py-cli/blob/main/multiversx_sdk_cli/ledger/ledger_app_handler.py + +``` +class LedgerWalletController: + // The constructor is not strictly captured by the specs; it's up to the implementing library to define it. + // Generally speaking, the constructor would take as input: a "IKeysComputer", an "IAddressComputer". + + // Returns the configuration of the MultiversX App. + get_configuration(): LedgerAppConfiguration; + + get_address(address_index: number): Address; + + get_current_address(): Address; + + select_current_address(address_index: number); + + // Part of the Native Authentication flow. + sign_auth_token(auth_token: bytes): bytes; + + sign_transaction(serialized_transaction: bytes): bytes; + sign_message(serialized_message: bytes): bytes; +``` + +``` +dto LedgerAppConfiguration: + is_transaction_data_allowed: boolean; + address_index: number; + version: string; +``` diff --git a/sdk-wallet/controllers/mnemonic_wallet_controller.md b/sdk-wallet/controllers/mnemonic_wallet_controller.md new file mode 100644 index 0000000..bc9363c --- /dev/null +++ b/sdk-wallet/controllers/mnemonic_wallet_controller.md @@ -0,0 +1,27 @@ +## MnemonicWalletController + +``` +class MnemonicWalletController implements IWalletController: + // The constructor is not strictly captured by the specs; it's up to the implementing library to define it. + // Generally speaking, the constructor would take as input: a "Mnemonic", an "IMnemonicComputer", a "IKeysComputer", an "IAddressComputer". + // Generally speaking, upon construction, the selected address would be the one at index 0. + + // Generally speaking, this would use the underlying mnemonic computer to derive the secret key, + // then compute the public key and, ultimately, the address. + get_address(address_index: number): Address; + + get_current_address(): Address; + + // Generally speaking, this would use the underlying mnemonic computer to derive the secret key. + select_current_address(address_index: number); + + // Part of the Native Authentication flow. + sign_auth_token(auth_token: bytes): bytes; + + // Kept separate from "sign_message", for clarity, and also because some wallet controllers require separate signing flows (e.g. Ledger). + sign_transaction(serialized_transaction: bytes): bytes; + // Kept separate from "sign_transaction", for clarity, and also because some wallet controllers require separate signing flows (e.g. Ledger). + sign_message(serialized_message: bytes): bytes; +``` + +**Note:** mnemonics cannot be retrieved from the wallet controller - at least, not directly; this use case is not supported by the specs. Wallet-like applications are responsible to enable support for retrieving the mnemonic, if they wish to do so (e.g. for display). diff --git a/sdk-wallet/controllers/secret_key_wallet_controller.md b/sdk-wallet/controllers/secret_key_wallet_controller.md new file mode 100644 index 0000000..9accc6b --- /dev/null +++ b/sdk-wallet/controllers/secret_key_wallet_controller.md @@ -0,0 +1,31 @@ +## SecretKeyWalletController + +``` +class SecretKeyWalletController implements IWalletController: + // The constructor is not strictly captured by the specs; it's up to the implementing library to define it. + // Generally speaking, the constructor would take as input: an "ISecretKey", a "IKeysComputer", an "IAddressComputer". + // If desired, the implementing library can support more than one secret key, but this is not required. + // Generally speaking, upon construction, the selected address would be the one at index 0. + + // Generally speaking, this throws if "address_index" is not 0. Though, implementations can choose to support more than one secret key, if desired. + // Can throw: + // - ErrAddressNotAvailable + get_address(address_index: number): Address; + + get_current_address(): Address; + + // Generally speaking, this throws if "address_index" is not 0. Though, implementations can choose to support more than one secret key, if desired. + // Can throw: + // - ErrAddressNotAvailable + select_current_address(address_index: number); + + // Part of the Native Authentication flow. + sign_auth_token(auth_token: bytes): bytes; + + // Kept separate from "sign_message", for clarity, and also because some wallet controllers require separate signing flows (e.g. Ledger). + sign_transaction(serialized_transaction: bytes): bytes; + // Kept separate from "sign_transaction", for clarity, and also because some wallet controllers require separate signing flows (e.g. Ledger). + sign_message(serialized_message: bytes): bytes; +``` + +**Note:** secret keys cannot be retrieved from the wallet controller - at least, not directly; this use case is not supported by the specs. Wallet-like applications are responsible to enable support for retrieving the secret key, if they wish to do so (e.g. for display). diff --git a/sdk-wallet/cookbook.md b/sdk-wallet/cookbook.md new file mode 100644 index 0000000..67ad1b4 --- /dev/null +++ b/sdk-wallet/cookbook.md @@ -0,0 +1,94 @@ + +## Cookbook (examples of usage) + +Note: error handling is out of scope for this cookbook (omitted for brevity). + +Generate a mnemonic and derive the first secret key: + +``` +provider = new UserWalletProvider() +mnemonic = provider.generate_mnemonic() +print(mnemonic.toString()) + +sk = provider.derive_secret_key_from_mnemonic(mnemonic, address_index=0 , passphrase="") +pk = provider.compute_public_key_from_secret_key(sk) +address = new Address(pk, "erd") +print(address.bech32()) +``` + +Generate a secret key and export it to a PEM keystore: + +``` +provider = new UserWalletProvider() +sk, _ = provider.generate_keypair() +keystore = PEMKeystore.new_from_secret_key(provider, sk) +keystore.export_to_file("wallet.pem", "erd") +``` + +Generate a mnemonic and export it to a JSON keystore: + +``` +provider = new UserWalletProvider() +mnemonic = provider.generate_mnemonic() +keystore = EncryptedKeystore.new_from_mnemonic(provider, mnemonic) +keystore.export_to_file("wallet.json", "password", "erd") +``` + +Load a JSON keystore which holds an encrypted mnemonic, then iterate over the first 3 accounts: + +``` +provider = new UserWalletProvider() +keystore = EncryptedKeystore.import_from_file(provider, "wallet.json", "password") +mnemonic = keystore.get_mnemonic() + +for i in [0, 1, 2]: + sk = provider.derive_secret_key_from_mnemonic(mnemonic, address_index=i, passphrase="") + pk = provider.compute_public_key_from_secret_key(sk) + address = new Address(pk, "erd") + print("Address", i, address.bech32()) +``` + +Load a PEM keystore, then iterate over the first 3 accounts: + +``` +provider = new UserWalletProvider() +keystore = PEMKeystore.import_from_file(provider, "wallet.pem") + +for i in [0, 1, 2]: + sk = keystore.get_secret_key(i) + pk = provider.compute_public_key_from_secret_key(sk) + address = new Address(pk, "erd") + print("Address", i, address.bech32()) +``` + +Change the password of a JSON keystore: + +``` +provider = new UserWalletProvider() +keystore = EncryptedKeystore.import_from_file(provider, "wallet.json", "password") +keystore.export_to_file("wallet.json", "new_password", "erd") +``` + +Load a PEM keystore and sign a piece of data: + +``` +provider = new UserWalletProvider() +keystore = PEMKeystore.import_from_file(provider, "wallet.pem") +sk = keystore.get_secret_key(0) +signature = provider.sign(data, sk) +``` + +Load a JSON keystore and sign data: + +``` +provider = new UserWalletProvider() +keystore = EncryptedKeystore.import_from_file(provider, "wallet.json", "password") + +if keystore.get_kind() == "secretKey": + sk = keystore.get_secret_key() +else: + mnemonic = keystore.get_mnemonic() + sk = provider.derive_secret_key_from_mnemonic(mnemonic, address_index=0, passphrase="") + +signature = provider.sign(data, sk) +``` diff --git a/sdk-wallet/crypto/keypair_based_encryptor_decryptor.md b/sdk-wallet/crypto/keypair_based_encryptor_decryptor.md index 5766d1a..cae1e55 100644 --- a/sdk-wallet/crypto/keypair_based_encryptor_decryptor.md +++ b/sdk-wallet/crypto/keypair_based_encryptor_decryptor.md @@ -1,5 +1,12 @@ ## KeyPairBasedEncryptorDecryptor +Optional, can be omitted by implementing libraries. + +Implementation suggestions: + - https://github.com/multiversx/mx-sdk-js-wallet/blob/v4.2.1/src/crypto/pubkeyEncryptor.ts + - https://github.com/multiversx/mx-sdk-js-wallet/blob/main/src/crypto/pubkeyDecryptor.ts + - + ``` class KeyPairBasedEncryptorDecryptor: encrypt(data: bytes, recipient_public_key: IPublicKey, auth_secret_key: ISecretKey): PublicKeyEncryptedData; diff --git a/sdk-wallet/crypto/password_based_encryptor_decryptor.md b/sdk-wallet/crypto/password_based_encryptor_decryptor.md index eb2989f..0cbeffe 100644 --- a/sdk-wallet/crypto/password_based_encryptor_decryptor.md +++ b/sdk-wallet/crypto/password_based_encryptor_decryptor.md @@ -1,5 +1,11 @@ ## PasswordBasedEncryptorDecryptor +Implementation suggestions: + - https://github.com/multiversx/mx-sdk-py-wallet/blob/v0.8.3/multiversx_sdk_wallet/crypto/encryptor.py + - https://github.com/multiversx/mx-sdk-py-wallet/blob/v0.8.3/multiversx_sdk_wallet/crypto/decryptor.py + - https://github.com/multiversx/mx-sdk-js-wallet/blob/v4.2.1/src/crypto/encryptor.ts + - https://github.com/multiversx/mx-sdk-js-wallet/blob/v4.2.1/src/crypto/decryptor.ts + ``` class PasswordBasedEncryptorDecryptor: encrypt(data: bytes, password: string): PasswordEncryptedData; diff --git a/sdk-wallet/crypto/password_encrypted_data.md b/sdk-wallet/crypto/password_encrypted_data.md index f184b14..5bd6840 100644 --- a/sdk-wallet/crypto/password_encrypted_data.md +++ b/sdk-wallet/crypto/password_encrypted_data.md @@ -1,5 +1,14 @@ ## PasswordEncryptedData +Implementation suggestions: + - `EncryptedData`: https://github.com/multiversx/mx-sdk-js-wallet/blob/v4.2.1/src/crypto/encryptedData.ts + - `EncryptedData`: https://github.com/multiversx/mx-sdk-py-wallet/blob/v0.8.3/multiversx_sdk_wallet/crypto/encrypted_data.py + +**Note:** this DTO is not a representation of the JSON keystore itself. Instead, it defines a data structure that is **agnostic to the type of file it might be stored in**. This abstraction was introduced July 2021: + - https://github.com/multiversx/mx-sdk-js-core/pull/11 + +In practice, such a data structure is, indeed, stored in a JSON keystore file, whose schema does resemble the DTO below - but it's not identical. Any similarities between `PasswordEncryptedData` and `EncryptedKeystoreContent` **should not be considered duplication**. + ``` dto PasswordEncryptedData: id: string; @@ -13,7 +22,7 @@ dto PasswordEncryptedData: mac: string; // Example of class compatible with "PasswordEncryptedData.kdfparams". -// Can be anything, and it's specific to a "PasswordEncryptedData.kdf" (e.g. "scrypt"). +// Can be anything, and it's correlated to a "PasswordEncryptedData.kdf" (e.g. "scrypt"). // EncryptorDecryptor.encrypt() puts this into "PasswordEncryptedData.kdfparams". // EncryptorDecryptor.decrypt() interprets (possibly validates) it. dto ScryptKeyDerivationParams: diff --git a/sdk-wallet/crypto/public_key_encrypted_data.md b/sdk-wallet/crypto/public_key_encrypted_data.md index 29495ed..1f4175e 100644 --- a/sdk-wallet/crypto/public_key_encrypted_data.md +++ b/sdk-wallet/crypto/public_key_encrypted_data.md @@ -1,5 +1,10 @@ ## PublicKeyEncryptedData +Optional, can be omitted by implementing libraries. + +Implementation suggestions: + - https://github.com/multiversx/mx-sdk-js-wallet/blob/main/src/crypto/x25519EncryptedData.ts + ``` dto PublicKeyEncryptedData: nonce: string; diff --git a/sdk-wallet/interfaces.md b/sdk-wallet/interfaces.md index 38427b4..4258fe1 100644 --- a/sdk-wallet/interfaces.md +++ b/sdk-wallet/interfaces.md @@ -5,12 +5,30 @@ For languages that support **structural typing** (e.g. Go, Python, TypeScript), For languages that only support **nominal typing** (e.g. C#), these interfaces can be _exported_. ``` -interface IWalletProvider: - generate_keypair(): (ISecretKey, IPublicKey) +interface IWalletController: + get_address(address_index: number): Address; + get_current_address(): Address; + select_current_address(address_index: number); + sign_auth_token(auth_token: bytes): bytes; + sign_transaction(serialized_transaction: bytes): bytes; + sign_message(serialized_message: bytes): bytes; +``` + +``` +interface ISigner: sign(data: bytes, secret_key: ISecretKey): bytes +``` + +``` +interface IVerifier: verify(data: bytes, signature: bytes, public_key: IPublicKey): bool - create_secret_key_from_bytes(data: bytes): ISecretKey - create_public_key_from_bytes(data: bytes): IPublicKey +``` + +``` +interface IKeysComputer: + generate_keypair(): (ISecretKey, IPublicKey) + compute_secret_key_from_bytes(data: bytes): ISecretKey + compute_public_key_from_bytes(data: bytes): IPublicKey compute_public_key_from_secret_key(secret_key: ISecretKey): IPublicKey ``` diff --git a/sdk-wallet/keystores/encrypted_keystore.md b/sdk-wallet/keystores/encrypted_keystore.md index 8238e8e..1f9d3b2 100644 --- a/sdk-wallet/keystores/encrypted_keystore.md +++ b/sdk-wallet/keystores/encrypted_keystore.md @@ -1,120 +1,52 @@ ## EncryptedKeystore +`EncryptedKeystore` handles encrypted JSON files. + +Note: current design is suboptimal. `EncryptedKeystore` handles both legacy keystore files (that hold encrypted secret keys) and new keystore files (that hold encrypted mnemonics). `get_kind()` can be used as a differentiator. A future design might involve two separate classes. + ``` class EncryptedKeystore: // The constructor is not captured by the specs; it's up to the implementing library to define it. + // Instance fields to be held (suggestion): + // - keys_computer: IKeysComputer + // - kind: "secretKey|mnemonic" + // - secretKey: ISecretKey (will be nil if kind == 'mnemonic') + // - mnemonic: Mnemonic (will be nil if kind == 'secretKey') // Named constructor - static new_from_secret_key(secret_key: ISecretKey): EncryptedKeystore + // This should have a trivial implementation. + static new_from_secret_key(keys_computer: IKeysComputer, secret_key: ISecretKey): EncryptedKeystore // Named constructor - // Below, "wallet_provider" should implement "derive_secret_key_from_mnemonic()". - // Advice: in the implementation all the parameters will be held as instance state (private fields). - static new_from_mnemonic(wallet_provider: IWalletProvider, mnemonic: Mnemonic): EncryptedKeystore + // This should have a trivial implementation. + static new_from_mnemonic(keys_computer: IKeysComputer, mnemonic: Mnemonic): EncryptedKeystore // Importing "constructor" - static import_from_object(wallet_provider: IWalletProvider, object: KeyfileObject, password: string): EncryptedKeystore + // This should decrypt the encrypted data, then call the (private) constructor. + // "password" should not be retained. + static import_from_object(keys_computer: IKeysComputer, object: KeyfileObject, password: string): EncryptedKeystore // Importing "constructor" - static import_from_file(wallet_provider: IWalletProvider, path: Path, password: string): EncryptedKeystore + // This should load the file content, decrypt the encrypted data, then call the (private) constructor. + // "password" should not be retained. + static import_from_file(keys_computer: IKeysComputer, path: Path, password: string): EncryptedKeystore - // When kind == 'secretKey', only index == 0 and passphrase == "" is supported. - // When kind == 'mnemonic', secret key derivation happens under the hood. - // Below, "passphrase" is the bip39 passphrase required to derive a secret key from a mnemonic (by default, it should be an empty string). - get_secret_key(index: int, passphrase: string): ISecretKey + get_kind(): "secretKey|mnemonic" + + // Can throw: + // - ErrSecretKeyNotAvailable + // + // Returns the secretKey (if available, i.e. if kind == 'secretKey'). + get_secret_key(): ISecretKey // Can throw: // - ErrMnemonicNotAvailable // // Returns the mnemonic used to create the keystore (if available, i.e. if kind == 'mnemonic'). - // This function is useful for UX flows where the application has to display the mnemonic etc. + // The caller of this can then derive secret keys, as needed. get_mnemonic(): Mnemonic export_to_object(password: string, address_hrp: string): KeyfileObject export_to_file(path: Path, password: string, address_hrp: string) ``` - -``` -dto KeyfileObject: - version: number; - - // "secretKey|mnemonic" - kind: string; - - // a GUID - id: string - - // hex representation of the address - address: string - - // bech32 representation of the address - bech32: string - - crypto: { - // PasswordEncryptedData.ciphertext - ciphertext: string; - - cipherparams: { - // PasswordEncryptedData.iv - iv: string; - }; - - // PasswordEncryptedData.cipher - cipher: string; - - // PasswordEncryptedData.kdf - kdf: string; - kdfparams: { - // PasswordEncryptedData.kdfparams.n - n: number; - - // PasswordEncryptedData.kdfparams.r - r: number; - - // PasswordEncryptedData.kdfparams.p - p: number; - - // PasswordEncryptedData.kdfparams.dklen - dklen: number; - - // PasswordEncryptedData.salt - salt: string; - }; - - // PasswordEncryptedData.mac - mac: string; - }; -``` - -## Examples of usage - -Create a new JSON keystore using a new mnemonic: - -``` -provider = new UserWalletProvider() -mnemonic = provider.generate_mnemonic() -keystore = EncryptedKeystore.new_from_mnemonic(provider, mnemonic) -keystore.export_to_file("file.json", "password", "erd") -``` - -Iterating over the first 3 accounts: - -``` -provider = new UserWalletProvider() -keystore = EncryptedKeystore.import_from_file(provider, "file.json", "password") - -for i in [0, 1, 2]: - secret_key = keystore.get_secret_key(i, "") - public_key = provider.compute_public_key_from_secret_key(secret_key) - address = new Address(public_key, "erd") - print("Address", i, address.bech32()) -``` - -Changing the password of an existing keystore: - -``` -provider = new UserWalletProvider() -keystore = EncryptedKeystore.import_from_file(provider, "file.json", "password") -keystore.export_to_file("file.json", "new_password", "erd") -``` diff --git a/sdk-wallet/keystores/encrypted_keystore_content.md b/sdk-wallet/keystores/encrypted_keystore_content.md new file mode 100644 index 0000000..4c68444 --- /dev/null +++ b/sdk-wallet/keystores/encrypted_keystore_content.md @@ -0,0 +1,71 @@ +## EncryptedKeystoreContent + +Also see (mere references, not implementation suggestions): + - `encryptedKeyJSONV4`: https://github.com/multiversx/mx-sdk-go/blob/v1.3.8/interactors/wallet.go + - Transforming the keystore content into an `PasswordEncryptedData`: https://github.com/multiversx/mx-sdk-js-wallet/blob/v4.2.1/src/userWallet.ts#L127 + +`EncryptedKeystoreContent` DTO is a representation of the JSON keystore file. + +Generally speaking, decrypting the payload of a keystore file happens as follows (high-level): + - the content of the keystore file is loaded into a `EncryptedKeystoreContent`. + - the `EncryptedKeystoreContent` is mapped (converted) to a `PasswordEncryptedData` + - the `PasswordEncryptedData` is fed to an `IPasswordBasedDecryptor` (e.g. `PasswordBasedEncryptorDecryptor`) for decryption. + - the decrypted payload is interpreted downstream (e.g. as a secret key, mnemonic etc.). + + +Generally speaking, saving a confidential payload into a keystore file happens as follows (high-level): + - the payload is encrypted using an `IPasswordBasedEncryptor` (e.g. `PasswordBasedEncryptorDecryptor`). What results is a `PasswordEncryptedData`. + - the resulted `PasswordEncryptedData` is mapped (converted) to an `EncryptedKeystoreContent`. + - the `EncryptedKeystoreContent` is written to a file. + +``` +dto EncryptedKeystoreContent: + version: number; + + // "secretKey|mnemonic" + kind: string; + + // a GUID + id: string + + // hex representation of the address + address: string + + // bech32 representation of the address + bech32: string + + crypto: { + // PasswordEncryptedData.ciphertext + ciphertext: string; + + cipherparams: { + // PasswordEncryptedData.iv + iv: string; + }; + + // PasswordEncryptedData.cipher + cipher: string; + + // PasswordEncryptedData.kdf + kdf: string; + kdfparams: { + // PasswordEncryptedData.kdfparams.n + n: number; + + // PasswordEncryptedData.kdfparams.r + r: number; + + // PasswordEncryptedData.kdfparams.p + p: number; + + // PasswordEncryptedData.kdfparams.dklen + dklen: number; + + // PasswordEncryptedData.salt + salt: string; + }; + + // PasswordEncryptedData.mac + mac: string; + }; +``` diff --git a/sdk-wallet/keystores/pem_keystore.md b/sdk-wallet/keystores/pem_keystore.md index 71a1c3b..889b8bf 100644 --- a/sdk-wallet/keystores/pem_keystore.md +++ b/sdk-wallet/keystores/pem_keystore.md @@ -2,19 +2,23 @@ ``` class PEMKeystore: - // The constructor is not captured by the specs; it's up to the implementing library to define it. + // The constructor is not strictly captured by the specs; it's up to the implementing library to define it. Suggestion below. + // In the implementation, all the parameters would be held as instance state (private fields). + private constructor(keys_computer: IKeysComputer, secret_keys: ISecretKey[]) // Named constructor - static new_from_secret_key(wallet_provider: IWalletProvider, secret_key: ISecretKey): PEMKeystore + // This should have a trivial implementation (e.g. a wrapper around the private constructor). + static new_from_secret_key(keys_computer: IKeysComputer, secret_key: ISecretKey): PEMKeystore // Named constructor - static new_from_secret_keys(wallet_provider: IWalletProvider, secret_keys: ISecretKey[]): PEMKeystore + // This should have a trivial implementation (e.g. a wrapper around the private constructor). + static new_from_secret_keys(keys_computer: IKeysComputer, secret_keys: ISecretKey[]): PEMKeystore // Importing "constructor" - static import_from_text(wallet_provider: IWalletProvider, text: string): PEMKeystore + static import_from_text(keys_computer: IKeysComputer, text: string): PEMKeystore // Importing "constructor" - static import_from_file(wallet_provider: IWalletProvider, path: Path): PEMKeystore + static import_from_file(keys_computer: IKeysComputer, path: Path): PEMKeystore get_secret_key(index: int): ISecretKey diff --git a/sdk-wallet/mnemonic.md b/sdk-wallet/mnemonic.md index 84e55ed..835e48c 100644 --- a/sdk-wallet/mnemonic.md +++ b/sdk-wallet/mnemonic.md @@ -20,41 +20,3 @@ class Mnemonic: toString(): string; } ``` - -## MnemonicComputer - -Encapsulates logic for generating and validating mnemonics and for deriving secret keys from mnemonics (i.e. BIP39). - -``` -class MnemonicComputer implements IMnemonicComputer: - // The constructor is not captured by the specs; it's up to the implementing library to define it. - - // Should not throw. - generate_mnemonic(): Mnemonic - - // Should not throw. - validate_mnemonic(mnemonic: Mnemonic): bool - - // Can throw: - // - ErrInvalidMnemonic - // Below, "passphrase" is the optional bip39 passphrase used to derive a secret key from a mnemonic. - // Reference: https://en.bitcoin.it/wiki/Seed_phrase#Two-factor_seed_phrases - derive_secret_key_from_mnemonic(mnemonic: Mnemonic, address_index: int, passphrase: string = ""): ISecretKey -``` - -## Examples of usage - -Creating a new mnemonic and deriving the first secret key. - -``` -computer = new MnemonicComputer() -provider = new UserWalletProvider() -mnemonic = computer.generate_mnemonic() -print(mnemonic.toString()) - -mnemonic = Mnemonic.newfromText("...") -sk = computer.derive_secret_key_from_mnemonic(mnemonic, address_index=0 , passphrase="") -pk = provider.compute_public_key_from_secret_key(sk) -address = new Address(public_key, "erd") -print(address.bech32()) -``` diff --git a/sdk-wallet/user_wallet_provider.md b/sdk-wallet/user_wallet_provider.md index be0cd34..962f622 100644 --- a/sdk-wallet/user_wallet_provider.md +++ b/sdk-wallet/user_wallet_provider.md @@ -1,7 +1,11 @@ ## UserWalletProvider +Provides functionality for generating secret keys, derivating public keys, signing & verifying data. + +Additionally, allows one to generate and validate mnemonics, and to derive secret keys from mnemonics (i.e. BIP39). + ``` -class UserWalletProvider implements IWalletProvider: +class UserWalletProvider implements ISigner, IVerifier, IKeysGenerator, IMnemonicComputer: // The constructor is not captured by the specs; it's up to the implementing library to define it. // For example, the constructor can be parametrized with underlying, more low-level crypto components, if applicable. @@ -28,4 +32,16 @@ class UserWalletProvider implements IWalletProvider: // Can throw: // - ErrInvalidSecretKey compute_public_key_from_secret_key(secret_key: ISecretKey): IPublicKey + + // Should not throw. + generate_mnemonic(): Mnemonic + + // Should not throw. + validate_mnemonic(mnemonic: Mnemonic): bool + + // Can throw: + // - ErrInvalidMnemonic + // Below, "passphrase" is the optional bip39 passphrase used to derive a secret key from a mnemonic. + // Reference: https://en.bitcoin.it/wiki/Seed_phrase#Two-factor_seed_phrases + derive_secret_key_from_mnemonic(mnemonic: Mnemonic, address_index: int, passphrase: string = ""): ISecretKey ``` diff --git a/sdk-wallet/validator_wallet_provider.md b/sdk-wallet/validator_wallet_provider.md index 9b52937..8e796dd 100644 --- a/sdk-wallet/validator_wallet_provider.md +++ b/sdk-wallet/validator_wallet_provider.md @@ -1,7 +1,7 @@ ## ValidatorWalletProvider ``` -class ValidatorWalletProvider implements IWalletProvider: +class ValidatorWalletProvider implements ISigner, IVerifier, IKeysGenerator: // The constructor is not captured by the specs; it's up to the implementing library to define it. // For example, the constructor can be parametrized with underlying, more low-level crypto components, if applicable.