casper-storage
v1.5.1
Published
Provides standard implementation of wallet workflow, following standards BIPs, SLIPs
Downloads
45
Maintainers
Readme
Casper storage
Following crypto standard libraries, BIPs, SLIPs, etc this library provides a generic solution which lets developers have a standard way to manage wallets
Audited by an independent security firm
Setup
NPM
npm install casper-storage
// or
yarn add casper-storage
Browser
<script src="https://cdn.jsdelivr.net/npm/casper-storage"></script>
<script>
const wallet = new CasperStorage.CasperHDWallet("master-key", CasperStorage.EncryptionType.Ed25519);
</script>
React-native
Due to missing features of JavascriptCore, we need to polyfill and override some features (e.g randombytes, encoding, etc)
Click here for more detailed information
Table of contents
Scenarios
A new user (Alice) accesses a wallet management (CasperWallet
) for the very first time
Alice asks for a new wallet
CasperWallet
generates 12-words keyphrase and shows her on the next screen, then asks her to back-up it (by writing down)
import { KeyFactory } from "casper-storage";
const keyphrase = KeyFactory.getInstance().generate();
// Example: "picture sight smart spike squeeze invest rose need basket error garden ski"
Alice confirms she keeps this master keyphrase in a safe place
CasperWallet
asks her to choose anencryption mode
(either secp256k1 or ed25519)
CasperWallet
recommends her to chooseed25519
over secp256k1 due to security and performance, unless Alice explicitly wants to use secp256k1 because of Bitcoin, Ethereum compatible
import { EncryptionType } from "casper-storage"
const encryptionType = EncryptionType.Ed25519;
CasperWallet
asks her for a securepassword
, Alice givesAbcd1234
andCasperWallet
tries to intialize aUser
instance
import { User } from "caper-storage";
// Alice's password
const password = "Abcd1234";
// Initialize a new user with password
const user = new User(password);
CasperWallet
rejects because the given password is not strong enoughCasperWallet
asks her to give another one which is stronger and more secureAlice gives
AliP2sw0rd.1
andCasperWallet
re-tries
const password = "AliP2sw0rd.1";
const user = new User(password);
// Successfully created the user instance, let's set the HDWallet information
user.setHDWallet(keyphrase, encryptionType);
CasperWallet
creates the first wallet account
const wallet = await user.addWalletAccount(0, new WalletDescriptor("Account 1"));
CasperWallet
serializes user's information and store it into storage
import { Storage } from "casper-storage";
const userInfo = user.serialize();
await Storage.getInstance().set("casperwallet_userinformation", userInfo);
- Alice renames her first account to "Salary account"
const wallet = await user.getWalletAccount(0);
user.setWalletInfo(wallet.getReferenceKey(), "Salary account");
const userInfo = user.serialize();
await Storage.getInstance().set("casperwallet_userinformation", userInfo);
Alice locks her wallet
Alice comes back,
CasperStorage
asks for the password. Assuming that she gives the right password,CasperStorage
retrieves back the user's information
import { Storage, User } from "casper-storage";
const userInfo = await Storage.getInstance().get("casperwallet_userinformation");
const user = new User(password);
user.deserialize(userInfo);
Usage
Key generator
- In order to work with keys, import the KeyFactory from casper-storage
import { KeyFactory } from "casper-storage";
const keyManager = KeyFactory.getInstance();
- To generate a new random key (default is mnemonic provider)
keyManager.generate();
// output will be something like: basket pluck awesome prison unveil umbrella spy safe powder lock swallow shuffle
// By default, the outpult will be a phrase with 12 words, we can ask for more
keyManager.generate(24);
- To convert the key to a seed, so we can use in as a master seed for HD wallet
// Convert a master-key to a seed as a hex string
const seed: string = keyManager.toSeed("your keyphrase here");
// Convert a master-key to a seed as byte-array
const seed: Uint8Array = keyManager.toSeedArray("your keyphrase here");
- To validate if a phrase is a valid key, following BIP39 standard
const isValid: boolean = keyManager.validate("your keyphrase here");
Casper HD wallet (with keyphrase)
import { KeyFactory, EncryptionType, CasperHDWallet } from "casper-storage"
const keyManager = KeyFactory.getInstance();
- Create a new keyphrase (master key), default is 24-words-length phrase
const masterKey = keyManager.generate()
- Convert the master key to the master seed
const masterSeed = keyManager.toSeed(masterKey)
const masterSeedArray = keyManager.toSeedArray(masterKey)
- Create a new instance HDWallet from the master seed (either hex value or array buffer), with desired encryption mode
const hdWallet = new CasperHDWallet(masterSeed, EncryptionType.Ed25519);
- Get account
const acc0 = await hdWallet.getAccount(0)
const acc1 = await hdWallet.getAccount(1)
- Play with wallets
// Get the private key of wallet
acc0.getPrivateKey()
// Get the raw public key of wallet, which is computed, untouched data from private key
await acc0.getRawPublicKey()
// Get the public key of wallet, which is alternated depends on the chain
// For examples: Casper will prefix 01 or 02 depends on the encryption type
await acc0.getPublicKey()
// Get the public address of wallet, which is computed from public key
await acc0.getPublicAddress()
Casper legacy wallet (with single private key)
import { KeyFactory, EncryptionType, CasperLegacyWallet } from "casper-storage"
Prepare a private key (input from user, or read from a file)
Create a new instance of CasperLegacyWallet with that private key (either hex string or Uint8Array data)
const wallet = new CasperLegacyWallet("a-private-key-hex-string", EncryptionType.Ed25519)
const wallet = new CasperLegacyWallet(privateUint8ArrayData, EncryptionType.Secp256k1)
If users have PEM files (which are exported from casper-signer), we need to use KeyParser to parse it into a hex private string.
- This wallet will also share the same methods from a wallet of HDWallet
await wallet.getPublicKey()
await wallet.getPublicAddress()
User
import { User } from "casper-storage"
- Prepare a new user instance
const user = new User("user-password")
By default, user-password will be verified to ensure it is strong enough (at least 10 characters, including lowercase, uppercase, numeric and a special character). We can override the validator by giving user options
By default, the derivation path of HD wallet is
m/PURPOSE'/COINT_TYPE'/INDEX'
. We can override it by giving the third option e.gm/PURPOSE'/COINT_TYPE'/0'/0/INDEX
// With a regex
const user = new User("user-password", {
passwordValidator: {
validatorRegex: "passwordRegexValidation",
},
"m/PURPOSE'/COINT_TYPE'/INDEX'"
});
// or with a custom function
const user = new User("user-password", {
passwordValidator: {
validatorFunc: function (password) {
if (!password || password.length <= 10) {
return new ValidationResult(
false,
"Password length must be greater than or equal to 10"
);
} else {
return new ValidationResult(true);
}
},
},
"m/PURPOSE'/COINT_TYPE'/0'/0/INDEX"
});
// we can also update the password if needed
user.updatePassword("new-user-password");
// By default, new-user-password will be also verified to ensure it is strong enough
// we can override the validator by giving options
user.updatePassword("new-user-password", {
passwordValidator: {
validatorRegex: "passwordRegexValidation",
}
});
- Set user's HD wallet with encryption type
// master-key is a keyphrase 12-24 words
await user.setHDWallet("master-key", EncryptionType.Ed25519);
// we can retrieve back the master key
const entropy = user.getHDWallet().keyEntropy;
const masterKey: string[] = KeyFactory.getInstance().toKey(entropy);
- Add user's default first account
// We can call addWalletAccount
user.addWalletAccount(0, new WalletDescriptor("Account 1"));
// or if we have the wallet account already
const acc0 = await user.getWalletAccount(0);
user.setWalletInfo(acc0.getReferenceKey(), new WalletDescriptor("Account 1"));
Scan all available users's account (index from 1+, maximum up to 20 following BIP's standard) and add them into the user instance
Optional, add user's legacy wallets
const wallet = new LegacyWallet("user-wallet-private-key", EncryptionType.Ed25519);
user.addLegacyWallet(wallet, new WalletDescriptor("Legacy wallet 1"));
- Retrieve all wallets to show on UI
// HDWallet account
const walletsInfo: WalletInfo[] = user.getHDWallet().derivedWallets;
// Legacy wallets
const legacyWalletsInfo: WalletInfo[] = user.getLegacyWallets();
// Wallet infornation
const walletInfo: WalletInfo = walletsInfo[0];
const refKey: string = walletInfo.key;
const encryptionType: EncryptionType = walletInfo.encryptionType;
const name: string = walletInfo.descriptor.name;
// Construct HD wallet's accounts
const wallet: IWallet<IHDKey> = await user.getWalletAccountByRefKey(refKey);
// or
const wallet: IWallet<IHDKey> = await user.getWalletAccount(walletInfo.index); // only for HD wallets
// Construct legacy wallet
const legacyWalletInfo = legacyWalletsInfo[0];
const wallet = new CasperLegacyWallet(legacyWalletInfo.key, legacyWalletInfo.encryptionType);
- Understand and retrieve information of wallets (
WalletInfo
)
WalletInfo
represents a legacy wallet or a derived HD wallet, which is available inUser
- Each
WalletInfo
contains 2 main things- Encryption type and id/uid
- id is the private key of a legacy wallet or path of a HD wallet
- uid is the hashed of id, which is secured to store in any storage
- Descriptor (name, icon, description)
- Encryption type and id/uid
// Asume that we have the wallet infornation at anytime
const storedWalletInfo: WalletInfo = walletsInfo[0];
// We store either id/uid to storage
const id = storedWalletInfo.id;
const uid = storedWalletInfo.uid;
// We have 2 ways to retrieve back wallet information
const walletInfo: WalletInfo = user.getWalletInfo(storedWalletInfo.id);
const walletInfo: WalletInfo = user.getWalletInfo(storedWalletInfo.uid);
// Both above calls return the same instance
// However we recommend developers to store `uid` of wallet info,
// and use it to retrieve back information from user later
- Serialize/Deserialize user's information
// Serialize the user information to a secure encrypted string
const user = new User("user-password");
const userInfo = user.serialize();
// Deserialize the user information from a secure encrypted string
const user2 = new User("user-password", encryptedUserInfo);
user2.deserialize("user-encrypted-information");
// or
const user2 = User.deserializeFrom("user-password", "user-encrypted-information");
// In additional, User also exposes 2 methods to encrypt/decrypt data with user's password
const encryptedText = user.encrypt("Raw string value");
const decryptedText = user.decrypt(encryptedText);
Storage
import { StorageManager } from "casper-storage";
// Create a secured password
const password = new Password("Abcd1234.");
// Retrieve a secured storage with your password
const storage = StorageManager.getInstance(password);
// In the other hand, user also exposes getting storage method
const storage = user.getStorage();
// Set item into storage
await storage.set("key", "value");
// Get items from storage
const value = await storage.get("key");
// Update password, it will automatically re-sync existing keys
await storage.updatePassword(newPassword);
// or while updating password for user, existing keys will also be re-synced automatically
await user.updatePassword(newPassword);
// Other utils
const exists = await storage.has("key");
await storage.remove("key");
await storage.clear();
Misc
Get private keys in PEM formats
const pem = wallet.getPrivateKeyInPEM();
Parse exported PEM files from Casper signer
import { KeyParser } from "casper-storage";
// If you know exactly the encryption type, e.g Ed25519
const keyParser = KeyParser.getInstance(EncryptionType.Ed25519);
const keyValue = keyParser.convertPEMToPrivateKey(yourPEMContent);
// Otherwise, let it tries to guess
// It will try to detect encryption type, if not possible then it will throw an error
const keyParser = KeyParser.getInstance();
const keyValue = keyParser.convertPEMToPrivateKey(yourPEMContent);
// The keyValue exposes 2 properties
// Imported private-key
const key = keyValue.key;
// and its encryption type (EncryptionType.Ed25519 or EncryptionType.Secp256k1)
const encryptionType = keyValue.encryptionType;
Security
Casper-storage is production-ready
- The library has been audited by Cure53, an independent security film
- Pentest report and fix confirmation: PDF
Development
Requirements and toolings
- LTS node 16x
yarn
to manage packages (npm install -g yarn
)typescript
tooling to developjest
to write unit-tests
Basic commands
yarn lint
to ensure coding standardsyarn test
to run testsyarn testci
to run tests with test coverageyarn build
to compiletypescript
tojavascript
with declarationsyarn build-all
to build the library to final outputyarn docs
to generate document withtypedoc
Progress
- [x] Key generator (mnemonic)
- [x] Cryptography
- [x] Asymmetric key implementation
- [x] Ed25519
- [x] Secp256k1
- [x] Utilities
- [x] Common hash functions (HMAC, SHA256, SHA512, etc)
- [x] AES encrypt/decrypt functions
- [x] Encoder functions
- [x] Asymmetric key implementation
- [x] Wallet
- [x] HD wallet
- [x] Legacy wallet
- [x] Supports PEM files (which are exported from Casper signer)
- [x] User management
- [x] Manage HD wallets, legacy wallets
- [x] Serialize/Deserialize (encrypted) user's information
- [x] Storage management
- [x] Cross-platform storage which supports web, react-native (iOS, Android)
License
Apache License 2.0