@sidhujag/sysweb3-keyring 1.0.628 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +174 -0
- package/cjs/bip84-native.js +8 -0
- package/cjs/bip84-native.js.map +1 -0
- package/cjs/hardware-wallet-manager.js +302 -128
- package/cjs/hardware-wallet-manager.js.map +1 -1
- package/cjs/keyring-core.js +319 -0
- package/cjs/keyring-core.js.map +1 -1
- package/cjs/keyring-manager.js +9 -3
- package/cjs/keyring-manager.js.map +1 -1
- package/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -1
- package/cjs/ledger/host-types.js +10 -0
- package/cjs/ledger/host-types.js.map +1 -0
- package/cjs/ledger/index.js +243 -447
- package/cjs/ledger/index.js.map +1 -1
- package/cjs/ledger/prepare-psbt.js +177 -0
- package/cjs/ledger/prepare-psbt.js.map +1 -0
- package/cjs/public-bip84.js +157 -0
- package/cjs/public-bip84.js.map +1 -0
- package/cjs/software-hd-account.js +23 -0
- package/cjs/software-hd-account.js.map +1 -0
- package/cjs/software-utxo-account.js +12 -0
- package/cjs/software-utxo-account.js.map +1 -0
- package/cjs/software.js +24 -0
- package/cjs/software.js.map +1 -1
- package/cjs/transactions/bip84-native-plan.js +481 -0
- package/cjs/transactions/bip84-native-plan.js.map +1 -0
- package/cjs/transactions/ethereum.js +55 -10
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/hardware-psbt.js +36 -44
- package/cjs/transactions/hardware-psbt.js.map +1 -1
- package/cjs/transactions/prepared-bip84.js +214 -0
- package/cjs/transactions/prepared-bip84.js.map +1 -0
- package/cjs/transactions/prepared-evm.js +197 -0
- package/cjs/transactions/prepared-evm.js.map +1 -0
- package/cjs/trezor/connect.js +243 -0
- package/cjs/trezor/connect.js.map +1 -0
- package/cjs/trezor/index.js +96 -58
- package/cjs/trezor/index.js.map +1 -1
- package/cjs/trezor/witness.js +25 -0
- package/cjs/trezor/witness.js.map +1 -0
- package/cjs/utils/psbt-account-scope.js +5 -3
- package/cjs/utils/psbt-account-scope.js.map +1 -1
- package/esm/errorUtils.js +72 -0
- package/esm/errorUtils.js.map +1 -0
- package/esm/ethers-v6.js +207 -0
- package/esm/ethers-v6.js.map +1 -0
- package/esm/hardware-wallet-manager.js +643 -0
- package/esm/hardware-wallet-manager.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/appClient.js +279 -0
- package/esm/ledger/bitcoin_client/lib/appClient.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/bip32.js +49 -0
- package/esm/ledger/bitcoin_client/lib/bip32.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/buffertools.js +119 -0
- package/esm/ledger/bitcoin_client/lib/buffertools.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/clientCommands.js +269 -0
- package/esm/ledger/bitcoin_client/lib/clientCommands.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/merkelizedPsbt.js +50 -0
- package/esm/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/merkle.js +111 -0
- package/esm/ledger/bitcoin_client/lib/merkle.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/merkleMap.js +42 -0
- package/esm/ledger/bitcoin_client/lib/merkleMap.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/policy.js +68 -0
- package/esm/ledger/bitcoin_client/lib/policy.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/psbtv2.js +645 -0
- package/esm/ledger/bitcoin_client/lib/psbtv2.js.map +1 -0
- package/esm/ledger/bitcoin_client/lib/varint.js +108 -0
- package/esm/ledger/bitcoin_client/lib/varint.js.map +1 -0
- package/esm/ledger/consts.js +4 -0
- package/esm/ledger/consts.js.map +1 -0
- package/esm/ledger/dmk-evm.js +258 -0
- package/esm/ledger/dmk-evm.js.map +1 -0
- package/esm/ledger/dmk-session.js +264 -0
- package/esm/ledger/dmk-session.js.map +1 -0
- package/esm/ledger/dmk-utxo.js +279 -0
- package/esm/ledger/dmk-utxo.js.map +1 -0
- package/esm/ledger/host-types.js +5 -0
- package/esm/ledger/host-types.js.map +1 -0
- package/esm/ledger/index.js +268 -0
- package/esm/ledger/index.js.map +1 -0
- package/esm/ledger/prepare-psbt.js +141 -0
- package/esm/ledger/prepare-psbt.js.map +1 -0
- package/esm/ledger/types.js +2 -0
- package/esm/ledger/types.js.map +1 -0
- package/esm/ledger-host.js +36 -0
- package/esm/ledger-host.js.map +1 -0
- package/esm/private-records.js +153 -0
- package/esm/private-records.js.map +1 -0
- package/esm/providers.js +588 -0
- package/esm/providers.js.map +1 -0
- package/esm/signers.js +28 -0
- package/esm/signers.js.map +1 -0
- package/esm/trezor/connect.js +206 -0
- package/esm/trezor/connect.js.map +1 -0
- package/esm/trezor/index.js +876 -0
- package/esm/trezor/index.js.map +1 -0
- package/esm/trezor/witness.js +21 -0
- package/esm/trezor/witness.js.map +1 -0
- package/esm/types.js +17 -0
- package/esm/types.js.map +1 -0
- package/esm/utils/derivation-paths.js +109 -0
- package/esm/utils/derivation-paths.js.map +1 -0
- package/esm/utils/psbt-account-scope.js +310 -0
- package/esm/utils/psbt-account-scope.js.map +1 -0
- package/package.json +34 -9
- package/types/bip84-native.d.ts +3 -0
- package/types/hardware-wallet-manager.d.ts +12 -19
- package/types/keyring-core.d.ts +14 -0
- package/types/keyring-manager.d.ts +2 -1
- package/types/ledger/bitcoin_client/lib/appClient.d.ts +6 -3
- package/types/ledger/dmk-evm.d.ts +8 -0
- package/types/ledger/dmk-session.d.ts +60 -0
- package/types/ledger/dmk-utxo.d.ts +26 -0
- package/types/ledger/host-types.d.ts +131 -0
- package/types/ledger/index.d.ts +21 -58
- package/types/ledger/prepare-psbt.d.ts +3 -0
- package/types/ledger/types.d.ts +3 -0
- package/types/ledger-host.d.ts +7 -0
- package/types/public-bip84.d.ts +39 -0
- package/types/software-hd-account.d.ts +16 -0
- package/types/software-utxo-account.d.ts +13 -0
- package/types/software.d.ts +26 -0
- package/types/transactions/bip84-native-plan.d.ts +83 -0
- package/types/transactions/ethereum.d.ts +1 -1
- package/types/transactions/prepared-bip84.d.ts +27 -0
- package/types/transactions/prepared-evm.d.ts +53 -0
- package/types/trezor/connect.d.ts +42 -0
- package/types/trezor/index.d.ts +22 -4
- package/types/trezor/witness.d.ts +3 -0
- package/types/utils/psbt-account-scope.d.ts +1 -0
package/cjs/ledger/index.js
CHANGED
|
@@ -1,475 +1,271 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
3
|
exports.LedgerKeyring = void 0;
|
|
40
|
-
const bitcoin_client_1 = __importStar(require("./bitcoin_client"));
|
|
41
|
-
const consts_1 = require("./consts");
|
|
42
|
-
const sysweb3_network_1 = require("@sidhujag/sysweb3-network");
|
|
43
|
-
const bip32_1 = __importDefault(require("bip32"));
|
|
44
|
-
const secp256k1_1 = __importDefault(require("@bitcoinerlab/secp256k1"));
|
|
45
|
-
const hw_app_eth_1 = __importStar(require("@ledgerhq/hw-app-eth"));
|
|
46
|
-
const syscoinjs_lib_1 = require("syscoinjs-lib");
|
|
47
4
|
const eth_sig_util_1 = require("@metamask/eth-sig-util");
|
|
48
|
-
const
|
|
5
|
+
const ethers_1 = require("ethers");
|
|
49
6
|
const hardware_wallet_manager_1 = require("../hardware-wallet-manager");
|
|
50
|
-
|
|
7
|
+
const host_types_1 = require("./host-types");
|
|
8
|
+
const invalidRequest = () => (0, host_types_1.ledgerHostError)('LEDGER_INVALID_REQUEST', 'Invalid Ledger signing request');
|
|
9
|
+
const invalidSignature = () => (0, host_types_1.ledgerHostError)('LEDGER_INVALID_SIGNATURE', 'Ledger returned an invalid signature for this request');
|
|
10
|
+
const accountIndex = (value) => {
|
|
11
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 0x7fffffff)
|
|
12
|
+
throw invalidRequest();
|
|
13
|
+
return value;
|
|
14
|
+
};
|
|
15
|
+
const hexBytes = (value) => {
|
|
16
|
+
if (typeof value !== 'string' || !/^(?:0x)?(?:[0-9a-f]{2})*$/i.test(value))
|
|
17
|
+
throw invalidRequest();
|
|
18
|
+
return Uint8Array.from(Buffer.from(value.replace(/^0x/i, ''), 'hex'));
|
|
19
|
+
};
|
|
20
|
+
const approvedAddress = (value) => {
|
|
21
|
+
try {
|
|
22
|
+
return (0, ethers_1.getAddress)(value);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw invalidRequest();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const checkedSignature = (value, digest, expected) => {
|
|
29
|
+
try {
|
|
30
|
+
let signature;
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
if (!/^0x[0-9a-f]{130}$/i.test(value))
|
|
33
|
+
throw invalidSignature();
|
|
34
|
+
signature = ethers_1.Signature.from(value);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
if (!value ||
|
|
38
|
+
!/^[0-9a-f]{64}$/i.test(value.r) ||
|
|
39
|
+
!/^[0-9a-f]{64}$/i.test(value.s) ||
|
|
40
|
+
!/^[0-9a-f]+$/i.test(value.v))
|
|
41
|
+
throw invalidSignature();
|
|
42
|
+
const v = Number.parseInt(value.v, 16);
|
|
43
|
+
if (!Number.isSafeInteger(v) || v < 0)
|
|
44
|
+
throw invalidSignature();
|
|
45
|
+
signature = ethers_1.Signature.from({ r: `0x${value.r}`, s: `0x${value.s}`, v });
|
|
46
|
+
}
|
|
47
|
+
if ((0, ethers_1.recoverAddress)(digest, signature) !== expected)
|
|
48
|
+
throw invalidSignature();
|
|
49
|
+
return signature;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw invalidSignature();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// Detach the approved typed data before connection or host code can await/mutate it.
|
|
56
|
+
const copyTypedData = (value, seen = new WeakSet(), depth = 0) => {
|
|
57
|
+
if (depth > 64)
|
|
58
|
+
throw invalidRequest();
|
|
59
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
60
|
+
return value;
|
|
61
|
+
if (typeof value === 'bigint')
|
|
62
|
+
return value.toString();
|
|
63
|
+
if (typeof value === 'number') {
|
|
64
|
+
if (!Number.isFinite(value))
|
|
65
|
+
throw invalidRequest();
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
if (value instanceof Uint8Array)
|
|
69
|
+
return `0x${Buffer.from(value).toString('hex')}`;
|
|
70
|
+
if (!value || typeof value !== 'object' || seen.has(value))
|
|
71
|
+
throw invalidRequest();
|
|
72
|
+
const array = Array.isArray(value);
|
|
73
|
+
const prototype = Object.getPrototypeOf(value);
|
|
74
|
+
if (!array && prototype !== Object.prototype && prototype !== null)
|
|
75
|
+
throw invalidRequest();
|
|
76
|
+
seen.add(value);
|
|
77
|
+
try {
|
|
78
|
+
const result = array ? [] : Object.create(null);
|
|
79
|
+
for (const key of Object.keys(value)) {
|
|
80
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
81
|
+
if (!('value' in descriptor))
|
|
82
|
+
throw invalidRequest();
|
|
83
|
+
if (descriptor.value !== undefined)
|
|
84
|
+
result[key] = copyTypedData(descriptor.value, seen, depth + 1);
|
|
85
|
+
}
|
|
86
|
+
return Object.freeze(result);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
seen.delete(value);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/** Small authority-side facade. Device SDKs belong exclusively in ledger-host. */
|
|
51
93
|
class LedgerKeyring {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
!!showInLedger);
|
|
77
|
-
return address;
|
|
78
|
-
}, 'getUtxoAddress');
|
|
94
|
+
// Retain the first constructor position for existing shared-manager callers.
|
|
95
|
+
// A manager alone cannot grant a new device capability.
|
|
96
|
+
constructor(sharedHardwareWalletManager, host) {
|
|
97
|
+
this.sharedHardwareWalletManager = sharedHardwareWalletManager;
|
|
98
|
+
this.host = host;
|
|
99
|
+
this.generation = 0;
|
|
100
|
+
this.ensureConnection = async () => this.run(async () => undefined);
|
|
101
|
+
this.verifyUtxoAddress = (accountIndex, currency, slip44) => this.run((host) => host.utxo.getAddress({
|
|
102
|
+
accountIndex,
|
|
103
|
+
currency,
|
|
104
|
+
slip44,
|
|
105
|
+
checkOnDevice: true,
|
|
106
|
+
}));
|
|
107
|
+
this.signUtxoMessage = (request) => {
|
|
108
|
+
const approved = Object.freeze({
|
|
109
|
+
currency: request.currency,
|
|
110
|
+
slip44: request.slip44,
|
|
111
|
+
accountIndex: request.accountIndex,
|
|
112
|
+
message: request.message,
|
|
113
|
+
addressIndex: request.addressIndex,
|
|
114
|
+
change: request.change,
|
|
115
|
+
accountXpub: request.accountXpub,
|
|
116
|
+
});
|
|
117
|
+
return this.run((host) => host.utxo.signMessage(approved));
|
|
79
118
|
};
|
|
80
|
-
this.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
this.signPsbt = (request) => {
|
|
120
|
+
const approved = Object.freeze({
|
|
121
|
+
currency: request.currency,
|
|
122
|
+
slip44: request.slip44,
|
|
123
|
+
accountIndex: request.accountIndex,
|
|
124
|
+
accountXpub: request.accountXpub,
|
|
125
|
+
psbtBase64: request.psbtBase64,
|
|
126
|
+
});
|
|
127
|
+
return this.run((host) => host.utxo.signPsbt(approved));
|
|
128
|
+
};
|
|
129
|
+
this.evm = {
|
|
130
|
+
getEvmAddressAndPubKey: async (request) => {
|
|
131
|
+
const approved = { accountIndex: accountIndex(request.accountIndex) };
|
|
132
|
+
return this.run(async (host, assertCurrent) => {
|
|
133
|
+
const result = await host.evm.getAddress(approved);
|
|
134
|
+
assertCurrent();
|
|
91
135
|
try {
|
|
92
|
-
|
|
136
|
+
const address = (0, ethers_1.getAddress)(result.address);
|
|
137
|
+
const publicKey = result.publicKey.replace(/^0x/i, '');
|
|
138
|
+
if ((0, ethers_1.computeAddress)(`0x${publicKey}`) !== address)
|
|
139
|
+
throw invalidRequest();
|
|
140
|
+
return { address, publicKey };
|
|
93
141
|
}
|
|
94
|
-
catch
|
|
95
|
-
|
|
96
|
-
return await this.ledgerUtxoClient.getExtendedPubkey(this.hdPath, true);
|
|
142
|
+
catch {
|
|
143
|
+
throw (0, host_types_1.ledgerHostError)('LEDGER_INVALID_RESPONSE', 'Ledger returned an invalid address or public key');
|
|
97
144
|
}
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
signEVMTransaction: async (request) => {
|
|
148
|
+
const index = accountIndex(request.accountIndex);
|
|
149
|
+
const expected = approvedAddress(request.expectedAddress);
|
|
150
|
+
let transaction;
|
|
151
|
+
try {
|
|
152
|
+
transaction = ethers_1.Transaction.from(`0x${Buffer.from(hexBytes(request.rawTx)).toString('hex')}`);
|
|
153
|
+
if (transaction.signature || transaction.chainId <= 0n)
|
|
154
|
+
throw invalidRequest();
|
|
98
155
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Sign a UTXO message - public method used by transaction classes
|
|
103
|
-
*/
|
|
104
|
-
this.signUtxoMessage = async (path, message) => {
|
|
105
|
-
return this.executeWithRetry(async () => {
|
|
106
|
-
const bufferMessage = Buffer.from(message);
|
|
107
|
-
const signature = await this.ledgerUtxoClient.signMessage(bufferMessage, path);
|
|
108
|
-
return signature;
|
|
109
|
-
}, 'signUtxoMessage');
|
|
110
|
-
};
|
|
111
|
-
this.signEVMTransaction = async ({ rawTx, accountIndex, }) => {
|
|
112
|
-
return this.executeWithRetry(async () => {
|
|
113
|
-
this.setHdPath('eth', accountIndex, 60);
|
|
114
|
-
const resolution = await hw_app_eth_1.ledgerService.resolveTransaction(rawTx, {}, {});
|
|
115
|
-
const signature = await this.ledgerEVMClient.signTransaction(this.hdPath.replace(/^m\//, ''), // Remove 'm/' prefix for EVM
|
|
116
|
-
rawTx, resolution);
|
|
117
|
-
return signature;
|
|
118
|
-
}, 'signEVMTransaction');
|
|
119
|
-
};
|
|
120
|
-
this.signPersonalMessage = async ({ message, accountIndex, }) => {
|
|
121
|
-
return this.executeWithRetry(async () => {
|
|
122
|
-
this.setHdPath('eth', accountIndex, 60);
|
|
123
|
-
const signature = await this.ledgerEVMClient.signPersonalMessage(this.hdPath.replace(/^m\//, ''), // Remove 'm/' prefix for EVM
|
|
124
|
-
message);
|
|
125
|
-
return `0x${signature.r}${signature.s}${signature.v.toString(16)}`;
|
|
126
|
-
}, 'signPersonalMessage');
|
|
127
|
-
};
|
|
128
|
-
this.transformTypedData = (data, version) => {
|
|
129
|
-
const { types, primaryType, domain, message } = this.sanitizeData(data);
|
|
130
|
-
const domainSeparatorHash = eth_sig_util_1.TypedDataUtils.hashStruct('EIP712Domain', this.sanitizeData(domain), types, version).toString('hex');
|
|
131
|
-
let messageHash = null;
|
|
132
|
-
if (primaryType !== 'EIP712Domain') {
|
|
133
|
-
messageHash = eth_sig_util_1.TypedDataUtils.hashStruct(primaryType, this.sanitizeData(message), types, version).toString('hex');
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
domain_separator_hash: domainSeparatorHash,
|
|
137
|
-
message_hash: messageHash,
|
|
138
|
-
...data,
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
this.getEvmAddressAndPubKey = async ({ accountIndex, }) => {
|
|
142
|
-
return this.executeWithRetry(async () => {
|
|
143
|
-
this.setHdPath('eth', accountIndex, 60);
|
|
144
|
-
const { address, publicKey } = await this.ledgerEVMClient.getAddress(this.hdPath.replace(/^m\//, '') // Remove 'm/' prefix for EVM
|
|
145
|
-
);
|
|
146
|
-
return { address, publicKey };
|
|
147
|
-
}, 'getEvmAddressAndPubKey');
|
|
148
|
-
};
|
|
149
|
-
this.signTypedData = async ({ version, data, accountIndex, }) => {
|
|
150
|
-
return this.executeWithRetry(async () => {
|
|
151
|
-
this.setHdPath('eth', accountIndex, 60);
|
|
152
|
-
// V1 typed data is not supported by hardware wallets
|
|
153
|
-
if (version === eth_sig_util_1.SignTypedDataVersion.V1) {
|
|
154
|
-
throw new Error('Ledger: V1 typed data signing is not supported. Please use V3 or V4.');
|
|
156
|
+
catch {
|
|
157
|
+
throw invalidRequest();
|
|
155
158
|
}
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
159
|
+
const approved = {
|
|
160
|
+
accountIndex: index,
|
|
161
|
+
expectedAddress: expected,
|
|
162
|
+
unsignedTxHex: transaction.unsignedSerialized.replace(/^0x/, ''),
|
|
163
|
+
};
|
|
164
|
+
const digest = transaction.unsignedHash;
|
|
165
|
+
return this.run(async (host, assertCurrent) => {
|
|
166
|
+
const result = await host.evm.signTransaction(approved);
|
|
167
|
+
assertCurrent();
|
|
168
|
+
const signature = checkedSignature(result, digest, expected);
|
|
169
|
+
if (signature.legacyChainId !== null &&
|
|
170
|
+
signature.legacyChainId !== transaction.chainId)
|
|
171
|
+
throw invalidSignature();
|
|
172
|
+
return {
|
|
173
|
+
r: signature.r.slice(2),
|
|
174
|
+
s: signature.s.slice(2),
|
|
175
|
+
v: signature.v.toString(16),
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
signPersonalMessage: async (request) => {
|
|
180
|
+
const expected = approvedAddress(request.expectedAddress);
|
|
181
|
+
const messageHex = Buffer.from(hexBytes(request.message)).toString('hex');
|
|
182
|
+
const digest = (0, ethers_1.hashMessage)(hexBytes(messageHex));
|
|
183
|
+
const approved = {
|
|
184
|
+
accountIndex: accountIndex(request.accountIndex),
|
|
185
|
+
expectedAddress: expected,
|
|
186
|
+
messageHex,
|
|
187
|
+
};
|
|
188
|
+
return this.run(async (host) => checkedSignature(await host.evm.signPersonalMessage(approved), digest, expected).serialized);
|
|
189
|
+
},
|
|
190
|
+
signTypedData: async (request) => {
|
|
191
|
+
const { version } = request;
|
|
192
|
+
if (version !== eth_sig_util_1.SignTypedDataVersion.V3 &&
|
|
193
|
+
version !== eth_sig_util_1.SignTypedDataVersion.V4)
|
|
194
|
+
throw invalidRequest();
|
|
195
|
+
const expected = approvedAddress(request.expectedAddress);
|
|
196
|
+
const index = accountIndex(request.accountIndex);
|
|
197
|
+
let data, digest;
|
|
198
|
+
try {
|
|
199
|
+
data = copyTypedData(request.data);
|
|
200
|
+
digest = `0x${eth_sig_util_1.TypedDataUtils.eip712Hash(data, version).toString('hex')}`;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
throw invalidRequest();
|
|
204
|
+
}
|
|
205
|
+
const approved = {
|
|
206
|
+
accountIndex: index,
|
|
207
|
+
expectedAddress: expected,
|
|
208
|
+
data,
|
|
209
|
+
version,
|
|
210
|
+
};
|
|
211
|
+
return this.run(async (host) => checkedSignature(await host.evm.signTypedData(approved), digest, expected).serialized);
|
|
212
|
+
},
|
|
201
213
|
};
|
|
202
214
|
this.utxo = {
|
|
203
|
-
getUtxoAddress: this.
|
|
204
|
-
getXpub: this.getXpub
|
|
215
|
+
getUtxoAddress: ({ coin, index, slip44 }) => this.run((host) => host.utxo.getAddress({ currency: coin, accountIndex: index, slip44 })),
|
|
216
|
+
getXpub: ({ coin, index, slip44 }) => this.run((host) => host.utxo.getXpub({
|
|
217
|
+
currency: coin,
|
|
218
|
+
accountIndex: index,
|
|
219
|
+
slip44,
|
|
220
|
+
// Import must remain available for paths the device classifies as
|
|
221
|
+
// unusual. Ask for its confirmation once, without retrying denial.
|
|
222
|
+
checkOnDevice: true,
|
|
223
|
+
})),
|
|
205
224
|
verifyUtxoAddress: this.verifyUtxoAddress,
|
|
206
225
|
};
|
|
207
226
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
*/
|
|
213
|
-
async ensureConnection() {
|
|
214
|
-
await this.hardwareWalletManager.ensureConnection(hardware_wallet_manager_1.HardwareWalletType.LEDGER);
|
|
215
|
-
this.transport = await this.hardwareWalletManager.getLedgerConnection();
|
|
216
|
-
// Create clients if transport is available
|
|
217
|
-
if (this.transport && (!this.ledgerEVMClient || !this.ledgerUtxoClient)) {
|
|
218
|
-
this.ledgerEVMClient = new hw_app_eth_1.default(this.transport);
|
|
219
|
-
this.ledgerUtxoClient = new bitcoin_client_1.default(this.transport);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
sanitizeData(data) {
|
|
223
|
-
switch (Object.prototype.toString.call(data)) {
|
|
224
|
-
case '[object Object]': {
|
|
225
|
-
const entries = Object.keys(data).map((k) => [
|
|
226
|
-
k,
|
|
227
|
-
this.sanitizeData(data[k]),
|
|
228
|
-
]);
|
|
229
|
-
return Object.fromEntries(entries);
|
|
230
|
-
}
|
|
231
|
-
case '[object Array]':
|
|
232
|
-
return data.map((v) => this.sanitizeData(v));
|
|
233
|
-
case '[object BigInt]':
|
|
234
|
-
return data.toString();
|
|
235
|
-
default:
|
|
236
|
-
return data;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
// Build a stable cache key for a policy bound to the device and derivation path
|
|
240
|
-
buildWalletCacheKey(fingerprint, walletPolicy) {
|
|
241
|
-
// Wallet HMACs are bound to a specific wallet policy id.
|
|
242
|
-
// The id commits to: version, wallet name, descriptor template hash, and the merkle root of keys.
|
|
243
|
-
// Therefore, caching by (fingerprint + walletId) avoids collisions across different networks
|
|
244
|
-
// that may share the same derivation path (e.g., multiple slip44=1 testnets).
|
|
245
|
-
return `${fingerprint}|${walletPolicy.getId().toString('hex')}`;
|
|
227
|
+
requireHost() {
|
|
228
|
+
if (!this.host)
|
|
229
|
+
throw (0, host_types_1.ledgerHostError)('HARDWARE_SIGNER_UNAVAILABLE', 'Ledger requires an authorized device host');
|
|
230
|
+
return this.host;
|
|
246
231
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
232
|
+
async run(operation) {
|
|
233
|
+
const generation = this.generation;
|
|
234
|
+
const host = this.requireHost();
|
|
235
|
+
// A visible platform host owns discovery, permission and device selection.
|
|
236
|
+
if (!host.getStatus().connected)
|
|
237
|
+
await host.connect({ useGrantedDevices: true });
|
|
238
|
+
const assertCurrent = () => {
|
|
239
|
+
if (generation !== this.generation)
|
|
240
|
+
throw (0, host_types_1.ledgerCancelledError)();
|
|
241
|
+
};
|
|
242
|
+
assertCurrent();
|
|
258
243
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (walletHMAC && Buffer.isBuffer(walletHMAC)) {
|
|
263
|
-
this.walletHmacCache.set(cacheKey, walletHMAC);
|
|
264
|
-
return walletHMAC;
|
|
265
|
-
}
|
|
266
|
-
return null;
|
|
244
|
+
const result = await operation(host, assertCurrent);
|
|
245
|
+
assertCurrent();
|
|
246
|
+
return result;
|
|
267
247
|
}
|
|
268
|
-
catch (
|
|
269
|
-
|
|
270
|
-
|
|
248
|
+
catch (error) {
|
|
249
|
+
assertCurrent();
|
|
250
|
+
throw error;
|
|
271
251
|
}
|
|
272
252
|
}
|
|
273
|
-
setHdPath(coin, accountIndex, slip44) {
|
|
274
|
-
if ((0, derivation_paths_1.isEvmCoin)(coin, slip44)) {
|
|
275
|
-
// For EVM, the "accountIndex" parameter is actually used as the address index
|
|
276
|
-
// EVM typically uses account 0, and different addresses are at different address indices
|
|
277
|
-
this.hdPath = (0, derivation_paths_1.getAddressDerivationPath)(coin, slip44, 0, // account is always 0 for EVM
|
|
278
|
-
false, // not a change address
|
|
279
|
-
accountIndex // this is actually the address index for EVM
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
// For UTXO, use account-level derivation path
|
|
284
|
-
this.hdPath = (0, derivation_paths_1.getAccountDerivationPath)(coin, slip44, accountIndex);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Convert PSBT to Ledger format with retry logic
|
|
289
|
-
*/
|
|
290
|
-
async convertToLedgerFormat(psbt, accountXpub, accountId, currency, slip44) {
|
|
291
|
-
return this.executeWithRetry(async () => {
|
|
292
|
-
// Ensure Ledger is connected before attempting operations
|
|
293
|
-
// This is now handled by executeWithRetry
|
|
294
|
-
// Build a bitcoinjs-bip32 network from slip44/currency so zpub/vpub parse directly
|
|
295
|
-
const { networks, types } = (0, sysweb3_network_1.getNetworkConfig)(slip44, currency);
|
|
296
|
-
const isTestnet = slip44 === 1;
|
|
297
|
-
const pubTypes = isTestnet
|
|
298
|
-
? types.zPubType.testnet
|
|
299
|
-
: types.zPubType.mainnet;
|
|
300
|
-
const baseNetwork = isTestnet ? networks.testnet : networks.mainnet;
|
|
301
|
-
const network = {
|
|
302
|
-
...baseNetwork,
|
|
303
|
-
bip32: {
|
|
304
|
-
public: parseInt(pubTypes.vpub || pubTypes.zpub, 16),
|
|
305
|
-
private: parseInt(pubTypes.vprv || pubTypes.zprv, 16),
|
|
306
|
-
},
|
|
307
|
-
};
|
|
308
|
-
const bip32 = (0, bip32_1.default)(secp256k1_1.default);
|
|
309
|
-
const accountNode = bip32.fromBase58(accountXpub, network);
|
|
310
|
-
// Get master fingerprint
|
|
311
|
-
const fingerprint = await this.getMasterFingerprint();
|
|
312
|
-
// Enhance each input with bip32Derivation
|
|
313
|
-
const missingInputDerivations = [];
|
|
314
|
-
for (let i = 0; i < psbt.inputCount; i++) {
|
|
315
|
-
const dataInput = psbt.data.inputs[i];
|
|
316
|
-
// External inputs are already authorized by their final scripts. Do
|
|
317
|
-
// not invent wallet derivations or ask the device to sign them again.
|
|
318
|
-
if (dataInput.finalScriptSig || dataInput.finalScriptWitness)
|
|
319
|
-
continue;
|
|
320
|
-
// Skip if already has bip32Derivation
|
|
321
|
-
if (dataInput.bip32Derivation && dataInput.bip32Derivation.length > 0) {
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
// Ensure witnessUtxo is present if nonWitnessUtxo exists
|
|
325
|
-
if (!dataInput.witnessUtxo && dataInput.nonWitnessUtxo) {
|
|
326
|
-
const txBuffer = dataInput.nonWitnessUtxo;
|
|
327
|
-
const tx = syscoinjs_lib_1.Transaction.fromBuffer(txBuffer);
|
|
328
|
-
const vout = psbt.txInputs[i].index;
|
|
329
|
-
if (tx.outs[vout]) {
|
|
330
|
-
dataInput.witnessUtxo = {
|
|
331
|
-
script: tx.outs[vout].script,
|
|
332
|
-
value: tx.outs[vout].value,
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
// Extract path from unknownKeyVals by searching for the key, not using hardcoded index
|
|
337
|
-
let pathFromInput = null;
|
|
338
|
-
if (dataInput.unknownKeyVals && dataInput.unknownKeyVals.length > 0) {
|
|
339
|
-
for (const kv of dataInput.unknownKeyVals) {
|
|
340
|
-
const keyStr = Buffer.from(kv.key).toString();
|
|
341
|
-
if (keyStr === 'path') {
|
|
342
|
-
pathFromInput = Buffer.from(kv.value).toString();
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
let bip32Derivation = null;
|
|
348
|
-
if (pathFromInput) {
|
|
349
|
-
const fullPath = pathFromInput;
|
|
350
|
-
const accountPath = (0, derivation_paths_1.getAccountDerivationPath)(currency, slip44, accountId);
|
|
351
|
-
const relativePath = fullPath
|
|
352
|
-
.replace(accountPath, '')
|
|
353
|
-
.replace(/^\//, '');
|
|
354
|
-
const derivationTokens = relativePath.split('/').filter((t) => t);
|
|
355
|
-
const derivedAccount = derivationTokens.reduce((acc, token) => {
|
|
356
|
-
const index = parseInt(token);
|
|
357
|
-
if (isNaN(index)) {
|
|
358
|
-
return acc;
|
|
359
|
-
}
|
|
360
|
-
return acc.derive(index);
|
|
361
|
-
}, accountNode);
|
|
362
|
-
const rawPubkey = derivedAccount.publicKey;
|
|
363
|
-
const pubkeyBuf = Buffer.isBuffer(rawPubkey)
|
|
364
|
-
? rawPubkey
|
|
365
|
-
: Buffer.from(rawPubkey);
|
|
366
|
-
if (pubkeyBuf && pubkeyBuf.length === 33) {
|
|
367
|
-
// Add the bip32Derivation that Ledger needs
|
|
368
|
-
bip32Derivation = {
|
|
369
|
-
masterFingerprint: Buffer.from(fingerprint, 'hex'),
|
|
370
|
-
path: fullPath,
|
|
371
|
-
pubkey: pubkeyBuf,
|
|
372
|
-
};
|
|
373
|
-
psbt.updateInput(i, {
|
|
374
|
-
bip32Derivation: [bip32Derivation],
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
// Check the updated PSBT state (avoid stale local references)
|
|
379
|
-
const updatedInput = psbt.data.inputs[i];
|
|
380
|
-
if (!updatedInput.bip32Derivation ||
|
|
381
|
-
updatedInput.bip32Derivation.length === 0) {
|
|
382
|
-
missingInputDerivations.push(i);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
// Enhance each output with bip32Derivation when it's a change/output owned by the wallet
|
|
386
|
-
for (let i = 0; i < psbt.data.outputs.length; i++) {
|
|
387
|
-
const dataOutput = psbt.data.outputs[i];
|
|
388
|
-
// Skip if derivation already present
|
|
389
|
-
if (dataOutput.bip32Derivation &&
|
|
390
|
-
dataOutput.bip32Derivation.length > 0) {
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
// Extract path from unknownKeyVals by searching for the key 'path'
|
|
394
|
-
let pathFromOutput = null;
|
|
395
|
-
if (dataOutput.unknownKeyVals && dataOutput.unknownKeyVals.length > 0) {
|
|
396
|
-
for (const kv of dataOutput.unknownKeyVals) {
|
|
397
|
-
const keyStr = Buffer.from(kv.key).toString();
|
|
398
|
-
if (keyStr === 'path') {
|
|
399
|
-
pathFromOutput = Buffer.from(kv.value).toString();
|
|
400
|
-
break;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
if (pathFromOutput) {
|
|
405
|
-
let bip32Derivation = null;
|
|
406
|
-
const fullPath = pathFromOutput;
|
|
407
|
-
const accountPath = (0, derivation_paths_1.getAccountDerivationPath)(currency, slip44, accountId);
|
|
408
|
-
const relativePath = fullPath
|
|
409
|
-
.replace(accountPath, '')
|
|
410
|
-
.replace(/^\//, '');
|
|
411
|
-
const derivationTokens = relativePath.split('/').filter((t) => t);
|
|
412
|
-
const derivedAccount = derivationTokens.reduce((acc, token) => {
|
|
413
|
-
const index = parseInt(token);
|
|
414
|
-
if (isNaN(index)) {
|
|
415
|
-
return acc;
|
|
416
|
-
}
|
|
417
|
-
return acc.derive(index);
|
|
418
|
-
}, accountNode);
|
|
419
|
-
const rawOutPubkey = derivedAccount.publicKey;
|
|
420
|
-
const outPubkeyBuf = Buffer.isBuffer(rawOutPubkey)
|
|
421
|
-
? rawOutPubkey
|
|
422
|
-
: Buffer.from(rawOutPubkey);
|
|
423
|
-
if (outPubkeyBuf && outPubkeyBuf.length === 33) {
|
|
424
|
-
bip32Derivation = {
|
|
425
|
-
masterFingerprint: Buffer.from(fingerprint, 'hex'),
|
|
426
|
-
path: fullPath,
|
|
427
|
-
pubkey: outPubkeyBuf,
|
|
428
|
-
};
|
|
429
|
-
psbt.updateOutput(i, {
|
|
430
|
-
bip32Derivation: [bip32Derivation],
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
// Outputs without valid derivation are allowed (external recipients)
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
// If any wallet-owned inputs/outputs are missing bip32Derivation, fail early with a clear error
|
|
437
|
-
if (missingInputDerivations.length > 0) {
|
|
438
|
-
const parts = [];
|
|
439
|
-
parts.push(`inputs [${missingInputDerivations.join(', ')}]`);
|
|
440
|
-
throw new Error(`convertToLedgerFormat: Missing bip32Derivation for ${parts.join(' and ')}. Ensure PSBT includes a 'path' unknownKeyVal or BIP32_DERIVATION for wallet-owned entries.`);
|
|
441
|
-
}
|
|
442
|
-
return psbt;
|
|
443
|
-
}, 'convertToLedgerFormat');
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Execute operation with automatic retry
|
|
447
|
-
*/
|
|
448
|
-
async executeWithRetry(operation, operationName) {
|
|
449
|
-
// Ensure connection first
|
|
450
|
-
await this.ensureConnection();
|
|
451
|
-
// Use hardware wallet manager's retry mechanism
|
|
452
|
-
return this.hardwareWalletManager.retryOperation(operation, operationName, {
|
|
453
|
-
maxRetries: 3,
|
|
454
|
-
baseDelay: 1000,
|
|
455
|
-
maxDelay: 5000,
|
|
456
|
-
backoffMultiplier: 2,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Get hardware wallet status
|
|
461
|
-
*/
|
|
462
253
|
getStatus() {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
254
|
+
const connected = this.host?.getStatus().connected === true;
|
|
255
|
+
return {
|
|
256
|
+
type: hardware_wallet_manager_1.HardwareWalletType.LEDGER,
|
|
257
|
+
connected,
|
|
258
|
+
status: connected
|
|
259
|
+
? hardware_wallet_manager_1.ConnectionStatus.CONNECTED
|
|
260
|
+
: hardware_wallet_manager_1.ConnectionStatus.DISCONNECTED,
|
|
261
|
+
};
|
|
466
262
|
}
|
|
467
|
-
/**
|
|
468
|
-
* Clean up resources
|
|
469
|
-
*/
|
|
470
263
|
async destroy() {
|
|
471
|
-
|
|
472
|
-
this.
|
|
264
|
+
// Revoke pending responses synchronously, before platform teardown awaits.
|
|
265
|
+
this.generation++;
|
|
266
|
+
const cleanup = this.sharedHardwareWalletManager?.destroy();
|
|
267
|
+
const disconnect = this.host?.disconnect();
|
|
268
|
+
await Promise.all([cleanup, disconnect]);
|
|
473
269
|
}
|
|
474
270
|
}
|
|
475
271
|
exports.LedgerKeyring = LedgerKeyring;
|