casper-storage

Casper storage CI codecov visitors

Following crypto standard libraries, BIPs, SLIPs, etc this library provides a generic solution which lets developers have a standard way to manage wallets

Technical document

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 an encryption mode (either secp256k1 or ed25519) 

    CasperWallet recommends her to choose ed25519 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 secure password, Alice gives Abcd1234 and CasperWallet tries to intialize a User 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 enough

  • CasperWallet asks her to give another one which is stronger and more secure

  • Alice gives AliP2sw0rd.1 and CasperWallet 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

  1. In order to work with keys, import the KeyFactory from casper-storage
import { KeyFactory } from "casper-storage";
const keyManager = KeyFactory.getInstance();
  1. 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);
  1. 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");
  1. 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();
  1. Create a new keyphrase (master key), default is 24-words-length phrase
const masterKey = keyManager.generate()
  1. Convert the master key to the master seed
const masterSeed = keyManager.toSeed(masterKey)
const masterSeedArray = keyManager.toSeedArray(masterKey)
  1. 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);
  1. Get account
const acc0 = await hdWallet.getAccount(0)
const acc1 = await hdWallet.getAccount(1)
  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"
  1. Prepare a private key (input from user, or read from a file)

  2. 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.

  1. 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"
  1. 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.g m/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",
}
});
  1. 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);
  1. 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"));
  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

  2. Optional, add user's legacy wallets

const wallet = new LegacyWallet("user-wallet-private-key", EncryptionType.Ed25519);
user.addLegacyWallet(wallet, new WalletDescriptor("Legacy wallet 1"));
  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);
  1. Understand and retrieve information of wallets (WalletInfo)

WalletInfo represents a legacy wallet or a derived HD wallet, which is available in User

  • 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)
// 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
  1. 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 develop
  • jest to write unit-tests

Basic commands

  • yarn lint to ensure coding standards
  • yarn test to run tests
  • yarn testci to run tests with test coverage
  • yarn build to compile typescript to javascript with declarations
  • yarn build-all to build the library to final output
  • yarn docs to generate document with typedoc

Progress

  • Key generator (mnemonic)
  • Cryptography
    • Asymmetric key implementation
      • Ed25519
      • Secp256k1
    • Utilities
      • Common hash functions (HMAC, SHA256, SHA512, etc)
      • AES encrypt/decrypt functions
      • Encoder functions
  • Wallet
    • HD wallet
    • Legacy wallet
      • Supports PEM files (which are exported from Casper signer)
  • User management
    • Manage HD wallets, legacy wallets
    • Serialize/Deserialize (encrypted) user's information
  • Storage management
    • Cross-platform storage which supports web, react-native (iOS, Android)

License

Apache License 2.0

Generated using TypeDoc