-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
52f0c19
commit 3401d8d
Showing
14 changed files
with
2,070 additions
and
2,904 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import Eth from "@ledgerhq/hw-app-eth"; | ||
import Transport from "@ledgerhq/hw-transport"; | ||
import TransportWebHID from "@ledgerhq/hw-transport-webhid"; | ||
import { | ||
JsonRpcProvider, | ||
Signature, | ||
Transaction, | ||
TransactionLike, | ||
TypedDataEncoder, | ||
} from "ethers"; | ||
import log from "loglevel"; | ||
|
||
import { config } from "../config"; | ||
|
||
class LedgerSigner { | ||
private static readonly ethereumApp = "Ethereum"; | ||
private static readonly path = "44'/60'/0'/0/0"; | ||
|
||
private readonly provider: JsonRpcProvider; | ||
private transport?: Transport; | ||
|
||
constructor() { | ||
this.provider = new JsonRpcProvider( | ||
config.assets["RBTC"].network.rpcUrls[0], | ||
); | ||
} | ||
|
||
public request = async (request: { | ||
method: string; | ||
params?: Array<unknown>; | ||
}) => { | ||
switch (request.method) { | ||
case "eth_requestAccounts": { | ||
log.debug("Getting Ledger accounts"); | ||
|
||
if (this.transport === undefined) { | ||
this.transport = await TransportWebHID.create(); | ||
} | ||
|
||
const openApp = (await this.getApp()).name; | ||
log.debug(`Ledger has app open: ${openApp}`); | ||
if (openApp !== LedgerSigner.ethereumApp) { | ||
log.info(`Opening Ledger ${LedgerSigner.ethereumApp} app`); | ||
await this.openApp(LedgerSigner.ethereumApp); | ||
} | ||
|
||
const eth = new Eth(this.transport); | ||
const { address } = await eth.getAddress(LedgerSigner.path); | ||
|
||
return [address]; | ||
} | ||
|
||
case "eth_sendTransaction": { | ||
log.debug("Signing transaction with Ledger"); | ||
|
||
const txParams = request.params[0] as TransactionLike; | ||
|
||
const [nonce, network, feeData] = await Promise.all([ | ||
this.provider.getTransactionCount(txParams.from), | ||
this.provider.getNetwork(), | ||
this.provider.getFeeData(), | ||
]); | ||
|
||
const tx = Transaction.from({ | ||
...txParams, | ||
nonce, | ||
type: 0, | ||
from: undefined, | ||
chainId: network.chainId, | ||
gasPrice: feeData.gasPrice, | ||
gasLimit: (txParams as any).gas, | ||
}); | ||
|
||
const eth = new Eth(this.transport); | ||
const signature = await eth.clearSignTransaction( | ||
LedgerSigner.path, | ||
tx.unsignedSerialized.substring(2), | ||
{}, | ||
); | ||
|
||
tx.signature = this.serializeSignature(signature); | ||
await this.provider.send("eth_sendRawTransaction", [ | ||
tx.serialized, | ||
]); | ||
|
||
return tx.hash; | ||
} | ||
|
||
case "eth_signTypedData_v4": { | ||
log.debug("Signing EIP-712 message with Ledger"); | ||
|
||
const eth = new Eth(this.transport); | ||
const message = JSON.parse(request.params[1] as string); | ||
|
||
try { | ||
const signature = await eth.signEIP712Message( | ||
LedgerSigner.path, | ||
message, | ||
); | ||
return this.serializeSignature(signature); | ||
} catch (e) { | ||
// Compatibility with Ledger Nano S | ||
log.warn("Clear signing EIP-712 message failed", e); | ||
|
||
const types = message.types; | ||
delete types["EIP712Domain"]; | ||
|
||
const signature = await eth.signEIP712HashedMessage( | ||
LedgerSigner.path, | ||
TypedDataEncoder.hashDomain(message.domain), | ||
TypedDataEncoder.hashStruct( | ||
message.primaryType, | ||
types, | ||
message.message, | ||
), | ||
); | ||
return this.serializeSignature(signature); | ||
} | ||
} | ||
} | ||
|
||
return this.provider.send(request.method, request.params); | ||
}; | ||
|
||
public on = () => {}; | ||
|
||
public removeAllListeners = () => {}; | ||
|
||
private getApp = async (): Promise<{ | ||
name: string; | ||
version: string; | ||
flags: number | Buffer; | ||
}> => { | ||
const r = await this.transport!.send(0xb0, 0x01, 0x00, 0x00); | ||
let i = 0; | ||
const format = r[i++]; | ||
|
||
if (format !== 1) { | ||
throw " format not supported"; | ||
} | ||
|
||
const nameLength = r[i++]; | ||
const name = r.subarray(i, (i += nameLength)).toString("ascii"); | ||
const versionLength = r[i++]; | ||
const version = r.subarray(i, (i += versionLength)).toString("ascii"); | ||
const flagLength = r[i++]; | ||
const flags = r.subarray(i, i + flagLength); | ||
return { | ||
name, | ||
version, | ||
flags, | ||
}; | ||
}; | ||
|
||
private openApp = async (name: string): Promise<void> => { | ||
await this.transport!.send( | ||
0xe0, | ||
0xd8, | ||
0x00, | ||
0x00, | ||
Buffer.from(name, "ascii"), | ||
); | ||
}; | ||
|
||
private serializeSignature = (signature: { | ||
v: number | string; | ||
r: string; | ||
s: string; | ||
}) => | ||
Signature.from({ | ||
v: signature.v, | ||
r: BigInt(`0x${signature.r}`).toString(), | ||
s: BigInt(`0x${signature.s}`).toString(), | ||
}).serialized; | ||
} | ||
|
||
export default LedgerSigner; |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters