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

Add private key onboarding flow #3119

Merged
merged 14 commits into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion .env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ USE_ANALYTICS_SOURCE="DEV"
POAP_API_KEY="Djq0jJrWaQCzzvvoTqy0BgFidzy4bugDX1mTY28EZpqcUDSNICLcVQnex2wY7D9t5QB2aQlPQRdw8GiOVcE5kgQywGiDraJDfd3GVxnDASm1PoRBjOBh9fOrgymNsMQc"
SUPPORT_WALLET_CONNECT=false
SUPPORT_CUSTOM_NETWORKS=false
SUPPORT_CUSTOM_RPCS=false
SUPPORT_CUSTOM_RPCS=false
SUPPORT_PRIV_KEYS=false
1 change: 1 addition & 0 deletions background/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const RuntimeFlag = {
SUPPORT_SWAP_QUOTE_REFRESH: process.env.SUPPORT_SWAP_QUOTE_REFRESH === "true",
SUPPORT_CUSTOM_NETWORKS: process.env.SUPPORT_CUSTOM_NETWORKS === "true",
SUPPORT_CUSTOM_RPCS: process.env.SUPPORT_CUSTOM_RPCS === "true",
SUPPORT_PRIV_KEYS: process.env.SUPPORT_PRIV_KEYS === "true",
} as const

type BuildTimeFlagType = keyof typeof BuildTimeFlag
Expand Down
1 change: 1 addition & 0 deletions background/redux-slices/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { convertFixedPoint } from "../lib/fixed-point"
*/
export const enum AccountType {
ReadOnly = "read-only",
PrivateKey = "privateKey",
Imported = "imported",
Ledger = "ledger",
Internal = "internal",
Expand Down
4 changes: 4 additions & 0 deletions background/redux-slices/selectors/accountsSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ export type CategorizedAccountTotals = { [key in AccountType]?: AccountTotal[] }

const signerTypeToAccountType: Record<SignerType, AccountType> = {
keyring: AccountType.Imported,
privateKey: AccountType.PrivateKey,
ledger: AccountType.Ledger,
"read-only": AccountType.ReadOnly,
}
Expand All @@ -323,6 +324,9 @@ const getAccountType = (
if (signerTypeToAccountType[signer.type] === "ledger") {
return AccountType.Ledger
}
if (signerTypeToAccountType[signer.type] === "privateKey") {
return AccountType.PrivateKey
}
if (addressSources[address] === "import") {
return AccountType.Imported
}
Expand Down
8 changes: 7 additions & 1 deletion background/redux-slices/selectors/keyringsSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createSelector, OutputSelector } from "@reduxjs/toolkit"
import { RootState } from ".."
import { Keyring } from "../../services/keyring"
import { Keyring, PrivateKey } from "../../services/keyring"
import { HexString } from "../../types"

export const selectKeyringStatus = createSelector(
Expand Down Expand Up @@ -37,6 +37,12 @@ export const selectKeyringsByAddresses = createSelector(
)
)

export const selectWalletsByAddress = createSelector(
(state: RootState) => state.keyrings.privateKeys,
(pkWallets): { [address: HexString]: PrivateKey } =>
Object.fromEntries(pkWallets.map((wallet) => [wallet.addresses[0], wallet]))
)

export const selectSourcesByAddress = createSelector(
(state: RootState) => state.keyrings.keyrings,
(state: RootState) => state.keyrings.metadata,
Expand Down
36 changes: 32 additions & 4 deletions background/redux-slices/selectors/signingSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { createSelector } from "@reduxjs/toolkit"
import { RootState } from ".."
import { isDefined } from "../../lib/utils/type-guards"
import { KeyringAccountSigner } from "../../services/keyring"
import {
KeyringAccountSigner,
WalletAccountSigner,
} from "../../services/keyring"
import { LedgerAccountSigner } from "../../services/ledger"
import { AccountSigner, ReadOnlyAccountSigner } from "../../services/signing"
import { HexString } from "../../types"
import { selectKeyringsByAddresses } from "./keyringsSelectors"
import {
selectKeyringsByAddresses,
selectWalletsByAddress,
} from "./keyringsSelectors"
import { selectCurrentAccount } from "./uiSelectors"

// FIXME: This has a duplicate in `accountSelectors.ts`, but importing causes a dependency cycle
Expand All @@ -24,7 +30,8 @@ export const selectAccountSignersByAddress = createSelector(
getAllAddresses,
(state: RootState) => state.ledger.devices,
selectKeyringsByAddresses,
(allAddresses, ledgerDevices, keyringsByAddress) => {
selectWalletsByAddress,
(allAddresses, ledgerDevices, keyringsByAddress, walletsByAddress) => {
const allAccountsSeen = new Set<string>()
const ledgerEntries = Object.values(ledgerDevices).flatMap((device) =>
Object.values(device.accounts).flatMap(
Expand Down Expand Up @@ -62,15 +69,36 @@ export const selectAccountSignersByAddress = createSelector(
)
.filter(isDefined)

const privateKeyEntries = Object.entries(walletsByAddress)
.map(
([address, wallet]): [HexString, WalletAccountSigner] | undefined => {
if (wallet.id === null) {
return undefined
}

allAccountsSeen.add(address)

return [
address,
{
type: "privateKey",
walletID: wallet.id,
},
]
}
)
.filter(isDefined)

const readOnlyEntries: [string, typeof ReadOnlyAccountSigner][] =
allAddresses
.filter((address) => !allAccountsSeen.has(address))
.map((address) => [address, ReadOnlyAccountSigner])

const entriesByPriority: [string, AccountSigner][] = [
...readOnlyEntries,
...privateKeyEntries,
...ledgerEntries,
// Give priority to keyring over Ledger, if an address is signable by
// Give priority to keyring over Ledger and private key, if an address is signable by
// both.
...keyringEntries,
]
Expand Down
7 changes: 6 additions & 1 deletion background/services/keyring/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export type KeyringAccountSigner = {
keyringID: string
}

export type WalletAccountSigner = {
type: "privateKey"
walletID: string
}

export type SignerMetadata = {
source: "import" | "internal"
}
Expand Down Expand Up @@ -478,7 +483,7 @@ export default class KeyringService extends BaseService<Events> {

return this.#privateKeys.map((wallet) => ({
type: KeyringTypes.singleSECP,
addresses: [wallet.address],
addresses: [normalizeEVMAddress(wallet.address)],
id: wallet.publicKey,
path: null,
}))
Expand Down
10 changes: 8 additions & 2 deletions background/services/preferences/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ type SignerRecordId = `${AccountSignerWithId["type"]}/${string}`
* in the form of "signerType/someId" e.g. "ledger/deviceId"
*/
const getSignerRecordId = (signer: AccountSignerWithId): SignerRecordId => {
const id = signer.type === "keyring" ? signer.keyringID : signer.deviceID
return `${signer.type}/${id}`
switch (signer.type) {
case "keyring":
return `${signer.type}/${signer.keyringID}`
case "privateKey":
return `${signer.type}/${signer.walletID}`
default:
return `${signer.type}/${signer.deviceID}`
}
}

// The idea is to use this interface to describe the data structure stored in indexedDb
Expand Down
10 changes: 9 additions & 1 deletion background/services/signing/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { StatusCodes, TransportStatusError } from "@ledgerhq/errors"
import KeyringService, { KeyringAccountSigner } from "../keyring"
import KeyringService, {
KeyringAccountSigner,
WalletAccountSigner,
} from "../keyring"
import LedgerService, { LedgerAccountSigner } from "../ledger"
import {
SignedTransaction,
Expand Down Expand Up @@ -53,6 +56,7 @@ export const ReadOnlyAccountSigner = { type: "read-only" } as const
*/
export type AccountSigner =
| typeof ReadOnlyAccountSigner
| WalletAccountSigner
| KeyringAccountSigner
| HardwareAccountSigner
export type HardwareAccountSigner = LedgerAccountSigner
Expand Down Expand Up @@ -134,6 +138,7 @@ export default class SigningService extends BaseService<Events> {
transactionWithNonce,
accountSigner
)
case "privateKey":
case "keyring":
return this.keyringService.signTransaction(
{
Expand All @@ -155,6 +160,7 @@ export default class SigningService extends BaseService<Events> {
): Promise<void> {
if (signerType) {
switch (signerType) {
case "privateKey":
case "keyring":
await this.keyringService.hideAccount(address)
break
Expand Down Expand Up @@ -241,6 +247,7 @@ export default class SigningService extends BaseService<Events> {
accountSigner
)
break
case "privateKey":
case "keyring":
signedData = await this.keyringService.signTypedData({
typedData,
Expand Down Expand Up @@ -286,6 +293,7 @@ export default class SigningService extends BaseService<Events> {
hexDataToSign
)
break
case "privateKey":
case "keyring":
signedData = await this.keyringService.personalSign({
signingData: hexDataToSign,
Expand Down
12 changes: 11 additions & 1 deletion ui/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"import": "Import",
"internal": "Taho",
"ledger": "Ledger",
"privateKey": "Private key",
"category": {
"readOnly": "Preview",
"ledger": "Hardware wallets",
Expand Down Expand Up @@ -335,6 +336,7 @@
"newWalletTitle": "New wallet",
"options": {
"importSeed": "Import recovery phrase",
"importPrivateKey": "Private Key import",
"ledger": "Connect to Ledger",
"readOnly": "Read-only address",
"createNew": "Create new wallet"
Expand All @@ -350,6 +352,13 @@
"invalidPhraseError": "Invalid recovery phrase"
}
},
"importPrivateKey": {
"title": "Import private key",
"subtitle": "Importing a private key does not associate it to a secret recovery phrase, but it’s still protected by the same password",
"inputLabel": "Paste private key string",
"submit": "Import account",
"error": "Invalid private key"
},
"viewOnly": {
"title": "Read-only address",
"subtitle": "Add an Ethereum address or ENS name to view an existing wallet in Taho",
Expand Down Expand Up @@ -891,7 +900,8 @@
"SUPPORT_ARBITRUM_NOVA": "Enable Arbitrum Nova network",
"SUPPORT_SWAP_QUOTE_REFRESH": "Enable automatic swap quote updates",
"SUPPORT_CUSTOM_NETWORKS": "Show custom network page on settings panel",
"SUPPORT_CUSTOM_RPCS": "Enable adding custom RPCs"
"SUPPORT_CUSTOM_RPCS": "Enable adding custom RPCs",
"SUPPORT_PRIV_KEYS": "Enable private key import"
}
}
},
Expand Down
Loading