Wallet Decrypt - Ledger (#238)
* add static vendor js libraries * add ledger config * add ledger components * add ledger wallet * bugfix: trezor, set dPath on change * add rlp type package, update types * change address to public * update tslint script to exclude all files in directory * revert to private address and use getAddress() * remove unnecessary eslint line out of library files * remove IWallet import * Fix ts errors * Remove version controlled vendor files from DLL
This commit is contained in:
parent
81c817600f
commit
aac0176ca2
|
@ -0,0 +1,26 @@
|
||||||
|
.LedgerDecrypt {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 30px;
|
||||||
|
|
||||||
|
&-decrypt {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-help {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
opacity: 0;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
&.is-showing {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-buy {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,150 @@
|
||||||
|
import './LedgerNano.scss';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import translate from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
|
import DeterministicWalletsModal from './DeterministicWalletsModal';
|
||||||
|
import LedgerWallet from 'libs/wallet/ledger';
|
||||||
|
import Ledger3 from 'vendor/ledger3';
|
||||||
|
import LedgerEth from 'vendor/ledger-eth';
|
||||||
|
import DPATHS from 'config/dpaths';
|
||||||
|
|
||||||
|
const DEFAULT_PATH = DPATHS.LEDGER[0].value;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onUnlock(param: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
publicKey: string;
|
||||||
|
chainCode: string;
|
||||||
|
dPath: string;
|
||||||
|
error: string | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class LedgerNanoSDecrypt extends Component<Props, State> {
|
||||||
|
public state: State = {
|
||||||
|
publicKey: '',
|
||||||
|
chainCode: '',
|
||||||
|
dPath: DEFAULT_PATH,
|
||||||
|
error: null,
|
||||||
|
isLoading: false
|
||||||
|
};
|
||||||
|
|
||||||
export default class LedgerNanoSDecrypt extends Component {
|
|
||||||
public render() {
|
public render() {
|
||||||
|
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
|
||||||
|
const showErr = error ? 'is-showing' : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="col-md-4 col-sm-6">
|
<section className="LedgerDecrypt col-md-4 col-sm-6">
|
||||||
<div id="selectedUploadKey">
|
<button
|
||||||
<h4>
|
className="LedgerDecrypt-decrypt btn btn-primary btn-lg"
|
||||||
{translate('ADD_Radio_2_alt')}
|
onClick={this.handleNullConnect}
|
||||||
</h4>
|
disabled={isLoading}
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<input type="file" id="fselector" />
|
|
||||||
|
|
||||||
<a
|
|
||||||
className="btn-file marg-v-sm"
|
|
||||||
id="aria1"
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
>
|
>
|
||||||
{translate('ADD_Radio_2_short')}
|
{isLoading ? 'Unlocking...' : translate('ADD_Ledger_scan')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="LedgerDecrypt-help">
|
||||||
|
Guides:
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href="http://support.ledgerwallet.com/knowledge_base/topics/how-to-use-myetherwallet-with-ledger"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
How to use MyEtherWallet with your Nano S
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href="https://ledger.groovehq.com/knowledge_base/topics/how-to-secure-your-eth-tokens-augur-rep-dot-dot-dot-with-your-nano-s"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
How to secure your tokens with your Nano S
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={`LedgerDecrypt-error alert alert-danger ${showErr}`}>
|
||||||
|
{error || '-'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
className="LedgerDecrypt-buy btn btn-sm btn-default"
|
||||||
|
href="https://www.ledgerwallet.com/r/fa4b?path=/products/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
{translate('Don’t have a Ledger? Order one now!')}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<DeterministicWalletsModal
|
||||||
|
isOpen={!!publicKey && !!chainCode}
|
||||||
|
publicKey={publicKey}
|
||||||
|
chainCode={chainCode}
|
||||||
|
dPath={dPath}
|
||||||
|
dPaths={DPATHS.LEDGER}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
onConfirmAddress={this.handleUnlock}
|
||||||
|
onPathChange={this.handlePathChange}
|
||||||
|
walletType={translateRaw('x_Ledger')}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handlePathChange = (dPath: string) => {
|
||||||
|
this.handleConnect(dPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleConnect = (dPath: string = this.state.dPath) => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: true,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const ledger = new Ledger3('w0w');
|
||||||
|
const ethApp = new LedgerEth(ledger);
|
||||||
|
|
||||||
|
ethApp.getAddress(
|
||||||
|
dPath,
|
||||||
|
(res, err) => {
|
||||||
|
if (err) {
|
||||||
|
err = ethApp.getError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
this.setState({
|
||||||
|
publicKey: res.publicKey,
|
||||||
|
chainCode: res.chainCode,
|
||||||
|
isLoading: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
error: err,
|
||||||
|
isLoading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleCancel = () => {
|
||||||
|
this.setState({
|
||||||
|
publicKey: '',
|
||||||
|
chainCode: '',
|
||||||
|
dPath: DEFAULT_PATH
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleUnlock = (address: string, index: number) => {
|
||||||
|
this.props.onUnlock(new LedgerWallet(address, this.state.dPath, index));
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleNullConnect = (): void => {
|
||||||
|
return this.handleConnect();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ export default class TrezorDecrypt extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handlePathChange = (dPath: string) => {
|
private handlePathChange = (dPath: string) => {
|
||||||
|
this.setState({ dPath });
|
||||||
this.handleConnect(dPath);
|
this.handleConnect(dPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,9 @@ const WALLETS = {
|
||||||
'ledger-nano-s': {
|
'ledger-nano-s': {
|
||||||
lid: 'x_Ledger',
|
lid: 'x_Ledger',
|
||||||
component: LedgerNanoSDecrypt,
|
component: LedgerNanoSDecrypt,
|
||||||
disabled: true
|
initialParams: {},
|
||||||
|
unlock: setWallet,
|
||||||
|
disabled: false
|
||||||
},
|
},
|
||||||
trezor: {
|
trezor: {
|
||||||
lid: 'x_Trezor',
|
lid: 'x_Trezor',
|
||||||
|
@ -120,9 +122,7 @@ export class WalletDecrypt extends Component<Props, State> {
|
||||||
onChange={this.handleDecryptionChoiceChange}
|
onChange={this.handleDecryptionChoiceChange}
|
||||||
disabled={wallet.disabled}
|
disabled={wallet.disabled}
|
||||||
/>
|
/>
|
||||||
<span id={`${key}-label`}>
|
<span id={`${key}-label`}>{translate(wallet.lid)}</span>
|
||||||
{translate(wallet.lid)}
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -149,19 +149,15 @@ export class WalletDecrypt extends Component<Props, State> {
|
||||||
return (
|
return (
|
||||||
<article className="Tab-content-pane row">
|
<article className="Tab-content-pane row">
|
||||||
<section className="col-md-4 col-sm-6">
|
<section className="col-md-4 col-sm-6">
|
||||||
<h4>
|
<h4>{translate('decrypt_Access')}</h4>
|
||||||
{translate('decrypt_Access')}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
{this.buildWalletOptions()}
|
{this.buildWalletOptions()}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{decryptionComponent}
|
{decryptionComponent}
|
||||||
{!!(this.state.value as PrivateKeyValue).valid &&
|
{!!(this.state.value as PrivateKeyValue).valid && (
|
||||||
<section className="col-md-4 col-sm-6">
|
<section className="col-md-4 col-sm-6">
|
||||||
<h4 id="uploadbtntxt-wallet">
|
<h4 id="uploadbtntxt-wallet">{translate('ADD_Label_6')}</h4>
|
||||||
{translate('ADD_Label_6')}
|
|
||||||
</h4>
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<a
|
<a
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@ -172,7 +168,8 @@ export class WalletDecrypt extends Component<Props, State> {
|
||||||
{translate('ADD_Label_6_short')}
|
{translate('ADD_Label_6_short')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>}
|
</section>
|
||||||
|
)}
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,10 @@ const MNEMONIC = [
|
||||||
EXPANSE
|
EXPANSE
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const LEDGER = [ETH_LEDGER, ETC_LEDGER, TESTNET];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
TREZOR,
|
TREZOR,
|
||||||
MNEMONIC
|
MNEMONIC,
|
||||||
|
LEDGER
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { IWallet } from './IWallet';
|
|
||||||
|
|
||||||
export default class DeterministicWallet {
|
export default class DeterministicWallet {
|
||||||
private address: string;
|
private address: string;
|
||||||
private dPath: string;
|
private dPath: string;
|
||||||
|
|
|
@ -5,3 +5,4 @@ export { default as PresaleWallet } from './presale';
|
||||||
export { default as MewV1Wallet } from './mewv1';
|
export { default as MewV1Wallet } from './mewv1';
|
||||||
export { default as UtcWallet } from './utc';
|
export { default as UtcWallet } from './utc';
|
||||||
export { default as MnemonicWallet } from './mnemonic';
|
export { default as MnemonicWallet } from './mnemonic';
|
||||||
|
export { default as LedgerWallet } from './ledger';
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import Ledger3 from 'vendor/ledger3';
|
||||||
|
import LedgerEth from 'vendor/ledger-eth';
|
||||||
|
import EthTx from 'ethereumjs-tx';
|
||||||
|
import { addHexPrefix, rlp } from 'ethereumjs-util';
|
||||||
|
import DeterministicWallet from './deterministic';
|
||||||
|
import { IWallet } from './IWallet';
|
||||||
|
import { RawTransaction } from 'libs/transaction';
|
||||||
|
|
||||||
|
export default class LedgerWallet extends DeterministicWallet
|
||||||
|
implements IWallet {
|
||||||
|
private ledger: any;
|
||||||
|
private ethApp: any;
|
||||||
|
|
||||||
|
constructor(address: string, dPath: string, index: number) {
|
||||||
|
super(address, dPath, index);
|
||||||
|
this.ledger = new Ledger3('w0w');
|
||||||
|
this.ethApp = new LedgerEth(this.ledger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// modeled after
|
||||||
|
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/uiFuncs.js#L58
|
||||||
|
public signRawTransaction(rawTx: RawTransaction): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const eTx = new EthTx({
|
||||||
|
...rawTx,
|
||||||
|
v: Buffer.from([rawTx.chainId]),
|
||||||
|
r: 0,
|
||||||
|
s: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ethApp.signTransaction(
|
||||||
|
this.getPath(),
|
||||||
|
rlp.encode(eTx.raw).toString('hex'),
|
||||||
|
(result, error) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(this.ethApp.getError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const txToSerialize = {
|
||||||
|
...rawTx,
|
||||||
|
v: addHexPrefix(result.v),
|
||||||
|
r: addHexPrefix(result.r),
|
||||||
|
s: addHexPrefix(result.s)
|
||||||
|
};
|
||||||
|
|
||||||
|
const serializedTx = new EthTx(txToSerialize)
|
||||||
|
.serialize()
|
||||||
|
.toString('hex');
|
||||||
|
|
||||||
|
resolve(addHexPrefix(serializedTx));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// modeled after
|
||||||
|
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/controllers/signMsgCtrl.js#L53
|
||||||
|
public signMessage(msg: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const msgHex = Buffer.from(msg).toString('hex');
|
||||||
|
|
||||||
|
this.ethApp.signPersonalMessage_async(
|
||||||
|
this.getPath(),
|
||||||
|
msgHex,
|
||||||
|
async (signed, error) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(this.ethApp.getError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const combined = signed.r + signed.s + signed.v;
|
||||||
|
const combinedHex = combined.toString('hex');
|
||||||
|
const signedMsg = JSON.stringify(
|
||||||
|
{
|
||||||
|
address: await this.getAddress(),
|
||||||
|
msg,
|
||||||
|
sig: addHexPrefix(combinedHex),
|
||||||
|
version: '2'
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
resolve(signedMsg);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,8 @@ declare module 'ethereumjs-tx' {
|
||||||
|
|
||||||
export = ITx;
|
export = ITx;
|
||||||
class ITx {
|
class ITx {
|
||||||
|
public raw: Buffer;
|
||||||
|
|
||||||
constructor(data: Data);
|
constructor(data: Data);
|
||||||
/**
|
/**
|
||||||
* If the tx's `to` is to the creation address
|
* If the tx's `to` is to the creation address
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
declare module 'ethereumjs-util' {
|
declare module 'ethereumjs-util' {
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import BN = require('bn.js');
|
import BN = require('bn.js');
|
||||||
|
export import rlp = require('rlp');
|
||||||
|
|
||||||
interface Signature {
|
interface Signature {
|
||||||
v: number;
|
v: number;
|
||||||
|
|
|
@ -0,0 +1,283 @@
|
||||||
|
/* prettier-ignore */
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
* Ledger Communication toolkit
|
||||||
|
* (c) 2016 Ledger
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// MEW - Require u2f instead of expecting it in global scope
|
||||||
|
var u2f = require('./u2f-api');
|
||||||
|
|
||||||
|
var LedgerEth = function(comm) {
|
||||||
|
this.comm = comm;
|
||||||
|
};
|
||||||
|
|
||||||
|
//MEW - Add error handling method
|
||||||
|
LedgerEth.prototype.getError = function(err) {
|
||||||
|
return err.errorCode
|
||||||
|
? u2f.getErrorByCode(err.errorCode)
|
||||||
|
: err;
|
||||||
|
};
|
||||||
|
|
||||||
|
LedgerEth.splitPath = function(path) {
|
||||||
|
var result = [];
|
||||||
|
var components = path.split('/');
|
||||||
|
components.forEach(function(element, index) {
|
||||||
|
var number = parseInt(element, 10);
|
||||||
|
if (isNaN(number)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (element.length > 1 && element[element.length - 1] == "'") {
|
||||||
|
number += 0x80000000;
|
||||||
|
}
|
||||||
|
result.push(number);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback is function(response, error)
|
||||||
|
LedgerEth.prototype.getAddress = function(
|
||||||
|
path,
|
||||||
|
callback,
|
||||||
|
boolDisplay,
|
||||||
|
boolChaincode
|
||||||
|
) {
|
||||||
|
var splitPath = LedgerEth.splitPath(path);
|
||||||
|
var buffer = new Buffer(5 + 1 + splitPath.length * 4);
|
||||||
|
buffer[0] = 0xe0;
|
||||||
|
buffer[1] = 0x02;
|
||||||
|
buffer[2] = boolDisplay ? 0x01 : 0x00;
|
||||||
|
buffer[3] = boolChaincode ? 0x01 : 0x00;
|
||||||
|
buffer[4] = 1 + splitPath.length * 4;
|
||||||
|
buffer[5] = splitPath.length;
|
||||||
|
splitPath.forEach(function(element, index) {
|
||||||
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
||||||
|
});
|
||||||
|
var self = this;
|
||||||
|
var localCallback = function(response, error) {
|
||||||
|
if (typeof error != 'undefined') {
|
||||||
|
callback(undefined, error);
|
||||||
|
} else {
|
||||||
|
var result = {};
|
||||||
|
response = new Buffer(response, 'hex');
|
||||||
|
var sw = response.readUInt16BE(response.length - 2);
|
||||||
|
if (sw != 0x9000) {
|
||||||
|
callback(
|
||||||
|
undefined,
|
||||||
|
'Invalid status ' +
|
||||||
|
sw.toString(16) +
|
||||||
|
'. Check to make sure the right application is selected ?'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var publicKeyLength = response[0];
|
||||||
|
var addressLength = response[1 + publicKeyLength];
|
||||||
|
result['publicKey'] = response
|
||||||
|
.slice(1, 1 + publicKeyLength)
|
||||||
|
.toString('hex');
|
||||||
|
result['address'] =
|
||||||
|
'0x' +
|
||||||
|
response
|
||||||
|
.slice(
|
||||||
|
1 + publicKeyLength + 1,
|
||||||
|
1 + publicKeyLength + 1 + addressLength
|
||||||
|
)
|
||||||
|
.toString('ascii');
|
||||||
|
if (boolChaincode) {
|
||||||
|
result['chainCode'] = response
|
||||||
|
.slice(
|
||||||
|
1 + publicKeyLength + 1 + addressLength,
|
||||||
|
1 + publicKeyLength + 1 + addressLength + 32
|
||||||
|
)
|
||||||
|
.toString('hex');
|
||||||
|
}
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.comm.exchange(buffer.toString('hex'), localCallback);
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback is function(response, error)
|
||||||
|
LedgerEth.prototype.signTransaction = function(path, rawTxHex, callback) {
|
||||||
|
var splitPath = LedgerEth.splitPath(path);
|
||||||
|
var offset = 0;
|
||||||
|
var rawTx = new Buffer(rawTxHex, 'hex');
|
||||||
|
var apdus = [];
|
||||||
|
while (offset != rawTx.length) {
|
||||||
|
var maxChunkSize = offset == 0 ? 150 - 1 - splitPath.length * 4 : 150;
|
||||||
|
var chunkSize =
|
||||||
|
offset + maxChunkSize > rawTx.length
|
||||||
|
? rawTx.length - offset
|
||||||
|
: maxChunkSize;
|
||||||
|
var buffer = new Buffer(
|
||||||
|
offset == 0 ? 5 + 1 + splitPath.length * 4 + chunkSize : 5 + chunkSize
|
||||||
|
);
|
||||||
|
buffer[0] = 0xe0;
|
||||||
|
buffer[1] = 0x04;
|
||||||
|
buffer[2] = offset == 0 ? 0x00 : 0x80;
|
||||||
|
buffer[3] = 0x00;
|
||||||
|
buffer[4] = offset == 0 ? 1 + splitPath.length * 4 + chunkSize : chunkSize;
|
||||||
|
if (offset == 0) {
|
||||||
|
buffer[5] = splitPath.length;
|
||||||
|
splitPath.forEach(function(element, index) {
|
||||||
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
||||||
|
});
|
||||||
|
rawTx.copy(buffer, 6 + 4 * splitPath.length, offset, offset + chunkSize);
|
||||||
|
} else {
|
||||||
|
rawTx.copy(buffer, 5, offset, offset + chunkSize);
|
||||||
|
}
|
||||||
|
apdus.push(buffer.toString('hex'));
|
||||||
|
offset += chunkSize;
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
var localCallback = function(response, error) {
|
||||||
|
if (typeof error != 'undefined') {
|
||||||
|
callback(undefined, error);
|
||||||
|
} else {
|
||||||
|
response = new Buffer(response, 'hex');
|
||||||
|
var sw = response.readUInt16BE(response.length - 2);
|
||||||
|
if (sw != 0x9000) {
|
||||||
|
callback(
|
||||||
|
undefined,
|
||||||
|
'Invalid status ' +
|
||||||
|
sw.toString(16) +
|
||||||
|
'. Check to make sure contract data is on ?'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (apdus.length == 0) {
|
||||||
|
var result = {};
|
||||||
|
result['v'] = response.slice(0, 1).toString('hex');
|
||||||
|
result['r'] = response.slice(1, 1 + 32).toString('hex');
|
||||||
|
result['s'] = response.slice(1 + 32, 1 + 32 + 32).toString('hex');
|
||||||
|
callback(result);
|
||||||
|
} else {
|
||||||
|
self.comm.exchange(apdus.shift(), localCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.comm.exchange(apdus.shift(), localCallback);
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback is function(response, error)
|
||||||
|
LedgerEth.prototype.getAppConfiguration = function(callback) {
|
||||||
|
var buffer = new Buffer(5);
|
||||||
|
buffer[0] = 0xe0;
|
||||||
|
buffer[1] = 0x06;
|
||||||
|
buffer[2] = 0x00;
|
||||||
|
buffer[3] = 0x00;
|
||||||
|
buffer[4] = 0x00;
|
||||||
|
var localCallback = function(response, error) {
|
||||||
|
if (typeof error != 'undefined') {
|
||||||
|
callback(undefined, error);
|
||||||
|
} else {
|
||||||
|
response = new Buffer(response, 'hex');
|
||||||
|
var result = {};
|
||||||
|
var sw = response.readUInt16BE(response.length - 2);
|
||||||
|
if (sw != 0x9000) {
|
||||||
|
callback(
|
||||||
|
undefined,
|
||||||
|
'Invalid status ' +
|
||||||
|
sw.toString(16) +
|
||||||
|
'. Check to make sure the right application is selected ?'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result['arbitraryDataEnabled'] = response[0] & 0x01;
|
||||||
|
result['version'] =
|
||||||
|
'' + response[1] + '.' + response[2] + '.' + response[3];
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.comm.exchange(buffer.toString('hex'), localCallback);
|
||||||
|
};
|
||||||
|
|
||||||
|
LedgerEth.prototype.signPersonalMessage_async = function(
|
||||||
|
path,
|
||||||
|
messageHex,
|
||||||
|
callback
|
||||||
|
) {
|
||||||
|
var splitPath = LedgerEth.splitPath(path);
|
||||||
|
var offset = 0;
|
||||||
|
var message = new Buffer(messageHex, 'hex');
|
||||||
|
var apdus = [];
|
||||||
|
var response = [];
|
||||||
|
var self = this;
|
||||||
|
while (offset != message.length) {
|
||||||
|
var maxChunkSize = offset == 0 ? 150 - 1 - splitPath.length * 4 - 4 : 150;
|
||||||
|
var chunkSize =
|
||||||
|
offset + maxChunkSize > message.length
|
||||||
|
? message.length - offset
|
||||||
|
: maxChunkSize;
|
||||||
|
var buffer = new Buffer(
|
||||||
|
offset == 0 ? 5 + 1 + splitPath.length * 4 + 4 + chunkSize : 5 + chunkSize
|
||||||
|
);
|
||||||
|
buffer[0] = 0xe0;
|
||||||
|
buffer[1] = 0x08;
|
||||||
|
buffer[2] = offset == 0 ? 0x00 : 0x80;
|
||||||
|
buffer[3] = 0x00;
|
||||||
|
buffer[4] =
|
||||||
|
offset == 0 ? 1 + splitPath.length * 4 + 4 + chunkSize : chunkSize;
|
||||||
|
if (offset == 0) {
|
||||||
|
buffer[5] = splitPath.length;
|
||||||
|
splitPath.forEach(function(element, index) {
|
||||||
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
||||||
|
});
|
||||||
|
buffer.writeUInt32BE(message.length, 6 + 4 * splitPath.length);
|
||||||
|
message.copy(
|
||||||
|
buffer,
|
||||||
|
6 + 4 * splitPath.length + 4,
|
||||||
|
offset,
|
||||||
|
offset + chunkSize
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message.copy(buffer, 5, offset, offset + chunkSize);
|
||||||
|
}
|
||||||
|
apdus.push(buffer.toString('hex'));
|
||||||
|
offset += chunkSize;
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
var localCallback = function(response, error) {
|
||||||
|
if (typeof error != 'undefined') {
|
||||||
|
callback(undefined, error);
|
||||||
|
} else {
|
||||||
|
response = new Buffer(response, 'hex');
|
||||||
|
var sw = response.readUInt16BE(response.length - 2);
|
||||||
|
if (sw != 0x9000) {
|
||||||
|
callback(
|
||||||
|
undefined,
|
||||||
|
'Invalid status ' +
|
||||||
|
sw.toString(16) +
|
||||||
|
'. Check to make sure the right application is selected ?'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (apdus.length == 0) {
|
||||||
|
var result = {};
|
||||||
|
result['v'] = response.slice(0, 1).toString('hex');
|
||||||
|
result['r'] = response.slice(1, 1 + 32).toString('hex');
|
||||||
|
result['s'] = response.slice(1 + 32, 1 + 32 + 32).toString('hex');
|
||||||
|
callback(result);
|
||||||
|
} else {
|
||||||
|
self.comm.exchange(apdus.shift(), localCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.comm.exchange(apdus.shift(), localCallback);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = LedgerEth;
|
|
@ -0,0 +1,87 @@
|
||||||
|
/* prettier-ignore */
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
* Ledger Communication toolkit
|
||||||
|
* (c) 2016 Ledger
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// MEW - Require u2f instead of expecting it in global scope
|
||||||
|
var u2f = require('./u2f-api');
|
||||||
|
|
||||||
|
var Ledger3 = function(scrambleKey, timeoutSeconds) {
|
||||||
|
this.scrambleKey = new Buffer(scrambleKey, 'ascii');
|
||||||
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
|
};
|
||||||
|
|
||||||
|
Ledger3.wrapApdu = function(apdu, key) {
|
||||||
|
var result = new Buffer(apdu.length);
|
||||||
|
for (var i = 0; i < apdu.length; i++) {
|
||||||
|
result[i] = apdu[i] ^ key[i % key.length];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert from normal to web-safe, strip trailing "="s
|
||||||
|
Ledger3.webSafe64 = function(base64) {
|
||||||
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert from web-safe to normal, add trailing "="s
|
||||||
|
Ledger3.normal64 = function(base64) {
|
||||||
|
return (
|
||||||
|
base64.replace(/\-/g, '+').replace(/_/g, '/') +
|
||||||
|
'=='.substring(0, 3 * base64.length % 4)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ledger3.prototype.u2fCallback = function(response, callback) {
|
||||||
|
if (typeof response['signatureData'] != 'undefined') {
|
||||||
|
var data = new Buffer(
|
||||||
|
Ledger3.normal64(response['signatureData']),
|
||||||
|
'base64'
|
||||||
|
);
|
||||||
|
callback(data.toString('hex', 5));
|
||||||
|
} else {
|
||||||
|
callback(undefined, response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback is function(response, error)
|
||||||
|
Ledger3.prototype.exchange = function(apduHex, callback) {
|
||||||
|
var apdu = new Buffer(apduHex, 'hex');
|
||||||
|
var keyHandle = Ledger3.wrapApdu(apdu, this.scrambleKey);
|
||||||
|
var challenge = new Buffer(
|
||||||
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
'hex'
|
||||||
|
);
|
||||||
|
var key = {};
|
||||||
|
key['version'] = 'U2F_V2';
|
||||||
|
key['keyHandle'] = Ledger3.webSafe64(keyHandle.toString('base64'));
|
||||||
|
var self = this;
|
||||||
|
var localCallback = function(result) {
|
||||||
|
self.u2fCallback(result, callback);
|
||||||
|
};
|
||||||
|
u2f.sign(
|
||||||
|
location.origin,
|
||||||
|
Ledger3.webSafe64(challenge.toString('base64')),
|
||||||
|
[key],
|
||||||
|
localCallback,
|
||||||
|
this.timeoutSeconds
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Ledger3;
|
|
@ -0,0 +1,832 @@
|
||||||
|
/* prettier-ignore */
|
||||||
|
|
||||||
|
//Copyright 2014-2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
//Use of this source code is governed by a BSD-style
|
||||||
|
//license that can be found in the LICENSE file or at
|
||||||
|
//https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview The U2F api.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for the U2F api.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
var u2f = u2f || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIDO U2F Javascript API Version
|
||||||
|
* @number
|
||||||
|
*/
|
||||||
|
var js_api_version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The U2F extension id
|
||||||
|
* @const {string}
|
||||||
|
*/
|
||||||
|
// The Chrome packaged app extension ID.
|
||||||
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
|
// the package Chrome app and does not require installing the U2F Chrome extension.
|
||||||
|
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
|
||||||
|
// The U2F Chrome extension ID.
|
||||||
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
|
// the U2F Chrome extension to authenticate.
|
||||||
|
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message types for messsages to/from the extension
|
||||||
|
* @const
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
u2f.MessageTypes = {
|
||||||
|
U2F_REGISTER_REQUEST: 'u2f_register_request',
|
||||||
|
U2F_REGISTER_RESPONSE: 'u2f_register_response',
|
||||||
|
U2F_SIGN_REQUEST: 'u2f_sign_request',
|
||||||
|
U2F_SIGN_RESPONSE: 'u2f_sign_response',
|
||||||
|
U2F_GET_API_VERSION_REQUEST: 'u2f_get_api_version_request',
|
||||||
|
U2F_GET_API_VERSION_RESPONSE: 'u2f_get_api_version_response'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response status codes
|
||||||
|
* @const
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
u2f.ErrorCodes = {
|
||||||
|
OK: 0,
|
||||||
|
OTHER_ERROR: 1,
|
||||||
|
BAD_REQUEST: 2,
|
||||||
|
CONFIGURATION_UNSUPPORTED: 3,
|
||||||
|
DEVICE_INELIGIBLE: 4,
|
||||||
|
TIMEOUT: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
u2f.getErrorByCode = function(code) {
|
||||||
|
for (var prop in u2f.ErrorCodes) {
|
||||||
|
if (u2f.ErrorCodes.hasOwnProperty(prop)) {
|
||||||
|
if (u2f.ErrorCodes[prop] === code) return prop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message for registration requests
|
||||||
|
* @typedef {{
|
||||||
|
* type: u2f.MessageTypes,
|
||||||
|
* appId: ?string,
|
||||||
|
* timeoutSeconds: ?number,
|
||||||
|
* requestId: ?number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.U2fRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message for registration responses
|
||||||
|
* @typedef {{
|
||||||
|
* type: u2f.MessageTypes,
|
||||||
|
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
|
||||||
|
* requestId: ?number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.U2fResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error object for responses
|
||||||
|
* @typedef {{
|
||||||
|
* errorCode: u2f.ErrorCodes,
|
||||||
|
* errorMessage: ?string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.Error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
|
||||||
|
*/
|
||||||
|
u2f.Transport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {Array<u2f.Transport>}
|
||||||
|
*/
|
||||||
|
u2f.Transports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* challenge: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* appId: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.SignRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a sign response.
|
||||||
|
* @typedef {{
|
||||||
|
* keyHandle: string,
|
||||||
|
* signatureData: string,
|
||||||
|
* clientData: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.SignResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registration request.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* challenge: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisterRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registration response.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* transports: Transports,
|
||||||
|
* appId: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisterResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registered key.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* transports: ?Transports,
|
||||||
|
* appId: ?string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisteredKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a get API register response.
|
||||||
|
* @typedef {{
|
||||||
|
* js_api_version: number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.GetJsApiVersionResponse;
|
||||||
|
|
||||||
|
//Low level MessagePort API support
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a MessagePort to the U2F extension using the
|
||||||
|
* available mechanisms.
|
||||||
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
|
*/
|
||||||
|
u2f.getMessagePort = function(callback) {
|
||||||
|
if (typeof chrome != 'undefined' && chrome.runtime) {
|
||||||
|
// The actual message here does not matter, but we need to get a reply
|
||||||
|
// for the callback to run. Thus, send an empty signature request
|
||||||
|
// in order to get a failure response.
|
||||||
|
var msg = {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
signRequests: []
|
||||||
|
};
|
||||||
|
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
|
||||||
|
if (!chrome.runtime.lastError) {
|
||||||
|
// We are on a whitelisted origin and can talk directly
|
||||||
|
// with the extension.
|
||||||
|
u2f.getChromeRuntimePort_(callback);
|
||||||
|
} else {
|
||||||
|
// chrome.runtime was available, but we couldn't message
|
||||||
|
// the extension directly, use iframe
|
||||||
|
u2f.getIframePort_(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (u2f.isAndroidChrome_()) {
|
||||||
|
u2f.getAuthenticatorPort_(callback);
|
||||||
|
} else if (u2f.isIosChrome_()) {
|
||||||
|
u2f.getIosPort_(callback);
|
||||||
|
} else {
|
||||||
|
// chrome.runtime was not available at all, which is normal
|
||||||
|
// when this origin doesn't have access to any extensions.
|
||||||
|
u2f.getIframePort_(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect chrome running on android based on the browser's useragent.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.isAndroidChrome_ = function() {
|
||||||
|
var userAgent = navigator.userAgent;
|
||||||
|
return (
|
||||||
|
userAgent.indexOf('Chrome') != -1 && userAgent.indexOf('Android') != -1
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect chrome running on iOS based on the browser's platform.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.isIosChrome_ = function() {
|
||||||
|
return $.inArray(navigator.platform, ['iPhone', 'iPad', 'iPod']) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects directly to the extension via chrome.runtime.connect.
|
||||||
|
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getChromeRuntimePort_ = function(callback) {
|
||||||
|
var port = chrome.runtime.connect(u2f.EXTENSION_ID, {
|
||||||
|
includeTlsChannelId: true
|
||||||
|
});
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedChromeRuntimePort_(port));
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a 'port' abstraction to the Authenticator app.
|
||||||
|
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getAuthenticatorPort_ = function(callback) {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedAuthenticatorPort_());
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a 'port' abstraction to the iOS client app.
|
||||||
|
* @param {function(u2f.WrappedIosPort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getIosPort_ = function(callback) {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedIosPort_());
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for chrome.runtime.Port that is compatible with MessagePort.
|
||||||
|
* @param {Port} port
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_ = function(port) {
|
||||||
|
this.port_ = port;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return a sign request compliant with the JS API version supported by the extension.
|
||||||
|
* @param {Array<u2f.SignRequest>} signRequests
|
||||||
|
* @param {number} timeoutSeconds
|
||||||
|
* @param {number} reqId
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
u2f.formatSignRequest_ = function(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
) {
|
||||||
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
|
// Adapt request to the 1.0 JS API
|
||||||
|
var signRequests = [];
|
||||||
|
for (var i = 0; i < registeredKeys.length; i++) {
|
||||||
|
signRequests[i] = {
|
||||||
|
version: registeredKeys[i].version,
|
||||||
|
challenge: challenge,
|
||||||
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
|
appId: appId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
signRequests: signRequests,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// JS 1.1 API
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
appId: appId,
|
||||||
|
challenge: challenge,
|
||||||
|
registeredKeys: registeredKeys,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return a register request compliant with the JS API version supported by the extension..
|
||||||
|
* @param {Array<u2f.SignRequest>} signRequests
|
||||||
|
* @param {Array<u2f.RegisterRequest>} signRequests
|
||||||
|
* @param {number} timeoutSeconds
|
||||||
|
* @param {number} reqId
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
u2f.formatRegisterRequest_ = function(
|
||||||
|
appId,
|
||||||
|
registeredKeys,
|
||||||
|
registerRequests,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
) {
|
||||||
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
|
// Adapt request to the 1.0 JS API
|
||||||
|
for (var i = 0; i < registerRequests.length; i++) {
|
||||||
|
registerRequests[i].appId = appId;
|
||||||
|
}
|
||||||
|
var signRequests = [];
|
||||||
|
for (var i = 0; i < registeredKeys.length; i++) {
|
||||||
|
signRequests[i] = {
|
||||||
|
version: registeredKeys[i].version,
|
||||||
|
challenge: registerRequests[0],
|
||||||
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
|
appId: appId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||||
|
signRequests: signRequests,
|
||||||
|
registerRequests: registerRequests,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// JS 1.1 API
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||||
|
appId: appId,
|
||||||
|
registerRequests: registerRequests,
|
||||||
|
registeredKeys: registeredKeys,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a message on the underlying channel.
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
|
||||||
|
this.port_.postMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface. Works only for the
|
||||||
|
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_.prototype.addEventListener = function(
|
||||||
|
eventName,
|
||||||
|
handler
|
||||||
|
) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name == 'message' || name == 'onmessage') {
|
||||||
|
this.port_.onMessage.addListener(function(message) {
|
||||||
|
// Emulate a minimal MessageEvent object
|
||||||
|
handler({ data: message });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('WrappedChromeRuntimePort only supports onMessage');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the Authenticator app with a MessagePort interface.
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_ = function() {
|
||||||
|
this.requestId_ = -1;
|
||||||
|
this.requestObject_ = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the Authenticator intent.
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
|
||||||
|
var intentUrl =
|
||||||
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
|
||||||
|
';S.request=' +
|
||||||
|
encodeURIComponent(JSON.stringify(message)) +
|
||||||
|
';end';
|
||||||
|
document.location = intentUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells what type of port this is.
|
||||||
|
* @return {String} port type
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
|
||||||
|
return 'WrappedAuthenticatorPort_';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(
|
||||||
|
eventName,
|
||||||
|
handler
|
||||||
|
) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name == 'message') {
|
||||||
|
var self = this;
|
||||||
|
/* Register a callback to that executes when
|
||||||
|
* chrome injects the response. */
|
||||||
|
window.addEventListener(
|
||||||
|
'message',
|
||||||
|
self.onRequestUpdate_.bind(self, handler),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('WrappedAuthenticatorPort only supports message');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked when a response is received from the Authenticator.
|
||||||
|
* @param function({data: Object}) callback
|
||||||
|
* @param {Object} message message Object
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = function(
|
||||||
|
callback,
|
||||||
|
message
|
||||||
|
) {
|
||||||
|
var messageObject = JSON.parse(message.data);
|
||||||
|
var intentUrl = messageObject['intentURL'];
|
||||||
|
|
||||||
|
var errorCode = messageObject['errorCode'];
|
||||||
|
var responseObject = null;
|
||||||
|
if (messageObject.hasOwnProperty('data')) {
|
||||||
|
responseObject /** @type {Object} */ = JSON.parse(messageObject['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({ data: responseObject });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base URL for intents to Authenticator.
|
||||||
|
* @const
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
|
||||||
|
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the iOS client app with a MessagePort interface.
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_ = function() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the iOS client app request
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.postMessage = function(message) {
|
||||||
|
var str = JSON.stringify(message);
|
||||||
|
var url = 'u2f://auth?' + encodeURI(str);
|
||||||
|
location.replace(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells what type of port this is.
|
||||||
|
* @return {String} port type
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.getPortType = function() {
|
||||||
|
return 'WrappedIosPort_';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name !== 'message') {
|
||||||
|
console.error('WrappedIosPort only supports message');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up an embedded trampoline iframe, sourced from the extension.
|
||||||
|
* @param {function(MessagePort)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getIframePort_ = function(callback) {
|
||||||
|
// Create the iframe
|
||||||
|
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
|
||||||
|
var iframe = document.createElement('iframe');
|
||||||
|
iframe.src = iframeOrigin + '/u2f-comms.html';
|
||||||
|
iframe.setAttribute('style', 'display:none');
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
|
var channel = new MessageChannel();
|
||||||
|
var ready = function(message) {
|
||||||
|
if (message.data == 'ready') {
|
||||||
|
channel.port1.removeEventListener('message', ready);
|
||||||
|
callback(channel.port1);
|
||||||
|
} else {
|
||||||
|
console.error('First event on iframe port was not "ready"');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
channel.port1.addEventListener('message', ready);
|
||||||
|
channel.port1.start();
|
||||||
|
|
||||||
|
iframe.addEventListener('load', function() {
|
||||||
|
// Deliver the port to the iframe and initialize
|
||||||
|
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//High-level JS API
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default extension response timeout in seconds.
|
||||||
|
* @const
|
||||||
|
*/
|
||||||
|
u2f.EXTENSION_TIMEOUT_SEC = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A singleton instance for a MessagePort to the extension.
|
||||||
|
* @type {MessagePort|u2f.WrappedChromeRuntimePort_}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.port_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks waiting for a port
|
||||||
|
* @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.waitingForPort_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A counter for requestIds.
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.reqCounter_ = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map from requestIds to client callbacks
|
||||||
|
* @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
|
||||||
|
* |function((u2f.Error|u2f.SignResponse)))>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.callbackMap_ = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or retrieves the MessagePort singleton to use.
|
||||||
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getPortSingleton_ = function(callback) {
|
||||||
|
if (u2f.port_) {
|
||||||
|
callback(u2f.port_);
|
||||||
|
} else {
|
||||||
|
if (u2f.waitingForPort_.length == 0) {
|
||||||
|
u2f.getMessagePort(function(port) {
|
||||||
|
u2f.port_ = port;
|
||||||
|
u2f.port_.addEventListener(
|
||||||
|
'message',
|
||||||
|
/** @type {function(Event)} */ u2f.responseHandler_
|
||||||
|
);
|
||||||
|
|
||||||
|
// Careful, here be async callbacks. Maybe.
|
||||||
|
while (u2f.waitingForPort_.length)
|
||||||
|
u2f.waitingForPort_.shift()(u2f.port_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
u2f.waitingForPort_.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles response messages from the extension.
|
||||||
|
* @param {MessageEvent.<u2f.Response>} message
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.responseHandler_ = function(message) {
|
||||||
|
var response = message.data;
|
||||||
|
var reqId = response['requestId'];
|
||||||
|
if (!reqId || !u2f.callbackMap_[reqId]) {
|
||||||
|
console.error('Unknown or missing requestId in response.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var cb = u2f.callbackMap_[reqId];
|
||||||
|
delete u2f.callbackMap_[reqId];
|
||||||
|
cb(response['responseData']);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an array of sign requests to available U2F tokens.
|
||||||
|
* If the JS API version supported by the extension is unknown, it first sends a
|
||||||
|
* message to the extension to find out the supported API version and then it sends
|
||||||
|
* the sign request.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {string=} challenge
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sign = function(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
|
if (js_api_version === undefined) {
|
||||||
|
// Send a message to get the extension to JS API version, then send the actual sign request.
|
||||||
|
u2f.getApiVersion(function(response) {
|
||||||
|
js_api_version =
|
||||||
|
response['js_api_version'] === undefined
|
||||||
|
? 0
|
||||||
|
: response['js_api_version'];
|
||||||
|
console.log('Extension JS API Version: ', js_api_version);
|
||||||
|
u2f.sendSignRequest(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We know the JS API version. Send the actual sign request in the supported API version.
|
||||||
|
u2f.sendSignRequest(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an array of sign requests to available U2F tokens.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {string=} challenge
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sendSignRequest = function(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var timeoutSeconds =
|
||||||
|
typeof opt_timeoutSeconds !== 'undefined'
|
||||||
|
? opt_timeoutSeconds
|
||||||
|
: u2f.EXTENSION_TIMEOUT_SEC;
|
||||||
|
var req = u2f.formatSignRequest_(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
);
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches register requests to available U2F tokens. An array of sign
|
||||||
|
* requests identifies already registered tokens.
|
||||||
|
* If the JS API version supported by the extension is unknown, it first sends a
|
||||||
|
* message to the extension to find out the supported API version and then it sends
|
||||||
|
* the register request.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.register = function(
|
||||||
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
|
if (js_api_version === undefined) {
|
||||||
|
// Send a message to get the extension to JS API version, then send the actual register request.
|
||||||
|
u2f.getApiVersion(function(response) {
|
||||||
|
js_api_version =
|
||||||
|
response['js_api_version'] === undefined
|
||||||
|
? 0
|
||||||
|
: response['js_api_version'];
|
||||||
|
console.log('Extension JS API Version: ', js_api_version);
|
||||||
|
u2f.sendRegisterRequest(
|
||||||
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We know the JS API version. Send the actual register request in the supported API version.
|
||||||
|
u2f.sendRegisterRequest(
|
||||||
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches register requests to available U2F tokens. An array of sign
|
||||||
|
* requests identifies already registered tokens.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sendRegisterRequest = function(
|
||||||
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var timeoutSeconds =
|
||||||
|
typeof opt_timeoutSeconds !== 'undefined'
|
||||||
|
? opt_timeoutSeconds
|
||||||
|
: u2f.EXTENSION_TIMEOUT_SEC;
|
||||||
|
var req = u2f.formatRegisterRequest_(
|
||||||
|
appId,
|
||||||
|
registeredKeys,
|
||||||
|
registerRequests,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
);
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a message to the extension to find out the supported
|
||||||
|
* JS API version.
|
||||||
|
* If the user is on a mobile phone and is thus using Google Authenticator instead
|
||||||
|
* of the Chrome extension, don't send the request and simply return 0.
|
||||||
|
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
// If we are using Android Google Authenticator or iOS client app,
|
||||||
|
// do not fire an intent to ask which JS API version to use.
|
||||||
|
if (port.getPortType) {
|
||||||
|
var apiVersion;
|
||||||
|
switch (port.getPortType()) {
|
||||||
|
case 'WrappedIosPort_':
|
||||||
|
case 'WrappedAuthenticatorPort_':
|
||||||
|
apiVersion = 1.1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
apiVersion = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
callback({ js_api_version: apiVersion });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var req = {
|
||||||
|
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
|
||||||
|
timeoutSeconds:
|
||||||
|
typeof opt_timeoutSeconds !== 'undefined'
|
||||||
|
? opt_timeoutSeconds
|
||||||
|
: u2f.EXTENSION_TIMEOUT_SEC,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
module.exports = u2f;
|
|
@ -24,4 +24,3 @@ require('qrcode');
|
||||||
require('qrcode.react');
|
require('qrcode.react');
|
||||||
require('bignumber.js');
|
require('bignumber.js');
|
||||||
require('classnames');
|
require('classnames');
|
||||||
require('./vendor/trezor-connect');
|
|
||||||
|
|
|
@ -101,6 +101,7 @@
|
||||||
"tslint-config-prettier": "^1.5.0",
|
"tslint-config-prettier": "^1.5.0",
|
||||||
"tslint-react": "^3.2.0",
|
"tslint-react": "^3.2.0",
|
||||||
"types-bn": "0.0.1",
|
"types-bn": "0.0.1",
|
||||||
|
"types-rlp": "0.0.1",
|
||||||
"typescript": "^2.5.2",
|
"typescript": "^2.5.2",
|
||||||
"url-loader": "^0.5.8",
|
"url-loader": "^0.5.8",
|
||||||
"webpack": "^3.6.0",
|
"webpack": "^3.6.0",
|
||||||
|
@ -120,7 +121,7 @@
|
||||||
"dev:https": "HTTPS=true node webpack_config/server.js",
|
"dev:https": "HTTPS=true node webpack_config/server.js",
|
||||||
"predev:https": "check-node-version --package",
|
"predev:https": "check-node-version --package",
|
||||||
"derivation-checker": "webpack --config=./webpack_config/webpack.derivation-checker.js && node ./dist/derivation-checker.js",
|
"derivation-checker": "webpack --config=./webpack_config/webpack.derivation-checker.js && node ./dist/derivation-checker.js",
|
||||||
"tslint": "tslint --project . --exclude common/vendor/*",
|
"tslint": "tslint --project . --exclude common/vendor/**/*",
|
||||||
"postinstall": "webpack --config=./webpack_config/webpack.dll.js",
|
"postinstall": "webpack --config=./webpack_config/webpack.dll.js",
|
||||||
"start": "npm run dev",
|
"start": "npm run dev",
|
||||||
"precommit": "lint-staged"
|
"precommit": "lint-staged"
|
||||||
|
|
|
@ -8,16 +8,14 @@
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"baseUrl": "./common/",
|
"baseUrl": "./common/",
|
||||||
"lib": [
|
"lib": ["es2017", "dom"],
|
||||||
"es2017",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noEmitOnError": false
|
"noEmitOnError": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./common/",
|
"./common/",
|
||||||
|
"./node_modules/types-rlp/index.d.ts",
|
||||||
"./node_modules/types-bn/index.d.ts"
|
"./node_modules/types-bn/index.d.ts"
|
||||||
],
|
],
|
||||||
"awesomeTypescriptLoaderOptions": {
|
"awesomeTypescriptLoaderOptions": {
|
||||||
|
|
Loading…
Reference in New Issue