From e82091bfc19d0d5460f6ebd4da4194ff2980f6ab Mon Sep 17 00:00:00 2001 From: Alessandro Candeago <54709706+alecande11@users.noreply.github.com> Date: Tue, 11 Apr 2023 16:35:21 +0200 Subject: [PATCH] Store seed instead of private key (#110) * Store seed instead of private key * replace pk with seed * Fix seed key * fix legacy 118 terra wallets * cleanup fix imports * cleanup --------- Co-authored-by: plubber <51789398+ericHgorski@users.noreply.github.com> Co-authored-by: plubber --- package-lock.json | 30 ++--- package.json | 2 +- src/auth/auth.d.ts | 13 +- src/auth/hooks/useAuth.ts | 119 +++++++++++++----- .../modules/create/CreateWalletWizard.tsx | 31 +++-- src/auth/modules/create/SelectAddress.tsx | 7 +- src/auth/scripts/is.ts | 6 + src/auth/scripts/keystore.ts | 104 +++++++++------ 8 files changed, 215 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6fa5537b9..c6df01845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@ledgerhq/hw-transport-web-ble": "^6.27.1", "@mui/icons-material": "^5.8.0", "@mui/material": "^5.9.1", - "@terra-money/feather.js": "^1.0.0-beta.11", + "@terra-money/feather.js": "^1.0.0-beta.15", "@terra-money/ledger-station-js": "^1.3.7", "@terra-money/log-finder-ruleset": "^3.0.0", "@terra-money/msg-reader": "^3.0.1", @@ -4266,12 +4266,12 @@ } }, "node_modules/@terra-money/feather.js": { - "version": "1.0.0-beta.11", - "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-1.0.0-beta.11.tgz", - "integrity": "sha512-OWi14FeBoPpuc822jvaFkIQUxLzKJtUOpjMa1L+1/FaARBmRDG1ZJe4h0KNrAKTDdzof77i2cdKxRpDr186UEw==", + "version": "1.0.0-beta.15", + "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-1.0.0-beta.15.tgz", + "integrity": "sha512-KKmwmLemorifApC7y7Rn8H8MzdPmHnTkpqncfHdvWLzhGxeRw0hv24ZPkOj+SOZbhdutwp2RtAH1IEZz23cHUQ==", "dependencies": { "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", - "@terra-money/terra.proto": "^2.2.0-beta.3", + "@terra-money/terra.proto": "^2.2.0-beta.4", "axios": "^0.27.2", "bech32": "^2.0.0", "bip32": "^2.0.6", @@ -4290,9 +4290,9 @@ } }, "node_modules/@terra-money/feather.js/node_modules/@terra-money/terra.proto": { - "version": "2.2.0-beta.3", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-2.2.0-beta.3.tgz", - "integrity": "sha512-L6E1wACw2JNtVFSVMFGgdYx3ICHElOg0Shactm8rjYSbmryLj/ZbW9p81igkCaOYgWBxZ/KMS0fltokbN9FyMw==", + "version": "2.2.0-beta.4", + "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-2.2.0-beta.4.tgz", + "integrity": "sha512-M6ARaIYVPPC08RV9zNLKnTkiyiqAoTM0xugUz0L6YiMsEEnilpY5ju+thlkxcYYKKynO9WFy1zcRRugOw+VlKQ==", "dependencies": { "@improbable-eng/grpc-web": "^0.14.1", "google-protobuf": "^3.17.3", @@ -31750,12 +31750,12 @@ } }, "@terra-money/feather.js": { - "version": "1.0.0-beta.11", - "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-1.0.0-beta.11.tgz", - "integrity": "sha512-OWi14FeBoPpuc822jvaFkIQUxLzKJtUOpjMa1L+1/FaARBmRDG1ZJe4h0KNrAKTDdzof77i2cdKxRpDr186UEw==", + "version": "1.0.0-beta.15", + "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-1.0.0-beta.15.tgz", + "integrity": "sha512-KKmwmLemorifApC7y7Rn8H8MzdPmHnTkpqncfHdvWLzhGxeRw0hv24ZPkOj+SOZbhdutwp2RtAH1IEZz23cHUQ==", "requires": { "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", - "@terra-money/terra.proto": "^2.2.0-beta.3", + "@terra-money/terra.proto": "^2.2.0-beta.4", "axios": "^0.27.2", "bech32": "^2.0.0", "bip32": "^2.0.6", @@ -31771,9 +31771,9 @@ }, "dependencies": { "@terra-money/terra.proto": { - "version": "2.2.0-beta.3", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-2.2.0-beta.3.tgz", - "integrity": "sha512-L6E1wACw2JNtVFSVMFGgdYx3ICHElOg0Shactm8rjYSbmryLj/ZbW9p81igkCaOYgWBxZ/KMS0fltokbN9FyMw==", + "version": "2.2.0-beta.4", + "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-2.2.0-beta.4.tgz", + "integrity": "sha512-M6ARaIYVPPC08RV9zNLKnTkiyiqAoTM0xugUz0L6YiMsEEnilpY5ju+thlkxcYYKKynO9WFy1zcRRugOw+VlKQ==", "requires": { "@improbable-eng/grpc-web": "^0.14.1", "google-protobuf": "^3.17.3", diff --git a/package.json b/package.json index 8b9c823eb..3ff03029c 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@ledgerhq/hw-transport-web-ble": "^6.27.1", "@mui/icons-material": "^5.8.0", "@mui/material": "^5.9.1", - "@terra-money/feather.js": "^1.0.0-beta.11", + "@terra-money/feather.js": "^1.0.0-beta.15", "@terra-money/ledger-station-js": "^1.3.7", "@terra-money/log-finder-ruleset": "^3.0.0", "@terra-money/msg-reader": "^3.0.1", diff --git a/src/auth/auth.d.ts b/src/auth/auth.d.ts index 138fd89a8..7031bd678 100644 --- a/src/auth/auth.d.ts +++ b/src/auth/auth.d.ts @@ -9,7 +9,12 @@ type StoredWallet = | StoredWalletLegacy | MultisigWallet | LedgerWallet -type ResultStoredWallet = LegacyStoredWallet | MultisigWallet | StoredWallet + | SeedStoredWallet +type ResultStoredWallet = + | LegacyStoredWallet + | MultisigWallet + | StoredWallet + | SeedStoredWallet // interchain types interface SingleWallet { @@ -51,6 +56,12 @@ interface InterchainStoredWallet extends SingleWallet { } } +interface SeedStoredWallet extends SingleWallet { + encryptedSeed: string + index: number + legacy: boolean +} + // legacy types (pre-interchain) interface LegacySingleWallet { address: string diff --git a/src/auth/hooks/useAuth.ts b/src/auth/hooks/useAuth.ts index cc7fc4fc6..0010826cd 100644 --- a/src/auth/hooks/useAuth.ts +++ b/src/auth/hooks/useAuth.ts @@ -1,7 +1,12 @@ import { useCallback, useMemo } from "react" import { atom, useRecoilState } from "recoil" import { encode } from "js-base64" -import { CreateTxOptions, Tx, isTxError } from "@terra-money/feather.js" +import { + CreateTxOptions, + Tx, + isTxError, + SeedKey, +} from "@terra-money/feather.js" import { AccAddress, SignDoc } from "@terra-money/feather.js" import { RawKey, SignatureV2 } from "@terra-money/feather.js" import { LedgerKey } from "@terra-money/ledger-station-js" @@ -127,6 +132,9 @@ const useAuth = () => { const { name, words } = getConnectedWallet() const key = getKey(password) if (!key) throw new PasswordError("Key do not exist") + // TODO: update key export + if ("seed" in key) throw new PasswordError("This key cannot be exported") + const data = { name, address: addressFromWords(words["330"], "terra"), @@ -181,12 +189,23 @@ const useAuth = () => { return await key.createSignatureAmino(doc) } else { const pk = getKey(password) - if (!pk || !pk[networks[chainID].coinType]) - throw new PasswordError("Incorrect password") - const key = new RawKey( - Buffer.from(pk[networks[chainID].coinType] ?? "", "hex") - ) - return await key.createSignatureAmino(doc) + if (!pk) throw new PasswordError("Incorrect password") + + if ("seed" in pk) { + const key = new SeedKey({ + seed: Buffer.from(pk.seed, "hex"), + coinType: pk.legacy ? 118 : parseInt(networks[chainID].coinType), + index: pk.index || 0, + }) + return await key.createSignatureAmino(doc) + } else { + if (!pk[networks[chainID].coinType]) + throw new PasswordError("Incorrect password") + const key = new RawKey( + Buffer.from(pk[networks[chainID].coinType] ?? "", "hex") + ) + return await key.createSignatureAmino(doc) + } } } @@ -195,13 +214,26 @@ const useAuth = () => { if (is.ledger(wallet)) { const key = await getLedgerKey(coinType) - return await key.publicKey + // @ts-expect-error + return key.publicKey.key } else { const pk = getKey(password) - if (!pk || !pk[coinType]) throw new PasswordError("Incorrect password") - const key = new RawKey(Buffer.from(pk[coinType] ?? "", "hex")) - // @ts-expect-error - return await key.publicKey.key + if (!pk) throw new PasswordError("Incorrect password") + + if ("seed" in pk) { + const key = new SeedKey({ + seed: Buffer.from(pk.seed, "hex"), + coinType: pk.legacy ? 118 : parseInt(coinType), + index: pk.index || 0, + }) + // @ts-expect-error + return key.publicKey.key + } else { + if (!pk[coinType]) throw new PasswordError("Incorrect password") + const key = new RawKey(Buffer.from(pk[coinType] ?? "", "hex")) + // @ts-expect-error + return key.publicKey.key + } } } @@ -216,18 +248,29 @@ const useAuth = () => { ...txOptions, signMode, }) - } /*else if (is.preconfigured(wallet)) { - const key = new MnemonicKey({ mnemonic: wallet.mnemonic }) - return await lcd.wallet(key).createAndSignTx(txOptions) - }*/ else { + } else { const pk = getKey(password) - if (!pk || !pk[networks[txOptions.chainID].coinType]) - throw new PasswordError("Incorrect password") - const key = new RawKey( - Buffer.from(pk[networks[txOptions.chainID].coinType] ?? "", "hex") - ) - const wallet = lcd.wallet(key) - return await wallet.createAndSignTx(txOptions) + if (!pk) throw new PasswordError("Incorrect password") + + if ("seed" in pk) { + const key = new SeedKey({ + seed: Buffer.from(pk.seed, "hex"), + coinType: pk.legacy + ? 118 + : parseInt(networks[txOptions.chainID].coinType), + index: pk.index || 0, + }) + const w = lcd.wallet(key) + return await w.createAndSignTx(txOptions) + } else { + if (!pk[networks[txOptions.chainID].coinType]) + throw new PasswordError("Incorrect password") + const key = new RawKey( + Buffer.from(pk[networks[txOptions.chainID].coinType] ?? "", "hex") + ) + const w = lcd.wallet(key) + return await w.createAndSignTx(txOptions) + } } } @@ -239,13 +282,29 @@ const useAuth = () => { } else { const pk = getKey(password) if (!pk) throw new PasswordError("Incorrect password") - const key = new RawKey(Buffer.from(pk["330"], "hex")) - const { signature, recid } = key.ecdsaSign(bytes) - if (!signature) throw new Error("Signature is undefined") - return { - recid, - signature: Buffer.from(signature).toString("base64"), - public_key: key.publicKey?.toAmino().value as string, + + if ("seed" in pk) { + const key = new SeedKey({ + seed: Buffer.from(pk.seed, "hex"), + coinType: pk.legacy ? 118 : 330, + index: pk.index || 0, + }) + const { signature, recid } = key.ecdsaSign(bytes) + if (!signature) throw new Error("Signature is undefined") + return { + recid, + signature: Buffer.from(signature).toString("base64"), + public_key: key.publicKey?.toAmino().value as string, + } + } else { + const key = new RawKey(Buffer.from(pk["330"], "hex")) + const { signature, recid } = key.ecdsaSign(bytes) + if (!signature) throw new Error("Signature is undefined") + return { + recid, + signature: Buffer.from(signature).toString("base64"), + public_key: key.publicKey?.toAmino().value as string, + } } } } diff --git a/src/auth/modules/create/CreateWalletWizard.tsx b/src/auth/modules/create/CreateWalletWizard.tsx index 2745c3483..e0fc32b71 100644 --- a/src/auth/modules/create/CreateWalletWizard.tsx +++ b/src/auth/modules/create/CreateWalletWizard.tsx @@ -1,6 +1,6 @@ import { ReactNode, useEffect, useState } from "react" import { useLocation, useNavigate } from "react-router-dom" -import { MnemonicKey } from "@terra-money/feather.js" +import { SeedKey } from "@terra-money/feather.js" import createContext from "utils/createContext" import { addWallet } from "../../scripts/keystore" import CreateWalletForm from "./CreateWalletForm" @@ -54,23 +54,30 @@ const CreateWalletWizard = ({ defaultMnemonic = "", beforeCreate }: Props) => { const [createdWallet, setCreatedWallet] = useState() const createWallet = (coinType: Bip, index = 0) => { const { name, password, mnemonic } = values - const mk330 = new MnemonicKey({ mnemonic, coinType, index }) - const mk118 = new MnemonicKey({ mnemonic, coinType: 118, index }) + + const seed = SeedKey.seedFromMnemonic(mnemonic) + const key330 = new SeedKey({ seed, coinType, index }) + const key118 = new SeedKey({ seed, coinType: 118, index }) const words = { - "330": wordsFromAddress(mk330.accAddress("terra")), - "118": wordsFromAddress(mk118.accAddress("terra")), + "330": wordsFromAddress(key330.accAddress("terra")), + "118": wordsFromAddress(key118.accAddress("terra")), } const pubkey = { // @ts-expect-error - "330": mk330.publicKey.key, + "330": key330.publicKey.key, // @ts-expect-error - "118": mk118.publicKey.key, - } - const key = { - "330": mk330.privateKey, - "118": mk118.privateKey, + "118": key118.publicKey.key, } - addWallet({ name, password, words, key, pubkey }) + + addWallet({ + name, + password, + words, + seed, + pubkey, + index, + legacy: coinType === 118, + }) setCreatedWallet({ name, words, pubkey }) setStep(3) } diff --git a/src/auth/modules/create/SelectAddress.tsx b/src/auth/modules/create/SelectAddress.tsx index a9a2914d3..14d883e65 100644 --- a/src/auth/modules/create/SelectAddress.tsx +++ b/src/auth/modules/create/SelectAddress.tsx @@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next" import { useQuery } from "react-query" import { useForm } from "react-hook-form" import { readAmount } from "@terra-money/terra-utils" -import { MnemonicKey, AccAddress } from "@terra-money/feather.js" +import { SeedKey, AccAddress } from "@terra-money/feather.js" import { Coins, Delegation, UnbondingDelegation } from "@terra-money/feather.js" import { sortCoins } from "utils/coin" import { useInterchainLCDClient } from "data/queries/lcdClient" @@ -23,15 +23,16 @@ const SelectAddress = () => { const readNativeDenom = useNativeDenoms() const { values, createWallet } = useCreateWallet() const { mnemonic, index } = values + const seed = SeedKey.seedFromMnemonic(mnemonic) /* query */ const { data: results } = useQuery( // FIXME: remove mnemonic from this array - ["mnemonic", mnemonic, index], + ["mnemonic", seed, index], async () => { const results = await Promise.allSettled( ([118, 330] as const).map(async (bip) => { - const mk = new MnemonicKey({ mnemonic, coinType: bip, index }) + const mk = new SeedKey({ seed, coinType: bip, index }) const address = mk.accAddress("terra") const [balance] = await lcd.bank.balance(address) const [delegations] = await lcd.staking.delegations(address) diff --git a/src/auth/scripts/is.ts b/src/auth/scripts/is.ts index a1f6b4e36..2643baae9 100644 --- a/src/auth/scripts/is.ts +++ b/src/auth/scripts/is.ts @@ -17,6 +17,11 @@ const isPreconfigured = (wallet?: Wallet): wallet is PreconfiguredWallet => { return "mnemonic" in wallet } +const isSeed = (wallet?: Wallet): wallet is SeedStoredWallet => { + if (!isLocal(wallet)) return false + return "encryptedSeed" in wallet +} + const isSingle = (wallet?: Wallet): wallet is SingleWallet => { if (!isLocal(wallet)) return false return !isPreconfigured(wallet) && !isMultisig(wallet) && !isLedger(wallet) @@ -35,6 +40,7 @@ const is = { multisig: isMultisig, single: isSingle, ledger: isLedger, + seed: isSeed, } export default is diff --git a/src/auth/scripts/keystore.ts b/src/auth/scripts/keystore.ts index 7df6e289e..ffa58438e 100644 --- a/src/auth/scripts/keystore.ts +++ b/src/auth/scripts/keystore.ts @@ -48,10 +48,12 @@ interface Params { password: string } -interface Key { - "330": string - "118"?: string -} +type Key = + | { + "330": string + "118"?: string + } + | { seed: string; index: number; legacy: boolean } export const getDecryptedKey = ({ name, @@ -78,8 +80,12 @@ export const getDecryptedKey = ({ // legacy const { privateKey: key } = JSON.parse(decrypt(wallet.wallet, password)) return { "330": key as string } - } else { - return + } else if ("encryptedSeed" in wallet) { + return { + seed: decrypt(wallet.encryptedSeed, password), + index: wallet.index, + legacy: wallet.legacy, + } } } catch { throw new PasswordError("Incorrect password") @@ -96,7 +102,16 @@ type AddWalletParams = | { words: { "330": string; "118"?: string } password: string - key: { "330": Buffer; "118"?: Buffer } + seed: Buffer + name: string + index: number + legacy: boolean + pubkey: { "330": string; "118"?: string } + } + | { + words: { "330": string; "118"?: string } + password: string + key: { "330": Buffer } name: string pubkey: { "330": string; "118"?: string } } @@ -118,12 +133,18 @@ export const addWallet = (params: AddWalletParams) => { if (is.multisig(params) || is.ledger(params)) { storeWallets([...next, params]) } else { - const { name, password, words, key, pubkey } = params - const encrypted = { - "330": encrypt(key["330"].toString("hex"), password), - "118": key["118"] && encrypt(key["118"].toString("hex"), password), + if ("seed" in params) { + const { name, password, words, seed, pubkey, index, legacy } = params + const encryptedSeed = encrypt(seed.toString("hex"), password) + storeWallets([ + ...next, + { name, words, encryptedSeed, pubkey, index, legacy }, + ]) + } else { + const { name, password, words, key, pubkey } = params + const encrypted = { "330": encrypt(key["330"].toString("hex"), password) } + storeWallets([...next, { name, words, encrypted, pubkey }]) } - storeWallets([...next, { name, words, encrypted, pubkey }]) } } @@ -138,31 +159,44 @@ export const changePassword = (params: ChangePasswordParams) => { testPassword({ name, password: oldPassword }) const key = getDecryptedKey({ name, password: oldPassword }) if (!key) throw new Error("Key does not exist, cannot change password") - const encrypted = { - "330": encrypt(key["330"], newPassword), - "118": key["118"] && encrypt(key["118"], newPassword), - } - const wallets = getStoredWallets() - const next = wallets.map((wallet) => { - if (wallet.name === name) { - if ("address" in wallet) { - const { address } = wallet - return { - name, - words: { - "330": wordsFromAddress(address), - }, - encrypted, - } - } else { - const { words } = wallet - return { name, words, encrypted } + if ("seed" in key) { + const encryptedSeed = encrypt(key.seed, newPassword) + + const wallets = getStoredWallets() + const next = wallets.map((wallet) => { + if (wallet.name === name && "encryptedSeed" in wallet) { + const { words, index, legacy } = wallet + return { name, words, encryptedSeed, index, legacy } } + return wallet + }) + storeWallets(next) + } else { + const encrypted = { + "330": encrypt(key["330"], newPassword), + "118": key["118"] && encrypt(key["118"], newPassword), } - return wallet - }) - - storeWallets(next) + const wallets = getStoredWallets() + const next = wallets.map((wallet) => { + if (wallet.name === name) { + if ("address" in wallet) { + const { address } = wallet + return { + name, + words: { + "330": wordsFromAddress(address), + }, + encrypted, + } + } else { + const { words } = wallet + return { name, words, encrypted } + } + } + return wallet + }) + storeWallets(next) + } } interface StorePubKeyParams {