From 43e3158e8eb2f942acc8af0a60679cfc9ca9701d Mon Sep 17 00:00:00 2001 From: Ahmed Hilali Date: Fri, 9 Jul 2021 17:59:23 +0800 Subject: [PATCH] `jellyfish-wallet-classic` (#454) * Added classic wallet implementation * Remove bip from readme * Address codecov --- .github/CODEOWNERS | 1 + jest.config.js | 1 + packages/jellyfish-wallet-classic/README.md | 6 ++ .../__tests__/wallet_classic.test.ts | 48 ++++++++++++++ .../jellyfish-wallet-classic/jest.config.js | 12 ++++ .../jellyfish-wallet-classic/package.json | 41 ++++++++++++ .../jellyfish-wallet-classic/src/classic.ts | 65 +++++++++++++++++++ .../jellyfish-wallet-classic/src/index.ts | 1 + .../tsconfig.build.json | 10 +++ tsconfig.build.json | 1 + tsconfig.json | 2 + website/docs/introduction.md | 1 + 12 files changed, 189 insertions(+) create mode 100644 packages/jellyfish-wallet-classic/README.md create mode 100644 packages/jellyfish-wallet-classic/__tests__/wallet_classic.test.ts create mode 100644 packages/jellyfish-wallet-classic/jest.config.js create mode 100644 packages/jellyfish-wallet-classic/package.json create mode 100644 packages/jellyfish-wallet-classic/src/classic.ts create mode 100644 packages/jellyfish-wallet-classic/src/index.ts create mode 100644 packages/jellyfish-wallet-classic/tsconfig.build.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 521cbf0eed..fd42ade9f4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,6 +28,7 @@ /packages/jellyfish-transaction-signature/ @fuxingloh @ivan-zynesis /packages/jellyfish-wallet/ @fuxingloh @ivan-zynesis @monstrobishi /packages/jellyfish-wallet-mnemonic/ @fuxingloh @ivan-zynesis +/packages/jellyfish-wallet-classic/ @fuxingloh @ivan-zynesis @monstrobishi /packages/testcontainers/ @fuxingloh /packages/testing/ @fuxingloh @canonbrother diff --git a/jest.config.js b/jest.config.js index 9a5779d0c1..b009be98b1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,6 +12,7 @@ module.exports = { '@defichain/jellyfish-transaction-builder': '/packages/jellyfish-transaction-builder/src', '@defichain/jellyfish-transaction': '/packages/jellyfish-transaction/src', '@defichain/jellyfish-wallet-mnemonic': '/packages/jellyfish-wallet-mnemonic/src', + '@defichain/jellyfish-wallet-classic': '/packages/jellyfish-wallet-classic/src', '@defichain/jellyfish-wallet': '/packages/jellyfish-wallet/src', '@defichain/testcontainers': '/packages/testcontainers/src', '@defichain/testing': '/packages/testing/src' diff --git a/packages/jellyfish-wallet-classic/README.md b/packages/jellyfish-wallet-classic/README.md new file mode 100644 index 0000000000..f654d15f54 --- /dev/null +++ b/packages/jellyfish-wallet-classic/README.md @@ -0,0 +1,6 @@ +[![npm](https://img.shields.io/npm/v/@defichain/jellyfish-wallet-classic)](https://www.npmjs.com/package/@defichain/jellyfish-wallet-classic/v/latest) +[![npm@next](https://img.shields.io/npm/v/@defichain/jellyfish-wallet-classic/next)](https://www.npmjs.com/package/@defichain/jellyfish-wallet-classic/v/next) + +# @defichain/jellyfish-wallet-classic + +WalletClassic implements a simple, single elliptic pair wallet. \ No newline at end of file diff --git a/packages/jellyfish-wallet-classic/__tests__/wallet_classic.test.ts b/packages/jellyfish-wallet-classic/__tests__/wallet_classic.test.ts new file mode 100644 index 0000000000..a8f8059e4e --- /dev/null +++ b/packages/jellyfish-wallet-classic/__tests__/wallet_classic.test.ts @@ -0,0 +1,48 @@ +import { WIF } from '@defichain/jellyfish-crypto' +import { BigNumber } from 'bignumber.js' +import { GenesisKeys } from '@defichain/testcontainers' +import { WalletClassic } from '../src' + +describe('WalletClassic', () => { + let wallet: WalletClassic + + beforeAll(() => { + wallet = new WalletClassic(WIF.asEllipticPair(GenesisKeys[GenesisKeys.length - 1].owner.privKey)) + }) + + it('should get public key', async () => { + const pubKey = await wallet.publicKey() + expect(pubKey.length).toStrictEqual(33) + expect(pubKey.toString('hex')).toStrictEqual('022b60b1d1ec292c4de571baaf9a776137fac1b69da89e9a4274880aa71b9d4890') + }) + + it('should get private key', async () => { + const privKey = await wallet.privateKey() + expect(privKey.length).toStrictEqual(32) + expect(privKey.toString('hex')).toStrictEqual('cf057c882aaa83a1461881812e9f1e9c7656e988a0847c2fbfb5b78bc7cdef5d') + }) + + it('should sign and verify', async () => { + const hash = Buffer.from('e9071e75e25b8a1e298a72f0d2e9f4f95a0f5cdf86a533cda597eb402ed13b3a', 'hex') + + const signature = await wallet.sign(hash) + expect(signature.toString('hex')).toStrictEqual('304402201832b770f7c0d8a124ab60552350c4347609dcd369c0cb39771300457a1abf4f022026da1f4bfeaf46c57ef230166ecd5b425d656c6321842cbcce98a54bec2153a9') + + const valid = await wallet.verify(hash, signature) + expect(valid).toStrictEqual(true) + }) + + it('should sign transaction and fail because invalid', async () => { + return await expect(async () => await wallet.signTx({ + version: 0, + vin: [], + vout: [], + lockTime: 0 + }, [{ + value: new BigNumber(0), + script: { stack: [] }, + tokenId: 0 + }]) + ).rejects.toThrow() + }) +}) diff --git a/packages/jellyfish-wallet-classic/jest.config.js b/packages/jellyfish-wallet-classic/jest.config.js new file mode 100644 index 0000000000..11d9802ff4 --- /dev/null +++ b/packages/jellyfish-wallet-classic/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + testEnvironment: 'node', + testMatch: [ + '**/__tests__/**/*.test.ts' + ], + transform: { + '^.+\\.ts$': 'ts-jest' + }, + verbose: true, + clearMocks: true, + testTimeout: 120000 +} diff --git a/packages/jellyfish-wallet-classic/package.json b/packages/jellyfish-wallet-classic/package.json new file mode 100644 index 0000000000..f77c47642c --- /dev/null +++ b/packages/jellyfish-wallet-classic/package.json @@ -0,0 +1,41 @@ +{ + "private": false, + "name": "@defichain/jellyfish-wallet-classic", + "version": "0.0.0", + "description": "A collection of TypeScript + JavaScript tools and libraries for DeFi Blockchain developers to build decentralized finance on Bitcoin", + "keywords": [ + "DeFiChain", + "DeFi", + "Blockchain", + "API", + "Bitcoin" + ], + "repository": "DeFiCh/jellyfish", + "bugs": "https://github.com/DeFiCh/jellyfish/issues", + "license": "MIT", + "contributors": [ + { + "name": "DeFiChain Foundation", + "email": "engineering@defichain.com", + "url": "https://defichain.com/" + }, + { + "name": "DeFi Blockchain Contributors" + }, + { + "name": "DeFi Jellyfish Contributors" + } + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b ./tsconfig.build.json" + }, + "dependencies": { + "@defichain/jellyfish-wallet": "0.0.0", + "@defichain/jellyfish-transaction": "0.0.0" + } +} diff --git a/packages/jellyfish-wallet-classic/src/classic.ts b/packages/jellyfish-wallet-classic/src/classic.ts new file mode 100644 index 0000000000..c14d25e15c --- /dev/null +++ b/packages/jellyfish-wallet-classic/src/classic.ts @@ -0,0 +1,65 @@ +import { WalletEllipticPair } from '@defichain/jellyfish-wallet' +import { EllipticPair } from '@defichain/jellyfish-crypto' +import { + Transaction, + Vout, + TransactionSegWit +} from '@defichain/jellyfish-transaction' +import { SignInputOption, TransactionSigner } from '@defichain/jellyfish-transaction-signature' + +/** + * WalletClassic extends WalletEllipticPair with a simple classic implementation. + * + * Single elliptic pair wallet. + */ +export class WalletClassic implements WalletEllipticPair { + constructor (public readonly ellipticPair: EllipticPair) { + } + + /** + * @return {Promise} compressed public key + */ + async publicKey (): Promise { + return await this.ellipticPair.publicKey() + } + + /** + * @return {Promise} privateKey + */ + async privateKey (): Promise { + return await this.ellipticPair.privateKey() + } + + /** + * @param {Buffer} hash to sign + * @return {Buffer} signature in DER format, SIGHASHTYPE not included + * @see https://tools.ietf.org/html/rfc6979 + * @see https://github.com/bitcoin/bitcoin/pull/13666 + */ + async sign (hash: Buffer): Promise { + return await this.ellipticPair.sign(hash) + } + + /** + * @param {Buffer} hash to verify with signature + * @param {Buffer} derSignature of the hash in encoded with DER, SIGHASHTYPE must not be included + * @return {boolean} validity of signature of the hash + */ + async verify (hash: Buffer, derSignature: Buffer): Promise { + return await this.ellipticPair.verify(hash, derSignature) + } + + /** + * WalletClassic transaction signing. + * + * @param {Transaction} transaction to sign + * @param {Vout[]} prevouts of the transaction to fund this transaction + * @return {TransactionSegWit} a signed transaction + */ + async signTx (transaction: Transaction, prevouts: Vout[]): Promise { + const inputs: SignInputOption[] = prevouts.map(prevout => { + return { prevout: prevout, ellipticPair: this } + }) + return TransactionSigner.sign(transaction, inputs) + } +} diff --git a/packages/jellyfish-wallet-classic/src/index.ts b/packages/jellyfish-wallet-classic/src/index.ts new file mode 100644 index 0000000000..1998fe3497 --- /dev/null +++ b/packages/jellyfish-wallet-classic/src/index.ts @@ -0,0 +1 @@ +export * from './classic' diff --git a/packages/jellyfish-wallet-classic/tsconfig.build.json b/packages/jellyfish-wallet-classic/tsconfig.build.json new file mode 100644 index 0000000000..e04e74398d --- /dev/null +++ b/packages/jellyfish-wallet-classic/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "./src/**/*" + ], + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 63d7bb7c5c..9851ff7583 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -19,6 +19,7 @@ {"path": "./packages/jellyfish-transaction-signature/tsconfig.build.json"}, {"path": "./packages/jellyfish-wallet/tsconfig.build.json"}, {"path": "./packages/jellyfish-wallet-mnemonic/tsconfig.build.json"}, + {"path": "./packages/jellyfish-wallet-classic/tsconfig.build.json"}, {"path": "./packages/testcontainers/tsconfig.build.json"}, {"path": "./packages/testing/tsconfig.build.json"} ] diff --git a/tsconfig.json b/tsconfig.json index d5e7562dc0..8262fdc9a5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "@defichain/jellyfish-transaction-signature": ["jellyfish-transaction-signature/src"], "@defichain/jellyfish-wallet": ["jellyfish-wallet/src"], "@defichain/jellyfish-wallet-mnemonic": ["jellyfish-wallet-mnemonic/src"], + "@defichain/jellyfish-wallet-classic": ["jellyfish-wallet-classic/src"], "@defichain/testcontainers": ["testcontainers/src"], "@defichain/testing": ["testing/src"], @@ -28,6 +29,7 @@ "@defichain/jellyfish-transaction-signature/*": ["jellyfish-transaction-signature/src/*"], "@defichain/jellyfish-wallet/*": ["jellyfish-wallet/src/*"], "@defichain/jellyfish-wallet-mnemonic/*": ["jellyfish-wallet-mnemonic/src/*"], + "@defichain/jellyfish-wallet-classic/*": ["jellyfish-wallet-classic/src/*"], "@defichain/testcontainers/*": ["testcontainers/src/*"], "@defichain/testing/*": ["testing/src/*"], } diff --git a/website/docs/introduction.md b/website/docs/introduction.md index 8712dc4165..5b0fcc45fc 100644 --- a/website/docs/introduction.md +++ b/website/docs/introduction.md @@ -39,5 +39,6 @@ Package | Description `@defichain/jellyfish-transaction-signature` | Stateless utility library to perform transaction signing. `@defichain/jellyfish-wallet` | Jellyfish wallet is a managed wallet, where account can get discovered from an HD seed. `@defichain/jellyfish-wallet-mnemonic` | MnemonicHdNode implements the WalletHdNode from jellyfish-wallet; a CoinType-agnostic HD Wallet for noncustodial DeFi. +`@defichain/jellyfish-wallet-classic` | WalletClassic implements a simple, single elliptic pair wallet. `@defichain/testcontainers` | Provides a lightweight, throw away instances for DeFiD node provisioned automatically in a Docker container. `@defichain/testing` | Provides rich test fixture setup functions for effective and effortless testing.