Skip to content

Commit

Permalink
feat: generate wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
martonlederer committed Oct 23, 2022
1 parent e67d70b commit 2cf1ea5
Show file tree
Hide file tree
Showing 7 changed files with 456 additions and 58 deletions.
4 changes: 4 additions & 0 deletions assets/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -600,5 +600,9 @@
"written_down_seed": {
"message": "I have written down my seedphrase",
"description": "Label for written down seedphrase checkbox"
},
"generating_wallet": {
"message": "Generating wallet...",
"description": "Generating wallet in progress text"
}
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@arconnect/webext-bridge": "^5.0.6",
"@iconicicons/react": "^1.5.0",
"@plasmohq/storage": "^0.12.1",
"@types/human-crypto-keys": "^0.1.0",
"ar-gql": "^0.0.6",
"arbundles": "^0.6.21",
"ardb": "^1.1.10",
Expand All @@ -38,6 +39,7 @@
"bip39-web-crypto": "^4.0.1",
"copy-to-clipboard": "^3.3.2",
"framer-motion": "^7.5.3",
"human-crypto-keys": "^0.1.4",
"nanoid": "^4.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand All @@ -55,6 +57,7 @@
"@types/styled-components": "^5.1.26",
"@types/webextension-polyfill": "^0.9.0",
"constants-browserify": "^1.0.0",
"crypto-browserify": "^3.12.0",
"husky": "^8.0.0",
"plasmo": "^0.55.0",
"prettier": "^2.2.1",
Expand Down
9 changes: 0 additions & 9 deletions shim.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ declare module "@arconnect/webext-bridge" {
switch_wallet_event: string;
copy_address: string;
chunk: ProtocolWithReturn<ApiCall<Chunk>, ApiResponse<number>>;
generate_wallet: ProtocolWithReturn<GenerateCall, GenerationResult>;
}
}

Expand All @@ -30,14 +29,6 @@ interface AuthResult {
data?: any;
}

interface GenerateCall {
seed: string;
}

interface GenerationResult {
address: string;
}

declare module "styled-components" {
export interface DefaultTheme {
theme: string;
Expand Down
4 changes: 0 additions & 4 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { addressChangeListener, walletsChangeListener } from "~wallets/event";
import { generateWalletInBackground } from "~wallets/generator";
import { handleApiCalls, handleChunkCalls } from "~api";
import { onMessage } from "@arconnect/webext-bridge";
import { handleTabUpdate } from "~applications/tab";
Expand All @@ -22,9 +21,6 @@ onMessage("api_call", handleApiCalls);
// watch for chunks
onMessage("chunk", handleChunkCalls);

// watch for wallet generation requests
onMessage("generate_wallet", generateWalletInBackground);

// handle tab change (icon, context menus)
browser.tabs.onUpdated.addListener((tabId) => handleTabUpdate(tabId));
browser.tabs.onActivated.addListener(({ tabId }) => handleTabUpdate(tabId));
Expand Down
82 changes: 56 additions & 26 deletions src/routes/welcome/generate.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { JWKInterface } from "arweave/web/lib/wallet";
import { AnimatePresence, motion } from "framer-motion";
import { defaultGateway } from "~applications/gateway";
import { jwkFromMnemonic } from "~wallets/generator";
import { formatAddress } from "~utils/format";
import { useEffect, useState } from "react";
import {
Button,
Card,
Checkbox,
Loading,
Spacer,
Text,
useCheckbox,
Expand All @@ -13,13 +20,11 @@ import {
EyeIcon,
EyeOffIcon
} from "@iconicicons/react";
import { sendMessage } from "@arconnect/webext-bridge";
import { formatAddress } from "~utils/format";
import { useEffect, useState } from "react";
import browser from "webextension-polyfill";
import * as bip39 from "bip39-web-crypto";
import styled from "styled-components";
import copy from "copy-to-clipboard";
import Arweave from "arweave";

export default function Generate() {
// active page
Expand All @@ -40,36 +45,31 @@ export default function Generate() {
// toasts
const { setToast } = useToasts();

// send wallet generating request
const [sendGenerationRequest, setSendGenerationRequest] = useState(false);

// wallet generator
// we send a message to the background worker
// to create the wallet
// this is needed because wallet generationg is very
// slow, so it is better to perform it in the background
//
// IMPORTANT: because a stored wallet keyfile can be undefined,
// as it has not yet generated it's keyfile, we need to display
// in the popup, if a wallet is still being generated, and will
// be pushed to the stored wallets array
// generation in progress
const [generatingWallet, setGeneratingWallet] = useState(false);

// keyfile
const [keyfile, setKeyfile] = useState<JWKInterface>();

// generate wallet
useEffect(() => {
(async () => {
if (seed === "" || sendGenerationRequest) return;
setSendGenerationRequest(true);
if (seed === "" || generatingWallet) return;
setGeneratingWallet(true);

try {
// try to generate wallet in the background
const res = await sendMessage(
"generate_wallet",
{ seed },
"background"
);
const arweave = new Arweave(defaultGateway);

// generate wallet from seedphrase
const generatedKeyfile = await jwkFromMnemonic(seed);
const address = await arweave.wallets.jwkToAddress(generatedKeyfile);

setKeyfile(generatedKeyfile);

setToast({
type: "success",
content: browser.i18n.getMessage("generated_wallet", [
formatAddress(res.address, 6)
formatAddress(address, 6)
]),
duration: 2300
});
Expand All @@ -81,8 +81,10 @@ export default function Generate() {
duration: 2300
});
}

setGeneratingWallet(false);
})();
}, [seed, sendGenerationRequest]);
}, [seed]);

// written down checkbox
const writtenDown = useCheckbox();
Expand Down Expand Up @@ -117,6 +119,14 @@ export default function Generate() {
{page !== 3 && <ArrowRightIcon />}
</Button>
</GenerateCard>
<AnimatePresence>
{generatingWallet && (
<Generating>
<Text noMargin>{browser.i18n.getMessage("generating_wallet")}</Text>
<GeneratingLoading />
</Generating>
)}
</AnimatePresence>
</Wrapper>
);
}
Expand Down Expand Up @@ -219,3 +229,23 @@ const CopySeed = styled(Text).attrs({
height: 1em;
}
`;

const Generating = styled(motion.div).attrs({
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
transition: { duration: 0.23, ease: "easeInOut" }
})`
position: fixed;
display: flex;
align-items: center;
bottom: 1rem;
right: 1rem;
gap: 0.36rem;
`;

const GeneratingLoading = styled(Loading)`
color: rgb(${(props) => props.theme.theme});
width: 1.23rem;
height: 1.23rem;
`;
64 changes: 54 additions & 10 deletions src/wallets/generator.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,58 @@
import type { OnMessageCallback } from "@arconnect/webext-bridge";
import type { GenerateCall, GenerationResult } from "shim";
import type { JWKInterface } from "arweave/web/lib/wallet";
import { getKeyPairFromMnemonic } from "human-crypto-keys";

export const generateWalletInBackground: OnMessageCallback<
// @ts-expect-error
GenerateCall,
GenerationResult
> = async ({ data, sender }) => {
if (sender.context !== "web_accessible") return;
/**
* Credits to arweave.app for the mnemonic wallet generation
*
* https://github.com/jfbeats/ArweaveWebWallet/blob/master/src/functions/Wallets.ts
* https://github.com/jfbeats/ArweaveWebWallet/blob/master/src/functions/Crypto.ts
*/

/**
* Generate a JWK from a mnemonic seedphrase
*
* @param mnemonic Mnemonic seedphrase to generate wallet from
* @returns Wallet JWK
*/
export async function jwkFromMnemonic(mnemonic: string) {
const { privateKey } = await getKeyPairFromMnemonic(
mnemonic,
{
id: "rsa",
modulusLength: 4096
},
{ privateKeyFormat: "pkcs8-der" }
);
const jwk = pkcs8ToJwk(privateKey as any);

return jwk;
}

/**
* Convert a PKCS8 private key to a JWK
*
* @param privateKey PKCS8 private key to convert
* @returns JWK
*/
async function pkcs8ToJwk(privateKey: Uint8Array): Promise<JWKInterface> {
const key = await window.crypto.subtle.importKey(
"pkcs8",
privateKey,
{ name: "RSA-PSS", hash: "SHA-256" },
true,
["sign"]
);
const jwk = await window.crypto.subtle.exportKey("jwk", key);

return {
address: "test"
kty: jwk.kty!,
e: jwk.e!,
n: jwk.n!,
d: jwk.d,
p: jwk.p,
q: jwk.q,
dp: jwk.dp,
dq: jwk.dq,
qi: jwk.qi
};
};
}
Loading

0 comments on commit 2cf1ea5

Please sign in to comment.