-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sdk-wallet: some refactoring, additional explanations #32
base: main
Are you sure you want to change the base?
Changes from 6 commits
cef89f8
6c70b72
8a31f7d
3185f3a
85b7ad6
f8f3b02
81fd122
b607a8a
de13d6c
baa0066
360f545
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
|
||
## Cookbook (examples of usage) | ||
|
||
Create a new 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(public_key, "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") | ||
``` | ||
|
||
Loading a JSON keystore which holds the 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]: | ||
secret_key = provider.derive_secret_key_from_mnemonic(mnemonic, address_index=i, passphrase="") | ||
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, "wallet.json", "password") | ||
keystore.export_to_file("wallet.json", "new_password", "erd") | ||
``` | ||
|
||
Loading a PEM keystore and sign a piece of data: | ||
|
||
``` | ||
provider = new UserWalletProvider() | ||
keystore = PEMKeystore.import_from_file(provider, "wallet.pem") | ||
first_secret_key = keystore.get_secret_key(0) | ||
signature = provider.sign(data, first_secret_key) | ||
``` | ||
|
||
Loading a JSON keystore and sign data: | ||
|
||
``` | ||
provider = new UserWalletProvider() | ||
keystore = EncryptedKeystore.import_from_file(provider, "wallet.json", "password") | ||
|
||
if keystore.get_kind() == "secretKey": | ||
first_secret_key = keystore.get_secret_key() | ||
else: | ||
mnemonic = keystore.get_mnemonic() | ||
first_secret_key = provider.derive_secret_key_from_mnemonic(mnemonic, address_index=0, passphrase="") | ||
|
||
signature = provider.sign(data, first_secret_key) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,12 +5,20 @@ 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 ISigner: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
See: #31 (comment). |
||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In User/Validator wallet provider this is called |
||
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 | ||
``` | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,48 @@ | ||
## 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question for review: all right for the moment? 🙏 Also, see the example(s) in cookbook.md. |
||
|
||
``` | ||
class EncryptedKeystore: | ||
// 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove this private constructor, it's out of the scope of the provided specs. We should have only those 2 named constructors as example There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed phrasing, removed private constructor. No switch should be made in the implementation. |
||
// If kind == 'secretKey', `secret_key` must be provided, while `mnemonic` must be nil. | ||
// If kind == 'mnemonic', `secret_key` must be nil, while `mnemonic` must be provided. | ||
// In the implementation, all the parameters would be held as instance state (private fields). | ||
private constructor(keys_computer: IKeysComputer, kind: string, secret_key?: ISecretKey, mnemonic?: Mnemonic) | ||
|
||
// Named constructor | ||
static new_from_secret_key(secret_key: ISecretKey): EncryptedKeystore | ||
// 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): 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 (e.g. a wrapper around the private constructor). | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// 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 | ||
|
@@ -86,35 +101,3 @@ dto KeyfileObject: | |
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") | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 deriving secret keys from mnemonics (i.e. BIP39). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deriving => derive There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
|
||
``` | ||
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 | ||
Comment on lines
+35
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wallet-like applications that support JSON keystores (e.g. web wallet, mxpy) have to handle the keystore with respect to it's
kind
.