Skip to content
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

feat(hd-wallet): add package #1151

Merged
merged 22 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this file?

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
36 changes: 36 additions & 0 deletions packages/libs/hd-wallet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!-- genericHeader start -->

# @kadena/hd-wallet

The library for managing seed generation and key derivation for hd wallets.
Chainweaver protocol: supports legacy chainweaver bip39/bip32 with a custom
derivation path

## BIP39

Seed generation based on bip39 and 12 words

## Chainweaver protocol:

it uses the bundle file from
[kadena-crypto](https://github.com/kadena-io/cardano-crypto.js/tree/jam%40chainweaver-keygen)
repo

## BIP44:

derivation path: `m/44'/626'/${index}'/0'/0'`; all keys hardened

## Public Key

public key generation algorithm: `ed25519`

<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](./docs/decisions/) documents for more information
53 changes: 53 additions & 0 deletions packages/libs/hd-wallet/docs/decisions/0001-use-bip44.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Use BIP44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe somewhere in this decision record, you could also mention something about not supporting extended-public-keys


Date: 2023-10-31

Status: accepted

## Context

In the context of HD (Hierarchical Deterministic) wallets, keys are generated
using the BIP32 algorithm. BIP32 allows for various approaches in defining
derivation paths, as it accepts a derivation path to create branched key trees.

## Decision

We have opted to utilize the [BIP44][1] protocol for our HD wallet
implementation. This decision is based on the following considerations:

- KDA (Kadena) uses coin-type [626][2].
- KDA is an account-model coin, and for each key, we change the account (third
item in BIP44).
- All keys are hardened due to the [ed25519][3] public key generation algorithm,
which is enforced by Kadena, necessitating hardened key restrictions.

As a result, the derivation path for our HD wallet will be as follows:

```
m/44'/626'/${index}'/0'/0'
```

## Consequences

- We use a standard approach for key derivation which would be more compatible
with other blockchians
- finding library for implementation would be easier

- We have to managed legacy algorithm like chainweaver for backward
compatibility

- Some wallets already use `m/44'/626'/0'/0'/${index}'` instead of the adopted
approach. So we need to support this path as well

- Since Kadena using ed25519 (only hardened keys) we can not use extended keys
mentioned in the BIP44 protocol

## Resources

- [BIP44 proposal][1]
- [SLIP-0044 : Registered coin types for BIP-0044][2]
- [SLIP-0010 : Universal private key derivation from master private key][3]

[1]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
[2]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
[3]: 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
66 changes: 66 additions & 0 deletions packages/libs/hd-wallet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@kadena/hd-wallet",
"version": "0.0.1",
"private": true,
"description": "HD Wallet; key derivation",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"description": "HD Wallet; key derivation",
"description": "A common approach to work with mnemonic keys and key derivation for Kadena based on BIP39 BIP44",

"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>"],
"exports": {
"./chainweaver": "./lib/chainweaver/index.js"
},
"types": "./lib/index.d.ts",
"main": "./lib/index.js",
"typesVersions": {
"*": {
"chainweaver": [
"./lib/chainweaver/index.d.ts"
]
}
},
"files": [
"lib"
],
"scripts": {
"build": "tsc && cpy ./src/chainweaver/vendor/**/* ./lib/chainweaver/vendor/",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add api-extractor to this package?

"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
}
}
10 changes: 10 additions & 0 deletions packages/libs/hd-wallet/src/chainweaver/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export {
kadenaChangePassword,
kadenaCheckMnemonic,
kadenaGenKeypair,
kadenaGenMnemonic,
kadenaGetPublic,
kadenaMnemonicToRootKeypair,
kadenaSign,
kadenaVerify,
} from './vendor/kadena-crypto';
76 changes: 76 additions & 0 deletions packages/libs/hd-wallet/src/chainweaver/tests/chainweaver.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect, it } from 'vitest';
import {
kadenaGenKeypair,
kadenaGenMnemonic,
kadenaMnemonicToRootKeypair,
} from '..';
import { harden } from '../../utils';

const toHexStr = (bytes: Uint8Array) => Buffer.from(bytes).toString('hex');

describe('kadenaGenMnemonic', () => {
it('should generate a mnemonic', () => {
const mnemonic = kadenaGenMnemonic();
const wordsList = mnemonic.split(' ');
expect(wordsList).toHaveLength(12);
expect(wordsList.every((word) => word.length > 2)).toBe(true);
});
});

describe('kadenaMnemonicToRootKeypair', () => {
it('should generate a Root key from mnemonic', () => {
const mnemonic =
'bubble fade wasp analyst then panel desert hold spatial sound lucky weekend';
const password = 'password';
const rootKey = kadenaMnemonicToRootKeypair(password, mnemonic);
expect(rootKey).toHaveLength(128);
});

it('should generate a the same root key from the same mnemonic', () => {
const mnemonic =
'bubble fade wasp analyst then panel desert hold spatial sound lucky weekend';
const password = 'password';
const rootKey1 = kadenaMnemonicToRootKeypair(password, mnemonic);
const rootKey2 = kadenaMnemonicToRootKeypair(password, mnemonic);

expect(toHexStr(rootKey1)).toBe(toHexStr(rootKey2));
});

it('should generate different root key for different mnemonic', () => {
const mnemonicFirst =
'bubble fade wasp analyst then panel desert hold spatial sound lucky weekend';

const mnemonicSecond =
'tuna nerve predict all catch early oblige inform hamster magnet century goddess';
const password = 'password';
const rootKey1 = kadenaMnemonicToRootKeypair(password, mnemonicFirst);
const rootKey2 = kadenaMnemonicToRootKeypair(password, mnemonicSecond);

expect(toHexStr(rootKey1)).not.toBe(toHexStr(rootKey2));
});

it('should generate different root key for different passwords same mnemonic', () => {
const mnemonic =
'bubble fade wasp analyst then panel desert hold spatial sound lucky weekend';
const rootKey1 = kadenaMnemonicToRootKeypair('pass-one', mnemonic);
const rootKey2 = kadenaMnemonicToRootKeypair('pass-two', mnemonic);

expect(toHexStr(rootKey1)).not.toBe(toHexStr(rootKey2));
});
});

describe('kadenaGenKeypair', () => {
it('should generate keypair frpm the rootKey', () => {
const mnemonic: string =
'bubble fade wasp analyst then panel desert hold spatial sound lucky weekend';
const password = 'password';
const rootKey = kadenaMnemonicToRootKeypair(password, mnemonic);
const [encryptedSecret, publicKey] = kadenaGenKeypair(
password,
rootKey,
harden(1),
);
expect(encryptedSecret.byteLength).toEqual(128);
expect(publicKey.byteLength).toBe(32);
});
});
36 changes: 36 additions & 0 deletions packages/libs/hd-wallet/src/chainweaver/vendor/kadena-crypto.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export declare const kadenaChangePassword: (
privateKey: string | Uint8Array,
oldPassword: string,
newPassword: string,
) => Uint8Array;

export declare const kadenaCheckMnemonic: (mnemonic: string) => boolean;

export declare const kadenaGenKeypair: (
password: string,
rootKey: string | Uint8Array,
index: number,
) => [Uint8Array, Uint8Array];

export declare const kadenaGenMnemonic: () => string;

export declare const kadenaGetPublic: (secretKey: Uint8Array) => Uint8Array;
Copy link
Member

@alber70g alber70g Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should probably accept the rootKey: string | Uint8Array, password: string, ...index: number[]


export declare const kadenaMnemonicToRootKeypair: (
password: string,
mnemonic: string,
) => Uint8Array;

export declare const kadenaSign: (
password: string,
message: string,
privateKey: string,
) => Uint8Array;
Copy link
Member

@alber70g alber70g Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change this to use the rootKey and
do we want to be signing multiple hashes at once to improve performance?

Suggested change
export declare const kadenaSign: (
password: string,
message: string,
privateKey: string,
) => Uint8Array;
export declare const kadenaSign: (
password: string,
rootKey: string | Uint8Array,
index: number,
...message: string[]
) => Uint8Array;


export declare const kadenaVerify: (
message: string,
publicKey: string,
signature: string,
) => boolean;

// Path: ./kadena-crypto.js
Loading
Loading