From 3492a5437fd1049d5b21d5890db5a4176d5ecfeb Mon Sep 17 00:00:00 2001 From: baryon2 Date: Mon, 19 Feb 2024 23:59:01 +0530 Subject: [PATCH 1/6] Update keychain 1. Allow Ethwallets to be created for all cointypes 2. Allow evm transaction signing. --- src/key/eth-wallet.ts | 21 ++++++++++++++++++++- src/key/wallet-utils.ts | 9 +++++++-- src/keychain/keychain.ts | 5 +++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/key/eth-wallet.ts b/src/key/eth-wallet.ts index 4140289..1bebb8c 100644 --- a/src/key/eth-wallet.ts +++ b/src/key/eth-wallet.ts @@ -11,6 +11,7 @@ import { encodeSecp256k1Signature } from '../utils/encode-signature'; import { HDNode } from '@ethersproject/hdnode'; import { bip39Token, getBip39 } from '../crypto/bip39/bip39-token'; import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; +import { TransactionRequest, Provider } from '@ethersproject/abstract-provider'; import Container from 'typedi'; export class EthWallet { @@ -19,6 +20,7 @@ export class EthWallet { private pvtKey: string, private walletType: 'mnemonic' | 'pvtKey', private options: WalletOptions, + private provider?: Provider, ) {} /** @@ -32,6 +34,10 @@ export class EthWallet { return new EthWallet(mnemonic, '', 'mnemonic', options); } + setProvider(provider: Provider) { + this.provider = provider; + } + /** * Generates a wallet from a private key. * @param {string} pvtKey - The private key to generate the wallet from. @@ -67,7 +73,7 @@ export class EthWallet { const ethAddr = EthereumUtilsAddress.fromString(hdWallet.address).toBuffer(); const bech32Address = bech32.encode(this.options.addressPrefix, bech32.toWords(ethAddr)); - const ethWallet = new Wallet(hdWallet.privateKey); + const ethWallet = new Wallet(hdWallet.privateKey, this.provider); return { algo: 'ethsecp256k1', address: bech32Address, @@ -87,6 +93,17 @@ export class EthWallet { return ethWallet._signingKey().signDigest(signBytes); } + public async sendTransaction(transaction: TransactionRequest) { + const accounts = this.getAccountsWithPrivKey(); + const account = accounts[0]; + if (!account) throw new Error('Account not found'); + // if (account === undefined) { + // throw new Error(`Address ${signerAddress} not found in wallet`); + // } + const { ethWallet } = account; + return await ethWallet.sendTransaction(transaction); + } + signMessage(signerAddress: string, message: Uint8Array) { const accounts = this.getAccountsWithPrivKey(); const account = accounts.find(({ address }) => address === signerAddress); @@ -104,6 +121,7 @@ export class EthWallet { throw new Error(`Address ${signerAddress} not found in wallet`); } const { ethWallet } = account; + return ethWallet.signTransaction(transaction); } @@ -117,6 +135,7 @@ export class EthWallet { const rawSignature = this.sign(signerAddress, keccak256(Buffer.from(hash))); const splitSignature = bytes.splitSignature(rawSignature); const signature = bytes.arrayify(bytes.concat([splitSignature.r, splitSignature.s])); + return { signed: signDoc, signature: encodeSecp256k1Signature(account.pubkey, signature), diff --git a/src/key/wallet-utils.ts b/src/key/wallet-utils.ts index 5262964..6bd8375 100644 --- a/src/key/wallet-utils.ts +++ b/src/key/wallet-utils.ts @@ -5,12 +5,17 @@ import { Wallet } from './wallet'; import * as base64js from 'base64-js'; import { bip39Token } from '../crypto/bip39/bip39-token'; -export function generateWalletFromMnemonic(mnemonic: string, hdPath: string, addressPrefix: string) { +export function generateWalletFromMnemonic( + mnemonic: string, + hdPath: string, + addressPrefix: string, + ethWallet?: boolean, +) { const bip39 = Container.get(bip39Token); bip39.mnemonicToEntropy(mnemonic); const hdPathParams = hdPath.split('/'); const coinType = hdPathParams[2]; - if (coinType?.replace("'", '') === '60') { + if (coinType?.replace("'", '') === '60' || ethWallet) { return EthWallet.generateWalletFromMnemonic(mnemonic, { paths: [hdPath], addressPrefix }); } return Wallet.generateWallet(mnemonic, { paths: [hdPath], addressPrefix }); diff --git a/src/keychain/keychain.ts b/src/keychain/keychain.ts index c9e72f0..fbb17ef 100644 --- a/src/keychain/keychain.ts +++ b/src/keychain/keychain.ts @@ -208,6 +208,7 @@ export class KeyChain { password: string, addressPrefix: string, coinType: string, + ethWallet?: boolean, ) { const storage = Container.get(storageToken); const keychain = (await storage.get(KEYCHAIN)) as unknown as Keystore; @@ -222,14 +223,14 @@ export class KeyChain { throw new Error('Wallet type not supported'); } if (walletData.walletType === WALLETTYPE.PRIVATE_KEY) { - if (coinType === '60') { + if (coinType === '60' || ethWallet) { const hdPath = getHDPath(coinType, walletData.addressIndex.toString()); return EthWallet.generateWalletFromPvtKey(secret, { paths: [hdPath], addressPrefix }); } return PvtKeyWallet.generateWallet(secret, addressPrefix); } else { const hdPath = getHDPath(coinType, walletData.addressIndex.toString()); - return generateWalletFromMnemonic(secret, hdPath, addressPrefix); + return generateWalletFromMnemonic(secret, hdPath, addressPrefix, ethWallet); } } From b10dda5e6a55fffec590eb8ee8daeef9f85cda83 Mon Sep 17 00:00:00 2001 From: baryon2 Date: Tue, 20 Feb 2024 00:00:52 +0530 Subject: [PATCH 2/6] Update package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 10f2cc4..8172366 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@leapwallet/leap-keychain", - "version": "0.2.2", + "version": "0.2.3", "description": "A javascript library for crypto key management", "scripts": { "test": "jest", From f058293dd4039a2d40af56fb806391bbe407aae9 Mon Sep 17 00:00:00 2001 From: baryon2 Date: Tue, 20 Feb 2024 00:33:47 +0530 Subject: [PATCH 3/6] Add tests for generating eth wallets --- tests/wallet.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/wallet.test.ts b/tests/wallet.test.ts index 3fb70c8..8b41091 100644 --- a/tests/wallet.test.ts +++ b/tests/wallet.test.ts @@ -76,12 +76,23 @@ describe('generateMnemonic', () => { const accounts = wallet.getAccounts(); expect(accounts[0]?.address).toBe(referenceWallets.ref2.addresses.cosmos); }); + test('generateWalletFromMnemonic for cointype=60', () => { + const wallet = generateWalletFromMnemonic(mnemonic, "m/44'/60'/0'/0/1", 'evmos'); + const accounts = wallet.getAccounts(); + expect(accounts[0]?.address).toBe(referenceWallets.ref2.addresses.evmos); + }); test('generateWalletsFromMnemonic', async () => { const wallet = generateWalletsFromMnemonic(mnemonic, ["m/44'/118'/0'/0/0", "m/44'/118'/0'/0/1"], 'cosmos'); const accounts = wallet.getAccounts(); expect(accounts[0]?.address).toBe(referenceWallets.ref1.addresses.cosmos); expect(accounts[1]?.address).toBe(referenceWallets.ref2.addresses.cosmos); }); + test('generateWalletsFromMnemonic for cointype=60', async () => { + const wallet = generateWalletsFromMnemonic(mnemonic, ["m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1"], 'evmos'); + const accounts = wallet.getAccounts(); + expect(accounts[0]?.address).toBe(referenceWallets.ref1.addresses.evmos); + expect(accounts[1]?.address).toBe(referenceWallets.ref2.addresses.evmos); + }); test('generateWalletFromMnemonic throws error if mnemonic is invalid', () => { expect(() => generateWalletFromMnemonic('', "m/44'/118'/0'/0/0", 'cosmos')).toThrow('Invalid mnemonic'); }); From 3280a18decbdaead431dfff7aac570cb95c49c18 Mon Sep 17 00:00:00 2001 From: baryon2 Date: Tue, 20 Feb 2024 00:39:00 +0530 Subject: [PATCH 4/6] Add a workflow to publish beta version of this package --- .github/workflows/publish-beta.workflow.yml | 101 ++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/publish-beta.workflow.yml diff --git a/.github/workflows/publish-beta.workflow.yml b/.github/workflows/publish-beta.workflow.yml new file mode 100644 index 0000000..48a035f --- /dev/null +++ b/.github/workflows/publish-beta.workflow.yml @@ -0,0 +1,101 @@ +on: + push: + branches: [staging] + pull_request: +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 16 + - run: yarn + - run: yarn lint:check + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 16 + - run: yarn + - run: yarn test:coverage + + - name: Comment Test Coverage + uses: raulanatol/jest-coverage-comment-action@main + with: + github-token: ${{ secrets.NODE_AUTH_TOKEN }} + use-existing-reports: true + host-docs: + if: github.ref == 'refs/heads/main' + needs: [lint, test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 16 + - run: yarn + - run: yarn doc:build + - uses: actions/upload-artifact@v2 + with: + name: docs + path: docs/ + - uses: crazy-max/ghaction-github-pages@v2 + with: + build_dir: docs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + publish-npm-registry: + if: github.ref == 'refs/heads/main' + needs: [lint, test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + registry-url: 'https://registry.npmjs.org' + - run: yarn + - run: yarn build + - run: yarn publish --tag beta + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + publish-github-registry: + if: github.ref == 'refs/heads/main' + needs: [lint, test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + registry-url: 'https://npm.pkg.github.com' + scope: '@leapwallet' + - run: yarn + - run: yarn build + - run: yarn publish --tag beta --non-interactive + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github-release: + if: github.ref == 'refs/heads/main' + needs: host-docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v2 + with: + name: docs + path: docs/ + - run: zip -r docs.zip docs/ + - name: Set $VERSION + run: echo "VERSION=$(yarn env | grep npm_package_version | grep -Eo '[0-9.]*')" >> $GITHUB_ENV + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create v$VERSION docs.zip \ + -t v$VERSION \ + -n "[Changelog](https://github.com/leapwallet/leap-keychain/blob/v$VERSION/CHANGELOG.md)" \ + $([[ $VERSION = 0* ]] && echo '-p') From 6a665542bc063964f21f0bb3f23eb0b46d464158 Mon Sep 17 00:00:00 2001 From: baryon2 Date: Tue, 20 Feb 2024 00:43:02 +0530 Subject: [PATCH 5/6] update publish on beta workflow --- .github/workflows/publish-beta.workflow.yml | 45 +-------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/.github/workflows/publish-beta.workflow.yml b/.github/workflows/publish-beta.workflow.yml index 48a035f..6745aca 100644 --- a/.github/workflows/publish-beta.workflow.yml +++ b/.github/workflows/publish-beta.workflow.yml @@ -27,28 +27,8 @@ jobs: with: github-token: ${{ secrets.NODE_AUTH_TOKEN }} use-existing-reports: true - host-docs: - if: github.ref == 'refs/heads/main' - needs: [lint, test] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16 - - run: yarn - - run: yarn doc:build - - uses: actions/upload-artifact@v2 - with: - name: docs - path: docs/ - - uses: crazy-max/ghaction-github-pages@v2 - with: - build_dir: docs - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish-npm-registry: - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/staging' needs: [lint, test] runs-on: ubuntu-latest steps: @@ -63,7 +43,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} publish-github-registry: - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/staging' needs: [lint, test] runs-on: ubuntu-latest steps: @@ -78,24 +58,3 @@ jobs: - run: yarn publish --tag beta --non-interactive env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - github-release: - if: github.ref == 'refs/heads/main' - needs: host-docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/download-artifact@v2 - with: - name: docs - path: docs/ - - run: zip -r docs.zip docs/ - - name: Set $VERSION - run: echo "VERSION=$(yarn env | grep npm_package_version | grep -Eo '[0-9.]*')" >> $GITHUB_ENV - - name: Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create v$VERSION docs.zip \ - -t v$VERSION \ - -n "[Changelog](https://github.com/leapwallet/leap-keychain/blob/v$VERSION/CHANGELOG.md)" \ - $([[ $VERSION = 0* ]] && echo '-p') From fe2b6e227b46a6c6604f9e587542ae7c625ccb19 Mon Sep 17 00:00:00 2001 From: baryon2 Date: Tue, 20 Feb 2024 00:45:18 +0530 Subject: [PATCH 6/6] Remove redundant lint and test jobs from publish beta workflow --- .github/workflows/publish-beta.workflow.yml | 24 --------------------- 1 file changed, 24 deletions(-) diff --git a/.github/workflows/publish-beta.workflow.yml b/.github/workflows/publish-beta.workflow.yml index 6745aca..9e1b109 100644 --- a/.github/workflows/publish-beta.workflow.yml +++ b/.github/workflows/publish-beta.workflow.yml @@ -3,30 +3,6 @@ on: branches: [staging] pull_request: jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16 - - run: yarn - - run: yarn lint:check - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16 - - run: yarn - - run: yarn test:coverage - - - name: Comment Test Coverage - uses: raulanatol/jest-coverage-comment-action@main - with: - github-token: ${{ secrets.NODE_AUTH_TOKEN }} - use-existing-reports: true publish-npm-registry: if: github.ref == 'refs/heads/staging' needs: [lint, test]