Skip to content

Commit

Permalink
Merge pull request #1151 from kadena-community/feat/hd-wallet
Browse files Browse the repository at this point in the history
feat(hd-wallet): add package
  • Loading branch information
javadkh2 authored Nov 17, 2023
2 parents c20d8e2 + e041095 commit 03b249a
Show file tree
Hide file tree
Showing 25 changed files with 11,663 additions and 21 deletions.
2 changes: 2 additions & 0 deletions .changeset/few-flowers-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
1 change: 1 addition & 0 deletions packages/libs/hd-wallet/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/chainweaver/vendor/**/*
11 changes: 11 additions & 0 deletions packages/libs/hd-wallet/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This is a workaround for https://github.com/eslint/eslint/issues/3458
require('@rushstack/eslint-config/patch/modern-module-resolution');

module.exports = {
extends: ['@kadena-dev/eslint-config/profile/lib'],
parserOptions: { tsconfigRootDir: __dirname },
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@rushstack/typedef-var': 'off',
},
};
1 change: 1 addition & 0 deletions packages/libs/hd-wallet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

6 changes: 6 additions & 0 deletions packages/libs/hd-wallet/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
etc
lib
temp
src/chainweaver/vendor/**/*
**/*.md
tsdoc-metadata.json
18 changes: 18 additions & 0 deletions packages/libs/hd-wallet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- genericHeader start -->

# @kadena/hd-wallet

HD Wallet; key derivation

<picture>
<source srcset="https://raw.githubusercontent.com/kadena-community/kadena.js/main/common/images/Kadena.JS_logo-white.png" media="(prefers-color-scheme: dark)"/>
<img src="https://raw.githubusercontent.com/kadena-community/kadena.js/main/common/images/Kadena.JS_logo-black.png" width="200" alt="kadena.js logo" />
</picture>

<!-- genericHeader end -->

### Architectural decisions

Check [ADRs][1] documents for more information

[1]: ./docs/decisions/
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ADR: Use BIP44 for Private Key Generation

**Date:** 2023-10-31

**Status:** Proposal

## Context

We require a deterministic method for generating private keys from a mnemonic,
as Kadena has already adopted the [BIP39][1] mnemonic standard. In the realm of
cryptocurrencies, this process is commonly referred to as creating an HD-wallet
(Hierarchical Deterministic wallet).

HD wallets derive keys from a master key, typically following the [BIP32][2]
algorithm, which permits various derivation paths for both hardened and
non-hardened keys to create a chain of keys. BIP32 enables the creation of
either a chain of private keys or a chain of public keys, each serving different
purposes. For example, a blockchain using an account model, like Kadena,
primarily employs a chain of private keys, as it doesn't require a new address
for each transaction. Conversely, a blockchain using the [UTXO][3] model, such
as Bitcoin, can benefit from extended public keys.

This record exclusively concentrates on private keys, as we currently have no
plans to use extended public keys in Kadena. Kadena employs the ed25519
algorithm for keys, and the solution should be compatible with bip32-ed25519.

## Decision

We have chosen to implement the [BIP44][4] protocol, defining the path
restriction for BIP32 as `m/44'/626'/${index}'/0'/0'`. This decision is based on
the following considerations:

- KDA (Kadena) coin-type is [626][5].
- KDA follows an account-model coin approach, and for each key, we modify the
**account** (third level in BIP44).
- We exclusively use BIP44 for **private key** generation.
- Extended public keys are beyond the scope of this decision.
- All private keys are **hardened** in accordance with the [ed25519][6]
algorithm.

## Consequences

- We adopt a standard approach for key derivation, promoting compatibility with
other blockchains and wallets.
- Finding libraries for implementation will be more straightforward.

- We will need to manage legacy algorithms, such as Chainweaver, for backward
compatibility.

- Some wallets already use `m/44'/626'/0'/0'/${index}'` instead of the adopted
path. Therefore, we should allow users to specify a custom path starting with
`m/44'/626'` as well.

## Resources

- [BIP39 Proposal][1]
- [BIP32 Proposal][2]
- [Unspent transaction output][3]
- [BIP44 Proposal][4]
- [SLIP-0044: Registered Coin Types for BIP-0044][5]
- [SLIP-0010: Universal Private Key Derivation from Master Private Key][6]

[1]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[2]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
[3]: https://en.wikipedia.org/wiki/Unspent_transaction_output
[4]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
[5]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
[6]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Supporting Chainweaver Key Derivation

**Date**: 2023-10-31

**Status**: Accepted

## Context

We need to support chainweaver users. Discussions have revolved around
supporting keypair import or key derivation from a seed.

## Decision

We have chosen to use the bundle file from the [kadena-io/cardano-crypto][1]
repository. This choice is driven by the following factors:

1. **Custom Algorithm**: The bundle file contains a custom key derivation
algorithm based on BIP32. that we cant find an alternative library for that.

2. **Lack of Documentation**: The library lacks comprehensive documentation,
making maintenance and understanding of its design challenging. To mitigate
potential issues stemming from this lack of documentation, we have opted to
use the bundle as-is.

3. **Dependency Considerations**: The library relies on a C library through
WebAssembly (WASM), introducing specific dependencies during the build
process. To maintain a streamlined monorepo, we have decided against
including these dependencies.

## Consequences

This decision has the following implications:

- The bundle file must be included in the git repository.
- We may need to find ways to let users use the library for BIP44 without
requiring its inclusion in output files.

## Resources

[kadena-io/cardano-crypto repository][1]

[1]:
https://github.com/kadena-io/cardano-crypto.js/tree/jam%40chainweaver-keygen
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Encrypting Sensitive Data in Library

**Date**: 2023-11-08

**Status**: Proposal

## Context

Generated seed and private are sensitive dat and should be encrypted to enhance
security. The responsibility for managing this encryption can either lie with
the consumer of the library or be implemented within the library itself.

## Decision

It is proposed to implement the encryption of sensitive data within the library,
ensuring a unified approach for consumers. This decision aligns with the
existing practice in the chainweaver API, making both the new and chainweaver
APIs consistent in handling encryption. The library functions will accept a
password as the first argument for consistency. Additionally, the decision
includes exporting the `kadenaDecrypt` function for consumers requiring data
decryption.

## Consequences

The decision to encrypt sensitive data within the library has the following
implications:

- A password becomes mandatory for all operations.
- The application of encryption to keys may introduce some performance overhead,
which requires monitoring.
- Consumers who prefer to work with unencrypted data will incur the cost of
encryption and decryption or may choose not to use the library.
69 changes: 69 additions & 0 deletions packages/libs/hd-wallet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "@kadena/hd-wallet",
"version": "0.0.1",
"private": true,
"description": "HD Wallet; key derivation",
"repository": {
"type": "git",
"url": "https://github.com/kadena-community/kadena.js.git",
"directory": "packages/libs/hd-wallet"
},
"license": "MIT",
"contributors": [
"Javad Khalilian <javad@kadena.io>",
"Danillo Felixdaal <danillo@kadena.io>"
],
"exports": {
"./chainweaver": "./lib/chainweaver/index.js"
},
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"typesVersions": {
"*": {
"chainweaver": [
"./lib/chainweaver/index.d.ts"
]
}
},
"files": [
"lib"
],
"scripts": {
"build": "tsc && cpy ./src/chainweaver/vendor/**/* ./lib/chainweaver/vendor/",
"format": "pnpm run --sequential /^format:.*/",
"format:lint": "pnpm run lint:src --fix",
"format:md": "remark *.md -o --use @kadena-dev/markdown",
"format:src": "prettier . --cache --write",
"lint": "pnpm run /^lint:.*/",
"lint:fmt": "prettier . --cache --check",
"lint:pkg": "lint-package",
"lint:src": "eslint src --ext .js,.ts",
"start": "ts-node --transpile-only src/index.ts",
"test": "vitest"
},
"dependencies": {
"@kadena/cryptography-utils": "workspace:*",
"debug": "~4.3.4"
},
"devDependencies": {
"@kadena-dev/eslint-config": "workspace:*",
"@kadena-dev/lint-package": "workspace:*",
"@kadena-dev/markdown": "workspace:*",
"@kadena-dev/shared-config": "workspace:*",
"@kadena/types": "workspace:*",
"@microsoft/api-extractor": "^7.38.0",
"@rushstack/eslint-config": "~3.3.0",
"@types/debug": "~4.1.7",
"@types/node": "^18.17.14",
"@vitest/coverage-v8": "^0.34.6",
"cpy-cli": "^5.0.0",
"eslint": "^8.45.0",
"prettier": "~3.0.3",
"ts-node": "~10.8.2",
"typescript": "5.2.2",
"vitest": "^0.34.6"
},
"publishConfig": {
"provenance": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './kadenaGenKeypair';
export * from './kadenaGetPublicFromRootKey';
export * from './kadenaSignFromRootKey';
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { HARDENED_OFFSET, harden } from '../../utils';
import { kadenaGenKeypair as kadenaGenKeypairOriginal } from '../vendor/kadena-crypto';

function kadenaGenOneKeypair(
password: string,
rootKey: string | Uint8Array,
index: number,
): [Uint8Array, Uint8Array] {
if (index < HARDENED_OFFSET) {
throw new Error('Index must be hardened');
}
return kadenaGenKeypairOriginal(password, rootKey, index);
}

/**
*
* @param password
* @param rootKey
* @param index start from 0; it will be hardened automatically
*/
export function kadenaGenKeypair(
password: string,
rootKey: string | Uint8Array,
index: number,
): [Uint8Array, Uint8Array];

/**
*
* @param password
* @param rootKey
* @param range [start, end] start from 0; it will be hardened automatically
*/
export function kadenaGenKeypair(
password: string,
rootKey: string | Uint8Array,
range: [start: number, end: number],
): [Uint8Array, Uint8Array][];

export function kadenaGenKeypair(
password: string,
rootKey: string | Uint8Array,
indexOrRange: number | [start: number, end: number],
) {
if (typeof indexOrRange === 'number') {
return kadenaGenOneKeypair(password, rootKey, harden(indexOrRange));
}
const [start, end] = indexOrRange;
const keypairs = [];
for (let i = start; i <= end; i += 1) {
keypairs.push(kadenaGenOneKeypair(password, rootKey, harden(i)));
}
return keypairs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { kadenaGenKeypair } from './kadenaGenKeypair';

export function kadenaGetPublicFromRootKey(
password: string,
rootKey: string | Uint8Array,
index: number,
): Uint8Array {
const [, publicKey] = kadenaGenKeypair(password, rootKey, index);
return publicKey;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { kadenaSign } from '../vendor/kadena-crypto';
import { kadenaGenKeypair } from './kadenaGenKeypair';

/**
* Sign a message with a root key and the index of the keypair to use
* @param password
* @param message
* @param rootKey
* @param index
* @returns signature
*/
export function kadenaSignFromRootKey(
password: string,
message: string,
rootKey: string | Uint8Array,
index: number,
): Uint8Array {
const [privateKey] = kadenaGenKeypair(password, rootKey, index);
return kadenaSign(password, message, privateKey);
}
11 changes: 11 additions & 0 deletions packages/libs/hd-wallet/src/chainweaver/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export {
kadenaChangePassword,
kadenaCheckMnemonic,
kadenaGenMnemonic,
kadenaGetPublic,
kadenaMnemonicToRootKeypair,
kadenaSign,
kadenaVerify,
} from './vendor/kadena-crypto';

export * from './compatibility';
Loading

0 comments on commit 03b249a

Please sign in to comment.