Merge branch 'develop' into babel-minify

This commit is contained in:
Daniel Ternyak 2017-10-08 18:28:24 -07:00 committed by GitHub
commit f719aeebcf
21 changed files with 1621 additions and 76 deletions

View File

@ -1,4 +1,4 @@
@import "common/sass/variables";
@import 'common/sass/variables';
// footer
.Footer {
@ -40,6 +40,20 @@
max-width: 28rem;
}
&-modal-button {
color: #4ac8ed;
background-color: rgba(0, 0, 0, 0);
border: none;
text-align: left;
padding: 0;
margin: 0;
font-weight: 300;
transition: 500ms all ease-in-out;
&:hover {
color: #0e97c0;
}
}
p {
margin: $space-xs 0 $space-sm;
}
@ -57,7 +71,7 @@
margin: $font-size-small 0 0;
i {
margin-right: .25em;
margin-right: 0.25em;
@media (min-width: $screen-sm-min) {
margin-left: -1.5em;
@ -79,8 +93,12 @@
@media screen and (max-width: $grid-float-breakpoint) {
.row {
margin-left: -.5rem;
margin-right: -.5rem;
margin-left: -0.5rem;
margin-right: -0.5rem;
}
}
}
.Modal {
color: #000;
}

View File

@ -4,6 +4,7 @@ import React, { Component } from 'react';
import translate from 'translations';
import './index.scss';
import PreFooter from './PreFooter';
import Modal, { IButton } from 'components/ui/Modal';
const LINKS_LEFT = [
{
@ -91,8 +92,28 @@ const LINKS_SOCIAL = [
}
];
export default class Footer extends Component {
interface ComponentState {
isOpen: boolean;
}
export default class Footer extends React.Component<{}, ComponentState> {
constructor(props) {
super(props);
this.state = { isOpen: false };
}
public openModal = () => {
this.setState({ isOpen: true });
};
public closeModal = () => {
this.setState({ isOpen: false });
};
public render() {
const buttons: IButton[] = [
{ text: 'Okay', type: 'default', onClick: this.closeModal }
];
return (
<div>
<PreFooter />
@ -110,12 +131,8 @@ export default class Footer extends Component {
</a>
</p>
<p className="Footer-about-text">
<span>
{translate('FOOTER_1')}
</span>
<span>
{translate('FOOTER_1b')}
</span>
<span>{translate('FOOTER_1')}</span>
<span>{translate('FOOTER_1b')}</span>
</p>
{LINKS_LEFT.map(link => {
@ -128,6 +145,78 @@ export default class Footer extends Component {
);
})}
<button className="Footer-modal-button" onClick={this.openModal}>
Disclaimer
</button>
<Modal
isOpen={this.state.isOpen}
title="Disclaimer"
buttons={buttons}
handleClose={this.closeModal}
>
<p>
<b>Be safe & secure: </b>
<a href="https://myetherwallet.groovehq.com/knowledge_base/topics/protecting-yourself-and-your-funds">
We highly recommend that you read our guide on How to Prevent
Loss & Theft for some recommendations on how to be proactive
about your security.
</a>
</p>
<p>
<b>Always backup your keys: </b>
MyEtherWallet.com & MyEtherWallet CX are not "web wallets". You
do not create an account or give us your funds to hold onto. No
data leaves your computer / your browser. We make it easy for
you to create, save, and access your information and interact
with the blockchain.
</p>
<p>
<b>We are not responsible for any loss: </b>
Ethereum, MyEtherWallet.com & MyEtherWallet CX, and some of the
underlying Javascript libraries we use are under active
development. While we have thoroughly tested & tens of thousands
of wallets have been successfully created by people all over the
globe, there is always the possibility something unexpected
happens that causes your funds to be lost. Please do not invest
more than you are willing to lose, and please be careful.
</p>
<p>
<b>Translations of MyEtherWallet: </b>
The community has done an amazing job translating MyEtherWallet
into a variety of languages. However, MyEtherWallet can only
verify the validity and accuracy of the information provided in
English and, because of this, the English version of our website
is the official text.
</p>
<p>
<b>MIT License</b> Copyright © 2015-2017 MyEtherWallet LLC
</p>
<p>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
</p>
<p>
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
</p>
<b>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
</b>
</Modal>
<p>&copy; 2017 MyEtherWallet, LLC</p>
</div>
@ -155,12 +244,12 @@ export default class Footer extends Component {
</h5>
<ul>
<li>
{' '}ETH:{' '}
<span className="mono wrap">{donationAddressMap.ETH}</span>
{' '}
ETH: <span className="mono wrap">{donationAddressMap.ETH}</span>
</li>
<li>
{' '}BTC:{' '}
<span className="mono wrap">{donationAddressMap.BTC}</span>
{' '}
BTC: <span className="mono wrap">{donationAddressMap.BTC}</span>
</li>
</ul>
</div>

View File

@ -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;
}
}

View File

@ -1,29 +1,150 @@
import './LedgerNano.scss';
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() {
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
const showErr = error ? 'is-showing' : '';
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>
{translate('ADD_Radio_2_alt')}
</h4>
<div className="form-group">
<input type="file" id="fselector" />
<section className="LedgerDecrypt col-md-4 col-sm-6">
<button
className="LedgerDecrypt-decrypt btn btn-primary btn-lg"
onClick={this.handleNullConnect}
disabled={isLoading}
>
{isLoading ? 'Unlocking...' : translate('ADD_Ledger_scan')}
</button>
<div className="LedgerDecrypt-help">
Guides:
<div>
<a
className="btn-file marg-v-sm"
id="aria1"
tabIndex={0}
role="button"
href="http://support.ledgerwallet.com/knowledge_base/topics/how-to-use-myetherwallet-with-ledger"
target="_blank"
rel="noopener"
>
{translate('ADD_Radio_2_short')}
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>
</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('Dont 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>
);
}
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();
};
}

View File

@ -81,6 +81,7 @@ export default class TrezorDecrypt extends Component<Props, State> {
}
private handlePathChange = (dPath: string) => {
this.setState({ dPath });
this.handleConnect(dPath);
};

View File

@ -51,7 +51,9 @@ const WALLETS = {
'ledger-nano-s': {
lid: 'x_Ledger',
component: LedgerNanoSDecrypt,
disabled: true
initialParams: {},
unlock: setWallet,
disabled: false
},
trezor: {
lid: 'x_Trezor',
@ -120,9 +122,7 @@ export class WalletDecrypt extends Component<Props, State> {
onChange={this.handleDecryptionChoiceChange}
disabled={wallet.disabled}
/>
<span id={`${key}-label`}>
{translate(wallet.lid)}
</span>
<span id={`${key}-label`}>{translate(wallet.lid)}</span>
</label>
);
});
@ -149,19 +149,15 @@ export class WalletDecrypt extends Component<Props, State> {
return (
<article className="Tab-content-pane row">
<section className="col-md-4 col-sm-6">
<h4>
{translate('decrypt_Access')}
</h4>
<h4>{translate('decrypt_Access')}</h4>
{this.buildWalletOptions()}
</section>
{decryptionComponent}
{!!(this.state.value as PrivateKeyValue).valid &&
{!!(this.state.value as PrivateKeyValue).valid && (
<section className="col-md-4 col-sm-6">
<h4 id="uploadbtntxt-wallet">
{translate('ADD_Label_6')}
</h4>
<h4 id="uploadbtntxt-wallet">{translate('ADD_Label_6')}</h4>
<div className="form-group">
<a
tabIndex={0}
@ -172,7 +168,8 @@ export class WalletDecrypt extends Component<Props, State> {
{translate('ADD_Label_6_short')}
</a>
</div>
</section>}
</section>
)}
</article>
);
}

View File

@ -1,12 +1,11 @@
@import "common/sass/variables";
@import "common/sass/mixins";
@import 'common/sass/variables';
@import 'common/sass/mixins';
$m-background: #fff;
$m-window-padding: 20px;
$m-header-padding: 15px;
$m-header-height: 62px;
$m-content-padding: 20px;
$m-footer-height: 58px;
$m-close-size: 26px;
$m-anim-speed: 400ms;
@ -117,7 +116,6 @@ $m-anim-speed: 400ms;
}
&-footer {
height: $m-footer-height;
padding: 7px 10px;
border-top: 1px solid $gray-lighter;
background: $m-background;
@ -129,4 +127,8 @@ $m-anim-speed: 400ms;
min-width: 100px;
}
}
}
@media(max-width: 820px) {
width: calc(100% - 40px);
}
}

View File

@ -52,20 +52,15 @@ export default class Modal extends Component<Props, {}> {
<div className={`Modalshade ${isOpen ? 'is-open' : ''}`} />
<div className={`Modal ${isOpen ? 'is-open' : ''}`}>
<div className="Modal-header">
<h2 className="Modal-header-title">
{title}
</h2>
<h2 className="Modal-header-title">{title}</h2>
<button className="Modal-header-close" onClick={handleClose}>
<img className="Modal-header-close-icon" src={closeIcon} />
</button>
</div>
<div className="Modal-content">
{isOpen && children}
</div>
{hasButtons &&
<div className="Modal-footer">
{this.renderButtons()}
</div>}
<div className="Modal-content">{isOpen && children}</div>
{hasButtons && (
<div className="Modal-footer">{this.renderButtons()}</div>
)}
</div>
</div>
);

View File

@ -44,7 +44,10 @@ const MNEMONIC = [
EXPANSE
];
const LEDGER = [ETH_LEDGER, ETC_LEDGER, TESTNET];
export default {
TREZOR,
MNEMONIC
MNEMONIC,
LEDGER
};

View File

@ -1,5 +1,3 @@
import { IWallet } from './IWallet';
export default class DeterministicWallet {
private address: string;
private dPath: string;

View File

@ -5,3 +5,4 @@ export { default as PresaleWallet } from './presale';
export { default as MewV1Wallet } from './mewv1';
export { default as UtcWallet } from './utc';
export { default as MnemonicWallet } from './mnemonic';
export { default as LedgerWallet } from './ledger';

View File

@ -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);
}
}
);
});
}
}

View File

@ -1,5 +1,5 @@
// Form overrides
@import "common/sass/variables";
@import 'common/sass/variables';
label {
margin-top: $space-sm;
@ -13,8 +13,8 @@ label + textarea {
margin-top: 0;
}
input[type="radio"],
input[type="checkbox"] {
input[type='radio'],
input[type='checkbox'] {
margin: 3px 0 0;
line-height: normal;
}
@ -32,7 +32,7 @@ input[readonly] {
&:focus {
border-color: $input-border-focus;
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075),
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 2px fadeout($brand-primary, 50%);
}
}
@ -53,33 +53,33 @@ input[readonly] {
.form-control {
&.is-valid {
border-color: lighten($brand-success, 25%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075),
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 1px rgba($brand-success, 0.5);
&:focus {
border-color: lighten($brand-success, 15%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075),
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 3px rgba($brand-success, 0.5);
}
}
&.is-invalid {
border-color: lighten($brand-danger, 25%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075),
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 1px rgba($brand-danger, 0.5);
&:focus {
border-color: lighten($brand-danger, 15%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075),
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 3px rgba($brand-danger, 0.5);
}
}
&.is-semivalid {
border-color: lighten($brand-warning, 25%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075),
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 1px rgba($brand-warning, 0.5);
&:focus {
border-color: lighten($brand-warning, 15%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075),
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 3px rgba($brand-warning, 0.5);
}
}

View File

@ -40,6 +40,8 @@ declare module 'ethereumjs-tx' {
export = ITx;
class ITx {
public raw: Buffer;
constructor(data: Data);
/**
* If the tx's `to` is to the creation address

View File

@ -1,6 +1,7 @@
declare module 'ethereumjs-util' {
import { Buffer } from 'buffer';
import BN = require('bn.js');
export import rlp = require('rlp');
interface Signature {
v: number;

283
common/vendor/ledger-eth.js vendored Normal file
View File

@ -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;

87
common/vendor/ledger3.js vendored Normal file
View File

@ -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;

832
common/vendor/u2f-api.js vendored Normal file
View File

@ -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;

View File

@ -24,4 +24,3 @@ require('qrcode');
require('qrcode.react');
require('bignumber.js');
require('classnames');
require('./vendor/trezor-connect');

View File

@ -102,6 +102,7 @@
"tslint-config-prettier": "^1.5.0",
"tslint-react": "^3.2.0",
"types-bn": "0.0.1",
"types-rlp": "0.0.1",
"typescript": "^2.5.2",
"url-loader": "^0.5.8",
"webpack": "^3.6.0",
@ -121,7 +122,7 @@
"dev:https": "HTTPS=true node webpack_config/server.js",
"predev:https": "check-node-version --package",
"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",
"start": "npm run dev",
"precommit": "lint-staged"

View File

@ -8,16 +8,14 @@
"target": "es5",
"allowJs": true,
"baseUrl": "./common/",
"lib": [
"es2017",
"dom"
],
"lib": ["es2017", "dom"],
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"noEmitOnError": false
},
"include": [
"./common/",
"./node_modules/types-rlp/index.d.ts",
"./node_modules/types-bn/index.d.ts"
],
"awesomeTypescriptLoaderOptions": {