diff --git a/examples/sandbox/package.json b/examples/sandbox/package.json index 94b93c775..ff78a4984 100644 --- a/examples/sandbox/package.json +++ b/examples/sandbox/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-sandbox", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "private": true, "browserslist": "> 0.5%, last 2 versions, not dead", @@ -10,21 +10,21 @@ "clean": "rm -rf dist node_modules public .parcel-cache" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey-tcp": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey-webusb": "1.52.6", - "@shapeshiftoss/hdwallet-keplr": "1.52.6", - "@shapeshiftoss/hdwallet-ledger": "1.52.6", - "@shapeshiftoss/hdwallet-ledger-webhid": "1.52.6", - "@shapeshiftoss/hdwallet-ledger-webusb": "1.52.6", - "@shapeshiftoss/hdwallet-metamask": "1.52.6", - "@shapeshiftoss/hdwallet-native": "1.52.6", - "@shapeshiftoss/hdwallet-portis": "1.52.6", - "@shapeshiftoss/hdwallet-tallyho": "1.52.6", - "@shapeshiftoss/hdwallet-trezor": "1.52.6", - "@shapeshiftoss/hdwallet-trezor-connect": "1.52.6", - "@shapeshiftoss/hdwallet-walletconnect": "1.52.6", - "@shapeshiftoss/hdwallet-xdefi": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey-tcp": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey-webusb": "1.52.7", + "@shapeshiftoss/hdwallet-keplr": "1.52.7", + "@shapeshiftoss/hdwallet-ledger": "1.52.7", + "@shapeshiftoss/hdwallet-ledger-webhid": "1.52.7", + "@shapeshiftoss/hdwallet-ledger-webusb": "1.52.7", + "@shapeshiftoss/hdwallet-metamask": "1.52.7", + "@shapeshiftoss/hdwallet-native": "1.52.7", + "@shapeshiftoss/hdwallet-portis": "1.52.7", + "@shapeshiftoss/hdwallet-tallyho": "1.52.7", + "@shapeshiftoss/hdwallet-trezor": "1.52.7", + "@shapeshiftoss/hdwallet-trezor-connect": "1.52.7", + "@shapeshiftoss/hdwallet-walletconnect": "1.52.7", + "@shapeshiftoss/hdwallet-xdefi": "1.52.7", "bip32": "^2.0.4", "jquery": "^3.4.1", "json": "^9.0.6", diff --git a/integration/package.json b/integration/package.json index 8f3d7ea38..e3157b29a 100644 --- a/integration/package.json +++ b/integration/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/integration", - "version": "1.52.6", + "version": "1.52.7", "main": "index.js", "license": "MIT", "private": true, @@ -10,15 +10,15 @@ "dev": "lerna run test --scope integration --parallel --include-filtered-dependencies" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey-nodewebusb": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey-tcp": "1.52.6", - "@shapeshiftoss/hdwallet-ledger": "1.52.6", - "@shapeshiftoss/hdwallet-native": "1.52.6", - "@shapeshiftoss/hdwallet-portis": "1.52.6", - "@shapeshiftoss/hdwallet-trezor": "1.52.6", - "@shapeshiftoss/hdwallet-xdefi": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey-nodewebusb": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey-tcp": "1.52.7", + "@shapeshiftoss/hdwallet-ledger": "1.52.7", + "@shapeshiftoss/hdwallet-native": "1.52.7", + "@shapeshiftoss/hdwallet-portis": "1.52.7", + "@shapeshiftoss/hdwallet-trezor": "1.52.7", + "@shapeshiftoss/hdwallet-xdefi": "1.52.7", "fast-json-stable-stringify": "^2.1.0", "msw": "^0.27.1", "whatwg-fetch": "^3.6.2" diff --git a/integration/src/thorchain/thorchain.ts b/integration/src/thorchain/thorchain.ts index cce066297..b95ec45a0 100644 --- a/integration/src/thorchain/thorchain.ts +++ b/integration/src/thorchain/thorchain.ts @@ -1,5 +1,6 @@ import * as core from "@shapeshiftoss/hdwallet-core"; import * as keepkey from "@shapeshiftoss/hdwallet-keepkey"; +import * as ledger from "@shapeshiftoss/hdwallet-ledger"; import tx_unsigned_swap_amino from "./tx01.mainnet.thorchain.swap.amino.json"; import tx_unsigned_swap from "./tx01.mainnet.thorchain.swap.json"; @@ -25,7 +26,7 @@ export function thorchainTests(get: () => { wallet: core.HDWallet; info: core.HD beforeAll(async () => { const { wallet: w } = get(); if (core.supportsThorchain(w)) wallet = w; - useAmino = w instanceof keepkey.KeepKeyHDWallet; + useAmino = w instanceof keepkey.KeepKeyHDWallet || w instanceof ledger.LedgerHDWallet; }); beforeEach(async () => { diff --git a/integration/src/wallets/ledger.ts b/integration/src/wallets/ledger.ts index 41a696d7a..7a4543051 100644 --- a/integration/src/wallets/ledger.ts +++ b/integration/src/wallets/ledger.ts @@ -1,5 +1,6 @@ import * as core from "@shapeshiftoss/hdwallet-core"; import * as ledger from "@shapeshiftoss/hdwallet-ledger"; +import { toByteArray } from "base64-js"; export class MockTransport extends ledger.LedgerTransport { memoized = new Map(); @@ -212,6 +213,61 @@ export class MockTransport extends ledger.LedgerTransport { '{"success":true,"coin":"Btc","method":"getWalletPublicKey","payload":{"bitcoinAddress":"1FH6ehAd5ZFXCM1cLGzHxK1s4dGdq1JusM","chainCode":"fixme","publicKey":"fixme"}}' ) ); + + // Thorchain + const compress_pk = toByteArray("AxUZcTuLQr3DZxEtMxMs8Uzt+SisV3HURLpFm5SXEXuj"); + this.memoize( + "Rune", + "getAddressAndPubKey", + JSON.parse(`[[${core.bip32ToAddressNList("m/44'/931'/0'/0/0")}], "thor"]`), + JSON.parse( + `{"success":true,"coin":"Rune","method":"getAddressAndPubkey","payload":{"bech32_address":"thor1ls33ayg26kmltw7jjy55p32ghjna09zp74t4az","compressed_pk":[${compress_pk}]}}` + ) + ); + + const sig1 = toByteArray( + "1s+0FVJ5R8O+ewGq5yNbTQuVG5MJZppFDqVJ4cd5D68ogOb2GMVHvYCH2dvQXo/uK/fT6Rk6dLGhK8tgW/HqtA==" + ); + const r1 = sig1.slice(0, 32); + const s1 = sig1.slice(32, 64); + const rawSig1 = Uint8Array.from([48, 68, 2, 32, ...r1, 2, 32, ...s1]); + this.memoize( + "Rune", + "sign", + JSON.parse( + '[{"tx":{"account_number":"17","chain_id":"thorchain-mainnet-v1","sequence":"2","fee":{"amount":[{"amount":"3000","denom":"rune"}],"gas":"200000"},"memo":"","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"100","denom":"rune"}],"from_address":"thor1ls33ayg26kmltw7jjy55p32ghjna09zp74t4az","to_address":"thor1wy58774wagy4hkljz9mchhqtgk949zdwwe80d5"}}],"signatures":[]},"addressNList":[2147483692,2147484579,2147483648,0,0],"chain_id":"thorchain-mainnet-v1","account_number":"17","sequence":"2"}]' + ), + { + success: true, + coin: "Rune", + method: "sign", + payload: { + signature: rawSig1, + }, + } + ); + + const sig2 = toByteArray( + "0Bjk7npdUw/Qa4MQTS4PH8sw8jM4JSzpd7G2DsF3DMVoYgdpO2fjHh/DUq6v30nghxUSJj0jNm0VIq9viPB+tQ==" + ); + const r2 = sig2.slice(0, 32); + const s2 = sig2.slice(32, 64); + const rawSig2 = Uint8Array.from([48, 68, 2, 32, ...r2, 2, 32, ...s2]); + this.memoize( + "Rune", + "sign", + JSON.parse( + '[{"tx":{"account_number":"2722","chain_id":"thorchain-mainnet-v1","sequence":"4","fee":{"amount":[{"amount":"0","denom":"rune"}],"gas":"350000"},"memo":"","msg":[{"type":"thorchain/MsgDeposit","value":{"coins":[{"asset":"THOR.RUNE","amount":"50994000"}],"memo":"SWAP:BNB.BNB:bnb12splwpg8jenr9pjw3dwc5rr35t8792y8pc4mtf:348953501","signer":"thor1ls33ayg26kmltw7jjy55p32ghjna09zp74t4az"}}],"signatures":[]},"addressNList":[2147483692,2147484579,2147483648,0,0],"chain_id":"thorchain-mainnet-v1","account_number":"2722","sequence":"4"}]' + ), + { + success: true, + coin: "Rune", + method: "sign", + payload: { + signature: rawSig2, + }, + } + ); } catch (e) { console.error(e); } diff --git a/lerna.json b/lerna.json index e39242c53..228369f0f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "lerna": "5.2.0", - "version": "1.52.6", + "version": "1.52.7", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/hdwallet-coinbase/package.json b/packages/hdwallet-coinbase/package.json index 0c953c777..8b78dbfa8 100644 --- a/packages/hdwallet-coinbase/package.json +++ b/packages/hdwallet-coinbase/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-coinbase", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -15,7 +15,7 @@ }, "dependencies": { "@coinbase/wallet-sdk": "^3.6.6", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "eth-rpc-errors": "^4.0.3", "lodash": "^4.17.21" }, diff --git a/packages/hdwallet-core/package.json b/packages/hdwallet-core/package.json index c576885e2..aec3e883a 100644 --- a/packages/hdwallet-core/package.json +++ b/packages/hdwallet-core/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-core", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" diff --git a/packages/hdwallet-keepkey-chromeusb/package.json b/packages/hdwallet-keepkey-chromeusb/package.json index b250827f3..2f10a55d6 100644 --- a/packages/hdwallet-keepkey-chromeusb/package.json +++ b/packages/hdwallet-keepkey-chromeusb/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keepkey-chromeusb", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,7 +14,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey": "1.52.6" + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey": "1.52.7" } } diff --git a/packages/hdwallet-keepkey-electron/package.json b/packages/hdwallet-keepkey-electron/package.json index 325339384..350706770 100644 --- a/packages/hdwallet-keepkey-electron/package.json +++ b/packages/hdwallet-keepkey-electron/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keepkey-electron", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,7 +14,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-keepkey": "1.52.6", + "@shapeshiftoss/hdwallet-keepkey": "1.52.7", "uuid": "^8.3.2" }, "peerDependencies": { diff --git a/packages/hdwallet-keepkey-nodehid/package.json b/packages/hdwallet-keepkey-nodehid/package.json index 1377b9a9f..d633dca41 100644 --- a/packages/hdwallet-keepkey-nodehid/package.json +++ b/packages/hdwallet-keepkey-nodehid/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keepkey-nodehid", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,7 +14,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-keepkey": "1.52.6" + "@shapeshiftoss/hdwallet-keepkey": "1.52.7" }, "peerDependencies": { "node-hid": "^2.1.1" diff --git a/packages/hdwallet-keepkey-nodewebusb/package.json b/packages/hdwallet-keepkey-nodewebusb/package.json index 20ae0e6ec..ab1ddbe87 100644 --- a/packages/hdwallet-keepkey-nodewebusb/package.json +++ b/packages/hdwallet-keepkey-nodewebusb/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keepkey-nodewebusb", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,8 +14,8 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey": "1.52.6" + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey": "1.52.7" }, "peerDependencies": { "usb": "^2.3.1" diff --git a/packages/hdwallet-keepkey-tcp/package.json b/packages/hdwallet-keepkey-tcp/package.json index e92b652e5..75561e143 100644 --- a/packages/hdwallet-keepkey-tcp/package.json +++ b/packages/hdwallet-keepkey-tcp/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keepkey-tcp", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,8 +14,8 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey": "1.52.7", "axios": "^0.21.1" } } diff --git a/packages/hdwallet-keepkey-webusb/package.json b/packages/hdwallet-keepkey-webusb/package.json index 7fe7323c8..326cb5142 100644 --- a/packages/hdwallet-keepkey-webusb/package.json +++ b/packages/hdwallet-keepkey-webusb/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keepkey-webusb", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,8 +14,8 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-keepkey": "1.52.6" + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-keepkey": "1.52.7" }, "devDependencies": { "@types/w3c-web-usb": "^1.0.4" diff --git a/packages/hdwallet-keepkey/package.json b/packages/hdwallet-keepkey/package.json index 91a950e4a..f2268faca 100644 --- a/packages/hdwallet-keepkey/package.json +++ b/packages/hdwallet-keepkey/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keepkey", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -19,7 +19,7 @@ "@ethereumjs/tx": "^3.3.0", "@keepkey/device-protocol": "^7.12.2", "@shapeshiftoss/bitcoinjs-lib": "5.2.0-shapeshift.2", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "@shapeshiftoss/proto-tx-builder": "^0.8.0", "bignumber.js": "^9.0.1", "bnb-javascript-sdk-nobroadcast": "^2.16.14", diff --git a/packages/hdwallet-keplr/package.json b/packages/hdwallet-keplr/package.json index 372f7da25..b063cd4fb 100644 --- a/packages/hdwallet-keplr/package.json +++ b/packages/hdwallet-keplr/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-keplr", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -15,7 +15,7 @@ }, "dependencies": { "@shapeshiftoss/caip": "8.15.0", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "@shapeshiftoss/proto-tx-builder": "^0.8.0", "@shapeshiftoss/types": "3.1.3", "base64-js": "^1.5.1", diff --git a/packages/hdwallet-ledger-webhid/package.json b/packages/hdwallet-ledger-webhid/package.json index 4c48c4236..ef31127a8 100644 --- a/packages/hdwallet-ledger-webhid/package.json +++ b/packages/hdwallet-ledger-webhid/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-ledger-webhid", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -19,8 +19,8 @@ "@ledgerhq/hw-transport": "^6.7.0", "@ledgerhq/hw-transport-webhid": "^6.7.0", "@ledgerhq/live-common": "^21.8.2", - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-ledger": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-ledger": "1.52.7", "@types/w3c-web-hid": "^1.0.2" }, "devDependencies": { diff --git a/packages/hdwallet-ledger-webusb/package.json b/packages/hdwallet-ledger-webusb/package.json index 82aaaaf56..55fb8552b 100644 --- a/packages/hdwallet-ledger-webusb/package.json +++ b/packages/hdwallet-ledger-webusb/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-ledger-webusb", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -20,8 +20,8 @@ "@ledgerhq/hw-transport-webusb": "^6.7.0", "@ledgerhq/live-common": "^21.8.2", "@ledgerhq/logs": "^6.10.1", - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-ledger": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-ledger": "1.52.7", "@types/w3c-web-usb": "^1.0.4", "p-queue": "^7.4.1" }, diff --git a/packages/hdwallet-ledger-webusb/src/transport.ts b/packages/hdwallet-ledger-webusb/src/transport.ts index 52623993a..b0efc05af 100644 --- a/packages/hdwallet-ledger-webusb/src/transport.ts +++ b/packages/hdwallet-ledger-webusb/src/transport.ts @@ -79,6 +79,11 @@ export async function translateCoinAndMethod> { switch (coin) { + case "Rune": { + const thor = new ledger.THORChainApp(transport); + const methodInstance = thor[method as LedgerTransportMethodName<"Rune">].bind(thor); + return methodInstance as LedgerTransportMethod; + } case "Btc": { const btc = new Btc({ transport }); const methodInstance = btc[method as LedgerTransportMethodName<"Btc">].bind(btc); diff --git a/packages/hdwallet-ledger/package.json b/packages/hdwallet-ledger/package.json index ac597b32f..b537567c3 100644 --- a/packages/hdwallet-ledger/package.json +++ b/packages/hdwallet-ledger/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-ledger", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -18,7 +18,7 @@ "@ethereumjs/common": "^2.4.0", "@ethereumjs/tx": "^3.3.0", "@shapeshiftoss/bitcoinjs-lib": "5.2.0-shapeshift.2", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "base64-js": "^1.5.1", "bchaddrjs": "^0.4.4", "bitcoinjs-message": "^2.0.0", @@ -36,6 +36,7 @@ "@types/bs58check": "^2.1.0", "@types/ethereumjs-tx": "1.0.1", "@types/ethereumjs-util": "^6.1.0", + "@types/ripemd160": "^2.0.2", "typedoc": "^0.20.36" } } diff --git a/packages/hdwallet-ledger/src/index.ts b/packages/hdwallet-ledger/src/index.ts index c73f59c8c..063c34f8e 100644 --- a/packages/hdwallet-ledger/src/index.ts +++ b/packages/hdwallet-ledger/src/index.ts @@ -1,5 +1,6 @@ export * from "./bitcoin"; export * from "./ethereum"; +export * from "./thorchain"; export * from "./ledger"; export * from "./transport"; export * from "./utils"; diff --git a/packages/hdwallet-ledger/src/ledger.ts b/packages/hdwallet-ledger/src/ledger.ts index 5a79bd54c..37ee83916 100644 --- a/packages/hdwallet-ledger/src/ledger.ts +++ b/packages/hdwallet-ledger/src/ledger.ts @@ -3,6 +3,7 @@ import _ from "lodash"; import * as btc from "./bitcoin"; import * as eth from "./ethereum"; +import * as thorchain from "./thorchain"; import { LedgerTransport } from "./transport"; import { coinToLedgerAppName, handleError } from "./utils"; @@ -134,9 +135,12 @@ function describeUTXOPath(path: core.BIP32Path, coin: core.Coin, scriptType?: co } } -export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo { +export class LedgerHDWalletInfo + implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo, core.ThorchainWalletInfo +{ readonly _supportsBTCInfo = true; readonly _supportsETHInfo = true; + readonly _supportsThorchainInfo = true; public getVendor(): string { return "Ledger"; @@ -186,6 +190,16 @@ export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo return eth.ethGetAccountPaths(msg); } + public thorchainGetAccountPaths(msg: core.ThorchainGetAccountPaths): Array { + const slip44 = core.slip44ByCoin("Thorchain"); + return [{ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0] }]; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public thorchainNextAccountPath(msg: core.ThorchainAccountPath): core.ThorchainAccountPath | undefined { + return undefined; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public hasNativeShapeShift(srcCoin: core.Coin, dstCoin: core.Coin): boolean { return false; @@ -223,6 +237,8 @@ export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo switch (msg.coin) { case "Ethereum": return describeETHPath(msg.path); + case "Thorchain": + return core.thorchainDescribePath(msg.path); default: return describeUTXOPath(msg.path, msg.coin, msg.scriptType); } @@ -290,7 +306,10 @@ export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo } } -export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWallet { +export class LedgerHDWallet + extends LedgerHDWalletInfo + implements core.HDWallet, core.BTCWallet, core.ETHWallet, core.ThorchainWallet +{ readonly _supportsETHInfo = true; readonly _supportsBTCInfo = true; readonly _supportsBTC = true; @@ -302,6 +321,8 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa readonly _supportsPolygon = true; readonly _supportsGnosis = true; readonly _supportsArbitrum = true; + readonly _supportsThorchainInfo = true; + readonly _supportsThorchain = true; _isLedger = true; @@ -309,6 +330,7 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa info: LedgerHDWalletInfo & core.HDWalletInfo; constructor(transport: LedgerTransport) { + super(); this.transport = transport; this.info = new LedgerHDWalletInfo(); } @@ -374,10 +396,6 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa return version; } - public getVendor(): string { - return "Ledger"; - } - public async getModel(): Promise { const { device: { productName }, @@ -416,39 +434,6 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public hasNativeShapeShift(srcCoin: core.Coin, dstCoin: core.Coin): boolean { - return false; - } - - public supportsBip44Accounts(): boolean { - return this.info.supportsBip44Accounts(); - } - - public supportsOfflineSigning(): boolean { - return true; - } - - public supportsBroadcast(): boolean { - return false; - } - - public hasOnDeviceDisplay(): boolean { - return true; - } - - public hasOnDevicePassphrase(): boolean { - return true; - } - - public hasOnDevicePinEntry(): boolean { - return true; - } - - public hasOnDeviceRecovery(): boolean { - return true; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars public async loadDevice(msg: core.LoadDevice): Promise { return; @@ -497,14 +482,6 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa return; } - public async btcSupportsCoin(coin: core.Coin): Promise { - return this.info.btcSupportsCoin(coin); - } - - public async btcSupportsScriptType(coin: core.Coin, scriptType: core.BTCInputScriptType): Promise { - return this.info.btcSupportsScriptType(coin, scriptType); - } - public async btcGetAddress(msg: core.BTCGetAddress): Promise { await this.validateCurrentApp(msg.coin); return btc.btcGetAddress(this.transport, msg); @@ -515,14 +492,6 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa return btc.btcSignTx(this, this.transport, msg); } - public async btcSupportsSecureTransfer(): Promise { - return this.info.btcSupportsSecureTransfer(); - } - - public btcSupportsNativeShapeShift(): boolean { - return this.info.btcSupportsNativeShapeShift(); - } - public async btcSignMessage(msg: core.BTCSignMessage): Promise { await this.validateCurrentApp(msg.coin); return btc.btcSignMessage(this, this.transport, msg); @@ -532,14 +501,6 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa return btc.btcVerifyMessage(msg); } - public btcGetAccountPaths(msg: core.BTCGetAccountPaths): Array { - return this.info.btcGetAccountPaths(msg); - } - - public btcIsSameAccount(msg: Array): boolean { - return this.info.btcIsSameAccount(msg); - } - public async ethSignTx(msg: core.ETHSignTx): Promise { await this.validateCurrentApp("Ethereum"); return eth.ethSignTx(this.transport, msg); @@ -559,41 +520,17 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa return eth.ethVerifyMessage(msg); } - public async ethSupportsNetwork(chain_id: number): Promise { - return this.info.ethSupportsNetwork(chain_id); - } - - public async ethSupportsSecureTransfer(): Promise { - return this.info.ethSupportsSecureTransfer(); - } - - public ethSupportsNativeShapeShift(): boolean { - return this.info.ethSupportsNativeShapeShift(); - } - - public async ethSupportsEIP1559(): Promise { - return await this.info.ethSupportsEIP1559(); + public thorchainGetAddress(msg: core.ThorchainGetAddress): Promise { + return thorchain.thorchainGetAddress(this.transport, msg); } - public ethGetAccountPaths(msg: core.ETHGetAccountPath): Array { - return this.info.ethGetAccountPaths(msg); - } - - public describePath(msg: core.DescribePath): core.PathDescription { - return this.info.describePath(msg); + public thorchainSignTx(msg: core.ThorchainSignTx): Promise { + return thorchain.thorchainSignTx(this.transport, msg); } public disconnect(): Promise { return this.transport.disconnect(); } - - public btcNextAccountPath(msg: core.BTCAccountPath): core.BTCAccountPath | undefined { - return this.info.btcNextAccountPath(msg); - } - - public ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined { - return this.info.ethNextAccountPath(msg); - } } export function info(): LedgerHDWalletInfo { diff --git a/packages/hdwallet-ledger/src/thorchain/common.ts b/packages/hdwallet-ledger/src/thorchain/common.ts new file mode 100644 index 000000000..0f6cb4f5a --- /dev/null +++ b/packages/hdwallet-ledger/src/thorchain/common.ts @@ -0,0 +1,113 @@ +export enum ErrorCode { + NoError = 0x9000, +} + +export const CLA = 0x55; +export const CHUNK_SIZE = 250; +export const APP_KEY = "CSM"; + +export const INS = { + GET_VERSION: 0x00, + INS_PUBLIC_KEY_SECP256K1: 0x01, // Obsolete + SIGN_SECP256K1: 0x02, + GET_ADDR_SECP256K1: 0x04, +}; + +export const PAYLOAD_TYPE = { + INIT: 0x00, + ADD: 0x01, + LAST: 0x02, +}; + +export const P1_VALUES = { + ONLY_RETRIEVE: 0x00, + SHOW_ADDRESS_IN_DEVICE: 0x01, +}; + +const ERROR_DESCRIPTION = { + 1: "U2F: Unknown", + 2: "U2F: Bad request", + 3: "U2F: Configuration unsupported", + 4: "U2F: Device Ineligible", + 5: "U2F: Timeout", + 14: "Timeout", + 0x9000: "No errors", + 0x9001: "Device is busy", + 0x6802: "Error deriving keys", + 0x6400: "Execution Error", + 0x6700: "Wrong Length", + 0x6982: "Empty Buffer", + 0x6983: "Output buffer too small", + 0x6984: "Data is invalid", + 0x6985: "Conditions not satisfied", + 0x6986: "Transaction rejected", + 0x6a80: "Bad key handle", + 0x6b00: "Invalid P1/P2", + 0x6d00: "Instruction not supported", + 0x6e00: "App does not seem to be open", + 0x6f00: "Unknown error", + 0x6f01: "Sign/verify error", +}; + +export function errorCodeToString(statusCode: any) { + if (statusCode in ERROR_DESCRIPTION) return ERROR_DESCRIPTION[statusCode as 1]; + return `Unknown Status Code: ${statusCode}`; +} + +export function processErrorResponse(response: any) { + if (response) { + if ( + typeof response === "object" && + response !== null && + !(response instanceof Array) && + !(response instanceof Date) + ) { + if (Object.prototype.hasOwnProperty.call(response, "statusCode")) { + return { + return_code: response.statusCode, + error_message: errorCodeToString(response.statusCode), + }; + } + + if ( + Object.prototype.hasOwnProperty.call(response, "return_code") && + Object.prototype.hasOwnProperty.call(response, "error_message") + ) { + return response; + } + } + return { + return_code: 0xffff, + error_message: response.toString(), + }; + } + + return { + return_code: 0xffff, + error_message: response.toString(), + }; +} + +export async function getVersion(transport: any) { + return transport.send(CLA, INS.GET_VERSION, 0, 0).then((response: any) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + + let targetId = 0; + if (response.length >= 9) { + targetId = (response[5] << 24) + (response[6] << 16) + (response[7] << 8) + (response[8] << 0); + } + + return { + return_code: returnCode, + error_message: errorCodeToString(returnCode), + // /// + test_mode: response[0] !== 0, + major: response[1], + minor: response[2], + patch: response[3], + device_locked: response[4] === 1, + target_id: targetId.toString(16), + }; + }, processErrorResponse); +} diff --git a/packages/hdwallet-ledger/src/thorchain/helpers.ts b/packages/hdwallet-ledger/src/thorchain/helpers.ts new file mode 100644 index 000000000..14a8893df --- /dev/null +++ b/packages/hdwallet-ledger/src/thorchain/helpers.ts @@ -0,0 +1,68 @@ +import { CLA, ErrorCode, errorCodeToString, INS, PAYLOAD_TYPE, processErrorResponse } from "./common"; + +const signSendChunkv1 = async (app: any, chunkIdx: any, chunkNum: any, chunk: any) => { + return app.transport + .send(CLA, INS.SIGN_SECP256K1, chunkIdx, chunkNum, chunk, [ErrorCode.NoError, 0x6984, 0x6a80]) + .then((response: any) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + let errorMessage = errorCodeToString(returnCode); + + if (returnCode === 0x6a80 || returnCode === 0x6984) { + errorMessage = `${errorMessage} : ${response.slice(0, response.length - 2).toString("ascii")}`; + } + + let signature = null; + if (response.length > 2) { + signature = response.slice(0, response.length - 2); + } + + return { + signature, + return_code: returnCode, + error_message: errorMessage, + }; + }, processErrorResponse); +}; + +export const serializePathv2 = (path: Array) => { + if (!path || path.length !== 5) { + throw new Error("Invalid path."); + } + + const buf = Buffer.alloc(20); + buf.writeUInt32LE(path[0], 0); + buf.writeUInt32LE(path[1], 4); + buf.writeUInt32LE(path[2], 8); + buf.writeUInt32LE(path[3], 12); + buf.writeUInt32LE(path[4], 16); + + return buf; +}; + +export const signSendChunkv2 = async (app: any, chunkIdx: any, chunkNum: any, chunk: any) => { + let payloadType = PAYLOAD_TYPE.ADD; + if (chunkIdx === 1) { + payloadType = PAYLOAD_TYPE.INIT; + } + if (chunkIdx === chunkNum) { + payloadType = PAYLOAD_TYPE.LAST; + } + + return signSendChunkv1(app, payloadType, 0, chunk); +}; + +export const publicKeyv2 = async (app: any, data: any) => { + return app.transport.send(CLA, INS.GET_ADDR_SECP256K1, 0, 0, data, [ErrorCode.NoError]).then((response: any) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + const compressedPk = Buffer.from(response.slice(0, 33)); + + return { + pk: "OBSOLETE PROPERTY", + compressed_pk: compressedPk, + return_code: returnCode, + error_message: errorCodeToString(returnCode), + }; + }, processErrorResponse); +}; diff --git a/packages/hdwallet-ledger/src/thorchain/hw-app-thor.ts b/packages/hdwallet-ledger/src/thorchain/hw-app-thor.ts new file mode 100644 index 000000000..1ff37717b --- /dev/null +++ b/packages/hdwallet-ledger/src/thorchain/hw-app-thor.ts @@ -0,0 +1,376 @@ +/** ****************************************************************************** + * (c) 2019 ZondaX GmbH + * (c) 2016-2017 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. + ******************************************************************************* */ +import { bech32 } from "@scure/base"; +import * as core from "@shapeshiftoss/hdwallet-core"; +import crypto from "crypto"; +import Ripemd160 from "ripemd160"; + +import { + APP_KEY, + CHUNK_SIZE, + CLA, + ErrorCode, + errorCodeToString, + getVersion, + INS, + P1_VALUES, + processErrorResponse, +} from "./common"; +import { publicKeyv2, serializePathv2, signSendChunkv2 } from "./helpers"; + +const THOR_CHAIN = "thorchain-mainnet-v1"; + +export type GetAddressAndPubKeyResponse = { + bech32_address: string; + compressed_pk: Uint8Array; + error_message: string; + return_code: number; +}; + +export type SignResponse = { + signature: Uint8Array; + error_message: string; + return_code: number; +}; + +const recursivelyOrderKeys = (unordered: any) => { + // If it's an array - recursively order any + // dictionary items within the array + if (Array.isArray(unordered)) { + unordered.forEach((item, index) => { + unordered[index] = recursivelyOrderKeys(item); + }); + return unordered; + } + + // If it's an object - let's order the keys + if (typeof unordered !== "object") return unordered; + const ordered: any = {}; + Object.keys(unordered) + .sort() + .forEach((key) => (ordered[key] = recursivelyOrderKeys(unordered[key]))); + return ordered; +}; + +const stringifyKeysInOrder = (data: any) => JSON.stringify(recursivelyOrderKeys(data)); + +class THORChainApp { + transport: any; + versionResponse: any; + + constructor(transport: any, scrambleKey = APP_KEY) { + if (!transport) { + throw new Error("Transport has not been defined"); + } + + this.transport = transport as any; + transport.decorateAppAPIMethods.bind(transport)( + this, + ["getVersion", "sign", "getAddressAndPubKey", "appInfo", "deviceInfo", "getBech32FromPK"], + scrambleKey + ); + } + + static serializeHRP(hrp: any) { + if (hrp == null || hrp.length < 3 || hrp.length > 83) { + throw new Error("Invalid HRP"); + } + const buf = Buffer.alloc(1 + hrp.length); + buf.writeUInt8(hrp.length, 0); + buf.write(hrp, 1); + return buf; + } + + static getBech32FromPK(hrp: any, pk: any) { + if (pk.length !== 33) { + throw new Error("expected compressed public key [31 bytes]"); + } + const hashSha256 = crypto.createHash("sha256").update(pk).digest(); + const hashRip = new Ripemd160().update(hashSha256).digest(); + // ts is drunk and doesn't like bech32.bech32 here + const encode = bech32.encode || (bech32 as any).bech32?.encode; + // ts is drunk and doesn't like bech32.bech32 here + const toWords = bech32.toWords || (bech32 as any).bech32?.toWords; + + return encode(hrp, toWords(hashRip)); + } + + async serializePath(path: Array) { + this.versionResponse = await getVersion(this.transport); + + if (this.versionResponse.return_code !== ErrorCode.NoError) { + throw this.versionResponse; + } + + switch (this.versionResponse.major) { + case 2: + return serializePathv2(path); + default: + return { + return_code: 0x6400, + error_message: "App Version is not supported", + }; + } + } + + async signGetChunks(path: Array, message: any) { + const serializedPath = await this.serializePath(path); + + const chunks = []; + chunks.push(serializedPath); + const buffer = Buffer.from(message); + + for (let i = 0; i < buffer.length; i += CHUNK_SIZE) { + let end = i + CHUNK_SIZE; + if (i > buffer.length) { + end = buffer.length; + } + chunks.push(buffer.slice(i, end)); + } + + return chunks; + } + + async getVersion() { + try { + this.versionResponse = await getVersion(this.transport); + return this.versionResponse; + } catch (e) { + return processErrorResponse(e); + } + } + + async appInfo() { + return this.transport.send(0xb0, 0x01, 0, 0).then((response: any) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + + const result = {} as any; + + let appName = "err"; + let appVersion = "err"; + let flagLen = 0; + let flagsValue = 0; + + if (response[0] !== 1) { + // Ledger responds with format ID 1. There is no spec for any format != 1 + result.error_message = "response format ID not recognized"; + result.return_code = 0x9001; + } else { + const appNameLen = response[1]; + appName = response.slice(2, 2 + appNameLen).toString("ascii"); + let idx = 2 + appNameLen; + const appVersionLen = response[idx]; + idx += 1; + appVersion = response.slice(idx, idx + appVersionLen).toString("ascii"); + idx += appVersionLen; + const appFlagsLen = response[idx]; + idx += 1; + flagLen = appFlagsLen; + flagsValue = response[idx]; + } + + return { + return_code: returnCode, + error_message: errorCodeToString(returnCode), + // // + appName, + appVersion, + flagLen, + flagsValue, + + flag_recovery: (flagsValue & 1) !== 0, + + flag_signed_mcu_code: (flagsValue & 2) !== 0, + + flag_onboarded: (flagsValue & 4) !== 0, + + flag_pin_validated: (flagsValue & 128) !== 0, + }; + }, processErrorResponse); + } + + async deviceInfo() { + return this.transport.send(0xe0, 0x01, 0, 0, Buffer.from([]), [ErrorCode.NoError, 0x6e00]).then((response: any) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + + if (returnCode === 0x6e00) { + return { + return_code: returnCode, + error_message: "This command is only available in the Dashboard", + }; + } + + const targetId = response.slice(0, 4).toString("hex"); + + let pos = 4; + const secureElementVersionLen = response[pos]; + pos += 1; + const seVersion = response.slice(pos, pos + secureElementVersionLen).toString(); + pos += secureElementVersionLen; + + const flagsLen = response[pos]; + pos += 1; + const flag = response.slice(pos, pos + flagsLen).toString("hex"); + pos += flagsLen; + + const mcuVersionLen = response[pos]; + pos += 1; + // Patch issue in mcu version + let tmp = response.slice(pos, pos + mcuVersionLen); + if (tmp[mcuVersionLen - 1] === 0) { + tmp = response.slice(pos, pos + mcuVersionLen - 1); + } + const mcuVersion = tmp.toString(); + + return { + return_code: returnCode, + error_message: errorCodeToString(returnCode), + // // + targetId, + seVersion, + flag, + mcuVersion, + }; + }, processErrorResponse); + } + + async publicKey(path: Array) { + try { + const serializedPath = await this.serializePath(path); + + switch (this.versionResponse.major) { + case 2: { + const data = Buffer.concat([THORChainApp.serializeHRP("thor"), serializedPath as any]); + return await publicKeyv2(this, data); + } + default: + return { + return_code: 0x6400, + error_message: "App Version is not supported", + }; + } + } catch (e) { + return processErrorResponse(e); + } + } + + async getAddressAndPubKey(path: Array, hrp: any): Promise { + try { + return await this.serializePath(path) + .then((serializedPath) => { + const data = Buffer.concat([THORChainApp.serializeHRP(hrp), serializedPath as any]); + return this.transport + .send(CLA, INS.GET_ADDR_SECP256K1, P1_VALUES.ONLY_RETRIEVE, 0, data, [ErrorCode.NoError]) + .then((response: any) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + + const compressedPk = Buffer.from(response.slice(0, 33)); + const bech32Address = Buffer.from(response.slice(33, -2)).toString(); + + return { + bech32_address: bech32Address, + compressed_pk: compressedPk, + return_code: returnCode, + error_message: errorCodeToString(returnCode), + }; + }, processErrorResponse); + }) + .catch((err) => processErrorResponse(err)); + } catch (e) { + return processErrorResponse(e); + } + } + + async showAddressAndPubKey(path: Array, hrp: any) { + try { + return await this.serializePath(path) + .then((serializedPath) => { + const data = Buffer.concat([THORChainApp.serializeHRP(hrp), serializedPath as any]); + return this.transport + .send(CLA, INS.GET_ADDR_SECP256K1, P1_VALUES.SHOW_ADDRESS_IN_DEVICE, 0, data, [ErrorCode.NoError]) + .then((response: any) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + + const compressedPk = Buffer.from(response.slice(0, 33)); + const bech32Address = Buffer.from(response.slice(33, -2)).toString(); + + return { + bech32_address: bech32Address, + compressed_pk: compressedPk, + return_code: returnCode, + error_message: errorCodeToString(returnCode), + }; + }, processErrorResponse); + }) + .catch((err) => processErrorResponse(err)); + } catch (e) { + return processErrorResponse(e); + } + } + + async signSendChunk(chunkIdx: number, chunkNum: number, chunk: any) { + switch (this.versionResponse.major) { + case 2: + return signSendChunkv2(this, chunkIdx, chunkNum, chunk); + default: + return { + return_code: 0x6400, + error_message: "App Version is not supported", + }; + } + } + + async sign(msg: core.ThorchainSignTx): Promise { + const rawTx = stringifyKeysInOrder({ + account_number: msg.account_number, + chain_id: THOR_CHAIN, + fee: { amount: msg.tx.fee.amount, gas: msg.tx.fee.gas }, + memo: msg.tx.memo, + msgs: msg.tx.msg, + sequence: msg.sequence, + }); + + return this.signGetChunks(msg.addressNList, rawTx).then((chunks) => { + return this.signSendChunk(1, chunks.length, chunks[0]).then(async (response) => { + let result = { + return_code: response.return_code, + error_message: response.error_message, + signature: null, + }; + + for (let i = 1; i < chunks.length; i += 1) { + result = await this.signSendChunk(1 + i, chunks.length, chunks[i]); + if (result.return_code !== ErrorCode.NoError) { + break; + } + } + + return { + return_code: result.return_code, + error_message: result.error_message, + signature: result.signature, + }; + }, processErrorResponse); + }, processErrorResponse); + } +} + +export { THORChainApp }; diff --git a/packages/hdwallet-ledger/src/thorchain/index.ts b/packages/hdwallet-ledger/src/thorchain/index.ts new file mode 100644 index 000000000..e9e595cde --- /dev/null +++ b/packages/hdwallet-ledger/src/thorchain/index.ts @@ -0,0 +1,77 @@ +import type { AccountData, AminoSignResponse, OfflineAminoSigner, StdSignDoc, StdTx } from "@cosmjs/amino"; +import type { SignerData } from "@cosmjs/stargate"; +import * as core from "@shapeshiftoss/hdwallet-core"; +import { fromByteArray } from "base64-js"; +import PLazy from "p-lazy"; + +import { handleError, LedgerTransport } from ".."; +import { getSignature } from "./utils"; +export * from "./common"; +export * from "./helpers"; +export * from "./hw-app-thor"; + +const protoTxBuilder = PLazy.from(() => import("@shapeshiftoss/proto-tx-builder")); + +export const thorchainGetAddress = async ( + transport: LedgerTransport, + msg: core.ThorchainGetAddress +): Promise => { + const addressAndPubkey = await transport.call("Rune", "getAddressAndPubKey", msg.addressNList, "thor"); + + handleError(addressAndPubkey, transport, "Unable to obtain address and public key from device."); + + return addressAndPubkey.payload.bech32_address; +}; + +export const thorchainSignTx = async ( + transport: LedgerTransport, + msg: core.ThorchainSignTx +): Promise => { + const addressAndPubkey = await transport.call("Rune", "getAddressAndPubKey", msg.addressNList, "thor"); + + handleError(addressAndPubkey, transport, "Unable to obtain address and public key from device."); + + const { bech32_address: address, compressed_pk } = addressAndPubkey.payload; + const pubkey = fromByteArray(compressed_pk); + + const signResponse = await transport.call("Rune", "sign", msg); + + handleError(signResponse, transport, "Unable to obtain signature from device."); + + const signature = signResponse.payload.signature; + + const offlineSigner: OfflineAminoSigner = { + async getAccounts(): Promise { + return [ + { + address, + algo: "secp256k1", + pubkey: compressed_pk, + }, + ]; + }, + + async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { + if (signerAddress !== address) throw new Error("expected signerAddress to match address"); + + return { + signed: signDoc, + signature: { + pub_key: { + type: "tendermint/PubKeySecp256k1", + value: pubkey, + }, + signature: getSignature(signature), + }, + }; + }, + }; + + const signerData: SignerData = { + sequence: Number(msg.sequence), + accountNumber: Number(msg.account_number), + chainId: msg.chain_id, + }; + + return (await protoTxBuilder).sign(address, msg.tx as StdTx, offlineSigner, signerData, "thor"); +}; diff --git a/packages/hdwallet-ledger/src/thorchain/utils.ts b/packages/hdwallet-ledger/src/thorchain/utils.ts new file mode 100644 index 000000000..1c4b14b7c --- /dev/null +++ b/packages/hdwallet-ledger/src/thorchain/utils.ts @@ -0,0 +1,68 @@ +import { fromByteArray } from "base64-js"; + +export const getSignature = (signatureArray: Uint8Array) => { + // Check Type Length Value encoding + if (signatureArray.length < 64) { + throw new Error("Invalid Signature: Too short"); + } + if (signatureArray[0] !== 0x30) { + throw new Error("Invalid Ledger Signature TLV encoding: expected first byte 0x30"); + } + if (signatureArray[1] + 2 !== signatureArray.length) { + throw new Error("Invalid Signature: signature length does not match TLV"); + } + if (signatureArray[2] !== 0x02) { + throw new Error("Invalid Ledger Signature TLV encoding: expected length type 0x02"); + } + + // r signature + const rLength = signatureArray[3]; + let rSignature = signatureArray.slice(4, rLength + 4); + + // Drop leading zero on some 'r' signatures that are 33 bytes. + if (rSignature.length === 33 && rSignature[0] === 0) { + rSignature = rSignature.slice(1, 33); + } else if (rSignature.length === 33) { + throw new Error('Invalid signature: "r" too long'); + } + + // add leading zero's to pad to 32 bytes + while (rSignature.length < 32) { + const rSignaturePadded = new Uint8Array(32); + rSignaturePadded.set(rSignature, 32 - rSignature.length); + rSignature = rSignaturePadded; + } + + // s signature + if (signatureArray[rLength + 4] !== 0x02) { + throw new Error("Invalid Ledger Signature TLV encoding: expected length type 0x02"); + } + + const sLength = signatureArray[rLength + 5]; + + if (4 + rLength + 2 + sLength !== signatureArray.length) { + throw new Error("Invalid Ledger Signature: TLV byte lengths do not match message length"); + } + + let sSignature = signatureArray.slice(rLength + 6, signatureArray.length); + + // Drop leading zero on 's' signatures that are 33 bytes. This shouldn't occur since ledger signs using "Small s" math. But just to be sure... + if (sSignature.length === 33 && sSignature[0] === 0) { + sSignature = sSignature.slice(1, 33); + } else if (sSignature.length === 33) { + throw new Error('Invalid signature: "s" too long'); + } + + // add leading zero's to pad to 32 bytes + while (sSignature.length < 32) { + const sSignaturePadded = new Uint8Array(32); + sSignaturePadded.set(sSignature, 32 - sSignature.length); + sSignature = sSignaturePadded; + } + + if (rSignature.length !== 32 || sSignature.length !== 32) { + throw new Error("Invalid signatures: must be 32 bytes each"); + } + + return fromByteArray(Buffer.concat([rSignature, sSignature])); +}; diff --git a/packages/hdwallet-ledger/src/transport.ts b/packages/hdwallet-ledger/src/transport.ts index 70178159d..f45cc14f8 100644 --- a/packages/hdwallet-ledger/src/transport.ts +++ b/packages/hdwallet-ledger/src/transport.ts @@ -6,13 +6,15 @@ import type getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; import type openApp from "@ledgerhq/live-common/lib/hw/openApp"; import * as core from "@shapeshiftoss/hdwallet-core"; +import { THORChainApp } from "./thorchain"; + type MethodsOnly = { [k in keyof T as T[k] extends (...args: any) => any ? k : never]: T[k]; }; type UnwrapPromise = T extends Promise ? R : T; type DefinitelyCallable = T extends (...args: any) => any ? T : never; -export type LedgerTransportCoinType = null | "Btc" | "Eth"; +export type LedgerTransportCoinType = null | "Btc" | "Eth" | "Rune"; type CurriedWithTransport any> = T extends ( transport: Transport, ...args: infer R @@ -33,6 +35,8 @@ type LedgerTransportMethodMap = T extends nul ? MethodsOnly : T extends "Eth" ? MethodsOnly + : T extends "Rune" + ? MethodsOnly : never; export type LedgerTransportMethodName = LedgerTransportMethodMap extends never ? never diff --git a/packages/hdwallet-metamask-shapeshift-multichain/package.json b/packages/hdwallet-metamask-shapeshift-multichain/package.json index ff58859c9..31d503001 100644 --- a/packages/hdwallet-metamask-shapeshift-multichain/package.json +++ b/packages/hdwallet-metamask-shapeshift-multichain/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-shapeshift-multichain", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -17,7 +17,7 @@ "@metamask/detect-provider": "^1.2.0", "@metamask/onboarding": "^1.0.1", "@shapeshiftoss/common-api": "^9.3.0", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "@shapeshiftoss/metamask-snaps-adapter": "^1.0.8", "@shapeshiftoss/metamask-snaps-types": "^1.0.8", "eth-rpc-errors": "^4.0.3", diff --git a/packages/hdwallet-metamask/package.json b/packages/hdwallet-metamask/package.json index e61eecc0f..b83f50c05 100644 --- a/packages/hdwallet-metamask/package.json +++ b/packages/hdwallet-metamask/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-metamask", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -16,7 +16,7 @@ "dependencies": { "@metamask/detect-provider": "^1.2.0", "@metamask/onboarding": "^1.0.1", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "eth-rpc-errors": "^4.0.3", "lodash": "^4.17.21" }, diff --git a/packages/hdwallet-native-vault/package.json b/packages/hdwallet-native-vault/package.json index 39dd35410..76b54db47 100644 --- a/packages/hdwallet-native-vault/package.json +++ b/packages/hdwallet-native-vault/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-native-vault", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,7 +14,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-native": "1.52.6", + "@shapeshiftoss/hdwallet-native": "1.52.7", "bip39": "^3.0.4", "hash-wasm": "^4.9.0", "idb-keyval": "^6.0.3", diff --git a/packages/hdwallet-native/package.json b/packages/hdwallet-native/package.json index 7e6a91ed3..7ef60f951 100644 --- a/packages/hdwallet-native/package.json +++ b/packages/hdwallet-native/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-native", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -16,7 +16,7 @@ "dependencies": { "@shapeshiftoss/bitcoinjs-lib": "5.2.0-shapeshift.2", "@shapeshiftoss/fiosdk": "1.2.1-shapeshift.6", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "@shapeshiftoss/proto-tx-builder": "^0.8.0", "@zxing/text-encoding": "^0.9.0", "bchaddrjs": "^0.4.9", diff --git a/packages/hdwallet-portis/package.json b/packages/hdwallet-portis/package.json index a83a27c5f..39e7fb26e 100644 --- a/packages/hdwallet-portis/package.json +++ b/packages/hdwallet-portis/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-portis", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -15,7 +15,7 @@ }, "dependencies": { "@portis/web3": "3.0.10", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "base64-js": "^1.5.1", "bip32": "^2.0.4", "bitcoinjs-lib": "^5.1.6", diff --git a/packages/hdwallet-tallyho/package.json b/packages/hdwallet-tallyho/package.json index 29e856e48..0a405a476 100644 --- a/packages/hdwallet-tallyho/package.json +++ b/packages/hdwallet-tallyho/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-tallyho", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,7 +14,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "lodash": "^4.17.21", "tallyho-onboarding": "^1.0.2" }, diff --git a/packages/hdwallet-trezor-connect/package.json b/packages/hdwallet-trezor-connect/package.json index 1d85846c0..8a571510c 100644 --- a/packages/hdwallet-trezor-connect/package.json +++ b/packages/hdwallet-trezor-connect/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-trezor-connect", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,8 +14,8 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", - "@shapeshiftoss/hdwallet-trezor": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", + "@shapeshiftoss/hdwallet-trezor": "1.52.7", "@trezor/rollout": "^1.2.0", "trezor-connect": "^8.2.1" } diff --git a/packages/hdwallet-trezor/package.json b/packages/hdwallet-trezor/package.json index a17e13756..9e6cec346 100644 --- a/packages/hdwallet-trezor/package.json +++ b/packages/hdwallet-trezor/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-trezor", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -17,7 +17,7 @@ "dependencies": { "@ethereumjs/common": "^2.4.0", "@ethereumjs/tx": "^3.3.0", - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "base64-js": "^1.5.1", "lodash": "^4.17.21" }, diff --git a/packages/hdwallet-walletconnect/package.json b/packages/hdwallet-walletconnect/package.json index 9c3425293..ed308ecb8 100644 --- a/packages/hdwallet-walletconnect/package.json +++ b/packages/hdwallet-walletconnect/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-walletconnect", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -15,7 +15,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "@walletconnect/qrcode-modal": "^1.7.8", "@walletconnect/web3-provider": "^1.7.8", "ethers": "^5.6.5" diff --git a/packages/hdwallet-walletconnectV2/package.json b/packages/hdwallet-walletconnectV2/package.json index 8c665baa8..987fa4f90 100644 --- a/packages/hdwallet-walletconnectV2/package.json +++ b/packages/hdwallet-walletconnectV2/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-walletconnectv2", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -15,7 +15,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "@walletconnect/ethereum-provider": "^2.10.1", "@walletconnect/modal": "^2.6.2", "ethers": "^5.6.5" diff --git a/packages/hdwallet-xdefi/package.json b/packages/hdwallet-xdefi/package.json index b75377706..82870af48 100644 --- a/packages/hdwallet-xdefi/package.json +++ b/packages/hdwallet-xdefi/package.json @@ -1,6 +1,6 @@ { "name": "@shapeshiftoss/hdwallet-xdefi", - "version": "1.52.6", + "version": "1.52.7", "license": "MIT", "publishConfig": { "access": "public" @@ -14,7 +14,7 @@ "prepublishOnly": "yarn clean && yarn build" }, "dependencies": { - "@shapeshiftoss/hdwallet-core": "1.52.6", + "@shapeshiftoss/hdwallet-core": "1.52.7", "lodash": "^4.17.21" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 2dcfd0cfa..271b5067c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6431,6 +6431,13 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/ripemd160@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/ripemd160/-/ripemd160-2.0.2.tgz#e8ec7b1572e25fff9b3d0c4bc79022df4e037d12" + integrity sha512-hv3Oh/+ldCqp1xBRGi/1G6y2fxV6wUiiwjPt2Q7fe4UgmbD52ChdmxJyjDsGCRb9yuTBS9281UhH4D9gO85k7A== + dependencies: + "@types/node" "*" + "@types/secp256k1@^4.0.1": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c"