on master help sign in
add ledger support to auth token functionality
2988d5fLiam Fitzgerald
src/lib/authToken.js+20−1
@@ -1,6 +1,7 @@
1import * as secp256k1 from 'secp256k1';
1import * as secp256k1 from 'secp256k1';
2
2
3import { keccak256, WALLET_TYPES } from './wallet';
3import { keccak256, WALLET_TYPES } from './wallet';
4+import { ledgerSignMessage } from './ledger';
4
5
5const MESSAGE = 'Bridge Authentication Token';
6const MESSAGE = 'Bridge Authentication Token';
6
7
@@ -18,10 +19,28 @@ function signMessage(privateKey) {
18 return ethSignature;
19 return ethSignature;
19}
20}
20
21
22+function signatureToHex(v, r, s) {
23+ const ethSignature = new Uint8Array(65);
24+ ethSignature.set(Buffer.from(r, 'hex'));
25+ ethSignature.set(Buffer.from(s, 'hex'), 32);
26+ ethSignature[64] = v;
27+
28+ return `0x${Buffer.from(ethSignature).toString('hex')}`;
29+}
30+
21−export const getAuthToken = ({ wallet, walletType, walletHdPath, web3 }) => {
31+export const getAuthToken = async ({
32+ wallet,
33+ walletType,
34+ walletHdPath,
35+ web3,
36+}) => {
22 if (walletType === WALLET_TYPES.METAMASK) {
37 if (walletType === WALLET_TYPES.METAMASK) {
23 return web3.eth.personal.sign(MESSAGE, wallet.address, '');
38 return web3.eth.personal.sign(MESSAGE, wallet.address, '');
24 }
39 }
40+ if (walletType === WALLET_TYPES.LEDGER) {
41+ const { v, r, s } = await ledgerSignMessage(MESSAGE, walletHdPath);
42+ return signatureToHex(v, r, s);
43+ }
25
44
26 const signature = signMessage(wallet.privateKey);
45 const signature = signMessage(wallet.privateKey);
27
46
src/lib/ledger.js+8
@@ -22,3 +22,11 @@ export const ledgerSignTransaction = async (txn, hdpath) => {
22
22
23 return txn;
23 return txn;
24};
24};
25+
26+export const ledgerSignMessage = async (message, hdPath) => {
27+ const transport = await Transport.create();
28+ const eth = new Eth(transport);
29+
30+ const path = chopHdPrefix(hdPath);
31+ return eth.signPersonalMessage(path, Buffer.from(message).toString('hex'));
32+};
src/lib/wallet.js+6
@@ -22,6 +22,12 @@ export const WALLET_TYPES = {
22 METAMASK: Symbol('METAMASK'),
22 METAMASK: Symbol('METAMASK'),
23};
23};
24
24
25+export const NONCUSTODIAL_WALLETS = new Set([
26+ WALLET_TYPES.METAMASK,
27+ WALLET_TYPES.TREZOR,
28+ WALLET_TYPES.LEDGER,
29+]);
30+
25export function EthereumWallet(privateKey) {
31export function EthereumWallet(privateKey) {
26 this.privateKey = privateKey;
32 this.privateKey = privateKey;
27 this.publicKey = secp256k1.publicKeyCreate(this.privateKey);
33 this.publicKey = secp256k1.publicKeyCreate(this.privateKey);
src/store/wallet.js+9−1
@@ -14,6 +14,7 @@ import {
14 DEFAULT_HD_PATH,
14 DEFAULT_HD_PATH,
15 addressFromSecp256k1Public,
15 addressFromSecp256k1Public,
16 walletFromMnemonic,
16 walletFromMnemonic,
17+ NONCUSTODIAL_WALLETS,
17} from 'lib/wallet';
18} from 'lib/wallet';
18import { getAuthToken } from 'lib/authToken';
19import { getAuthToken } from 'lib/authToken';
19import { BRIDGE_ERROR } from 'lib/error';
20import { BRIDGE_ERROR } from 'lib/error';
@@ -56,7 +57,11 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
56
57
57 useEffect(() => {
58 useEffect(() => {
58 (async () => {
59 (async () => {
60+ if (
59− if (!Just.hasInstance(wallet) || !Just.hasInstance(web3)) {
61+ !Just.hasInstance(wallet) ||
62+ !Just.hasInstance(web3) ||
63+ NONCUSTODIAL_WALLETS.has(walletType)
64+ ) {
60 return;
65 return;
61 }
66 }
62 const _wallet = wallet.value;
67 const _wallet = wallet.value;
@@ -158,6 +163,9 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
158 setNetworkRevision,
163 setNetworkRevision,
159 //
164 //
160 resetWallet,
165 resetWallet,
166+ //
167+ authToken,
168+ setAuthToken,
161 };
169 };
162}
170}
163
171
src/views/Login/Ledger.js+13−3
@@ -19,6 +19,7 @@ import {
19import { WALLET_TYPES } from 'lib/wallet';
19import { WALLET_TYPES } from 'lib/wallet';
20import useLoginView from 'lib/useLoginView';
20import useLoginView from 'lib/useLoginView';
21import useBreakpoints from 'lib/useBreakpoints';
21import useBreakpoints from 'lib/useBreakpoints';
22+
22import {
23import {
23 composeValidator,
24 composeValidator,
24 buildCheckboxValidator,
25 buildCheckboxValidator,
@@ -31,6 +32,7 @@ import SubmitButton from 'form/SubmitButton';
31import Condition from 'form/Condition';
32import Condition from 'form/Condition';
32import { FORM_ERROR } from 'final-form';
33import { FORM_ERROR } from 'final-form';
33import { HdPathInput } from 'form/Inputs';
34import { HdPathInput } from 'form/Inputs';
35+import { getAuthToken } from 'lib/authToken';
34
36
35const PATH_OPTIONS = [
37const PATH_OPTIONS = [
36 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
38 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
@@ -45,7 +47,7 @@ const ACCOUNT_OPTIONS = times(20, i => ({
45export default function Ledger({ className, goHome }) {
47export default function Ledger({ className, goHome }) {
46 useLoginView(WALLET_TYPES.LEDGER);
48 useLoginView(WALLET_TYPES.LEDGER);
47
49
48− const { setWallet, setWalletHdPath } = useWallet();
50+ const { setWallet, setWalletHdPath, setAuthToken } = useWallet();
49
51
50 const validate = useMemo(
52 const validate = useMemo(
51 () =>
53 () =>
@@ -70,14 +72,21 @@ export default function Ledger({ className, goHome }) {
70 const chainCode = Buffer.from(info.chainCode, 'hex');
72 const chainCode = Buffer.from(info.chainCode, 'hex');
71 const pub = secp256k1.publicKeyConvert(publicKey, true);
73 const pub = secp256k1.publicKeyConvert(publicKey, true);
72 const hd = bip32.fromPublicKey(pub, chainCode);
74 const hd = bip32.fromPublicKey(pub, chainCode);
75+ const authToken = await getAuthToken({
76+ wallet: hd,
77+ walletType: WALLET_TYPES.LEDGER,
78+ walletHdPath: addHdPrefix(values.hdpath),
79+ });
73
80
81+ setAuthToken(authToken);
74 setWallet(Just(hd));
82 setWallet(Just(hd));
75 setWalletHdPath(addHdPrefix(values.hdpath));
83 setWalletHdPath(addHdPrefix(values.hdpath));
76 } catch (error) {
84 } catch (error) {
85+ console.error(error);
77 return { [FORM_ERROR]: error.message };
86 return { [FORM_ERROR]: error.message };
78 }
87 }
79 },
88 },
80− [setWallet, setWalletHdPath]
89+ [setAuthToken, setWallet, setWalletHdPath]
81 );
90 );
82
91
83 const onValues = useCallback(({ valid, values, form }) => {
92 const onValues = useCallback(({ valid, values, form }) => {
@@ -154,7 +163,8 @@ export default function Ledger({ className, goHome }) {
154 Connect and authenticate to your Ledger, and then open the "Ethereum"
163 Connect and authenticate to your Ledger, and then open the "Ethereum"
155 application. If you're running on older firmware, make sure the "browser
164 application. If you're running on older firmware, make sure the "browser
156 support" option is turned on. To sign transactions, you'll also need to
165 support" option is turned on. To sign transactions, you'll also need to
157− enable the "contract data" option.
166+ enable the "contract data" option. You will be prompted to sign a
167+ message. This allows Bridge to operate correctly.
158 </Grid.Item>
168 </Grid.Item>
159
169
160 <BridgeForm
170 <BridgeForm
src/views/Login/Metamask.js+26−6
@@ -1,26 +1,46 @@
1import React, { useCallback } from 'react';
1import React, { useCallback } from 'react';
2import { Grid, Text, LinkButton, H5 } from 'indigo-react';
2import { Grid, Text, LinkButton, H5 } from 'indigo-react';
3import { Just } from 'folktale/maybe';
3import { Just } from 'folktale/maybe';
4+import { FORM_ERROR } from 'final-form';
4
5
5import { useWallet } from 'store/wallet';
6import { useWallet } from 'store/wallet';
7+import { useNetwork } from 'store/network';
6
8
7import { WALLET_TYPES } from 'lib/wallet';
9import { WALLET_TYPES } from 'lib/wallet';
8import { MetamaskWallet } from 'lib/metamask';
10import { MetamaskWallet } from 'lib/metamask';
11+import { getAuthToken } from 'lib/authToken';
9import useLoginView from 'lib/useLoginView';
12import useLoginView from 'lib/useLoginView';
13+import * as need from 'lib/need';
10
14
11import SubmitButton from 'form/SubmitButton';
15import SubmitButton from 'form/SubmitButton';
12import BridgeForm from 'form/BridgeForm';
16import BridgeForm from 'form/BridgeForm';
13
17
14export default function Metamask({ className, goHome }) {
18export default function Metamask({ className, goHome }) {
15 useLoginView(WALLET_TYPES.METAMASK);
19 useLoginView(WALLET_TYPES.METAMASK);
16− const { setWallet, setWalletType } = useWallet();
20+ const { setWallet, setWalletType, setAuthToken } = useWallet();
21+
22+ const { web3 } = useNetwork();
23+ const _web3 = need.web3(web3);
17
24
18 const onSubmit = useCallback(async () => {
25 const onSubmit = useCallback(async () => {
26+ try {
19− const accounts = await window.ethereum.enable();
27+ const accounts = await window.ethereum.enable();
20− const wallet = new MetamaskWallet(accounts[0]);
28+ const wallet = new MetamaskWallet(accounts[0]);
29+
30+ const authToken = await getAuthToken({
31+ wallet,
32+ walletType: WALLET_TYPES.METAMASK,
33+ web3: _web3,
34+ });
35+
36+ setAuthToken(authToken);
21− setWallet(Just(wallet));
37+ setWallet(Just(wallet));
22− setWalletType(WALLET_TYPES.METAMASK);
38+ setWalletType(WALLET_TYPES.METAMASK);
39+ } catch (e) {
40+ console.error(e);
41+ return { [FORM_ERROR]: e.message };
42+ }
23− }, [setWallet, setWalletType]);
43+ }, [_web3, setAuthToken, setWallet, setWalletType]);
24
44
25 return (
45 return (
26 <Grid className={className}>
46 <Grid className={className}>