on master help sign in
add ledger support to auth token functionality
commit2988d5f0ff4ec76ab13fb3c4c087b10341bee514 authorLiam Fitzgerald <liam@urizen.co> dateFri 27 Mar 2020, 14:02 (7 years ago) parentb578685
6 files changed+82−11
src/lib/authToken.js+20−1 src/lib/ledger.js+8 src/lib/wallet.js+6 src/store/wallet.js+9−1 src/views/Login/Ledger.js+13−3 src/views/Login/Metamask.js+26−6
src/lib/authToken.js+20−1
@@ -1,6 +1,7 @@
11import * as secp256k1 from 'secp256k1';
22
33import { keccak256, WALLET_TYPES } from './wallet';
4+import { ledgerSignMessage } from './ledger';
45
56const MESSAGE = 'Bridge Authentication Token';
67
@@ -18,10 +19,28 @@ function signMessage(privateKey) {
1819 return ethSignature;
1920}
2021
21−export const getAuthToken = ({ wallet, walletType, walletHdPath, web3 }) => {
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+
31+export const getAuthToken = async ({
32+ wallet,
33+ walletType,
34+ walletHdPath,
35+ web3,
36+}) => {
2237 if (walletType === WALLET_TYPES.METAMASK) {
2338 return web3.eth.personal.sign(MESSAGE, wallet.address, '');
2439 }
40+ if (walletType === WALLET_TYPES.LEDGER) {
41+ const { v, r, s } = await ledgerSignMessage(MESSAGE, walletHdPath);
42+ return signatureToHex(v, r, s);
43+ }
2544
2645 const signature = signMessage(wallet.privateKey);
2746
src/lib/ledger.js+8
@@ -22,3 +22,11 @@ export const ledgerSignTransaction = async (txn, hdpath) => {
2222
2323 return txn;
2424};
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 = {
2222 METAMASK: Symbol('METAMASK'),
2323};
2424
25+export const NONCUSTODIAL_WALLETS = new Set([
26+ WALLET_TYPES.METAMASK,
27+ WALLET_TYPES.TREZOR,
28+ WALLET_TYPES.LEDGER,
29+]);
30+
2531export function EthereumWallet(privateKey) {
2632 this.privateKey = privateKey;
2733 this.publicKey = secp256k1.publicKeyCreate(this.privateKey);
src/store/wallet.js+9−1
@@ -14,6 +14,7 @@ import {
1414 DEFAULT_HD_PATH,
1515 addressFromSecp256k1Public,
1616 walletFromMnemonic,
17+ NONCUSTODIAL_WALLETS,
1718} from 'lib/wallet';
1819import { getAuthToken } from 'lib/authToken';
1920import { BRIDGE_ERROR } from 'lib/error';
@@ -56,7 +57,11 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
5657
5758 useEffect(() => {
5859 (async () => {
59− if (!Just.hasInstance(wallet) || !Just.hasInstance(web3)) {
60+ if (
61+ !Just.hasInstance(wallet) ||
62+ !Just.hasInstance(web3) ||
63+ NONCUSTODIAL_WALLETS.has(walletType)
64+ ) {
6065 return;
6166 }
6267 const _wallet = wallet.value;
@@ -158,6 +163,9 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
158163 setNetworkRevision,
159164 //
160165 resetWallet,
166+ //
167+ authToken,
168+ setAuthToken,
161169 };
162170}
163171
src/views/Login/Ledger.js+13−3
@@ -19,6 +19,7 @@ import {
1919import { WALLET_TYPES } from 'lib/wallet';
2020import useLoginView from 'lib/useLoginView';
2121import useBreakpoints from 'lib/useBreakpoints';
22+
2223import {
2324 composeValidator,
2425 buildCheckboxValidator,
@@ -31,6 +32,7 @@ import SubmitButton from 'form/SubmitButton';
3132import Condition from 'form/Condition';
3233import { FORM_ERROR } from 'final-form';
3334import { HdPathInput } from 'form/Inputs';
35+import { getAuthToken } from 'lib/authToken';
3436
3537const PATH_OPTIONS = [
3638 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
@@ -45,7 +47,7 @@ const ACCOUNT_OPTIONS = times(20, i => ({
4547export default function Ledger({ className, goHome }) {
4648 useLoginView(WALLET_TYPES.LEDGER);
4749
48− const { setWallet, setWalletHdPath } = useWallet();
50+ const { setWallet, setWalletHdPath, setAuthToken } = useWallet();
4951
5052 const validate = useMemo(
5153 () =>
@@ -70,14 +72,21 @@ export default function Ledger({ className, goHome }) {
7072 const chainCode = Buffer.from(info.chainCode, 'hex');
7173 const pub = secp256k1.publicKeyConvert(publicKey, true);
7274 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+ });
7380
81+ setAuthToken(authToken);
7482 setWallet(Just(hd));
7583 setWalletHdPath(addHdPrefix(values.hdpath));
7684 } catch (error) {
85+ console.error(error);
7786 return { [FORM_ERROR]: error.message };
7887 }
7988 },
80− [setWallet, setWalletHdPath]
89+ [setAuthToken, setWallet, setWalletHdPath]
8190 );
8291
8392 const onValues = useCallback(({ valid, values, form }) => {
@@ -154,7 +163,8 @@ export default function Ledger({ className, goHome }) {
154163 Connect and authenticate to your Ledger, and then open the "Ethereum"
155164 application. If you're running on older firmware, make sure the "browser
156165 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.
158168 </Grid.Item>
159169
160170 <BridgeForm
src/views/Login/Metamask.js+26−6
@@ -1,26 +1,46 @@
11import React, { useCallback } from 'react';
22import { Grid, Text, LinkButton, H5 } from 'indigo-react';
33import { Just } from 'folktale/maybe';
4+import { FORM_ERROR } from 'final-form';
45
56import { useWallet } from 'store/wallet';
7+import { useNetwork } from 'store/network';
68
79import { WALLET_TYPES } from 'lib/wallet';
810import { MetamaskWallet } from 'lib/metamask';
11+import { getAuthToken } from 'lib/authToken';
912import useLoginView from 'lib/useLoginView';
13+import * as need from 'lib/need';
1014
1115import SubmitButton from 'form/SubmitButton';
1216import BridgeForm from 'form/BridgeForm';
1317
1418export default function Metamask({ className, goHome }) {
1519 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);
1724
1825 const onSubmit = useCallback(async () => {
19− const accounts = await window.ethereum.enable();
20− const wallet = new MetamaskWallet(accounts[0]);
21− setWallet(Just(wallet));
22− setWalletType(WALLET_TYPES.METAMASK);
23− }, [setWallet, setWalletType]);
26+ try {
27+ const accounts = await window.ethereum.enable();
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);
37+ setWallet(Just(wallet));
38+ setWalletType(WALLET_TYPES.METAMASK);
39+ } catch (e) {
40+ console.error(e);
41+ return { [FORM_ERROR]: e.message };
42+ }
43+ }, [_web3, setAuthToken, setWallet, setWalletType]);
2444
2545 return (
2646 <Grid className={className}>