@metamask/eth-simple-keyring 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [5.0.0]
10
+ ### Changed
11
+ - **BREAKING:** Makes version-specific `signTypedData` methods private ([#84](https://github.com/MetaMask/eth-simple-keyring/pull/84))
12
+ - Consumers should use the generic `signTypedData` method and pass the version they'd like as a property in the options argument.
13
+ - **BREAKING:** Makes the `wallets` property private ([#87](https://github.com/MetaMask/eth-simple-keyring/pull/87))
14
+ - Consumers should not use this property as it is intended for internal use only.
15
+ - **BREAKING:** Makes `getPrivateKeyFor` a private method ([#83](https://github.com/MetaMask/eth-simple-keyring/pull/83))
16
+ - Consumers who wish to get the private key for a given account should use the `exportAccount` method.
17
+ - **BREAKING:** Set the minimum Node.js version to 12 ([#68](https://github.com/MetaMask/eth-simple-keyring/pull/68))
18
+ - Always return rejected Promise upon failure ([#85](https://github.com/MetaMask/eth-simple-keyring/pull/85))
19
+
20
+ ### Removed
21
+ - **BREAKING:** Remove redundant `newGethSignMessage` method ([#72](https://github.com/MetaMask/eth-simple-keyring/pull/72))
22
+ - Consumers can use `signPersonalMessage` method as a replacement for `newGethSignMessage`.
23
+
24
+ [Unreleased]: https://github.com/MetaMask/eth-simple-keyring/compare/v5.0.0...HEAD
25
+ [5.0.0]: https://github.com/MetaMask/eth-simple-keyring/releases/tag/v5.0.0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2020 MetaMask
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # Simple Keyring
2
+
3
+ A simple JS class wrapped around [ethereumjs-wallet](https://github.com/ethereumjs/ethereumjs-wallet) designed to expose an interface common to many different signing strategies to be used in a `KeyringController`; such as the one used in [MetaMask](https://metamask.io/)
4
+
5
+ ## The Keyring Class Protocol
6
+
7
+ One of the goals of this class is to allow developers to easily add new signing strategies to MetaMask. We call these signing strategies Keyrings, because they can manage multiple keys.
8
+
9
+ ### Keyring.type
10
+
11
+ A class property that returns a unique string describing the Keyring.
12
+ This is the only class property or method, the remaining methods are instance methods.
13
+
14
+ ### constructor( options )
15
+
16
+ As a Javascript class, your Keyring object will be used to instantiate new Keyring instances using the new keyword. For example:
17
+
18
+ ```
19
+ const keyring = new YourKeyringClass(options);
20
+ ```
21
+
22
+ The constructor currently receives an options object that will be defined by your keyring-building UI, once the user has gone through the steps required for you to fully instantiate a new keyring. For example, choosing a pattern for a vanity account, or entering a seed phrase.
23
+
24
+ We haven't defined the protocol for this account-generating UI yet, so for now please ensure your Keyring behaves nicely when not passed any options object.
25
+
26
+ ## Keyring Instance Methods
27
+
28
+ All below instance methods must return Promises to allow asynchronous resolution.
29
+
30
+ ### serialize()
31
+
32
+ In this method, you must return any JSON-serializable JavaScript object that you like. It will be encoded to a string, encrypted with the user's password, and stored to disk. This is the same object you will receive in the deserialize() method, so it should capture all the information you need to restore the Keyring's state.
33
+
34
+ ### deserialize( object )
35
+
36
+ As discussed above, the deserialize() method will be passed the JavaScript object that you returned when the serialize() method was called.
37
+
38
+ ### addAccounts( n = 1 )
39
+
40
+ The addAccounts(n) method is used to inform your keyring that the user wishes to create a new account. You should perform whatever internal steps are needed so that a call to serialize() will persist the new account, and then return an array of the new account addresses.
41
+
42
+ The method may be called with or without an argument, specifying the number of accounts to create. You should generally default to 1 per call.
43
+
44
+ ### getAccounts()
45
+
46
+ When this method is called, you must return an array of hex-string addresses for the accounts that your Keyring is able to sign for.
47
+
48
+ ### signTransaction(address, transaction)
49
+
50
+ This method will receive a hex-prefixed, all-lowercase address string for the account you should sign the incoming transaction with.
51
+
52
+ For your convenience, the transaction is an instance of ethereumjs-tx, (https://github.com/ethereumjs/ethereumjs-tx) so signing can be as simple as:
53
+
54
+ ```
55
+ transaction.sign(privateKey)
56
+ ```
57
+
58
+ You must return a valid signed ethereumjs-tx (https://github.com/ethereumjs/ethereumjs-tx) object when complete, it can be the same transaction you received.
59
+
60
+ ### signMessage(address, data)
61
+
62
+ The `eth_sign` method will receive the incoming data, already hashed, and must sign that hash, and then return the raw signed hash.
63
+
64
+ ### getEncryptionPublicKey(address)
65
+
66
+ This provides the public key for encryption function.
67
+
68
+ ### decryptMessage(address, data)
69
+
70
+ The `eth_decryptMessage` method will receive the incoming data in array format that returns `encrypt` function in `eth-sig-util` and must decrypt message, and then return the raw message.
71
+
72
+ ### exportAccount(address)
73
+
74
+ Exports the specified account as a private key hex string.
75
+
76
+ ### removeAccount(address)
77
+
78
+ removes the specified account from the list of accounts.
79
+
80
+ ## Contributing
81
+
82
+ ### Setup
83
+
84
+ - Install [Node.js](https://nodejs.org) version 12
85
+ - If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
86
+ - Install [Yarn v3](https://yarnpkg.com/getting-started/install)
87
+ - Run `yarn install` to install dependencies and run any required post-install scripts
88
+
89
+ ### Testing and Linting
90
+
91
+ Run `yarn test` to run the tests once. To run tests on file changes, run `yarn test:watch`.
92
+
93
+ Run `yarn lint` to run the linter, or run `yarn lint:fix` to run the linter and fix any automatically fixable issues.
94
+
95
+ ### Release & Publishing
96
+
97
+ The project follows the same release process as the other libraries in the MetaMask organization. The GitHub Actions [`action-create-release-pr`](https://github.com/MetaMask/action-create-release-pr) and [`action-publish-release`](https://github.com/MetaMask/action-publish-release) are used to automate the release process; see those repositories for more information about how they work.
98
+
99
+ 1. Choose a release version.
100
+
101
+ - The release version should be chosen according to SemVer. Analyze the changes to see whether they include any breaking changes, new features, or deprecations, then choose the appropriate SemVer version. See [the SemVer specification](https://semver.org/) for more information.
102
+
103
+ 2. If this release is backporting changes onto a previous release, then ensure there is a major version branch for that version (e.g. `1.x` for a `v1` backport release).
104
+
105
+ - The major version branch should be set to the most recent release with that major version. For example, when backporting a `v1.0.2` release, you'd want to ensure there was a `1.x` branch that was set to the `v1.0.1` tag.
106
+
107
+ 3. Trigger the [`workflow_dispatch`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch) event [manually](https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow) for the `Create Release Pull Request` action to create the release PR.
108
+
109
+ - For a backport release, the base branch should be the major version branch that you ensured existed in step 2. For a normal release, the base branch should be the main branch for that repository (which should be the default value).
110
+ - This should trigger the [`action-create-release-pr`](https://github.com/MetaMask/action-create-release-pr) workflow to create the release PR.
111
+
112
+ 4. Update the changelog to move each change entry into the appropriate change category ([See here](https://keepachangelog.com/en/1.0.0/#types) for the full list of change categories, and the correct ordering), and edit them to be more easily understood by users of the package.
113
+
114
+ - Generally any changes that don't affect consumers of the package (e.g. lockfile changes or development environment changes) are omitted. Exceptions may be made for changes that might be of interest despite not having an effect upon the published package (e.g. major test improvements, security improvements, improved documentation, etc.).
115
+ - Try to explain each change in terms that users of the package would understand (e.g. avoid referencing internal variables/concepts).
116
+ - Consolidate related changes into one change entry if it makes it easier to explain.
117
+ - Run `yarn auto-changelog validate --rc` to check that the changelog is correctly formatted.
118
+
119
+ 5. Review and QA the release.
120
+
121
+ - If changes are made to the base branch, the release branch will need to be updated with these changes and review/QA will need to restart again. As such, it's probably best to avoid merging other PRs into the base branch while review is underway.
122
+
123
+ 6. Squash & Merge the release.
124
+
125
+ - This should trigger the [`action-publish-release`](https://github.com/MetaMask/action-publish-release) workflow to tag the final release commit and publish the release on GitHub.
126
+
127
+ 7. Publish the release on npm.
128
+
129
+ - Be very careful to use a clean local environment to publish the release, and follow exactly the same steps used during CI.
130
+ - Use `npm publish --dry-run` to examine the release contents to ensure the correct files are included. Compare to previous releases if necessary (e.g. using `https://unpkg.com/browse/[package name]@[package version]/`).
131
+ - Once you are confident the release contents are correct, publish the release using `npm publish`.
package/index.js ADDED
@@ -0,0 +1,206 @@
1
+ const { EventEmitter } = require('events');
2
+ const {
3
+ isValidPrivate,
4
+ stripHexPrefix,
5
+ privateToPublic,
6
+ bufferToHex,
7
+ publicToAddress,
8
+ ecsign,
9
+ arrToBufArr,
10
+ } = require('@ethereumjs/util');
11
+ const randomBytes = require('randombytes');
12
+ const { keccak256 } = require('ethereum-cryptography/keccak');
13
+
14
+ const type = 'Simple Key Pair';
15
+ const {
16
+ concatSig,
17
+ decrypt,
18
+ getEncryptionPublicKey,
19
+ normalize,
20
+ personalSign,
21
+ signTypedData,
22
+ SignTypedDataVersion,
23
+ } = require('@metamask/eth-sig-util');
24
+
25
+ function generateKey() {
26
+ const privateKey = randomBytes(32);
27
+ // I don't think this is possible, but this validation was here previously,
28
+ // so it has been preserved just in case.
29
+ // istanbul ignore next
30
+ if (!isValidPrivate(privateKey)) {
31
+ throw new Error(
32
+ 'Private key does not satisfy the curve requirements (ie. it is invalid)',
33
+ );
34
+ }
35
+ return privateKey;
36
+ }
37
+
38
+ class SimpleKeyring extends EventEmitter {
39
+ constructor(opts) {
40
+ super();
41
+ this.type = type;
42
+ this._wallets = [];
43
+ this.deserialize(opts);
44
+ }
45
+
46
+ async serialize() {
47
+ return this._wallets.map(({ privateKey }) => privateKey.toString('hex'));
48
+ }
49
+
50
+ async deserialize(privateKeys = []) {
51
+ this._wallets = privateKeys.map((hexPrivateKey) => {
52
+ const strippedHexPrivateKey = stripHexPrefix(hexPrivateKey);
53
+ const privateKey = Buffer.from(strippedHexPrivateKey, 'hex');
54
+ const publicKey = privateToPublic(privateKey);
55
+ return { privateKey, publicKey };
56
+ });
57
+ }
58
+
59
+ async addAccounts(n = 1) {
60
+ const newWallets = [];
61
+ for (let i = 0; i < n; i++) {
62
+ const privateKey = generateKey();
63
+ const publicKey = privateToPublic(privateKey);
64
+ newWallets.push({ privateKey, publicKey });
65
+ }
66
+ this._wallets = this._wallets.concat(newWallets);
67
+ const hexWallets = newWallets.map(({ publicKey }) =>
68
+ bufferToHex(publicToAddress(publicKey)),
69
+ );
70
+ return hexWallets;
71
+ }
72
+
73
+ async getAccounts() {
74
+ return this._wallets.map(({ publicKey }) =>
75
+ bufferToHex(publicToAddress(publicKey)),
76
+ );
77
+ }
78
+
79
+ // tx is an instance of the ethereumjs-transaction class.
80
+ async signTransaction(address, tx, opts = {}) {
81
+ const privKey = this._getPrivateKeyFor(address, opts);
82
+ const signedTx = tx.sign(privKey);
83
+ // Newer versions of Ethereumjs-tx are immutable and return a new tx object
84
+ return signedTx === undefined ? tx : signedTx;
85
+ }
86
+
87
+ // For eth_sign, we need to sign arbitrary data:
88
+ async signMessage(address, data, opts = {}) {
89
+ const message = stripHexPrefix(data);
90
+ const privKey = this._getPrivateKeyFor(address, opts);
91
+ const msgSig = ecsign(Buffer.from(message, 'hex'), privKey);
92
+ const rawMsgSig = concatSig(msgSig.v, msgSig.r, msgSig.s);
93
+ return rawMsgSig;
94
+ }
95
+
96
+ // For personal_sign, we need to prefix the message:
97
+ async signPersonalMessage(address, msgHex, opts = {}) {
98
+ const privKey = this._getPrivateKeyFor(address, opts);
99
+ const privateKey = Buffer.from(privKey, 'hex');
100
+ const sig = personalSign({ privateKey, data: msgHex });
101
+ return sig;
102
+ }
103
+
104
+ // For eth_decryptMessage:
105
+ async decryptMessage(withAccount, encryptedData) {
106
+ const wallet = this._getWalletForAccount(withAccount);
107
+ const { privateKey } = wallet;
108
+ const sig = decrypt({ privateKey, encryptedData });
109
+ return sig;
110
+ }
111
+
112
+ // personal_signTypedData, signs data along with the schema
113
+ async signTypedData(
114
+ withAccount,
115
+ typedData,
116
+ opts = { version: SignTypedDataVersion.V1 },
117
+ ) {
118
+ // Treat invalid versions as "V1"
119
+ const version = Object.keys(SignTypedDataVersion).includes(opts.version)
120
+ ? opts.version
121
+ : SignTypedDataVersion.V1;
122
+
123
+ const privateKey = this._getPrivateKeyFor(withAccount, opts);
124
+ return signTypedData({ privateKey, data: typedData, version });
125
+ }
126
+
127
+ // get public key for nacl
128
+ async getEncryptionPublicKey(withAccount, opts = {}) {
129
+ const privKey = this._getPrivateKeyFor(withAccount, opts);
130
+ const publicKey = getEncryptionPublicKey(privKey);
131
+ return publicKey;
132
+ }
133
+
134
+ _getPrivateKeyFor(address, opts = {}) {
135
+ if (!address) {
136
+ throw new Error('Must specify address.');
137
+ }
138
+ const wallet = this._getWalletForAccount(address, opts);
139
+ return wallet.privateKey;
140
+ }
141
+
142
+ // returns an address specific to an app
143
+ async getAppKeyAddress(address, origin) {
144
+ if (!origin || typeof origin !== 'string') {
145
+ throw new Error(`'origin' must be a non-empty string`);
146
+ }
147
+ const wallet = this._getWalletForAccount(address, {
148
+ withAppKeyOrigin: origin,
149
+ });
150
+ const appKeyAddress = normalize(
151
+ publicToAddress(wallet.publicKey).toString('hex'),
152
+ );
153
+ return appKeyAddress;
154
+ }
155
+
156
+ // exportAccount should return a hex-encoded private key:
157
+ async exportAccount(address, opts = {}) {
158
+ const wallet = this._getWalletForAccount(address, opts);
159
+ return wallet.privateKey.toString('hex');
160
+ }
161
+
162
+ removeAccount(address) {
163
+ if (
164
+ !this._wallets
165
+ .map(({ publicKey }) =>
166
+ bufferToHex(publicToAddress(publicKey)).toLowerCase(),
167
+ )
168
+ .includes(address.toLowerCase())
169
+ ) {
170
+ throw new Error(`Address ${address} not found in this keyring`);
171
+ }
172
+
173
+ this._wallets = this._wallets.filter(
174
+ ({ publicKey }) =>
175
+ bufferToHex(publicToAddress(publicKey)).toLowerCase() !==
176
+ address.toLowerCase(),
177
+ );
178
+ }
179
+
180
+ /**
181
+ * @private
182
+ */
183
+ _getWalletForAccount(account, opts = {}) {
184
+ const address = normalize(account);
185
+ let wallet = this._wallets.find(
186
+ ({ publicKey }) => bufferToHex(publicToAddress(publicKey)) === address,
187
+ );
188
+ if (!wallet) {
189
+ throw new Error('Simple Keyring - Unable to find matching address.');
190
+ }
191
+
192
+ if (opts.withAppKeyOrigin) {
193
+ const { privateKey } = wallet;
194
+ const appKeyOriginBuffer = Buffer.from(opts.withAppKeyOrigin, 'utf8');
195
+ const appKeyBuffer = Buffer.concat([privateKey, appKeyOriginBuffer]);
196
+ const appKeyPrivateKey = arrToBufArr(keccak256(appKeyBuffer, 256));
197
+ const appKeyPublicKey = privateToPublic(appKeyPrivateKey);
198
+ wallet = { privateKey: appKeyPrivateKey, publicKey: appKeyPublicKey };
199
+ }
200
+
201
+ return wallet;
202
+ }
203
+ }
204
+
205
+ SimpleKeyring.type = type;
206
+ module.exports = SimpleKeyring;
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@metamask/eth-simple-keyring",
3
+ "version": "5.0.0",
4
+ "description": "A simple standard interface for a series of Ethereum private keys.",
5
+ "keywords": [
6
+ "ethereum",
7
+ "keyring"
8
+ ],
9
+ "homepage": "https://github.com/MetaMask/eth-simple-keyring#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/MetaMask/eth-simple-keyring/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/MetaMask/eth-simple-keyring.git"
16
+ },
17
+ "license": "ISC",
18
+ "author": "Dan Finlay",
19
+ "main": "index.js",
20
+ "files": [
21
+ "index.js"
22
+ ],
23
+ "scripts": {
24
+ "test": "jest",
25
+ "test:watch": "jest --watch",
26
+ "lint:eslint": "eslint . --cache --ext js,ts",
27
+ "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
28
+ "lint": "yarn lint:eslint && yarn lint:misc --check",
29
+ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write"
30
+ },
31
+ "dependencies": {
32
+ "@ethereumjs/util": "^8.0.0",
33
+ "@metamask/eth-sig-util": "^5.0.1",
34
+ "ethereum-cryptography": "^1.1.2",
35
+ "randombytes": "^2.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@ethereumjs/tx": "^4.0.0",
39
+ "@lavamoat/allow-scripts": "^2.1.0",
40
+ "@metamask/auto-changelog": "^3.0.0",
41
+ "@metamask/eslint-config": "^8.0.0",
42
+ "@metamask/eslint-config-jest": "^11.0.0",
43
+ "@metamask/eslint-config-nodejs": "^11.0.1",
44
+ "@types/jest": "^26.0.24",
45
+ "eslint": "^7.30.0",
46
+ "eslint-config-prettier": "^8.3.0",
47
+ "eslint-plugin-import": "^2.23.4",
48
+ "eslint-plugin-jest": "^27.1.5",
49
+ "eslint-plugin-node": "^11.1.0",
50
+ "eslint-plugin-prettier": "^3.4.0",
51
+ "jest": "^27.0.6",
52
+ "prettier": "^2.3.2",
53
+ "prettier-plugin-packagejson": "^2.2.11"
54
+ },
55
+ "engines": {
56
+ "node": ">=14.0.0"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public",
60
+ "registry": "https://registry.npmjs.org/"
61
+ },
62
+ "lavamoat": {
63
+ "allowScripts": {
64
+ "keccak": true,
65
+ "secp256k1": true,
66
+ "@lavamoat/preinstall-always-fail": false
67
+ }
68
+ },
69
+ "packageManager": "yarn@3.3.0"
70
+ }
package/test/index.js ADDED
@@ -0,0 +1,759 @@
1
+ const {
2
+ stripHexPrefix,
3
+ bufferToHex,
4
+ toBuffer,
5
+ ecrecover,
6
+ pubToAddress,
7
+ isValidAddress,
8
+ } = require('@ethereumjs/util');
9
+ const { keccak256 } = require('ethereum-cryptography/keccak');
10
+ const {
11
+ encrypt,
12
+ getEncryptionPublicKey,
13
+ personalSign,
14
+ recoverPersonalSignature,
15
+ recoverTypedSignature,
16
+ signTypedData,
17
+ SignTypedDataVersion,
18
+ } = require('@metamask/eth-sig-util');
19
+ const {
20
+ TransactionFactory,
21
+ Transaction: EthereumTx,
22
+ } = require('@ethereumjs/tx');
23
+ const SimpleKeyring = require('..');
24
+
25
+ const TYPE_STR = 'Simple Key Pair';
26
+
27
+ // Sample account:
28
+ const testAccount = {
29
+ key: '0xb8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952',
30
+ address: '0x01560cd3bac62cc6d7e6380600d9317363400896',
31
+ };
32
+
33
+ const notKeyringAddress = '0xbD20F6F5F1616947a39E11926E78ec94817B3931';
34
+
35
+ describe('simple-keyring', function () {
36
+ let keyring;
37
+ beforeEach(function () {
38
+ keyring = new SimpleKeyring();
39
+ });
40
+
41
+ describe('Keyring.type', function () {
42
+ it('is a class property that returns the type string.', function () {
43
+ const { type } = SimpleKeyring;
44
+ expect(type).toBe(TYPE_STR);
45
+ });
46
+ });
47
+
48
+ describe('#serialize empty wallets.', function () {
49
+ it('serializes an empty array', async function () {
50
+ const output = await keyring.serialize();
51
+ expect(output).toHaveLength(0);
52
+ });
53
+ });
54
+
55
+ describe('#deserialize a private key', function () {
56
+ it('serializes what it deserializes', async function () {
57
+ await keyring.deserialize([testAccount.key]);
58
+ const serialized = await keyring.serialize();
59
+ expect(serialized).toHaveLength(1);
60
+ expect(serialized[0]).toBe(stripHexPrefix(testAccount.key));
61
+ });
62
+ });
63
+
64
+ describe('#constructor with a private key', function () {
65
+ it('has the correct addresses', async function () {
66
+ const newKeyring = new SimpleKeyring([testAccount.key]);
67
+ const accounts = await newKeyring.getAccounts();
68
+ expect(accounts).toStrictEqual([testAccount.address]);
69
+ });
70
+ });
71
+
72
+ describe('#signTransaction', function () {
73
+ const address = '0x9858e7d8b79fc3e6d989636721584498926da38a';
74
+ const privateKey =
75
+ '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18';
76
+ const txParams = {
77
+ from: address,
78
+ nonce: '0x00',
79
+ gasPrice: '0x09184e72a000',
80
+ gasLimit: '0x2710',
81
+ to: address,
82
+ value: '0x1000',
83
+ };
84
+
85
+ it('returns a signed legacy tx object', async function () {
86
+ await keyring.deserialize([privateKey]);
87
+ const tx = new EthereumTx(txParams);
88
+ expect(tx.isSigned()).toBe(false);
89
+
90
+ const signed = await keyring.signTransaction(address, tx);
91
+ expect(signed.isSigned()).toBe(true);
92
+ });
93
+
94
+ it('returns a signed tx object', async function () {
95
+ await keyring.deserialize([privateKey]);
96
+ const tx = TransactionFactory.fromTxData(txParams);
97
+ expect(tx.isSigned()).toBe(false);
98
+
99
+ const signed = await keyring.signTransaction(address, tx);
100
+ expect(signed.isSigned()).toBe(true);
101
+ });
102
+
103
+ it('returns rejected promise if empty address is passed', async function () {
104
+ await keyring.deserialize([privateKey]);
105
+ const tx = TransactionFactory.fromTxData(txParams);
106
+ await expect(keyring.signTransaction('', tx)).rejects.toThrow(
107
+ 'Must specify address.',
108
+ );
109
+ });
110
+
111
+ it('throw error if wrong address is passed', async function () {
112
+ await keyring.deserialize([privateKey]);
113
+ const tx = TransactionFactory.fromTxData(txParams);
114
+ await expect(
115
+ keyring.signTransaction(notKeyringAddress, tx),
116
+ ).rejects.toThrow('Simple Keyring - Unable to find matching address.');
117
+ });
118
+ });
119
+
120
+ describe('#signMessage', function () {
121
+ const address = '0x9858e7d8b79fc3e6d989636721584498926da38a';
122
+ const message =
123
+ '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0';
124
+ const privateKey =
125
+ '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18';
126
+ const expectedResult =
127
+ '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c';
128
+
129
+ it('passes the dennis test', async function () {
130
+ await keyring.deserialize([privateKey]);
131
+ const result = await keyring.signMessage(address, message);
132
+ expect(result).toBe(expectedResult);
133
+ });
134
+
135
+ it('reliably can decode messages it signs', async function () {
136
+ await keyring.deserialize([privateKey]);
137
+ const localMessage = 'hello there!';
138
+ const msgHashHex = bufferToHex(keccak256(Buffer.from(localMessage)));
139
+
140
+ await keyring.addAccounts(9);
141
+ const addresses = await keyring.getAccounts();
142
+ const signatures = await Promise.all(
143
+ addresses.map(async (accountAddress) => {
144
+ return await keyring.signMessage(accountAddress, msgHashHex);
145
+ }),
146
+ );
147
+ signatures.forEach((sgn, index) => {
148
+ const accountAddress = addresses[index];
149
+
150
+ const r = toBuffer(sgn.slice(0, 66));
151
+ const s = toBuffer(`0x${sgn.slice(66, 130)}`);
152
+ const v = BigInt(`0x${sgn.slice(130, 132)}`);
153
+ const m = toBuffer(msgHashHex);
154
+ const pub = ecrecover(m, v, r, s);
155
+ const adr = `0x${pubToAddress(pub).toString('hex')}`;
156
+
157
+ expect(adr).toBe(accountAddress);
158
+ });
159
+ });
160
+
161
+ it('throw error for invalid message', async function () {
162
+ await keyring.deserialize([privateKey]);
163
+ await expect(keyring.signMessage(address, '')).rejects.toThrow(
164
+ 'Cannot convert 0x to a BigInt',
165
+ );
166
+ });
167
+
168
+ it('throw error if empty address is passed', async function () {
169
+ await keyring.deserialize([privateKey]);
170
+ await expect(keyring.signMessage('', message)).rejects.toThrow(
171
+ 'Must specify address.',
172
+ );
173
+ });
174
+
175
+ it('throw error if address not associated with the current keyring is passed', async function () {
176
+ await keyring.deserialize([privateKey]);
177
+ await expect(
178
+ keyring.signMessage(notKeyringAddress, message),
179
+ ).rejects.toThrow('Simple Keyring - Unable to find matching address.');
180
+ });
181
+ });
182
+
183
+ describe('#addAccounts', function () {
184
+ describe('with no arguments', function () {
185
+ it('creates a single wallet', async function () {
186
+ await keyring.addAccounts();
187
+ const serializedKeyring = await keyring.serialize();
188
+ expect(serializedKeyring).toHaveLength(1);
189
+ });
190
+ });
191
+
192
+ describe('with a numeric argument', function () {
193
+ it('creates that number of wallets', async function () {
194
+ await keyring.addAccounts(3);
195
+ const serializedKeyring = await keyring.serialize();
196
+ expect(serializedKeyring).toHaveLength(3);
197
+ });
198
+ });
199
+ });
200
+
201
+ describe('#getAccounts', function () {
202
+ it('should return a list of addresses in wallet', async function () {
203
+ // Push a mock wallet
204
+ keyring.deserialize([testAccount.key]);
205
+
206
+ const output = await keyring.getAccounts();
207
+ expect(output).toHaveLength(1);
208
+ expect(output[0]).toBe(testAccount.address);
209
+ });
210
+ });
211
+
212
+ describe('#removeAccount', function () {
213
+ describe('if the account exists', function () {
214
+ it('should remove that account', async function () {
215
+ await keyring.addAccounts();
216
+ const addresses = await keyring.getAccounts();
217
+ expect(addresses).toHaveLength(1);
218
+ keyring.removeAccount(addresses[0]);
219
+ const addressesAfterRemoval = await keyring.getAccounts();
220
+ expect(addressesAfterRemoval).toHaveLength(0);
221
+ });
222
+ });
223
+
224
+ describe('if the account does not exist', function () {
225
+ it('should throw an error', function () {
226
+ const unexistingAccount = '0x0000000000000000000000000000000000000000';
227
+ expect(() => keyring.removeAccount(unexistingAccount)).toThrow(
228
+ `Address ${unexistingAccount} not found in this keyring`,
229
+ );
230
+ });
231
+ });
232
+ });
233
+
234
+ describe('#signPersonalMessage', function () {
235
+ const address = '0xbe93f9bacbcffc8ee6663f2647917ed7a20a57bb';
236
+ const privateKey = Buffer.from(
237
+ '6969696969696969696969696969696969696969696969696969696969696969',
238
+ 'hex',
239
+ );
240
+ const privKeyHex = bufferToHex(privateKey);
241
+ const message = '0x68656c6c6f20776f726c64';
242
+ const expectedSignature =
243
+ '0xce909e8ea6851bc36c007a0072d0524b07a3ff8d4e623aca4c71ca8e57250c4d0a3fc38fa8fbaaa81ead4b9f6bd03356b6f8bf18bccad167d78891636e1d69561b';
244
+
245
+ it('returns the expected value', async function () {
246
+ await keyring.deserialize([privKeyHex]);
247
+ const signature = await keyring.signPersonalMessage(address, message);
248
+ expect(signature).toBe(expectedSignature);
249
+
250
+ const restored = recoverPersonalSignature({
251
+ data: message,
252
+ signature,
253
+ });
254
+ expect(restored).toBe(address);
255
+ });
256
+
257
+ it('throw error if empty address is passed', async function () {
258
+ await keyring.deserialize([privKeyHex]);
259
+ await expect(keyring.signPersonalMessage('', message)).rejects.toThrow(
260
+ 'Must specify address.',
261
+ );
262
+ });
263
+
264
+ it('throw error if wrong address is passed', async function () {
265
+ await keyring.deserialize([privKeyHex]);
266
+ await expect(
267
+ keyring.signPersonalMessage(notKeyringAddress, message),
268
+ ).rejects.toThrow('Simple Keyring - Unable to find matching address.');
269
+ });
270
+ });
271
+
272
+ describe('#signTypedData', function () {
273
+ const address = '0x29c76e6ad8f28bb1004902578fb108c507be341b';
274
+ const privKeyHex =
275
+ '4af1bceebf7f3634ec3cff8a2c38e51178d5d4ce585c52d6043e5e2cc3418bb0';
276
+ const expectedSignature =
277
+ '0x49e75d475d767de7fcc67f521e0d86590723d872e6111e51c393e8c1e2f21d032dfaf5833af158915f035db6af4f37bf2d5d29781cd81f28a44c5cb4b9d241531b';
278
+
279
+ const typedData = [
280
+ {
281
+ type: 'string',
282
+ name: 'message',
283
+ value: 'Hi, Alice!',
284
+ },
285
+ ];
286
+
287
+ it('returns the expected value', async function () {
288
+ await keyring.deserialize([privKeyHex]);
289
+ const signature = await keyring.signTypedData(address, typedData);
290
+ expect(signature).toBe(expectedSignature);
291
+ const restored = recoverTypedSignature({
292
+ data: typedData,
293
+ signature,
294
+ version: SignTypedDataVersion.V1,
295
+ });
296
+ expect(restored).toBe(address);
297
+ });
298
+
299
+ it('returns the expected value if invalid version is given', async function () {
300
+ await keyring.deserialize([privKeyHex]);
301
+ const signature = await keyring.signTypedData(address, typedData, {
302
+ version: 'FOO',
303
+ });
304
+ expect(signature).toBe(expectedSignature);
305
+ const restored = recoverTypedSignature({
306
+ data: typedData,
307
+ signature,
308
+ version: SignTypedDataVersion.V1,
309
+ });
310
+ expect(restored).toBe(address);
311
+ });
312
+ });
313
+
314
+ describe('#signTypedData V1', function () {
315
+ const address = '0x29c76e6ad8f28bb1004902578fb108c507be341b';
316
+ const privKeyHex =
317
+ '4af1bceebf7f3634ec3cff8a2c38e51178d5d4ce585c52d6043e5e2cc3418bb0';
318
+ const expectedSignature =
319
+ '0x49e75d475d767de7fcc67f521e0d86590723d872e6111e51c393e8c1e2f21d032dfaf5833af158915f035db6af4f37bf2d5d29781cd81f28a44c5cb4b9d241531b';
320
+
321
+ const typedData = [
322
+ {
323
+ type: 'string',
324
+ name: 'message',
325
+ value: 'Hi, Alice!',
326
+ },
327
+ ];
328
+
329
+ it('returns the expected value', async function () {
330
+ await keyring.deserialize([privKeyHex]);
331
+ const signature = await keyring.signTypedData(address, typedData, {
332
+ version: 'V1',
333
+ });
334
+ expect(signature).toBe(expectedSignature);
335
+ const restored = recoverTypedSignature({
336
+ data: typedData,
337
+ signature,
338
+ version: SignTypedDataVersion.V1,
339
+ });
340
+ expect(restored).toBe(address);
341
+ });
342
+
343
+ it('works via version paramter', async function () {
344
+ await keyring.deserialize([privKeyHex]);
345
+ const signature = await keyring.signTypedData(address, typedData);
346
+ expect(signature).toBe(expectedSignature);
347
+ const restored = recoverTypedSignature({
348
+ data: typedData,
349
+ signature,
350
+ version: SignTypedDataVersion.V1,
351
+ });
352
+ expect(restored).toBe(address);
353
+ });
354
+ });
355
+
356
+ describe('#signTypedData V3', function () {
357
+ const address = '0x29c76e6ad8f28bb1004902578fb108c507be341b';
358
+ const privKeyHex =
359
+ '0x4af1bceebf7f3634ec3cff8a2c38e51178d5d4ce585c52d6043e5e2cc3418bb0';
360
+
361
+ it('returns the expected value', async function () {
362
+ const typedData = {
363
+ types: {
364
+ EIP712Domain: [],
365
+ },
366
+ domain: {},
367
+ primaryType: 'EIP712Domain',
368
+ message: {},
369
+ };
370
+
371
+ await keyring.deserialize([privKeyHex]);
372
+ const signature = await keyring.signTypedData(address, typedData, {
373
+ version: 'V3',
374
+ });
375
+ const restored = recoverTypedSignature({
376
+ data: typedData,
377
+ signature,
378
+ version: SignTypedDataVersion.V3,
379
+ });
380
+ expect(restored).toBe(address);
381
+ });
382
+ });
383
+
384
+ describe('#signTypedData V3 signature verification', function () {
385
+ const privKeyHex =
386
+ 'c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4';
387
+ const expectedSignature =
388
+ '0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c';
389
+
390
+ it('returns the expected value', async function () {
391
+ const typedData = {
392
+ types: {
393
+ EIP712Domain: [
394
+ { name: 'name', type: 'string' },
395
+ { name: 'version', type: 'string' },
396
+ { name: 'chainId', type: 'uint256' },
397
+ { name: 'verifyingContract', type: 'address' },
398
+ ],
399
+ Person: [
400
+ { name: 'name', type: 'string' },
401
+ { name: 'wallet', type: 'address' },
402
+ ],
403
+ Mail: [
404
+ { name: 'from', type: 'Person' },
405
+ { name: 'to', type: 'Person' },
406
+ { name: 'contents', type: 'string' },
407
+ ],
408
+ },
409
+ primaryType: 'Mail',
410
+ domain: {
411
+ name: 'Ether Mail',
412
+ version: '1',
413
+ chainId: 1,
414
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
415
+ },
416
+ message: {
417
+ from: {
418
+ name: 'Cow',
419
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
420
+ },
421
+ to: {
422
+ name: 'Bob',
423
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
424
+ },
425
+ contents: 'Hello, Bob!',
426
+ },
427
+ };
428
+
429
+ await keyring.deserialize([privKeyHex]);
430
+ const addresses = await keyring.getAccounts();
431
+ const [address] = addresses;
432
+ const signature = await keyring.signTypedData(address, typedData, {
433
+ version: 'V3',
434
+ });
435
+ expect(signature).toBe(expectedSignature);
436
+ const restored = recoverTypedSignature({
437
+ data: typedData,
438
+ signature,
439
+ version: SignTypedDataVersion.V3,
440
+ });
441
+ expect(restored).toBe(address);
442
+ });
443
+ });
444
+
445
+ describe('#signTypedData V4', function () {
446
+ const address = '0x29c76e6ad8f28bb1004902578fb108c507be341b';
447
+ const privKeyHex =
448
+ '0x4af1bceebf7f3634ec3cff8a2c38e51178d5d4ce585c52d6043e5e2cc3418bb0';
449
+
450
+ it('returns the expected value', async function () {
451
+ const typedData = {
452
+ types: {
453
+ EIP712Domain: [],
454
+ },
455
+ domain: {},
456
+ primaryType: 'EIP712Domain',
457
+ message: {},
458
+ };
459
+
460
+ await keyring.deserialize([privKeyHex]);
461
+ const signature = await keyring.signTypedData(address, typedData, {
462
+ version: 'V4',
463
+ });
464
+ const restored = recoverTypedSignature({
465
+ data: typedData,
466
+ signature,
467
+ version: SignTypedDataVersion.V4,
468
+ });
469
+ expect(restored).toBe(address);
470
+ });
471
+ });
472
+
473
+ describe('#decryptMessage', function () {
474
+ const address = '0xbe93f9bacbcffc8ee6663f2647917ed7a20a57bb';
475
+ const privateKey = Buffer.from(
476
+ '6969696969696969696969696969696969696969696969696969696969696969',
477
+ 'hex',
478
+ );
479
+ const privKeyHex = bufferToHex(privateKey);
480
+ const message = 'Hello world!';
481
+ const encryptedMessage = encrypt({
482
+ publicKey: getEncryptionPublicKey(privateKey),
483
+ data: message,
484
+ version: 'x25519-xsalsa20-poly1305',
485
+ });
486
+
487
+ it('returns the expected value', async function () {
488
+ await keyring.deserialize([privKeyHex]);
489
+ const decryptedMessage = await keyring.decryptMessage(
490
+ address,
491
+ encryptedMessage,
492
+ );
493
+ expect(message).toBe(decryptedMessage);
494
+ });
495
+
496
+ it('throw error if address passed is not present in the keyring', async function () {
497
+ await keyring.deserialize([privKeyHex]);
498
+ await expect(
499
+ keyring.decryptMessage(notKeyringAddress, encryptedMessage),
500
+ ).rejects.toThrow('Simple Keyring - Unable to find matching address.');
501
+ });
502
+
503
+ it('throw error if wrong encrypted data object is passed', async function () {
504
+ await keyring.deserialize([privKeyHex]);
505
+ await expect(keyring.decryptMessage(address, {})).rejects.toThrow(
506
+ 'Encryption type/version not supported.',
507
+ );
508
+ });
509
+ });
510
+
511
+ describe('#encryptionPublicKey', function () {
512
+ const address = '0xbe93f9bacbcffc8ee6663f2647917ed7a20a57bb';
513
+ const privateKey = Buffer.from(
514
+ '6969696969696969696969696969696969696969696969696969696969696969',
515
+ 'hex',
516
+ );
517
+ const publicKey = 'GxuMqoE2oHsZzcQtv/WMNB3gCH2P6uzynuwO1P0MM1U=';
518
+ const privKeyHex = bufferToHex(privateKey);
519
+
520
+ it('returns the expected value', async function () {
521
+ await keyring.deserialize([privKeyHex]);
522
+ const encryptionPublicKey = await keyring.getEncryptionPublicKey(
523
+ address,
524
+ privateKey,
525
+ );
526
+ expect(publicKey).toBe(encryptionPublicKey);
527
+ });
528
+
529
+ it('throw error if address is blank', async function () {
530
+ await keyring.deserialize([privKeyHex]);
531
+ await expect(
532
+ keyring.getEncryptionPublicKey('', privateKey),
533
+ ).rejects.toThrow('Must specify address.');
534
+ });
535
+
536
+ it('throw error if address is not present in the keyring', async function () {
537
+ await keyring.deserialize([privKeyHex]);
538
+ await expect(
539
+ keyring.getEncryptionPublicKey(notKeyringAddress, privateKey),
540
+ ).rejects.toThrow('Simple Keyring - Unable to find matching address.');
541
+ });
542
+ });
543
+
544
+ describe('#signTypedData V4 signature verification', function () {
545
+ const privKeyHex =
546
+ 'c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4';
547
+ const expectedSignature =
548
+ '0x65cbd956f2fae28a601bebc9b906cea0191744bd4c4247bcd27cd08f8eb6b71c78efdf7a31dc9abee78f492292721f362d296cf86b4538e07b51303b67f749061b';
549
+
550
+ it('returns the expected value', async function () {
551
+ const typedData = {
552
+ types: {
553
+ EIP712Domain: [
554
+ { name: 'name', type: 'string' },
555
+ { name: 'version', type: 'string' },
556
+ { name: 'chainId', type: 'uint256' },
557
+ { name: 'verifyingContract', type: 'address' },
558
+ ],
559
+ Person: [
560
+ { name: 'name', type: 'string' },
561
+ { name: 'wallets', type: 'address[]' },
562
+ ],
563
+ Mail: [
564
+ { name: 'from', type: 'Person' },
565
+ { name: 'to', type: 'Person[]' },
566
+ { name: 'contents', type: 'string' },
567
+ ],
568
+ Group: [
569
+ { name: 'name', type: 'string' },
570
+ { name: 'members', type: 'Person[]' },
571
+ ],
572
+ },
573
+ domain: {
574
+ name: 'Ether Mail',
575
+ version: '1',
576
+ chainId: 1,
577
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
578
+ },
579
+ primaryType: 'Mail',
580
+ message: {
581
+ from: {
582
+ name: 'Cow',
583
+ wallets: [
584
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
585
+ '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF',
586
+ ],
587
+ },
588
+ to: [
589
+ {
590
+ name: 'Bob',
591
+ wallets: [
592
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
593
+ '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57',
594
+ '0xB0B0b0b0b0b0B000000000000000000000000000',
595
+ ],
596
+ },
597
+ ],
598
+ contents: 'Hello, Bob!',
599
+ },
600
+ };
601
+
602
+ await keyring.deserialize([privKeyHex]);
603
+
604
+ const addresses = await keyring.getAccounts();
605
+ const [address] = addresses;
606
+
607
+ const signature = await keyring.signTypedData(address, typedData, {
608
+ version: 'V4',
609
+ });
610
+ expect(signature).toBe(expectedSignature);
611
+ const restored = recoverTypedSignature({
612
+ data: typedData,
613
+ signature,
614
+ version: SignTypedDataVersion.V4,
615
+ });
616
+ expect(restored).toBe(address);
617
+ });
618
+ });
619
+
620
+ describe('getAppKeyAddress', function () {
621
+ it('should return a public address custom to the provided app key origin', async function () {
622
+ const { address } = testAccount;
623
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
624
+
625
+ const appKeyAddress = await simpleKeyring.getAppKeyAddress(
626
+ address,
627
+ 'someapp.origin.io',
628
+ );
629
+
630
+ expect(address).not.toBe(appKeyAddress);
631
+ expect(isValidAddress(appKeyAddress)).toBe(true);
632
+ });
633
+
634
+ it('should return different addresses when provided different app key origins', async function () {
635
+ const { address } = testAccount;
636
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
637
+
638
+ const appKeyAddress1 = await simpleKeyring.getAppKeyAddress(
639
+ address,
640
+ 'someapp.origin.io',
641
+ );
642
+
643
+ expect(isValidAddress(appKeyAddress1)).toBe(true);
644
+
645
+ const appKeyAddress2 = await simpleKeyring.getAppKeyAddress(
646
+ address,
647
+ 'anotherapp.origin.io',
648
+ );
649
+
650
+ expect(isValidAddress(appKeyAddress2)).toBe(true);
651
+ expect(appKeyAddress1).not.toBe(appKeyAddress2);
652
+ });
653
+
654
+ it('should return the same address when called multiple times with the same params', async function () {
655
+ const { address } = testAccount;
656
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
657
+
658
+ const appKeyAddress1 = await simpleKeyring.getAppKeyAddress(
659
+ address,
660
+ 'someapp.origin.io',
661
+ );
662
+
663
+ expect(isValidAddress(appKeyAddress1)).toBe(true);
664
+
665
+ const appKeyAddress2 = await simpleKeyring.getAppKeyAddress(
666
+ address,
667
+ 'someapp.origin.io',
668
+ );
669
+
670
+ expect(isValidAddress(appKeyAddress2)).toBe(true);
671
+ expect(appKeyAddress1).toBe(appKeyAddress2);
672
+ });
673
+
674
+ it('should throw error if the provided origin is not a string', async function () {
675
+ const { address } = testAccount;
676
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
677
+
678
+ await expect(simpleKeyring.getAppKeyAddress(address, [])).rejects.toThrow(
679
+ `'origin' must be a non-empty string`,
680
+ );
681
+ });
682
+
683
+ it('should throw error if the provided origin is an empty string', async function () {
684
+ const { address } = testAccount;
685
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
686
+
687
+ await expect(simpleKeyring.getAppKeyAddress(address, '')).rejects.toThrow(
688
+ `'origin' must be a non-empty string`,
689
+ );
690
+ });
691
+ });
692
+
693
+ describe('exportAccount', function () {
694
+ it('should return a hex-encoded private key', async function () {
695
+ const { address } = testAccount;
696
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
697
+ const privKeyHexValue = await simpleKeyring.exportAccount(address);
698
+ expect(testAccount.key).toBe(`0x${privKeyHexValue}`);
699
+ });
700
+
701
+ it('throw error if account is not present', async function () {
702
+ await expect(keyring.exportAccount(notKeyringAddress)).rejects.toThrow(
703
+ 'Simple Keyring - Unable to find matching address.',
704
+ );
705
+ });
706
+ });
707
+
708
+ describe('signing methods withAppKeyOrigin option', function () {
709
+ it('should signPersonalMessage with the expected key when passed a withAppKeyOrigin', async function () {
710
+ const { address } = testAccount;
711
+ const message = '0x68656c6c6f20776f726c64';
712
+
713
+ const privateKeyHex =
714
+ '4fbe006f0e9c2374f53eb1aef1b6970d20206c61ea05ad9591ef42176eb842c0';
715
+ const privateKey = Buffer.from(privateKeyHex, 'hex');
716
+ const expectedSignature = personalSign({ privateKey, data: message });
717
+
718
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
719
+ const signature = await simpleKeyring.signPersonalMessage(
720
+ address,
721
+ message,
722
+ {
723
+ withAppKeyOrigin: 'someapp.origin.io',
724
+ },
725
+ );
726
+
727
+ expect(expectedSignature).toBe(signature);
728
+ });
729
+
730
+ it('should signTypedData V3 with the expected key when passed a withAppKeyOrigin', async function () {
731
+ const { address } = testAccount;
732
+ const typedData = {
733
+ types: {
734
+ EIP712Domain: [],
735
+ },
736
+ domain: {},
737
+ primaryType: 'EIP712Domain',
738
+ message: {},
739
+ };
740
+
741
+ const privateKeyHex =
742
+ '4fbe006f0e9c2374f53eb1aef1b6970d20206c61ea05ad9591ef42176eb842c0';
743
+ const privateKey = Buffer.from(privateKeyHex, 'hex');
744
+ const expectedSignature = signTypedData({
745
+ privateKey,
746
+ data: typedData,
747
+ version: SignTypedDataVersion.V3,
748
+ });
749
+
750
+ const simpleKeyring = new SimpleKeyring([testAccount.key]);
751
+ const signature = await simpleKeyring.signTypedData(address, typedData, {
752
+ withAppKeyOrigin: 'someapp.origin.io',
753
+ version: 'V3',
754
+ });
755
+
756
+ expect(expectedSignature).toBe(signature);
757
+ });
758
+ });
759
+ });