Initial code drop of new hardware wallet package.

This commit is contained in:
Richard Moore 2020-01-10 02:50:09 -05:00
parent 381a72ddaa
commit 2e8f5ca7ed
No known key found for this signature in database
GPG Key ID: 665176BE8E9DC651
9 changed files with 244 additions and 0 deletions

2
packages/hardware-wallets/.gitignore vendored Normal file

@ -0,0 +1,2 @@
lib/bin/*.d.ts
lib.esm/bin/*.d.ts

@ -0,0 +1,3 @@
tsconfig.json
src.ts/
tsconfig.tsbuildinfo

@ -0,0 +1,29 @@
Hardware Wallets
================
Thid is still very experimental.
I only have 1 ledger nano, and testing is done locally (CirlceCI doesn't have
ledgers plugged in ;)).
API
===
```
import { LedgerSigner } from "@ethersproject/hardware-wallets";
const signer = new LedgerSigner(provider, type, path);
// By default:
// - in node, type = "usb"
// - path is the default Ethereum path (i.e. `m/44'/60'/0'/0/0`)
```
License
=======
All ethers code is MIT License.
Each hardware wallet manufacturer may impose additional license
requirements so please check the related abstraction libraries
they provide.
All Firefly abstraction is also MIT Licensed.

@ -0,0 +1,42 @@
{
"author": "Richard Moore <me@ricmoo.com>",
"dependencies": {
"@ethersproject/abstract-provider": ">=5.0.0-beta.136",
"@ethersproject/abstract-signer": ">=5.0.0-beta.137",
"@ethersproject/address": ">=5.0.0-beta.134",
"@ethersproject/bytes": ">=5.0.0-beta.134",
"@ethersproject/properties": ">=5.0.0-beta.136",
"@ethersproject/strings": ">=5.0.0-beta.135",
"@ethersproject/transactions": ">=5.0.0-beta.133",
"@ledgerhq/hw-app-eth": "5.3.0",
"@ledgerhq/hw-transport": "5.3.0",
"@ledgerhq/hw-transport-node-hid": "5.3.0",
"@ledgerhq/hw-transport-u2f": "5.3.0"
},
"description": "Hardware Wallet support for ethers.",
"devDependencies": {
"@types/node": "^12.7.4"
},
"ethereum": "donations.ethers.eth",
"keywords": [
"Ethereum",
"ethers",
"cli"
],
"license": "MIT",
"main": "./lib/index.js",
"module": "./lib.esm/index.js",
"name": "@ethersproject/hardware-wallets",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git://github.com/ethers-io/ethers.js.git"
},
"scripts": {
"test": "exit 1"
},
"types": "./lib/index.d.ts",
"version": "5.0.0-beta.1"
}

@ -0,0 +1,7 @@
"use strict";
import { LedgerSigner } from "./ledger";
export {
LedgerSigner
};

@ -0,0 +1,12 @@
"use strict";
import hid from "@ledgerhq/hw-transport-node-hid";
export type TransportCreator = {
create: () => Promise<Transport>;
};
export const transports: { [ name: string ]: TransportCreator } = {
"hid": hid,
"default": hid
};

@ -0,0 +1,99 @@
"use strict";
import { getAddress } from "@ethersproject/address";
import { Bytes, hexlify, joinSignature } from "@ethersproject/bytes";
import { Signer } from "@ethersproject/abstract-signer";
import { Provider, TransactionRequest } from "@ethersproject/abstract-provider";
import { defineReadOnly, resolveProperties } from "@ethersproject/properties";
import { toUtf8Bytes } from "@ethersproject/strings";
import { serialize as serializeTransaction } from "@ethersproject/transactions";
import Eth from "@ledgerhq/hw-app-eth";
// We store these in a separated import so it is easier to swap them out
// at bundle time; browsers do not get HID, for example. This maps a string
// "type" to a Transport with create.
import { transports } from "./ledger-transport";
const defaultPath = "m/44'/60'/0'/0/0";
export class LedgerSigner extends Signer {
readonly type: string;
readonly path: string
readonly _eth: Promise<Eth>;
constructor(provider?: Provider, type?: string, path?: string) {
super();
if (path == null) { path = defaultPath; }
if (type == null) { type = "default"; }
defineReadOnly(this, "path", path);
defineReadOnly(this, "type", type);
defineReadOnly(this, "provider", provider || null);
const transport = transports[type];
if (!transport) { throw new Error("unknown or unsupport type"); }
defineReadOnly(this, "_eth", transport.create().then((transport) => {
const eth = new Eth(transport);
return eth.getAppConfiguration().then((config) => {
return eth;
}, (error) => {
return Promise.reject(error);
});
}, (error) => {
return Promise.reject(error);
}));
}
async getAddress(): Promise<string> {
const eth = await this._eth;
if (eth == null) { throw new Error("failed to connect"); }
const o = await eth.getAddress(this.path);
return getAddress(o.address);
}
async signMessage(message: Bytes | string): Promise<string> {
if (typeof(message) === 'string') {
message = toUtf8Bytes(message);
}
const messageHex = hexlify(message).substring(2);
const eth = await this._eth;
const sig = await eth.signPersonalMessage(this.path, messageHex);
sig.r = '0x' + sig.r;
sig.s = '0x' + sig.s;
return joinSignature(sig);
}
async signTransaction(transaction: TransactionRequest): Promise<string> {
const eth = await this._eth;
return resolveProperties(transaction).then((tx) => {
const unsignedTx = serializeTransaction(tx).substring(2);
return eth.signTransaction(this.path, unsignedTx).then((sig) => {
return serializeTransaction(tx, {
v: sig.v,
r: ("0x" + sig.r),
s: ("0x" + sig.s),
});
});
});
}
connect(provider: Provider): Signer {
return new LedgerSigner(provider, this.type, this.path);
}
}
(async function() {
const signer = new LedgerSigner();
console.log(signer);
try {
const sig = await signer.signMessage("Hello World");
console.log(sig);
} catch (error) {
console.log("ERR", error);
}
})();

@ -0,0 +1,38 @@
declare module "@ledgerhq/hw-app-eth" {
export type PublicAccount = {
publicKey: string;
address: string;
chainCode: string;
};
export type Config = {
arbitraryDataEnabled: number,
version: string
};
export type Signature = {
r: string,
s: string,
v: number
};
export class Transport { }
export class Eth {
constructor(transport: Transport);
getAppConfiguration(): Promise<Config>;
getAddress(path: string): Promise<PublicAccount>;
signPersonalMessage(path: string, message: string): Promise<Signature>;
signTransaction(path: string, unsignedTx: string): Promise<Signature>;
}
export default Eth;
}
declare module "@ledgerhq/hw-transport-node-hid" {
export function create(): Promise<Transport>;
}
declare module "@ledgerhq/hw-transport-u2f" {
export function create(): Promise<Transport>;
}

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./lib/"
},
"include": [
"./thirdparty.d.ts",
"./src.ts/*"
],
"exclude": []
}