@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
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import { TypedDataUtils, SignTypedDataVersion, } from '@metamask/eth-sig-util';
|
|
3
|
+
import bitcoinops from 'bitcoin-ops';
|
|
4
|
+
import bs58check from 'bs58check';
|
|
5
|
+
import { Buffer } from 'buffer';
|
|
6
|
+
import * as syscoinjs from 'syscoinjs-lib';
|
|
7
|
+
import TrezorConnect, { subscribeTrezorEvents, trezorResponseError, } from './connect';
|
|
8
|
+
import { serializeWitness } from './witness';
|
|
9
|
+
import { HardwareWalletManager, HardwareWalletType, } from '../hardware-wallet-manager';
|
|
10
|
+
import { getAccountDerivationPath, getAddressDerivationPath, isEvmCoin, } from '../utils/derivation-paths';
|
|
11
|
+
const bitcoinjs = syscoinjs.utils.bitcoinjs;
|
|
12
|
+
const { p2wsh } = bitcoinjs.payments;
|
|
13
|
+
const { decompile } = bitcoinjs.script;
|
|
14
|
+
const { fromBech32 } = bitcoinjs.address;
|
|
15
|
+
const fromBase58Check = (value) => {
|
|
16
|
+
try {
|
|
17
|
+
return bitcoinjs.address.fromBase58Check(value);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
// The former decoder also accepted a two-byte network version. Preserve
|
|
21
|
+
// exactly that checksum-verified format without loading the legacy fork.
|
|
22
|
+
const payload = bs58check.decode(value);
|
|
23
|
+
if (payload.length === 22) {
|
|
24
|
+
return {
|
|
25
|
+
version: (payload[0] << 8) | payload[1],
|
|
26
|
+
hash: payload.subarray(2),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const initialHDPath = `m/44'/60'/0'/0/0`;
|
|
33
|
+
const DELAY_BETWEEN_POPUPS = 2000; // Increased from 1000ms to 2000ms for more reliable operation
|
|
34
|
+
const stripHexPrefix = (value) => value.startsWith('0x') || value.startsWith('0X') ? value.slice(2) : value;
|
|
35
|
+
const finalizedFields = (input) => {
|
|
36
|
+
if (!input.finalScriptSig && !input.finalScriptWitness)
|
|
37
|
+
return null;
|
|
38
|
+
return {
|
|
39
|
+
scriptSig: Buffer.from(input.finalScriptSig || []).toString('hex'),
|
|
40
|
+
witness: input.finalScriptWitness
|
|
41
|
+
? Buffer.from(input.finalScriptWitness).toString('hex')
|
|
42
|
+
: '00',
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export class TrezorKeyring {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.hdPath = initialHDPath;
|
|
48
|
+
this.paths = {};
|
|
49
|
+
this.initialized = false;
|
|
50
|
+
this.lifecycleGeneration = 0;
|
|
51
|
+
this.onTrezorConnected = ({ type, }) => {
|
|
52
|
+
if (type === HardwareWalletType.TREZOR) {
|
|
53
|
+
console.log('Trezor connected');
|
|
54
|
+
this.initialized = true;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
this.onTrezorDisconnected = ({ type, }) => {
|
|
58
|
+
if (type === HardwareWalletType.TREZOR) {
|
|
59
|
+
console.log('Trezor disconnected');
|
|
60
|
+
this.initialized = false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
this._transformTypedData = (data, version) => {
|
|
64
|
+
const { types, primaryType, domain, message } = this._sanitizeData(data);
|
|
65
|
+
const domainSeparatorHash = TypedDataUtils.hashStruct('EIP712Domain', this._sanitizeData(domain), types, version).toString('hex');
|
|
66
|
+
let messageHash = null;
|
|
67
|
+
if (primaryType !== 'EIP712Domain') {
|
|
68
|
+
messageHash = TypedDataUtils.hashStruct(primaryType, this._sanitizeData(message), types, version).toString('hex');
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
domain_separator_hash: domainSeparatorHash,
|
|
72
|
+
message_hash: messageHash,
|
|
73
|
+
...data,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
this.publicKey = Buffer.from('', 'hex');
|
|
77
|
+
this.chainCode = Buffer.from('', 'hex');
|
|
78
|
+
this.hdPath = '';
|
|
79
|
+
this.paths = {};
|
|
80
|
+
this.hardwareWalletManager = new HardwareWalletManager();
|
|
81
|
+
this.observeTrezor();
|
|
82
|
+
}
|
|
83
|
+
observeTrezor() {
|
|
84
|
+
// Manager destruction removes its listeners. Reusing an unlocked keyring
|
|
85
|
+
// must restore these same callbacks without multiplying them on each init.
|
|
86
|
+
if (!this.hardwareWalletManager
|
|
87
|
+
.listeners('connected')
|
|
88
|
+
.includes(this.onTrezorConnected))
|
|
89
|
+
this.hardwareWalletManager.on('connected', this.onTrezorConnected);
|
|
90
|
+
if (!this.hardwareWalletManager
|
|
91
|
+
.listeners('disconnected')
|
|
92
|
+
.includes(this.onTrezorDisconnected))
|
|
93
|
+
this.hardwareWalletManager.on('disconnected', this.onTrezorDisconnected);
|
|
94
|
+
if (!this.unsubscribeTrezor)
|
|
95
|
+
this.unsubscribeTrezor = subscribeTrezorEvents((event) => {
|
|
96
|
+
if (event.type === 'model')
|
|
97
|
+
this.model = event.model;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Initialize Trezor script.
|
|
102
|
+
*/
|
|
103
|
+
async init() {
|
|
104
|
+
const generation = this.lifecycleGeneration;
|
|
105
|
+
this.observeTrezor();
|
|
106
|
+
if (this.initialized) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// Add a small delay to ensure Chrome extension context is ready
|
|
110
|
+
// This helps prevent "waiting for handshake" errors
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
112
|
+
if (generation !== this.lifecycleGeneration)
|
|
113
|
+
return false;
|
|
114
|
+
const result = await this.hardwareWalletManager.initializeTrezor();
|
|
115
|
+
if (generation !== this.lifecycleGeneration)
|
|
116
|
+
return false;
|
|
117
|
+
if (result) {
|
|
118
|
+
this.initialized = true;
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Execute operation with automatic retry
|
|
124
|
+
*/
|
|
125
|
+
async executeWithRetry(operation, operationName) {
|
|
126
|
+
// Ensure initialization first
|
|
127
|
+
if (!this.initialized) {
|
|
128
|
+
const initResult = await this.init();
|
|
129
|
+
if (!initResult) {
|
|
130
|
+
throw new Error('Failed to initialize Trezor');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// For Trezor operations, use reduced retry config to prevent popup spam
|
|
134
|
+
const trezorRetryConfig = {
|
|
135
|
+
maxRetries: 1, // Only retry once
|
|
136
|
+
baseDelay: 1000,
|
|
137
|
+
maxDelay: 5000,
|
|
138
|
+
backoffMultiplier: 2,
|
|
139
|
+
};
|
|
140
|
+
// Use hardware wallet manager's retry mechanism with custom config
|
|
141
|
+
return this.hardwareWalletManager
|
|
142
|
+
.retryOperation(operation, operationName, trezorRetryConfig)
|
|
143
|
+
.catch(async (error) => {
|
|
144
|
+
// Clean up Trezor state on failure
|
|
145
|
+
const message = String(error.message || '').toLowerCase();
|
|
146
|
+
if (message.includes('popup closed') ||
|
|
147
|
+
message.includes('cancelled') ||
|
|
148
|
+
message.includes('canceled') ||
|
|
149
|
+
message.includes('denied')) {
|
|
150
|
+
this.initialized = false;
|
|
151
|
+
// Dispose Trezor connection to clean up
|
|
152
|
+
try {
|
|
153
|
+
await TrezorConnect.dispose();
|
|
154
|
+
}
|
|
155
|
+
catch (disposeError) {
|
|
156
|
+
console.log('Failed to dispose Trezor on error:', disposeError);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* This return account info based in params provided.
|
|
164
|
+
*
|
|
165
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
166
|
+
* @param slip44 - network slip44 number
|
|
167
|
+
* @param hdPath - path derivation. Example: m/84'/57'/0'
|
|
168
|
+
* @param index - index of account for path derivation
|
|
169
|
+
* @returns derivated account info or error
|
|
170
|
+
*/
|
|
171
|
+
async getAccountInfo({ coin, slip44, hdPath, index, }) {
|
|
172
|
+
return this.executeWithRetry(async () => {
|
|
173
|
+
// Use dynamic path generation instead of hardcoded switch
|
|
174
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
175
|
+
if (hdPath)
|
|
176
|
+
this.hdPath = hdPath;
|
|
177
|
+
// For EVM networks, getAccountInfo is not supported
|
|
178
|
+
// We need to use getAddress instead
|
|
179
|
+
if (slip44 === 60) {
|
|
180
|
+
const addressResponse = await TrezorConnect.ethereumGetAddress({
|
|
181
|
+
path: this.hdPath,
|
|
182
|
+
showOnTrezor: false,
|
|
183
|
+
});
|
|
184
|
+
if (!addressResponse.success) {
|
|
185
|
+
throw trezorResponseError(addressResponse);
|
|
186
|
+
}
|
|
187
|
+
// Return a compatible AccountInfo structure
|
|
188
|
+
return {
|
|
189
|
+
descriptor: addressResponse.payload.address,
|
|
190
|
+
balance: '0', // Balance is fetched separately for EVM
|
|
191
|
+
empty: true,
|
|
192
|
+
history: {
|
|
193
|
+
total: 0,
|
|
194
|
+
unconfirmed: 0,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// For UTXO networks, use the standard getAccountInfo
|
|
199
|
+
const response = await TrezorConnect.getAccountInfo({
|
|
200
|
+
coin: coin,
|
|
201
|
+
path: this.hdPath,
|
|
202
|
+
});
|
|
203
|
+
if (response.success) {
|
|
204
|
+
return response.payload;
|
|
205
|
+
}
|
|
206
|
+
throw trezorResponseError(response);
|
|
207
|
+
}, 'getAccountInfo');
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Gets the model, if known.
|
|
211
|
+
* This may be `undefined` if the model hasn't been loaded yet.
|
|
212
|
+
*
|
|
213
|
+
* @returns
|
|
214
|
+
*/
|
|
215
|
+
getModel() {
|
|
216
|
+
return this.model;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* This disposes the active Trezor Suite connection
|
|
220
|
+
*
|
|
221
|
+
* @returns void
|
|
222
|
+
*/
|
|
223
|
+
dispose() {
|
|
224
|
+
this.lifecycleGeneration++;
|
|
225
|
+
try {
|
|
226
|
+
void TrezorConnect.dispose().catch((error) => {
|
|
227
|
+
console.log('Error disposing Trezor:', error);
|
|
228
|
+
});
|
|
229
|
+
this.initialized = false;
|
|
230
|
+
// Clear any cached data
|
|
231
|
+
this.publicKey = Buffer.from('', 'hex');
|
|
232
|
+
this.chainCode = Buffer.from('', 'hex');
|
|
233
|
+
this.hdPath = '';
|
|
234
|
+
this.paths = {};
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
console.log('Error disposing Trezor:', error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* This verify if message is valid or not.
|
|
242
|
+
*
|
|
243
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
244
|
+
* @param address - account address that signed message
|
|
245
|
+
* @param message - message to be verified. Example: 'Test message'
|
|
246
|
+
* @param signature - signature received in sign method. Example: I6BrpivjCwZmScZ6BMAHWGQPo+JjX2kzKXU5LcGVfEgvFb2VfJuKo3g6eSQcykQZiILoWNUDn5rDHkwJg3EcvuY=
|
|
247
|
+
* @returns derivated account info or error
|
|
248
|
+
*/
|
|
249
|
+
async verifyMessage({ coin, address, message, signature, }) {
|
|
250
|
+
return this.executeWithRetry(async () => {
|
|
251
|
+
let method = '';
|
|
252
|
+
switch (coin) {
|
|
253
|
+
case 'eth':
|
|
254
|
+
method = 'ethereumVerifyMessage';
|
|
255
|
+
break;
|
|
256
|
+
default:
|
|
257
|
+
method = 'verifyMessage';
|
|
258
|
+
}
|
|
259
|
+
// @ts-ignore
|
|
260
|
+
const { success, payload } = await TrezorConnect[method]({
|
|
261
|
+
coin,
|
|
262
|
+
address,
|
|
263
|
+
message,
|
|
264
|
+
signature,
|
|
265
|
+
});
|
|
266
|
+
if (success) {
|
|
267
|
+
return { success, payload };
|
|
268
|
+
}
|
|
269
|
+
throw trezorResponseError({ payload });
|
|
270
|
+
}, 'verifyMessage');
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* This return account public key.
|
|
274
|
+
*
|
|
275
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
276
|
+
* @param slip44 - network slip44 number
|
|
277
|
+
* @param hdPath - path derivation. Example: m/44'/57'/0'/0/0
|
|
278
|
+
* @returns publicKey and chainCode
|
|
279
|
+
*/
|
|
280
|
+
async getPublicKey({ coin, slip44, hdPath, index, }) {
|
|
281
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
282
|
+
if (hdPath)
|
|
283
|
+
this.hdPath = hdPath;
|
|
284
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
285
|
+
try {
|
|
286
|
+
// For EVM networks, use ethereumGetPublicKey
|
|
287
|
+
if (slip44 === 60) {
|
|
288
|
+
const { success, payload } = await TrezorConnect.ethereumGetPublicKey({
|
|
289
|
+
path: this.hdPath,
|
|
290
|
+
showOnTrezor: false,
|
|
291
|
+
});
|
|
292
|
+
if (success) {
|
|
293
|
+
const { publicKey } = payload;
|
|
294
|
+
// For Ethereum, we don't get chainCode from ethereumGetPublicKey
|
|
295
|
+
this.publicKey = Buffer.from(publicKey, 'hex');
|
|
296
|
+
return {
|
|
297
|
+
publicKey: `0x${publicKey}`,
|
|
298
|
+
chainCode: '', // Ethereum doesn't use chainCode in the same way
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return { success: false, payload };
|
|
302
|
+
}
|
|
303
|
+
// For UTXO networks, use standard getPublicKey
|
|
304
|
+
const { success, payload } = await TrezorConnect.getPublicKey({
|
|
305
|
+
coin: coin,
|
|
306
|
+
path: this.hdPath,
|
|
307
|
+
});
|
|
308
|
+
if (success) {
|
|
309
|
+
const { publicKey, chainCode } = payload;
|
|
310
|
+
this.publicKey = Buffer.from(publicKey, 'hex');
|
|
311
|
+
this.chainCode = Buffer.from(chainCode, 'hex');
|
|
312
|
+
return {
|
|
313
|
+
publicKey: `0x${this.publicKey.toString('hex')}`,
|
|
314
|
+
chainCode: `0x${this.chainCode.toString('hex')}`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return { success: false, payload };
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
return error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
range(n) {
|
|
324
|
+
return [...Array(n).keys()];
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* This sign UTXO tx.
|
|
328
|
+
*
|
|
329
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
330
|
+
* @param inputs - utxo transaction inputs
|
|
331
|
+
* @param outputs - utxo transaction outputs
|
|
332
|
+
* @returns signature object
|
|
333
|
+
*/
|
|
334
|
+
async signUtxoTransaction(utxoTransaction, psbt) {
|
|
335
|
+
// Capture approval before the device await. Witnesses and scriptSigs are
|
|
336
|
+
// signatures, but every unsigned transaction field must remain identical.
|
|
337
|
+
const unsignedSnapshot = (transaction) => JSON.stringify({
|
|
338
|
+
version: transaction.version,
|
|
339
|
+
locktime: transaction.locktime,
|
|
340
|
+
inputs: (transaction.txInputs || transaction.ins).map((input) => ({
|
|
341
|
+
hash: Buffer.from(input.hash).toString('hex'),
|
|
342
|
+
index: input.index,
|
|
343
|
+
sequence: input.sequence,
|
|
344
|
+
})),
|
|
345
|
+
outputs: (transaction.txOutputs || transaction.outs).map((output) => ({
|
|
346
|
+
script: Buffer.from(output.script).toString('hex'),
|
|
347
|
+
value: output.value.toString(),
|
|
348
|
+
})),
|
|
349
|
+
});
|
|
350
|
+
const approvedTransaction = unsignedSnapshot(psbt);
|
|
351
|
+
const approvedFinalInputs = psbt.data.inputs.map(finalizedFields);
|
|
352
|
+
const payload = await this.executeWithRetry(async () => {
|
|
353
|
+
const response = await TrezorConnect.signTransaction(utxoTransaction);
|
|
354
|
+
if (!response.success) {
|
|
355
|
+
throw trezorResponseError(response, 'Trezor sign failed: ');
|
|
356
|
+
}
|
|
357
|
+
return response.payload;
|
|
358
|
+
}, 'signUtxoTransaction');
|
|
359
|
+
const tx = bitcoinjs.Transaction.fromHex(payload.serializedTx);
|
|
360
|
+
if (unsignedSnapshot(tx) !== approvedTransaction ||
|
|
361
|
+
unsignedSnapshot(psbt) !== approvedTransaction) {
|
|
362
|
+
throw new Error('Trezor returned a different unsigned transaction');
|
|
363
|
+
}
|
|
364
|
+
// Finalized inputs belong to another signer. Neither the device response
|
|
365
|
+
// nor concurrent code may replace their approved scripts or witnesses.
|
|
366
|
+
if (approvedFinalInputs.some((approved, index) => {
|
|
367
|
+
const current = finalizedFields(psbt.data.inputs[index]);
|
|
368
|
+
if (!approved)
|
|
369
|
+
return current !== null;
|
|
370
|
+
return (!current ||
|
|
371
|
+
current.scriptSig !== approved.scriptSig ||
|
|
372
|
+
current.witness !== approved.witness ||
|
|
373
|
+
Buffer.from(tx.ins[index].script).toString('hex') !==
|
|
374
|
+
approved.scriptSig ||
|
|
375
|
+
serializeWitness(tx.ins[index].witness || []).toString('hex') !==
|
|
376
|
+
approved.witness);
|
|
377
|
+
})) {
|
|
378
|
+
throw new Error('Trezor changed an already-finalized input');
|
|
379
|
+
}
|
|
380
|
+
for (const i of this.range(psbt.data.inputs.length)) {
|
|
381
|
+
if (approvedFinalInputs[i])
|
|
382
|
+
continue;
|
|
383
|
+
if (tx.ins[i].witness == null || tx.ins[i].witness.length !== 2) {
|
|
384
|
+
throw new Error('Please move your funds to a Segwit address: https://wiki.trezor.io/Account');
|
|
385
|
+
}
|
|
386
|
+
const partialSig = [
|
|
387
|
+
{
|
|
388
|
+
pubkey: tx.ins[i].witness[1],
|
|
389
|
+
signature: tx.ins[i].witness[0],
|
|
390
|
+
},
|
|
391
|
+
];
|
|
392
|
+
psbt.updateInput(i, { partialSig });
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
syscoinjs.utils.finalizePSBT(psbt);
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
console.log(err);
|
|
399
|
+
}
|
|
400
|
+
return psbt;
|
|
401
|
+
}
|
|
402
|
+
setHdPath(coin, accountIndex, slip44) {
|
|
403
|
+
if (isEvmCoin(coin, slip44)) {
|
|
404
|
+
// For EVM, the "accountIndex" parameter is actually used as the address index
|
|
405
|
+
// EVM typically uses account 0, and different addresses are at different address indices
|
|
406
|
+
this.hdPath = getAddressDerivationPath(coin, slip44, 0, // account is always 0 for EVM
|
|
407
|
+
false, // not a change address
|
|
408
|
+
accountIndex // this is actually the address index for EVM
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
// For UTXO, use account-level derivation path
|
|
413
|
+
this.hdPath = getAccountDerivationPath(coin, slip44, accountIndex);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
convertToAddressNFormat(path) {
|
|
417
|
+
const pathArray = path.replace(/'/g, '').split('/');
|
|
418
|
+
pathArray.shift();
|
|
419
|
+
const addressN = [];
|
|
420
|
+
for (const index in pathArray) {
|
|
421
|
+
if (Number(index) <= 2 && Number(index) >= 0) {
|
|
422
|
+
addressN[Number(index)] = Number(pathArray[index]) | 0x80000000;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
addressN[Number(index)] = Number(pathArray[index]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return addressN;
|
|
429
|
+
}
|
|
430
|
+
isScriptHash(address, networkInfo) {
|
|
431
|
+
if (!networkInfo) {
|
|
432
|
+
// If network info is not available, make a best guess based on address format
|
|
433
|
+
// This is a fallback for hardware wallet scenarios
|
|
434
|
+
if (this.isBech32(address)) {
|
|
435
|
+
const decoded = fromBech32(address);
|
|
436
|
+
return decoded.data.length === 32; // 32 bytes = P2WSH
|
|
437
|
+
}
|
|
438
|
+
// For non-bech32, we can't reliably determine without network info
|
|
439
|
+
// Default to false (P2PKH) as it's more common
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
if (!this.isBech32(address)) {
|
|
443
|
+
const decoded = fromBase58Check(address);
|
|
444
|
+
if (decoded.version === networkInfo.pubKeyHash) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
if (decoded.version === networkInfo.scriptHash) {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const decoded = fromBech32(address);
|
|
453
|
+
if (decoded.data.length === 20) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
if (decoded.data.length === 32) {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
throw new Error('isScriptHash: Unknown address type');
|
|
461
|
+
}
|
|
462
|
+
isPaymentFactory(payment) {
|
|
463
|
+
return (script) => {
|
|
464
|
+
try {
|
|
465
|
+
payment({ output: script });
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
isBech32(address) {
|
|
474
|
+
try {
|
|
475
|
+
fromBech32(address);
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
catch (e) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
isP2WSHScript(script) {
|
|
483
|
+
this.isPaymentFactory(p2wsh)(script);
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
convertToTrezorFormat({ psbt, coin, network }) {
|
|
487
|
+
const trezortx = {};
|
|
488
|
+
trezortx.coin = coin;
|
|
489
|
+
trezortx.version = psbt.version;
|
|
490
|
+
trezortx.locktime = psbt.locktime;
|
|
491
|
+
trezortx.inputs = [];
|
|
492
|
+
trezortx.outputs = [];
|
|
493
|
+
for (let i = 0; i < psbt.txInputs.length; i++) {
|
|
494
|
+
const input = psbt.txInputs[i];
|
|
495
|
+
const inputItem = {};
|
|
496
|
+
inputItem.prev_index = input.index;
|
|
497
|
+
const hashBuf = Buffer.from(input.hash);
|
|
498
|
+
const prevHashHex = hashBuf.reverse().toString('hex'); // 64-char hex
|
|
499
|
+
inputItem.prev_hash = prevHashHex;
|
|
500
|
+
if (input.sequence !== undefined)
|
|
501
|
+
inputItem.sequence = input.sequence;
|
|
502
|
+
const dataInput = psbt.data.inputs[i];
|
|
503
|
+
if (dataInput.finalScriptSig || dataInput.finalScriptWitness) {
|
|
504
|
+
// A finalized co-signer input is supplied as public transaction data,
|
|
505
|
+
// never as a private derivation request to this device.
|
|
506
|
+
let prevout = dataInput.witnessUtxo;
|
|
507
|
+
if (dataInput.nonWitnessUtxo) {
|
|
508
|
+
const previous = bitcoinjs.Transaction.fromBuffer(Buffer.from(dataInput.nonWitnessUtxo));
|
|
509
|
+
const output = previous.outs[input.index];
|
|
510
|
+
if (!Buffer.from(previous.getHash()).equals(Buffer.from(input.hash)) ||
|
|
511
|
+
!output ||
|
|
512
|
+
(prevout &&
|
|
513
|
+
(!Buffer.from(prevout.script).equals(Buffer.from(output.script)) ||
|
|
514
|
+
prevout.value.toString() !== output.value.toString()))) {
|
|
515
|
+
throw new Error('Trezor external input has an invalid prevout');
|
|
516
|
+
}
|
|
517
|
+
prevout = output;
|
|
518
|
+
}
|
|
519
|
+
if (!prevout || prevout.value === undefined || !prevout.script) {
|
|
520
|
+
throw new Error('Trezor external input is missing its prevout');
|
|
521
|
+
}
|
|
522
|
+
inputItem.script_type = 'EXTERNAL';
|
|
523
|
+
inputItem.amount = prevout.value.toString();
|
|
524
|
+
inputItem.script_pubkey = Buffer.from(prevout.script).toString('hex');
|
|
525
|
+
if (dataInput.finalScriptSig)
|
|
526
|
+
inputItem.script_sig = Buffer.from(dataInput.finalScriptSig).toString('hex');
|
|
527
|
+
if (dataInput.finalScriptWitness)
|
|
528
|
+
inputItem.witness = Buffer.from(dataInput.finalScriptWitness).toString('hex');
|
|
529
|
+
trezortx.inputs.push(inputItem);
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
// Resolve derivation path for this input
|
|
533
|
+
let resolvedPath = null;
|
|
534
|
+
const tapDer = dataInput.tapBip32Derivation && dataInput.tapBip32Derivation.length > 0
|
|
535
|
+
? dataInput.tapBip32Derivation[0]
|
|
536
|
+
: null;
|
|
537
|
+
const b32Der = dataInput.bip32Derivation && dataInput.bip32Derivation.length > 0
|
|
538
|
+
? dataInput.bip32Derivation[0]
|
|
539
|
+
: null;
|
|
540
|
+
if (tapDer && tapDer.path) {
|
|
541
|
+
resolvedPath = tapDer.path;
|
|
542
|
+
}
|
|
543
|
+
else if (b32Der && b32Der.path) {
|
|
544
|
+
resolvedPath = b32Der.path;
|
|
545
|
+
}
|
|
546
|
+
else if (dataInput.unknownKeyVals && dataInput.unknownKeyVals.length) {
|
|
547
|
+
for (const kv of dataInput.unknownKeyVals) {
|
|
548
|
+
if (Buffer.isBuffer(kv.key) && kv.key.toString() === 'path') {
|
|
549
|
+
resolvedPath = kv.value.toString();
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (!resolvedPath) {
|
|
555
|
+
throw new Error('convertToTrezorFormat: Missing path (tapBip32Derivation/bip32Derivation/unknownKeyVals)');
|
|
556
|
+
}
|
|
557
|
+
// Map path to Trezor address_n
|
|
558
|
+
inputItem.address_n = this.convertToAddressNFormat(resolvedPath);
|
|
559
|
+
// Coerce witnessUtxo.script to Buffer for downstream helpers
|
|
560
|
+
const inScriptBuf = dataInput && dataInput.witnessUtxo && dataInput.witnessUtxo.script
|
|
561
|
+
? Buffer.isBuffer(dataInput.witnessUtxo.script)
|
|
562
|
+
? dataInput.witnessUtxo.script
|
|
563
|
+
: Buffer.from(dataInput.witnessUtxo.script)
|
|
564
|
+
: Buffer.alloc(0);
|
|
565
|
+
// Select Trezor script_type (detect Taproot explicitly)
|
|
566
|
+
const isTaproot = inScriptBuf &&
|
|
567
|
+
inScriptBuf.length === 34 &&
|
|
568
|
+
inScriptBuf[0] === bitcoinops.OP_1 &&
|
|
569
|
+
inScriptBuf[1] === 0x20;
|
|
570
|
+
if (isTaproot) {
|
|
571
|
+
inputItem.script_type = 'SPENDTAPROOT';
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
const scriptTypes = psbt.getInputType(i);
|
|
575
|
+
switch (scriptTypes) {
|
|
576
|
+
case 'multisig':
|
|
577
|
+
inputItem.script_type = 'SPENDMULTISIG';
|
|
578
|
+
break;
|
|
579
|
+
case 'witnesspubkeyhash':
|
|
580
|
+
inputItem.script_type = 'SPENDWITNESS';
|
|
581
|
+
break;
|
|
582
|
+
default:
|
|
583
|
+
inputItem.script_type = this.isP2WSHScript(inScriptBuf)
|
|
584
|
+
? 'SPENDP2SHWITNESS'
|
|
585
|
+
: 'SPENDADDRESS';
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
trezortx.inputs.push(inputItem);
|
|
590
|
+
}
|
|
591
|
+
for (let i = 0; i < psbt.txOutputs.length; i++) {
|
|
592
|
+
const output = psbt.txOutputs[i];
|
|
593
|
+
const outputItem = {};
|
|
594
|
+
const scriptBuf = Buffer.isBuffer(output.script)
|
|
595
|
+
? output.script
|
|
596
|
+
: Buffer.from(output.script || []);
|
|
597
|
+
const chunks = decompile(scriptBuf);
|
|
598
|
+
// Debug logging to understand the output structure
|
|
599
|
+
console.log(`[Trezor] Processing output ${i}:`, {
|
|
600
|
+
value: output.value,
|
|
601
|
+
valueType: typeof output.value,
|
|
602
|
+
hasAddress: !!output.address,
|
|
603
|
+
address: output.address,
|
|
604
|
+
scriptLength: output.script?.length,
|
|
605
|
+
});
|
|
606
|
+
// Ensure value exists and convert properly
|
|
607
|
+
if (output.value === undefined || output.value === null) {
|
|
608
|
+
throw new Error(`Output ${i} has no value`);
|
|
609
|
+
}
|
|
610
|
+
// Handle different value types
|
|
611
|
+
let amountStr;
|
|
612
|
+
if (typeof output.value === 'bigint') {
|
|
613
|
+
amountStr = output.value.toString();
|
|
614
|
+
}
|
|
615
|
+
else if (typeof output.value === 'number') {
|
|
616
|
+
amountStr = output.value.toString();
|
|
617
|
+
}
|
|
618
|
+
else if (typeof output.value === 'string') {
|
|
619
|
+
amountStr = output.value;
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
throw new Error(`Output ${i} has unexpected value type: ${typeof output.value}`);
|
|
623
|
+
}
|
|
624
|
+
outputItem.amount = amountStr;
|
|
625
|
+
if (chunks && chunks[0] === bitcoinops.OP_RETURN) {
|
|
626
|
+
outputItem.script_type = 'PAYTOOPRETURN';
|
|
627
|
+
// @ts-ignore
|
|
628
|
+
outputItem.op_return_data = Buffer.from(chunks[1]).toString('hex');
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
if (output && this.isBech32(output.address)) {
|
|
632
|
+
// Any bech32 witness program (P2WPKH or P2WSH) signs as PAYTOWITNESS
|
|
633
|
+
outputItem.script_type = 'PAYTOWITNESS';
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
outputItem.script_type = this.isScriptHash(output.address, network)
|
|
637
|
+
? 'PAYTOSCRIPTHASH'
|
|
638
|
+
: 'PAYTOADDRESS';
|
|
639
|
+
}
|
|
640
|
+
if (output.address)
|
|
641
|
+
outputItem.address = output.address;
|
|
642
|
+
}
|
|
643
|
+
// Validate that the output has required fields for Trezor
|
|
644
|
+
if (outputItem.script_type === 'PAYTOADDRESS' ||
|
|
645
|
+
outputItem.script_type === 'PAYTOSCRIPTHASH' ||
|
|
646
|
+
outputItem.script_type === 'PAYTOWITNESS') {
|
|
647
|
+
if (!outputItem.address) {
|
|
648
|
+
console.error(`[Trezor] Output ${i} missing address for script type ${outputItem.script_type}`);
|
|
649
|
+
throw new Error(`Output ${i} requires an address for script type ${outputItem.script_type}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
trezortx.outputs.push(outputItem);
|
|
653
|
+
}
|
|
654
|
+
return trezortx;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* This sign EVM tx.
|
|
658
|
+
*
|
|
659
|
+
* @param index - index of account for path derivation
|
|
660
|
+
* @param tx - ethereum tx object
|
|
661
|
+
* @returns signature object
|
|
662
|
+
*/
|
|
663
|
+
async signEthTransaction({ tx, index, coin, slip44, }) {
|
|
664
|
+
return this.executeWithRetry(async () => {
|
|
665
|
+
// Wait between popups
|
|
666
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
667
|
+
// Use dynamic path generation based on actual network parameters
|
|
668
|
+
this.setHdPath(coin, Number(index) || 0, slip44);
|
|
669
|
+
const response = await TrezorConnect.ethereumSignTransaction({
|
|
670
|
+
path: this.hdPath,
|
|
671
|
+
transaction: tx,
|
|
672
|
+
});
|
|
673
|
+
if (response.success) {
|
|
674
|
+
return response;
|
|
675
|
+
}
|
|
676
|
+
throw trezorResponseError(response);
|
|
677
|
+
}, 'signEthTransaction');
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* This sign message.
|
|
681
|
+
*
|
|
682
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
683
|
+
* @param slip44 - network slip44 number
|
|
684
|
+
* @param message - message to be signed. Example: 'Test message'
|
|
685
|
+
* @param index - index of account for path derivation
|
|
686
|
+
* @returns signature object
|
|
687
|
+
*/
|
|
688
|
+
async signMessage({ index, message, coin, slip44, address, }) {
|
|
689
|
+
return this.executeWithRetry(async () => {
|
|
690
|
+
// Wait between popups
|
|
691
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
692
|
+
if (isEvmCoin(coin, slip44) && `${index ? index : 0}` && message) {
|
|
693
|
+
return this._signEthPersonalMessage(Number(index), message, address);
|
|
694
|
+
}
|
|
695
|
+
return this._signUtxoPersonalMessage({ coin, index, slip44, message });
|
|
696
|
+
}, 'signMessage');
|
|
697
|
+
}
|
|
698
|
+
async _signUtxoPersonalMessage({ coin, index, slip44, message, }) {
|
|
699
|
+
try {
|
|
700
|
+
if (typeof message !== 'string')
|
|
701
|
+
throw new Error('Message must be a string');
|
|
702
|
+
// Use dynamic path generation instead of hardcoded switch
|
|
703
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
704
|
+
const { success, payload } = await TrezorConnect.signMessage({
|
|
705
|
+
path: this.hdPath,
|
|
706
|
+
coin: coin,
|
|
707
|
+
message,
|
|
708
|
+
});
|
|
709
|
+
if (success) {
|
|
710
|
+
return { success, payload };
|
|
711
|
+
}
|
|
712
|
+
return { success: false, payload };
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
return { error };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// For personal_sign, we need to prefix the message:
|
|
719
|
+
async _signEthPersonalMessage(index, message, address) {
|
|
720
|
+
return new Promise((resolve, reject) => {
|
|
721
|
+
setTimeout(async () => {
|
|
722
|
+
try {
|
|
723
|
+
this.setHdPath('eth', index, 60);
|
|
724
|
+
TrezorConnect.ethereumSignMessage({
|
|
725
|
+
path: this.hdPath,
|
|
726
|
+
message: stripHexPrefix(message),
|
|
727
|
+
hex: true,
|
|
728
|
+
})
|
|
729
|
+
.then((response) => {
|
|
730
|
+
if (response.success) {
|
|
731
|
+
if (address &&
|
|
732
|
+
response.payload.address.toLowerCase() !==
|
|
733
|
+
address.toLowerCase()) {
|
|
734
|
+
reject(new Error('signature doesnt match the right address'));
|
|
735
|
+
}
|
|
736
|
+
const signature = `0x${response.payload.signature}`;
|
|
737
|
+
resolve({ signature, success: true });
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
reject(
|
|
741
|
+
// @ts-ignore
|
|
742
|
+
trezorResponseError(response));
|
|
743
|
+
}
|
|
744
|
+
})
|
|
745
|
+
.catch((e) => {
|
|
746
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
reject(error);
|
|
751
|
+
}
|
|
752
|
+
// This is necessary to avoid popup collision
|
|
753
|
+
// between the unlock & sign trezor popups
|
|
754
|
+
}, DELAY_BETWEEN_POPUPS);
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
_sanitizeData(data) {
|
|
758
|
+
switch (Object.prototype.toString.call(data)) {
|
|
759
|
+
case '[object Object]': {
|
|
760
|
+
const entries = Object.keys(data).map((k) => [
|
|
761
|
+
k,
|
|
762
|
+
this._sanitizeData(data[k]),
|
|
763
|
+
]);
|
|
764
|
+
return Object.fromEntries(entries);
|
|
765
|
+
}
|
|
766
|
+
case '[object Array]':
|
|
767
|
+
return data.map((v) => this._sanitizeData(v));
|
|
768
|
+
case '[object BigInt]':
|
|
769
|
+
return data.toString();
|
|
770
|
+
default:
|
|
771
|
+
return data;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* EIP-712 Sign Typed Data
|
|
776
|
+
*/
|
|
777
|
+
async signTypedData({ version, address, data, index, }) {
|
|
778
|
+
return this.executeWithRetry(async () => {
|
|
779
|
+
// Wait between popups
|
|
780
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
781
|
+
this.setHdPath('eth', index, 60);
|
|
782
|
+
// Use dynamic path generation for ETH (EVM) - typed data is only used for EVM
|
|
783
|
+
// V1 typed data is not supported by hardware wallets
|
|
784
|
+
if (version === SignTypedDataVersion.V1) {
|
|
785
|
+
throw new Error('Trezor: V1 typed data signing is not supported. Please use V3 or V4.');
|
|
786
|
+
}
|
|
787
|
+
const dataWithHashes = this._transformTypedData(data, version);
|
|
788
|
+
// set default values for signTypedData
|
|
789
|
+
// Trezor is stricter than @metamask/eth-sig-util in what it accepts
|
|
790
|
+
const { types, message = {}, domain = {}, primaryType,
|
|
791
|
+
// snake_case since Trezor uses Protobuf naming conventions here
|
|
792
|
+
domain_separator_hash, // eslint-disable-line camelcase
|
|
793
|
+
message_hash, // eslint-disable-line camelcase
|
|
794
|
+
} = dataWithHashes;
|
|
795
|
+
// This is necessary to avoid popup collision
|
|
796
|
+
// between the unlock & sign trezor popups
|
|
797
|
+
const response = await TrezorConnect.ethereumSignTypedData({
|
|
798
|
+
path: this.hdPath,
|
|
799
|
+
data: {
|
|
800
|
+
types: {
|
|
801
|
+
...types,
|
|
802
|
+
EIP712Domain: types.EIP712Domain ? types.EIP712Domain : [],
|
|
803
|
+
},
|
|
804
|
+
message,
|
|
805
|
+
domain,
|
|
806
|
+
primaryType: primaryType,
|
|
807
|
+
},
|
|
808
|
+
metamask_v4_compat: version === SignTypedDataVersion.V4,
|
|
809
|
+
// Trezor 1 only supports blindly signing hashes
|
|
810
|
+
domain_separator_hash,
|
|
811
|
+
message_hash: message_hash ? message_hash : '',
|
|
812
|
+
});
|
|
813
|
+
if (response.success) {
|
|
814
|
+
if (address !== response.payload.address) {
|
|
815
|
+
throw new Error('signature doesnt match the right address');
|
|
816
|
+
}
|
|
817
|
+
return response.payload.signature;
|
|
818
|
+
}
|
|
819
|
+
// @ts-ignore
|
|
820
|
+
throw trezorResponseError(response);
|
|
821
|
+
}, 'signTypedData');
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Verify UTXO address by displaying it on the Trezor device
|
|
825
|
+
* @param accountIndex - The account index
|
|
826
|
+
* @param currency - The currency (coin type)
|
|
827
|
+
* @param slip44 - The slip44 value for the network
|
|
828
|
+
* @returns The verified address
|
|
829
|
+
*/
|
|
830
|
+
async verifyUtxoAddress(accountIndex, currency, slip44) {
|
|
831
|
+
return this.executeWithRetry(async () => {
|
|
832
|
+
const fullPath = getAddressDerivationPath(currency, slip44, accountIndex, false, // Not a change address
|
|
833
|
+
0);
|
|
834
|
+
try {
|
|
835
|
+
const { payload, success } = await TrezorConnect.getAddress({
|
|
836
|
+
path: fullPath,
|
|
837
|
+
coin: currency,
|
|
838
|
+
showOnTrezor: true, // This displays the address on device for verification
|
|
839
|
+
});
|
|
840
|
+
if (success) {
|
|
841
|
+
return payload.address;
|
|
842
|
+
}
|
|
843
|
+
throw new Error('Address verification cancelled by user');
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
throw error;
|
|
847
|
+
}
|
|
848
|
+
}, 'verifyUtxoAddress');
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Check Trezor status
|
|
852
|
+
*/
|
|
853
|
+
getStatus() {
|
|
854
|
+
return this.hardwareWalletManager
|
|
855
|
+
.getStatus()
|
|
856
|
+
.find((s) => s.type === HardwareWalletType.TREZOR);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Clean up resources
|
|
860
|
+
*/
|
|
861
|
+
async destroy() {
|
|
862
|
+
this.unsubscribeTrezor?.();
|
|
863
|
+
this.unsubscribeTrezor = undefined;
|
|
864
|
+
try {
|
|
865
|
+
// First dispose Trezor Connect
|
|
866
|
+
this.dispose();
|
|
867
|
+
// Then destroy hardware wallet manager
|
|
868
|
+
await this.hardwareWalletManager.destroy();
|
|
869
|
+
this.initialized = false;
|
|
870
|
+
}
|
|
871
|
+
catch (error) {
|
|
872
|
+
console.error('Error destroying Trezor keyring:', error);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
//# sourceMappingURL=index.js.map
|