mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-02-20 00:33:07 +01:00
Adding Flow files to dist
This commit is contained in:
@@ -7,5 +7,7 @@ node_modules:
|
||||
|
||||
build:
|
||||
rm -rf dist
|
||||
mkdir -p dist
|
||||
cp -r src/ dist
|
||||
find dist/ -type f ! -name '*.js' | xargs -I {} rm {}
|
||||
find dist/ -name '*.js' | xargs -I {} mv {} {}.flow
|
||||
`npm bin`/browserify src/index.js > dist/index.js
|
||||
|
||||
23
packages/transport/dist/defered.js.flow
vendored
Normal file
23
packages/transport/dist/defered.js.flow
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
export type Defered = {
|
||||
promise: Promise<void>;
|
||||
resolve: () => void;
|
||||
reject: (e: Error) => void;
|
||||
};
|
||||
|
||||
export function create(): Defered {
|
||||
let _resolve: () => void = () => {};
|
||||
let _reject: (e: Error) => void = (e) => {};
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
_resolve = resolve;
|
||||
_reject = reject;
|
||||
});
|
||||
return {
|
||||
promise,
|
||||
resolve: _resolve,
|
||||
reject: _reject,
|
||||
};
|
||||
}
|
||||
217
packages/transport/dist/handler/index.js.flow
vendored
Normal file
217
packages/transport/dist/handler/index.js.flow
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
import type {TrezorDeviceInfo, Transport} from '../transport';
|
||||
import {create as createDefered} from '../defered';
|
||||
import {parseConfigure} from '../protobuf/parse_protocol';
|
||||
import {verifyHexBin} from './verify';
|
||||
import {buildAndSend} from './send';
|
||||
import {receiveAndParse} from './receive';
|
||||
|
||||
import type {Defered} from '../defered';
|
||||
import type {Messages} from '../protobuf/messages';
|
||||
|
||||
export type MessageFromTrezor = {type: string, message: Object};
|
||||
|
||||
// eslint-disable-next-line quotes
|
||||
const stringify = require('json-stable-stringify');
|
||||
|
||||
type TrezorDeviceInfoWithSession = TrezorDeviceInfo & {
|
||||
session: ?string;
|
||||
}
|
||||
|
||||
type InternalAcquireInput = {
|
||||
path: string;
|
||||
previous: ?string;
|
||||
checkPrevious: boolean;
|
||||
}
|
||||
|
||||
type AcquireInput = {
|
||||
path: string;
|
||||
previous: ?string;
|
||||
} | string;
|
||||
|
||||
function parseAcquireInput(input: AcquireInput): InternalAcquireInput {
|
||||
// eslint-disable-next-line quotes
|
||||
if (typeof input !== 'string') {
|
||||
const path = input.path.toString();
|
||||
const previous = input.previous == null ? null : input.previous.toString();
|
||||
return {
|
||||
path,
|
||||
previous,
|
||||
checkPrevious: true,
|
||||
};
|
||||
} else {
|
||||
const path = input.toString();
|
||||
return {
|
||||
path,
|
||||
previous: null,
|
||||
checkPrevious: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function compare(a: TrezorDeviceInfoWithSession, b: TrezorDeviceInfoWithSession): number {
|
||||
if (!isNaN(a.path)) {
|
||||
return parseInt(a.path) - parseInt(b.path);
|
||||
} else {
|
||||
return a.path < a.path ? -1 : (a.path > a.path ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
function timeoutPromise(delay: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(() => resolve(), delay);
|
||||
});
|
||||
}
|
||||
|
||||
const ITER_MAX = 60;
|
||||
const ITER_DELAY = 500;
|
||||
|
||||
export class Handler {
|
||||
transport: Transport;
|
||||
_lock: Promise<any> = Promise.resolve();
|
||||
|
||||
// path => promise rejecting on release
|
||||
deferedOnRelease: {[path: string]: Defered} = {};
|
||||
|
||||
// path => session
|
||||
connections: {[path: string]: string} = {};
|
||||
|
||||
// session => path
|
||||
reverse: {[session: string]: string} = {};
|
||||
|
||||
_messages: ?Messages;
|
||||
|
||||
constructor(transport: Transport) {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
lock<X>(fn: () => (X|Promise<X>)): Promise<X> {
|
||||
const res = this._lock.then(() => fn());
|
||||
this._lock = res.catch(() => {});
|
||||
return res;
|
||||
}
|
||||
|
||||
enumerate(): Promise<Array<TrezorDeviceInfoWithSession>> {
|
||||
return this.lock((): Promise<Array<TrezorDeviceInfoWithSession>> => {
|
||||
return this.transport.enumerate().then((devices) => devices.map(device => {
|
||||
return {
|
||||
...device,
|
||||
session: this.connections[device.path],
|
||||
};
|
||||
})).then(devices => {
|
||||
this._releaseDisconnected(devices);
|
||||
return devices;
|
||||
}).then(devices => {
|
||||
return devices.sort(compare);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_releaseDisconnected(devices: Array<TrezorDeviceInfoWithSession>) {
|
||||
}
|
||||
|
||||
_lastStringified: string = ``;
|
||||
|
||||
listen(old: ?Array<TrezorDeviceInfoWithSession>): Promise<Array<TrezorDeviceInfoWithSession>> {
|
||||
const oldStringified = stringify(old);
|
||||
const last = old == null ? this._lastStringified : oldStringified;
|
||||
return this._runIter(0, last);
|
||||
}
|
||||
|
||||
_runIter(iteration: number, oldStringified: string): Promise<Array<TrezorDeviceInfoWithSession>> {
|
||||
return this.enumerate().then(devices => {
|
||||
const stringified = stringify(devices);
|
||||
if ((stringified !== oldStringified) || (iteration === ITER_MAX)) {
|
||||
this._lastStringified = stringified;
|
||||
return devices;
|
||||
}
|
||||
return timeoutPromise(ITER_DELAY).then(() => this._runIter(iteration + 1, stringified));
|
||||
});
|
||||
}
|
||||
|
||||
_checkAndReleaseBeforeAcquire(parsed: InternalAcquireInput): Promise<any> {
|
||||
const realPrevious = this.connections[parsed.path];
|
||||
if (parsed.checkPrevious) {
|
||||
let error = false;
|
||||
if (realPrevious == null) {
|
||||
error = (parsed.previous != null);
|
||||
} else {
|
||||
error = (parsed.previous !== realPrevious);
|
||||
}
|
||||
if (error) {
|
||||
throw new Error(`wrong previous session`);
|
||||
}
|
||||
}
|
||||
if (realPrevious != null) {
|
||||
const releasePromise: Promise<void> = this._realRelease(parsed.path, realPrevious);
|
||||
return releasePromise;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
acquire(input: AcquireInput): Promise<string> {
|
||||
const parsed = parseAcquireInput(input);
|
||||
return this.lock((): Promise<string> => {
|
||||
return this._checkAndReleaseBeforeAcquire(parsed).then(() =>
|
||||
this.transport.connect(parsed.path)
|
||||
).then((session: string) => {
|
||||
this.connections[parsed.path] = session;
|
||||
this.reverse[session] = parsed.path;
|
||||
this.deferedOnRelease[parsed.path] = createDefered();
|
||||
return session;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
release(session: string): Promise<void> {
|
||||
const path = this.reverse[session];
|
||||
return this.lock(() => this._realRelease(path, session));
|
||||
}
|
||||
|
||||
_realRelease(path:string, session: string): Promise<void> {
|
||||
return this.transport.disconnect(path, session).then(() => {
|
||||
this._releaseCleanup(session);
|
||||
});
|
||||
}
|
||||
|
||||
_releaseCleanup(session: string) {
|
||||
const path: string = this.reverse[session];
|
||||
delete this.reverse[session];
|
||||
delete this.connections[path];
|
||||
this.deferedOnRelease[path].reject(new Error(`Device released or disconnected`));
|
||||
return;
|
||||
}
|
||||
|
||||
configure(signedData: string): Promise<void> {
|
||||
return verifyHexBin(signedData).then((data: Buffer): Messages => {
|
||||
return parseConfigure(data);
|
||||
}).then((messages: Messages) => {
|
||||
this._messages = messages;
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
_sendTransport(session: string): (data: ArrayBuffer) => Promise<void> {
|
||||
const path: string = this.reverse[session];
|
||||
return (data) => this.transport.send(path, session, data);
|
||||
}
|
||||
|
||||
_receiveTransport(session: string): () => Promise<ArrayBuffer> {
|
||||
const path: string = this.reverse[session];
|
||||
return () => this.transport.receive(path, session);
|
||||
}
|
||||
|
||||
call(session: string, name: string, data: Object): Promise<MessageFromTrezor> {
|
||||
if (this._messages == null) {
|
||||
return Promise.reject(new Error(`Handler not configured.`));
|
||||
}
|
||||
const messages = this._messages;
|
||||
return buildAndSend(messages, this._sendTransport(session), name, data).then(() => {
|
||||
return receiveAndParse(messages, this._receiveTransport(session));
|
||||
});
|
||||
}
|
||||
}
|
||||
112
packages/transport/dist/handler/receive.js.flow
vendored
Normal file
112
packages/transport/dist/handler/receive.js.flow
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Logic of recieving data from trezor
|
||||
// Logic of "call" is broken to two parts - sending and recieving
|
||||
|
||||
import {MessageDecoder} from "../protobuf/message_decoder.js";
|
||||
import {ByteBuffer} from "protobufjs";
|
||||
import type {Messages} from "../protobuf/messages.js";
|
||||
import type {MessageFromTrezor} from "./index";
|
||||
|
||||
const MESSAGE_HEADER_BYTE: number = 0x23;
|
||||
|
||||
// input that might or might not be fully parsed yet
|
||||
class PartiallyParsedInput {
|
||||
// Message type number
|
||||
typeNumber: number;
|
||||
// Expected length of the raq message, in bytes
|
||||
expectedLength: number;
|
||||
// Buffer with the beginning of message; can be non-complete and WILL be modified
|
||||
// during the object's lifetime
|
||||
buffer: ByteBuffer;
|
||||
constructor(typeNumber: number, length: number) {
|
||||
this.typeNumber = typeNumber;
|
||||
this.expectedLength = length;
|
||||
this.buffer = new ByteBuffer(length);
|
||||
}
|
||||
isDone(): boolean {
|
||||
return (this.buffer.offset >= this.expectedLength);
|
||||
}
|
||||
append(buffer: ByteBuffer):void {
|
||||
this.buffer.append(buffer);
|
||||
}
|
||||
arrayBuffer(): ArrayBuffer {
|
||||
const byteBuffer: ByteBuffer = this.buffer;
|
||||
byteBuffer.reset();
|
||||
return byteBuffer.toArrayBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
// Parses first raw input that comes from Trezor and returns some information about the whole message.
|
||||
function parseFirstInput(bytes: ArrayBuffer): PartiallyParsedInput {
|
||||
// convert to ByteBuffer so it's easier to read
|
||||
const byteBuffer: ByteBuffer = ByteBuffer.concat([bytes]);
|
||||
|
||||
// checking first two bytes
|
||||
const sharp1: number = byteBuffer.readByte();
|
||||
const sharp2: number = byteBuffer.readByte();
|
||||
if (sharp1 !== MESSAGE_HEADER_BYTE || sharp2 !== MESSAGE_HEADER_BYTE) {
|
||||
throw new Error(`Didn't receive expected header signature.`);
|
||||
}
|
||||
|
||||
// reading things from header
|
||||
const type: number = byteBuffer.readUint16();
|
||||
const length: number = byteBuffer.readUint32();
|
||||
|
||||
// creating a new buffer with the right size
|
||||
const res: PartiallyParsedInput = new PartiallyParsedInput(type, length);
|
||||
res.append(byteBuffer);
|
||||
return res;
|
||||
}
|
||||
|
||||
// If the whole message wasn't loaded in the first input, loads more inputs until everything is loaded.
|
||||
// note: the return value is not at all important since it's still the same parsedinput
|
||||
function receiveRest(
|
||||
parsedInput: PartiallyParsedInput,
|
||||
receiver: () => Promise<ArrayBuffer>
|
||||
): Promise<void> {
|
||||
if (parsedInput.isDone()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return receiver().then((data) => {
|
||||
// sanity check
|
||||
if (data == null) {
|
||||
throw new Error(`Received no data.`);
|
||||
}
|
||||
|
||||
parsedInput.append(data);
|
||||
return receiveRest(parsedInput, receiver);
|
||||
});
|
||||
}
|
||||
|
||||
// Receives the whole message as a raw data buffer (but without headers or type info)
|
||||
function receiveBuffer(
|
||||
receiver: () => Promise<ArrayBuffer>
|
||||
): Promise<PartiallyParsedInput> {
|
||||
return receiver().then((data: ArrayBuffer) => {
|
||||
const partialInput: PartiallyParsedInput = parseFirstInput(data);
|
||||
|
||||
return receiveRest(partialInput, receiver).then(() => {
|
||||
return partialInput;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Reads data from device and returns decoded message, that can be sent back to trezor.js
|
||||
export function receiveAndParse(
|
||||
messages: Messages,
|
||||
receiver: () => Promise<ArrayBuffer>
|
||||
): Promise<MessageFromTrezor> {
|
||||
return receiveBuffer(receiver).then((received) => {
|
||||
const typeId: number = received.typeNumber;
|
||||
const buffer: ArrayBuffer = received.arrayBuffer();
|
||||
const decoder: MessageDecoder = new MessageDecoder(messages, typeId, buffer);
|
||||
return {
|
||||
message: decoder.decodedJSON(),
|
||||
type: decoder.messageName(),
|
||||
};
|
||||
});
|
||||
}
|
||||
152
packages/transport/dist/handler/send.js.flow
vendored
Normal file
152
packages/transport/dist/handler/send.js.flow
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Logic of sending data to trezor
|
||||
//
|
||||
// Logic of "call" is broken to two parts - sending and recieving
|
||||
|
||||
import * as ProtoBuf from "protobufjs";
|
||||
import {ByteBuffer} from "protobufjs";
|
||||
import type {Messages} from "../protobuf/messages.js";
|
||||
|
||||
const HEADER_SIZE = 1 + 1 + 4 + 2;
|
||||
const MESSAGE_HEADER_BYTE: number = 0x23;
|
||||
const BUFFER_SIZE: number = 63;
|
||||
|
||||
// Sends more buffers to device.
|
||||
function sendBuffers(
|
||||
sender: (data: ArrayBuffer) => Promise<void>,
|
||||
buffers: Array<ArrayBuffer>
|
||||
): Promise<void> {
|
||||
return buffers.reduce((prevPromise: Promise<void>, buffer: ArrayBuffer) => {
|
||||
return prevPromise.then(() => {
|
||||
return sender(buffer);
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
// already built PB message
|
||||
class BuiltMessage {
|
||||
message: ProtoBuf.Builder.Message;
|
||||
type: number;
|
||||
|
||||
constructor(messages: Messages, // Builders, generated by reading config
|
||||
name: string, // Name of the message
|
||||
data: Object // data as "pure" object, from trezor.js
|
||||
) {
|
||||
const Builder = messages.messagesByName[name];
|
||||
if (Builder == null) {
|
||||
throw new Error(`The message name ${name} is not found.`);
|
||||
}
|
||||
|
||||
// cleans up stuff from angular and remove "null" that crashes in builder
|
||||
cleanupInput(data);
|
||||
|
||||
if (data) {
|
||||
this.message = new Builder(data);
|
||||
} else {
|
||||
this.message = new Builder();
|
||||
}
|
||||
|
||||
this.type = messages.messageTypes[`MessageType_${name}`];
|
||||
}
|
||||
|
||||
// encodes into "raw" data, but it can be too long and needs to be split into
|
||||
// smaller buffers
|
||||
_encodeLong(): Uint8Array {
|
||||
const headerSize: number = HEADER_SIZE; // should be 8
|
||||
const bytes: Uint8Array = new Uint8Array(this.message.encodeAB());
|
||||
const fullSize: number = headerSize + bytes.length;
|
||||
|
||||
const encodedByteBuffer = new ByteBuffer(fullSize);
|
||||
|
||||
// first encode header
|
||||
|
||||
// 2*1 byte
|
||||
encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
|
||||
encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
|
||||
|
||||
// 2 bytes
|
||||
encodedByteBuffer.writeUint16(this.type);
|
||||
|
||||
// 4 bytes (so 8 in total)
|
||||
encodedByteBuffer.writeUint32(bytes.length);
|
||||
|
||||
// then put in the actual message
|
||||
encodedByteBuffer.append(bytes);
|
||||
|
||||
// and convert to uint8 array
|
||||
// (it can still be too long to send though)
|
||||
const encoded: Uint8Array = new Uint8Array(encodedByteBuffer.buffer);
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
// encodes itself and splits into "nice" chunks
|
||||
encode(): Array<ArrayBuffer> {
|
||||
const bytes: Uint8Array = this._encodeLong();
|
||||
|
||||
const result: Array<ArrayBuffer> = [];
|
||||
const size: number = BUFFER_SIZE;
|
||||
|
||||
// How many pieces will there actually be
|
||||
const count: number = Math.floor((bytes.length - 1) / size) + 1;
|
||||
|
||||
// slice and dice
|
||||
for (let i = 0; i < count; i++) {
|
||||
const slice: Uint8Array = bytes.subarray(i * size, (i + 1) * size);
|
||||
const newArray: Uint8Array = new Uint8Array(size);
|
||||
newArray.set(slice);
|
||||
result.push(newArray.buffer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes $$hashkey from angular and remove nulls
|
||||
function cleanupInput(message: Object): void {
|
||||
delete message.$$hashKey;
|
||||
|
||||
for (const key in message) {
|
||||
const value = message[key];
|
||||
if (value == null) {
|
||||
delete message[key];
|
||||
} else {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((i) => {
|
||||
if (typeof i === `object`) {
|
||||
cleanupInput(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (typeof value === `object`) {
|
||||
cleanupInput(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Builds buffers to send.
|
||||
// messages: Builders, generated by reading config
|
||||
// name: Name of the message
|
||||
// data: Data to serialize, exactly as given by trezor.js
|
||||
// Returning buffers that will be sent to Trezor
|
||||
function buildBuffers(messages: Messages, name: string, data: Object): Array<ArrayBuffer> {
|
||||
const message: BuiltMessage = new BuiltMessage(messages, name, data);
|
||||
const encoded: Array<ArrayBuffer> = message.encode();
|
||||
return encoded;
|
||||
}
|
||||
|
||||
// Sends message to device.
|
||||
// Resolves iff everything gets sent
|
||||
export function buildAndSend(
|
||||
messages: Messages,
|
||||
sender: (data: ArrayBuffer) => Promise<void>,
|
||||
name: string,
|
||||
data: Object
|
||||
): Promise<void> {
|
||||
const buffers: Array<ArrayBuffer> = buildBuffers(messages, name, data);
|
||||
return sendBuffers(sender, buffers);
|
||||
}
|
||||
52
packages/transport/dist/handler/verify.js.flow
vendored
Normal file
52
packages/transport/dist/handler/verify.js.flow
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Module for verifying ECDSA signature of configuration.
|
||||
|
||||
import {ECPair, ECSignature, crypto} from "bitcoinjs-lib";
|
||||
|
||||
import BigInteger from "bigi";
|
||||
|
||||
/* eslint-disable quotes */
|
||||
const SATOSHI_KEYS: Array<string> = [
|
||||
'\x04\xd5\x71\xb7\xf1\x48\xc5\xe4\x23\x2c\x38\x14\xf7\x77\xd8\xfa\xea\xf1\xa8\x42\x16\xc7\x8d\x56\x9b\x71\x04\x1f\xfc\x76\x8a\x5b\x2d\x81\x0f\xc3\xbb\x13\x4d\xd0\x26\xb5\x7e\x65\x00\x52\x75\xae\xde\xf4\x3e\x15\x5f\x48\xfc\x11\xa3\x2e\xc7\x90\xa9\x33\x12\xbd\x58',
|
||||
'\x04\x63\x27\x9c\x0c\x08\x66\xe5\x0c\x05\xc7\x99\xd3\x2b\xd6\xba\xb0\x18\x8b\x6d\xe0\x65\x36\xd1\x10\x9d\x2e\xd9\xce\x76\xcb\x33\x5c\x49\x0e\x55\xae\xe1\x0c\xc9\x01\x21\x51\x32\xe8\x53\x09\x7d\x54\x32\xed\xa0\x6b\x79\x20\x73\xbd\x77\x40\xc9\x4c\xe4\x51\x6c\xb1',
|
||||
'\x04\x43\xae\xdb\xb6\xf7\xe7\x1c\x56\x3f\x8e\xd2\xef\x64\xec\x99\x81\x48\x25\x19\xe7\xef\x4f\x4a\xa9\x8b\x27\x85\x4e\x8c\x49\x12\x6d\x49\x56\xd3\x00\xab\x45\xfd\xc3\x4c\xd2\x6b\xc8\x71\x0d\xe0\xa3\x1d\xbd\xf6\xde\x74\x35\xfd\x0b\x49\x2b\xe7\x0a\xc7\x5f\xde\x58',
|
||||
'\x04\x87\x7c\x39\xfd\x7c\x62\x23\x7e\x03\x82\x35\xe9\xc0\x75\xda\xb2\x61\x63\x0f\x78\xee\xb8\xed\xb9\x24\x87\x15\x9f\xff\xed\xfd\xf6\x04\x6c\x6f\x8b\x88\x1f\xa4\x07\xc4\xa4\xce\x6c\x28\xde\x0b\x19\xc1\xf4\xe2\x9f\x1f\xcb\xc5\xa5\x8f\xfd\x14\x32\xa3\xe0\x93\x8a',
|
||||
'\x04\x73\x84\xc5\x1a\xe8\x1a\xdd\x0a\x52\x3a\xdb\xb1\x86\xc9\x1b\x90\x6f\xfb\x64\xc2\xc7\x65\x80\x2b\xf2\x6d\xbd\x13\xbd\xf1\x2c\x31\x9e\x80\xc2\x21\x3a\x13\x6c\x8e\xe0\x3d\x78\x74\xfd\x22\xb7\x0d\x68\xe7\xde\xe4\x69\xde\xcf\xbb\xb5\x10\xee\x9a\x46\x0c\xda\x45',
|
||||
];
|
||||
/* eslint-enable */
|
||||
|
||||
const keys: Array<Buffer> = SATOSHI_KEYS.map(key => new Buffer(key, `binary`));
|
||||
|
||||
// Verifies ECDSA signature
|
||||
// pubkeys - Public keys
|
||||
// signature - ECDSA signature (concatenated R and S, both 32 bytes)
|
||||
// data - Data that are signed
|
||||
// returns True, iff the signature is correct with any of the pubkeys
|
||||
function verify(pubkeys: Array<Buffer>, bsignature: Buffer, data: Buffer): boolean {
|
||||
const r = BigInteger.fromBuffer(bsignature.slice(0, 32));
|
||||
const s = BigInteger.fromBuffer(bsignature.slice(32));
|
||||
const signature = new ECSignature(r, s);
|
||||
|
||||
const hash = crypto.sha256(data);
|
||||
|
||||
return pubkeys.some(pubkey => {
|
||||
const pair = ECPair.fromPublicKeyBuffer(pubkey);
|
||||
return pair.verify(hash, signature);
|
||||
});
|
||||
}
|
||||
|
||||
// Verifies if a given data is a correctly signed config
|
||||
// Returns the data, if correctly signed, else reject
|
||||
export function verifyHexBin(data: string): Promise<Buffer> {
|
||||
const signature = new Buffer(data.slice(0, 64 * 2), `hex`);
|
||||
const dataB = new Buffer(data.slice(64 * 2), `hex`);
|
||||
const verified = verify(keys, signature, dataB);
|
||||
if (!verified) {
|
||||
return Promise.reject(`Not correctly signed.`);
|
||||
} else {
|
||||
return Promise.resolve(dataB);
|
||||
}
|
||||
}
|
||||
6
packages/transport/dist/index.js.flow
vendored
Normal file
6
packages/transport/dist/index.js.flow
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import {Handler} from './handler';
|
||||
|
||||
// not sure how to do this in ES6 syntax, so I won't
|
||||
module.exports = Handler;
|
||||
1059
packages/transport/dist/protobuf/config_proto_compiled.js.flow
vendored
Normal file
1059
packages/transport/dist/protobuf/config_proto_compiled.js.flow
vendored
Normal file
File diff suppressed because it is too large
Load Diff
102
packages/transport/dist/protobuf/message_decoder.js.flow
vendored
Normal file
102
packages/transport/dist/protobuf/message_decoder.js.flow
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Helper module for converting Trezor's raw input to
|
||||
// ProtoBuf's message and from there to regular JSON to trezor.js
|
||||
|
||||
import * as ProtoBuf from "protobufjs";
|
||||
import {ByteBuffer, Long} from "protobufjs";
|
||||
|
||||
import {Messages} from "./messages.js";
|
||||
|
||||
export class MessageDecoder {
|
||||
// Builders, generated by reading config
|
||||
messages: Messages;
|
||||
// message type number
|
||||
type: number;
|
||||
// raw data to push to Trezor
|
||||
data: ArrayBuffer;
|
||||
|
||||
constructor(messages: Messages, type: number, data: ArrayBuffer) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
// Returns an info about this message,
|
||||
// which includes the constructor object and a name
|
||||
_messageInfo() : MessageInfo {
|
||||
const r = this.messages.messagesByType[this.type];
|
||||
if (r == null) {
|
||||
throw new Error(`Method type not found`, this.type);
|
||||
}
|
||||
return new MessageInfo(r.constructor, r.name);
|
||||
}
|
||||
|
||||
// Returns the name of the message
|
||||
messageName() : string {
|
||||
return this._messageInfo().name;
|
||||
}
|
||||
|
||||
// Returns the actual decoded message, as a ProtoBuf.js object
|
||||
_decodedMessage() : ProtoBuf.Builder.Message {
|
||||
const constructor = this._messageInfo().messageConstructor;
|
||||
return constructor.decode(this.data);
|
||||
}
|
||||
|
||||
// Returns the message decoded to JSON, that could be handed back
|
||||
// to trezor.js
|
||||
decodedJSON() : Object {
|
||||
const decoded = this._decodedMessage();
|
||||
const converted = messageToJSON(decoded);
|
||||
|
||||
return JSON.parse(JSON.stringify(converted));
|
||||
}
|
||||
}
|
||||
|
||||
class MessageInfo {
|
||||
messageConstructor: ProtoBuf.Builder.Message;
|
||||
name: string;
|
||||
constructor(messageConstructor: ProtoBuf.Builder.Message, name: string) {
|
||||
this.messageConstructor = messageConstructor;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts any ProtoBuf message to JSON in Trezor.js-friendly format
|
||||
function messageToJSON(message: ProtoBuf.Builder.Message) : Object {
|
||||
const res = {};
|
||||
const meta = message.$type;
|
||||
|
||||
for (const key in message) {
|
||||
const value = message[key];
|
||||
if (typeof value === `function`) {
|
||||
// ignoring
|
||||
} else if (value instanceof ByteBuffer) {
|
||||
const hex = value.toHex();
|
||||
res[key] = hex;
|
||||
} else if (value instanceof Long) {
|
||||
const num = value.toNumber();
|
||||
res[key] = num;
|
||||
} else if (Array.isArray(value)) {
|
||||
const decodedArr = value.map((i) => {
|
||||
if (typeof i === `object`) {
|
||||
return messageToJSON(i);
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
});
|
||||
res[key] = decodedArr;
|
||||
} else if (value instanceof ProtoBuf.Builder.Message) {
|
||||
res[key] = messageToJSON(value);
|
||||
} else if (meta._fieldsByName[key].type.name === `enum`) {
|
||||
const enumValues = meta._fieldsByName[key].resolvedType.getChildren();
|
||||
res[key] = enumValues.find(e => e.id === value).name;
|
||||
} else {
|
||||
res[key] = value;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
34
packages/transport/dist/protobuf/messages.js.flow
vendored
Normal file
34
packages/transport/dist/protobuf/messages.js.flow
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// This is a simple class that represents information about messages,
|
||||
// as they are loaded from the protobuf definition,
|
||||
// so they are understood by both sending and recieving code.
|
||||
|
||||
import * as ProtoBuf from "protobufjs";
|
||||
|
||||
type MessageArray<KeyType> = { [key: KeyType]: ProtoBuf.Bulder.Message };
|
||||
|
||||
export class Messages {
|
||||
messagesByName: MessageArray<string>;
|
||||
messagesByType: MessageArray<number>;
|
||||
messageTypes: { [key: string]: number };
|
||||
|
||||
constructor(messages: MessageArray<string>) {
|
||||
this.messagesByName = messages;
|
||||
|
||||
const messagesByType: MessageArray<number> = {};
|
||||
Object.keys(messages.MessageType).forEach(longName => {
|
||||
const typeId = messages.MessageType[longName];
|
||||
const shortName = longName.split(`_`)[1];
|
||||
messagesByType[typeId] = {
|
||||
name: shortName,
|
||||
constructor: messages[shortName],
|
||||
};
|
||||
});
|
||||
this.messagesByType = messagesByType;
|
||||
this.messageTypes = messages.MessageType;
|
||||
}
|
||||
}
|
||||
|
||||
30
packages/transport/dist/protobuf/parse_protocol.js.flow
vendored
Normal file
30
packages/transport/dist/protobuf/parse_protocol.js.flow
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Module for loading the protobuf description from serialized description
|
||||
|
||||
import * as ProtoBuf from "protobufjs";
|
||||
|
||||
import {Messages} from "./messages.js";
|
||||
import {protocolToJSON} from "./to_json.js";
|
||||
import * as compiledConfigProto from "./config_proto_compiled.js";
|
||||
|
||||
// Parse configure data (it has to be already verified)
|
||||
export function parseConfigure(data: Buffer): Messages {
|
||||
const configBuilder = compiledConfigProto[`Configuration`];
|
||||
const loadedConfig = configBuilder.decode(data);
|
||||
|
||||
const validUntil = loadedConfig.valid_until;
|
||||
const timeNow = Math.floor(Date.now() / 1000);
|
||||
if (timeNow >= validUntil) {
|
||||
throw new Error(`Config too old; ` + timeNow + ` >= ` + validUntil);
|
||||
}
|
||||
|
||||
const wireProtocol = loadedConfig.wire_protocol;
|
||||
const protocolJSON = protocolToJSON(wireProtocol.toRaw());
|
||||
const protobufMessages = ProtoBuf.newBuilder({})[`import`](protocolJSON).build();
|
||||
|
||||
return new Messages(protobufMessages);
|
||||
}
|
||||
|
||||
121
packages/transport/dist/protobuf/to_json.js.flow
vendored
Normal file
121
packages/transport/dist/protobuf/to_json.js.flow
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Helper module that does conversion from already parsed protobuf's
|
||||
// FileDescriptorSet to JSON, that can be used to initialize ProtoBuf.js
|
||||
//
|
||||
// Theoretically this should not be necessary, since FileDescriptorSet is protobuf "native" description,
|
||||
// but ProtoBuf.js does NOT know how to make Builder from FileDescriptorSet, but it can build it from JSON.
|
||||
// See https://github.com/dcodeIO/ProtoBuf.js/issues/250
|
||||
//
|
||||
// This conversion is probably not very stable and does not "scale" that well, since it's
|
||||
// intended just for our relatively small usecase.
|
||||
// But it works here.
|
||||
|
||||
import {shim} from 'object.values';
|
||||
if (!Object.values) {
|
||||
shim();
|
||||
}
|
||||
|
||||
export function protocolToJSON(p: any): Object {
|
||||
// TODO: what if there are more files?
|
||||
const res = fileToJSON(p.file[2]);
|
||||
res.imports = [fileToJSON(p.file[1])];
|
||||
return res;
|
||||
}
|
||||
|
||||
function fileToJSON(f: any): Object {
|
||||
const res = {};
|
||||
res.package = f.package;
|
||||
res.options = f.options;
|
||||
res.services = [];
|
||||
const messagesSimple = Object.values(f.message_type).map(messageToJSON);
|
||||
const messagesRef = extensionToJSON(f.extension);
|
||||
res.messages = messagesRef.concat(messagesSimple);
|
||||
res.enums = Object.values(f.enum_type).map(enumToJSON);
|
||||
return res;
|
||||
}
|
||||
|
||||
function enumToJSON(enumm: any): Object {
|
||||
const res = {};
|
||||
res.name = enumm.name;
|
||||
res.values = Object.values(enumm.value).map(enum_valueToJSON);
|
||||
res.options = {};
|
||||
return res;
|
||||
}
|
||||
|
||||
function extensionToJSON(extensions: {[key: string]: any}): Array<any> {
|
||||
const res = {};
|
||||
Object.values(extensions).forEach(function (extension: any) {
|
||||
const extendee = extension.extendee.slice(1);
|
||||
if (res[extendee] == null) {
|
||||
res[extendee] = {};
|
||||
res[extendee].ref = extendee;
|
||||
res[extendee].fields = [];
|
||||
}
|
||||
res[extendee].fields.push(fieldToJSON(extension));
|
||||
});
|
||||
return Object.values(res);
|
||||
}
|
||||
|
||||
function enum_valueToJSON(val: any): Object {
|
||||
const res = {};
|
||||
res.name = val.name;
|
||||
res.id = val.number;
|
||||
return res;
|
||||
}
|
||||
|
||||
function messageToJSON(message: any): Object {
|
||||
const res = {};
|
||||
res.enums = [];
|
||||
res.name = message.name;
|
||||
res.options = message.options || {};
|
||||
res.messages = [];
|
||||
res.fields = Object.values(message.field).map(fieldToJSON);
|
||||
res.oneofs = {};
|
||||
return res;
|
||||
}
|
||||
|
||||
const type_map = {
|
||||
"1": `double`,
|
||||
"2": `float`,
|
||||
"3": `int64`,
|
||||
"4": `uint64`,
|
||||
"5": `int32`,
|
||||
"6": `fixed64`,
|
||||
"7": `fixed32`,
|
||||
"8": `bool`,
|
||||
"9": `string`,
|
||||
"10": `group`,
|
||||
"11": `message`,
|
||||
"12": `bytes`,
|
||||
"13": `uint32`,
|
||||
"14": `enum`,
|
||||
"15": `sfixed32`,
|
||||
"16": `sfixed64`,
|
||||
"17": `sint32`,
|
||||
"18": `sint64`,
|
||||
};
|
||||
|
||||
function fieldToJSON(field: any): Object {
|
||||
const res = {};
|
||||
if (field.label === 1) {
|
||||
res.rule = `optional`;
|
||||
}
|
||||
if (field.label === 2) {
|
||||
res.rule = `required`;
|
||||
}
|
||||
if (field.label === 3) {
|
||||
res.rule = `repeated`;
|
||||
}
|
||||
res.type = type_map[field.type];
|
||||
if (field.type_name) {
|
||||
res.type = field.type_name.slice(1);
|
||||
}
|
||||
res.name = field.name;
|
||||
res.options = field.options || {};
|
||||
res.id = field.number;
|
||||
return res;
|
||||
}
|
||||
|
||||
17
packages/transport/dist/transport.js.flow
vendored
Normal file
17
packages/transport/dist/transport.js.flow
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/* @flow */
|
||||
|
||||
"use strict";
|
||||
|
||||
// does not have session
|
||||
export type TrezorDeviceInfo = {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export type Transport = {
|
||||
enumerate: () => Promise<Array<TrezorDeviceInfo>>;
|
||||
send: (path: string, session: string, data: ArrayBuffer) => Promise<void>;
|
||||
receive: (path: string, session: string) => Promise<ArrayBuffer>;
|
||||
connect: (path: string) => Promise<string>;
|
||||
disconnect: (path: string, session: string) => Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user