@sidhujag/sysweb3-keyring 1.0.491
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/README.md +201 -0
- package/cjs/errorUtils.js +75 -0
- package/cjs/errorUtils.js.map +1 -0
- package/cjs/hardware-wallet-manager.js +462 -0
- package/cjs/hardware-wallet-manager.js.map +1 -0
- package/cjs/index.js +31 -0
- package/cjs/index.js.map +1 -0
- package/cjs/initial-state.js +105 -0
- package/cjs/initial-state.js.map +1 -0
- package/cjs/keyring-manager.js +1687 -0
- package/cjs/keyring-manager.js.map +1 -0
- package/cjs/ledger/bitcoin_client/index.js +47 -0
- package/cjs/ledger/bitcoin_client/index.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/appClient.js +408 -0
- package/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/bip32.js +61 -0
- package/cjs/ledger/bitcoin_client/lib/bip32.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/buffertools.js +126 -0
- package/cjs/ledger/bitcoin_client/lib/buffertools.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/clientCommands.js +270 -0
- package/cjs/ledger/bitcoin_client/lib/clientCommands.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/constants.js +16 -0
- package/cjs/ledger/bitcoin_client/lib/constants.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/merkelizedPsbt.js +54 -0
- package/cjs/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/merkle.js +109 -0
- package/cjs/ledger/bitcoin_client/lib/merkle.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/merkleMap.js +46 -0
- package/cjs/ledger/bitcoin_client/lib/merkleMap.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/policy.js +66 -0
- package/cjs/ledger/bitcoin_client/lib/policy.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/psbtv2.js +640 -0
- package/cjs/ledger/bitcoin_client/lib/psbtv2.js.map +1 -0
- package/cjs/ledger/bitcoin_client/lib/varint.js +113 -0
- package/cjs/ledger/bitcoin_client/lib/varint.js.map +1 -0
- package/cjs/ledger/consts.js +7 -0
- package/cjs/ledger/consts.js.map +1 -0
- package/cjs/ledger/index.js +319 -0
- package/cjs/ledger/index.js.map +1 -0
- package/cjs/ledger/types.js +3 -0
- package/cjs/ledger/types.js.map +1 -0
- package/cjs/network-utils.js +76 -0
- package/cjs/network-utils.js.map +1 -0
- package/cjs/providers.js +270 -0
- package/cjs/providers.js.map +1 -0
- package/cjs/signers.js +64 -0
- package/cjs/signers.js.map +1 -0
- package/cjs/storage.js +30 -0
- package/cjs/storage.js.map +1 -0
- package/cjs/transactions/__tests__/integration.test.js +237 -0
- package/cjs/transactions/__tests__/integration.test.js.map +1 -0
- package/cjs/transactions/__tests__/syscoin.test.js +361 -0
- package/cjs/transactions/__tests__/syscoin.test.js.map +1 -0
- package/cjs/transactions/ethereum.js +1577 -0
- package/cjs/transactions/ethereum.js.map +1 -0
- package/cjs/transactions/index.js +19 -0
- package/cjs/transactions/index.js.map +1 -0
- package/cjs/transactions/syscoin.js +328 -0
- package/cjs/transactions/syscoin.js.map +1 -0
- package/cjs/trezor/index.js +718 -0
- package/cjs/trezor/index.js.map +1 -0
- package/cjs/types.js +12 -0
- package/cjs/types.js.map +1 -0
- package/cjs/utils/derivation-paths.js +99 -0
- package/cjs/utils/derivation-paths.js.map +1 -0
- package/cjs/utils/psbt.js +60 -0
- package/cjs/utils/psbt.js.map +1 -0
- package/cjs/utils.js +130 -0
- package/cjs/utils.js.map +1 -0
- package/package.json +46 -0
- package/types/errorUtils.d.ts +1 -0
- package/types/hardware-wallet-manager.d.ts +110 -0
- package/types/index.d.ts +12 -0
- package/types/initial-state.d.ts +79 -0
- package/types/keyring-manager.d.ts +184 -0
- package/types/ledger/bitcoin_client/index.d.ts +5 -0
- package/types/ledger/bitcoin_client/lib/appClient.d.ts +106 -0
- package/types/ledger/bitcoin_client/lib/bip32.d.ts +11 -0
- package/types/ledger/bitcoin_client/lib/buffertools.d.ts +28 -0
- package/types/ledger/bitcoin_client/lib/clientCommands.d.ts +77 -0
- package/types/ledger/bitcoin_client/lib/constants.d.ts +12 -0
- package/types/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +24 -0
- package/types/ledger/bitcoin_client/lib/merkle.d.ts +32 -0
- package/types/ledger/bitcoin_client/lib/merkleMap.d.ts +23 -0
- package/types/ledger/bitcoin_client/lib/policy.d.ts +36 -0
- package/types/ledger/bitcoin_client/lib/psbtv2.d.ts +167 -0
- package/types/ledger/bitcoin_client/lib/varint.d.ts +23 -0
- package/types/ledger/consts.d.ts +3 -0
- package/types/ledger/index.d.ts +51 -0
- package/types/ledger/types.d.ts +48 -0
- package/types/network-utils.d.ts +14 -0
- package/types/providers.d.ts +47 -0
- package/types/signers.d.ts +95 -0
- package/types/storage.d.ts +2 -0
- package/types/transactions/__tests__/integration.test.d.ts +1 -0
- package/types/transactions/__tests__/syscoin.test.d.ts +1 -0
- package/types/transactions/ethereum.d.ts +80 -0
- package/types/transactions/index.d.ts +2 -0
- package/types/transactions/syscoin.d.ts +61 -0
- package/types/trezor/index.d.ts +170 -0
- package/types/types.d.ts +294 -0
- package/types/utils/derivation-paths.d.ts +35 -0
- package/types/utils/psbt.d.ts +17 -0
- package/types/utils.d.ts +4 -0
|
@@ -0,0 +1,718 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.TrezorKeyring = void 0;
|
|
40
|
+
/* eslint-disable camelcase */
|
|
41
|
+
const connect_webextension_1 = __importStar(require("@trezor/connect-webextension"));
|
|
42
|
+
const utxo_lib_1 = require("@trezor/utxo-lib");
|
|
43
|
+
const bitcoin_ops_1 = __importDefault(require("bitcoin-ops"));
|
|
44
|
+
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
45
|
+
const buffer_1 = require("buffer");
|
|
46
|
+
const eth_sig_util_1 = require("eth-sig-util");
|
|
47
|
+
const ethereumjs_util_1 = require("ethereumjs-util");
|
|
48
|
+
const hardware_wallet_manager_1 = require("../hardware-wallet-manager");
|
|
49
|
+
const derivation_paths_1 = require("../utils/derivation-paths");
|
|
50
|
+
const { p2wsh } = bitcoinjs_lib_1.payments;
|
|
51
|
+
const { decompile } = bitcoinjs_lib_1.script;
|
|
52
|
+
const { fromBase58Check, fromBech32 } = utxo_lib_1.address;
|
|
53
|
+
const initialHDPath = `m/44'/60'/0'/0/0`;
|
|
54
|
+
const DELAY_BETWEEN_POPUPS = 2000; // Increased from 1000ms to 2000ms for more reliable operation
|
|
55
|
+
class TrezorKeyring {
|
|
56
|
+
constructor(getSyscoinSigner) {
|
|
57
|
+
this.hdPath = initialHDPath;
|
|
58
|
+
this.paths = {};
|
|
59
|
+
this.initialized = false;
|
|
60
|
+
this._transformTypedData = (data, metamask_v4_compat) => {
|
|
61
|
+
if (!metamask_v4_compat) {
|
|
62
|
+
throw new Error('Trezor: Only version 4 of typed data signing is supported');
|
|
63
|
+
}
|
|
64
|
+
const { types, primaryType, domain, message } = this._sanitizeData(data);
|
|
65
|
+
const domainSeparatorHash = eth_sig_util_1.TypedDataUtils.hashStruct('EIP712Domain', this._sanitizeData(domain), types, true).toString('hex');
|
|
66
|
+
let messageHash = null;
|
|
67
|
+
if (primaryType !== 'EIP712Domain') {
|
|
68
|
+
messageHash = eth_sig_util_1.TypedDataUtils.hashStruct(primaryType, this._sanitizeData(message), types, true).toString('hex');
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
domain_separator_hash: domainSeparatorHash,
|
|
72
|
+
message_hash: messageHash,
|
|
73
|
+
...data,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
this.publicKey = buffer_1.Buffer.from('', 'hex');
|
|
77
|
+
this.chainCode = buffer_1.Buffer.from('', 'hex');
|
|
78
|
+
this.hdPath = '';
|
|
79
|
+
this.paths = {};
|
|
80
|
+
this.getSigner = getSyscoinSigner;
|
|
81
|
+
this.hardwareWalletManager = new hardware_wallet_manager_1.HardwareWalletManager();
|
|
82
|
+
// Set up event listeners
|
|
83
|
+
this.hardwareWalletManager.on('connected', ({ type }) => {
|
|
84
|
+
if (type === hardware_wallet_manager_1.HardwareWalletType.TREZOR) {
|
|
85
|
+
console.log('Trezor connected');
|
|
86
|
+
this.initialized = true;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
this.hardwareWalletManager.on('disconnected', ({ type }) => {
|
|
90
|
+
if (type === hardware_wallet_manager_1.HardwareWalletType.TREZOR) {
|
|
91
|
+
console.log('Trezor disconnected');
|
|
92
|
+
this.initialized = false;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
connect_webextension_1.default.on(connect_webextension_1.DEVICE_EVENT, (event) => {
|
|
96
|
+
if (event.payload.features) {
|
|
97
|
+
this.model = event.payload.features.model;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Initialize Trezor script.
|
|
103
|
+
*/
|
|
104
|
+
async init() {
|
|
105
|
+
if (this.initialized) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
// Add a small delay to ensure Chrome extension context is ready
|
|
109
|
+
// This helps prevent "waiting for handshake" errors
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
111
|
+
const result = await this.hardwareWalletManager.initializeTrezor();
|
|
112
|
+
if (result) {
|
|
113
|
+
this.initialized = true;
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Execute operation with automatic retry
|
|
119
|
+
*/
|
|
120
|
+
async executeWithRetry(operation, operationName) {
|
|
121
|
+
// Ensure initialization first
|
|
122
|
+
if (!this.initialized) {
|
|
123
|
+
const initResult = await this.init();
|
|
124
|
+
if (!initResult) {
|
|
125
|
+
throw new Error('Failed to initialize Trezor');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// For Trezor operations, use reduced retry config to prevent popup spam
|
|
129
|
+
const trezorRetryConfig = {
|
|
130
|
+
maxRetries: 1, // Only retry once
|
|
131
|
+
baseDelay: 1000,
|
|
132
|
+
maxDelay: 5000,
|
|
133
|
+
backoffMultiplier: 2,
|
|
134
|
+
};
|
|
135
|
+
// Use hardware wallet manager's retry mechanism with custom config
|
|
136
|
+
return this.hardwareWalletManager
|
|
137
|
+
.retryOperation(operation, operationName, trezorRetryConfig)
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
// Clean up Trezor state on failure
|
|
140
|
+
if (error.message?.includes('Popup closed') ||
|
|
141
|
+
error.message?.includes('cancelled') ||
|
|
142
|
+
error.message?.includes('denied')) {
|
|
143
|
+
this.initialized = false;
|
|
144
|
+
// Dispose Trezor connection to clean up
|
|
145
|
+
try {
|
|
146
|
+
connect_webextension_1.default.dispose();
|
|
147
|
+
}
|
|
148
|
+
catch (disposeError) {
|
|
149
|
+
console.log('Failed to dispose Trezor on error:', disposeError);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* This return account info based in params provided.
|
|
157
|
+
*
|
|
158
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
159
|
+
* @param slip44 - network slip44 number
|
|
160
|
+
* @param hdPath - path derivation. Example: m/84'/57'/0'
|
|
161
|
+
* @param index - index of account for path derivation
|
|
162
|
+
* @returns derivated account info or error
|
|
163
|
+
*/
|
|
164
|
+
async getAccountInfo({ coin, slip44, hdPath, index, }) {
|
|
165
|
+
return this.executeWithRetry(async () => {
|
|
166
|
+
// Use dynamic path generation instead of hardcoded switch
|
|
167
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
168
|
+
if (hdPath)
|
|
169
|
+
this.hdPath = hdPath;
|
|
170
|
+
// For EVM networks, getAccountInfo is not supported
|
|
171
|
+
// We need to use getAddress instead
|
|
172
|
+
if (slip44 === 60) {
|
|
173
|
+
const addressResponse = await connect_webextension_1.default.ethereumGetAddress({
|
|
174
|
+
path: this.hdPath,
|
|
175
|
+
showOnTrezor: false,
|
|
176
|
+
});
|
|
177
|
+
if (!addressResponse.success) {
|
|
178
|
+
throw new Error(addressResponse.payload.error || 'Failed to get EVM address');
|
|
179
|
+
}
|
|
180
|
+
// Return a compatible AccountInfo structure
|
|
181
|
+
return {
|
|
182
|
+
descriptor: addressResponse.payload.address,
|
|
183
|
+
balance: '0', // Balance is fetched separately for EVM
|
|
184
|
+
empty: true,
|
|
185
|
+
history: {
|
|
186
|
+
total: 0,
|
|
187
|
+
unconfirmed: 0,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// For UTXO networks, use the standard getAccountInfo
|
|
192
|
+
const response = await connect_webextension_1.default.getAccountInfo({
|
|
193
|
+
coin,
|
|
194
|
+
path: this.hdPath,
|
|
195
|
+
});
|
|
196
|
+
if (response.success) {
|
|
197
|
+
return response.payload;
|
|
198
|
+
}
|
|
199
|
+
throw new Error(response.payload.error);
|
|
200
|
+
}, 'getAccountInfo');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Gets the model, if known.
|
|
204
|
+
* This may be `undefined` if the model hasn't been loaded yet.
|
|
205
|
+
*
|
|
206
|
+
* @returns
|
|
207
|
+
*/
|
|
208
|
+
getModel() {
|
|
209
|
+
return this.model;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* This removes the Trezor Connect iframe from the DOM
|
|
213
|
+
*
|
|
214
|
+
* @returns void
|
|
215
|
+
*/
|
|
216
|
+
dispose() {
|
|
217
|
+
try {
|
|
218
|
+
connect_webextension_1.default.dispose();
|
|
219
|
+
this.initialized = false;
|
|
220
|
+
// Clear any cached data
|
|
221
|
+
this.publicKey = buffer_1.Buffer.from('', 'hex');
|
|
222
|
+
this.chainCode = buffer_1.Buffer.from('', 'hex');
|
|
223
|
+
this.hdPath = '';
|
|
224
|
+
this.paths = {};
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
console.log('Error disposing Trezor:', error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* This verify if message is valid or not.
|
|
232
|
+
*
|
|
233
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
234
|
+
* @param address - account address that signed message
|
|
235
|
+
* @param message - message to be verified. Example: 'Test message'
|
|
236
|
+
* @param signature - signature received in sign method. Example: I6BrpivjCwZmScZ6BMAHWGQPo+JjX2kzKXU5LcGVfEgvFb2VfJuKo3g6eSQcykQZiILoWNUDn5rDHkwJg3EcvuY=
|
|
237
|
+
* @returns derivated account info or error
|
|
238
|
+
*/
|
|
239
|
+
async verifyMessage({ coin, address, message, signature, }) {
|
|
240
|
+
return this.executeWithRetry(async () => {
|
|
241
|
+
let method = '';
|
|
242
|
+
switch (coin) {
|
|
243
|
+
case 'eth':
|
|
244
|
+
method = 'ethereumVerifyMessage';
|
|
245
|
+
break;
|
|
246
|
+
default:
|
|
247
|
+
method = 'verifyMessage';
|
|
248
|
+
}
|
|
249
|
+
// @ts-ignore
|
|
250
|
+
const { success, payload } = await connect_webextension_1.default[method]({
|
|
251
|
+
coin,
|
|
252
|
+
address,
|
|
253
|
+
message,
|
|
254
|
+
signature,
|
|
255
|
+
});
|
|
256
|
+
if (success) {
|
|
257
|
+
return { success, payload };
|
|
258
|
+
}
|
|
259
|
+
throw new Error(payload.error);
|
|
260
|
+
}, 'verifyMessage');
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* This return account public key.
|
|
264
|
+
*
|
|
265
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
266
|
+
* @param slip44 - network slip44 number
|
|
267
|
+
* @param hdPath - path derivation. Example: m/44'/57'/0'/0/0
|
|
268
|
+
* @returns publicKey and chainCode
|
|
269
|
+
*/
|
|
270
|
+
async getPublicKey({ coin, slip44, hdPath, index, }) {
|
|
271
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
272
|
+
if (hdPath)
|
|
273
|
+
this.hdPath = hdPath;
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
275
|
+
try {
|
|
276
|
+
// For EVM networks, use ethereumGetPublicKey
|
|
277
|
+
if (slip44 === 60) {
|
|
278
|
+
const { success, payload } = await connect_webextension_1.default.ethereumGetPublicKey({
|
|
279
|
+
path: this.hdPath,
|
|
280
|
+
showOnTrezor: false,
|
|
281
|
+
});
|
|
282
|
+
if (success) {
|
|
283
|
+
const { publicKey } = payload;
|
|
284
|
+
// For Ethereum, we don't get chainCode from ethereumGetPublicKey
|
|
285
|
+
this.publicKey = buffer_1.Buffer.from(publicKey, 'hex');
|
|
286
|
+
return {
|
|
287
|
+
publicKey: `0x${publicKey}`,
|
|
288
|
+
chainCode: '', // Ethereum doesn't use chainCode in the same way
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return { success: false, payload };
|
|
292
|
+
}
|
|
293
|
+
// For UTXO networks, use standard getPublicKey
|
|
294
|
+
const { success, payload } = await connect_webextension_1.default.getPublicKey({
|
|
295
|
+
coin: coin,
|
|
296
|
+
path: this.hdPath,
|
|
297
|
+
});
|
|
298
|
+
if (success) {
|
|
299
|
+
const { publicKey, chainCode } = payload;
|
|
300
|
+
this.publicKey = buffer_1.Buffer.from(publicKey, 'hex');
|
|
301
|
+
this.chainCode = buffer_1.Buffer.from(chainCode, 'hex');
|
|
302
|
+
return {
|
|
303
|
+
publicKey: `0x${this.publicKey.toString('hex')}`,
|
|
304
|
+
chainCode: `0x${this.chainCode.toString('hex')}`,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return { success: false, payload };
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
return error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
range(n) {
|
|
314
|
+
return [...Array(n).keys()];
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* This sign UTXO tx.
|
|
318
|
+
*
|
|
319
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
320
|
+
* @param inputs - utxo transaction inputs
|
|
321
|
+
* @param outputs - utxo transaction outputs
|
|
322
|
+
* @returns signature object
|
|
323
|
+
*/
|
|
324
|
+
async signUtxoTransaction(utxoTransaction, psbt) {
|
|
325
|
+
return this.executeWithRetry(async () => {
|
|
326
|
+
const { payload, success } = await connect_webextension_1.default.signTransaction(utxoTransaction);
|
|
327
|
+
if (success) {
|
|
328
|
+
const tx = bitcoinjs_lib_1.Transaction.fromHex(payload.serializedTx);
|
|
329
|
+
for (const i of this.range(psbt.data.inputs.length)) {
|
|
330
|
+
if (tx.ins[i].witness == null) {
|
|
331
|
+
throw new Error('Please move your funds to a Segwit address: https://wiki.trezor.io/Account');
|
|
332
|
+
}
|
|
333
|
+
const partialSig = [
|
|
334
|
+
{
|
|
335
|
+
pubkey: tx.ins[i].witness[1],
|
|
336
|
+
signature: tx.ins[i].witness[0],
|
|
337
|
+
},
|
|
338
|
+
];
|
|
339
|
+
psbt.updateInput(i, { partialSig });
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
if (psbt.validateSignaturesOfAllInputs()) {
|
|
343
|
+
psbt.finalizeAllInputs();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
console.log(err);
|
|
348
|
+
}
|
|
349
|
+
return psbt;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
throw new Error('Trezor sign failed: ' + payload.error);
|
|
353
|
+
}
|
|
354
|
+
}, 'signUtxoTransaction');
|
|
355
|
+
}
|
|
356
|
+
setHdPath(coin, accountIndex, slip44) {
|
|
357
|
+
if ((0, derivation_paths_1.isEvmCoin)(coin, slip44)) {
|
|
358
|
+
// For EVM, the "accountIndex" parameter is actually used as the address index
|
|
359
|
+
// EVM typically uses account 0, and different addresses are at different address indices
|
|
360
|
+
this.hdPath = (0, derivation_paths_1.getAddressDerivationPath)(coin, slip44, 0, // account is always 0 for EVM
|
|
361
|
+
false, // not a change address
|
|
362
|
+
accountIndex // this is actually the address index for EVM
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// For UTXO, use account-level derivation path
|
|
367
|
+
this.hdPath = (0, derivation_paths_1.getAccountDerivationPath)(coin, slip44, accountIndex);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
convertToAddressNFormat(path) {
|
|
371
|
+
const pathArray = path.replace(/'/g, '').split('/');
|
|
372
|
+
pathArray.shift();
|
|
373
|
+
const addressN = [];
|
|
374
|
+
for (const index in pathArray) {
|
|
375
|
+
if (Number(index) <= 2 && Number(index) >= 0) {
|
|
376
|
+
addressN[Number(index)] = Number(pathArray[index]) | 0x80000000;
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
addressN[Number(index)] = Number(pathArray[index]);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return addressN;
|
|
383
|
+
}
|
|
384
|
+
isScriptHash(address, networkInfo) {
|
|
385
|
+
if (!this.isBech32(address)) {
|
|
386
|
+
const decoded = fromBase58Check(address);
|
|
387
|
+
if (decoded.version === networkInfo.pubKeyHash) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
if (decoded.version === networkInfo.scriptHash) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
const decoded = fromBech32(address);
|
|
396
|
+
if (decoded.data.length === 20) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
if (decoded.data.length === 32) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
throw new Error('isScriptHash: Unknown address type');
|
|
404
|
+
}
|
|
405
|
+
isPaymentFactory(payment) {
|
|
406
|
+
return (script) => {
|
|
407
|
+
try {
|
|
408
|
+
payment({ output: script });
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
isBech32(address) {
|
|
417
|
+
try {
|
|
418
|
+
fromBech32(address);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
isP2WSHScript(script) {
|
|
426
|
+
this.isPaymentFactory(p2wsh)(script);
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
convertToTrezorFormat({ psbt, pathIn, coin }) {
|
|
430
|
+
const { hd } = this.getSigner();
|
|
431
|
+
const trezortx = {};
|
|
432
|
+
trezortx.coin = coin;
|
|
433
|
+
trezortx.version = psbt.version;
|
|
434
|
+
trezortx.inputs = [];
|
|
435
|
+
trezortx.outputs = [];
|
|
436
|
+
for (let i = 0; i < psbt.txInputs.length; i++) {
|
|
437
|
+
const scriptTypes = psbt.getInputType(i);
|
|
438
|
+
const input = psbt.txInputs[i];
|
|
439
|
+
const inputItem = {};
|
|
440
|
+
inputItem.prev_index = input.index;
|
|
441
|
+
inputItem.prev_hash = input.hash.reverse().toString('hex');
|
|
442
|
+
if (input.sequence)
|
|
443
|
+
inputItem.sequence = input.sequence;
|
|
444
|
+
const dataInput = psbt.data.inputs[i];
|
|
445
|
+
let path = '';
|
|
446
|
+
if (pathIn ||
|
|
447
|
+
(dataInput.unknownKeyVals &&
|
|
448
|
+
dataInput.unknownKeyVals.length > 1 &&
|
|
449
|
+
dataInput.unknownKeyVals[1].key.equals(buffer_1.Buffer.from('path')) &&
|
|
450
|
+
(!dataInput.bip32Derivation ||
|
|
451
|
+
dataInput.bip32Derivation.length === 0))) {
|
|
452
|
+
path = pathIn || dataInput.unknownKeyVals[1].value.toString();
|
|
453
|
+
inputItem.address_n = this.convertToAddressNFormat(path);
|
|
454
|
+
}
|
|
455
|
+
switch (scriptTypes) {
|
|
456
|
+
case 'multisig':
|
|
457
|
+
inputItem.script_type = 'SPENDMULTISIG';
|
|
458
|
+
break;
|
|
459
|
+
case 'witnesspubkeyhash':
|
|
460
|
+
inputItem.script_type = 'SPENDWITNESS';
|
|
461
|
+
break;
|
|
462
|
+
default:
|
|
463
|
+
inputItem.script_type = this.isP2WSHScript(psbt.data.inputs[i].witnessUtxo.script
|
|
464
|
+
? psbt.data.inputs[i].witnessUtxo.script
|
|
465
|
+
: '')
|
|
466
|
+
? 'SPENDP2SHWITNESS'
|
|
467
|
+
: 'SPENDADDRESS';
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
trezortx.inputs.push(inputItem);
|
|
471
|
+
}
|
|
472
|
+
for (let i = 0; i < psbt.txOutputs.length; i++) {
|
|
473
|
+
const output = psbt.txOutputs[i];
|
|
474
|
+
const outputItem = {};
|
|
475
|
+
const chunks = decompile(output.script);
|
|
476
|
+
outputItem.amount = output.value.toString();
|
|
477
|
+
if (chunks && chunks[0] === bitcoin_ops_1.default.OP_RETURN) {
|
|
478
|
+
outputItem.script_type = 'PAYTOOPRETURN';
|
|
479
|
+
// @ts-ignore
|
|
480
|
+
outputItem.op_return_data = chunks[1].toString('hex');
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
if (output && this.isBech32(output.address)) {
|
|
484
|
+
if (output.script.length === 34 &&
|
|
485
|
+
output.script[0] === 0 &&
|
|
486
|
+
output.script[1] === 0x20) {
|
|
487
|
+
outputItem.script_type = 'PAYTOP2SHWITNESS';
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
outputItem.script_type = 'PAYTOWITNESS';
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
outputItem.script_type = this.isScriptHash(output.address, hd.Signer.network)
|
|
495
|
+
? 'PAYTOSCRIPTHASH'
|
|
496
|
+
: 'PAYTOADDRESS';
|
|
497
|
+
}
|
|
498
|
+
if (output.address)
|
|
499
|
+
outputItem.address = output.address;
|
|
500
|
+
}
|
|
501
|
+
trezortx.outputs.push(outputItem);
|
|
502
|
+
}
|
|
503
|
+
return trezortx;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* This sign EVM tx.
|
|
507
|
+
*
|
|
508
|
+
* @param index - index of account for path derivation
|
|
509
|
+
* @param tx - ethereum tx object
|
|
510
|
+
* @returns signature object
|
|
511
|
+
*/
|
|
512
|
+
async signEthTransaction({ tx, index, coin, slip44, }) {
|
|
513
|
+
return this.executeWithRetry(async () => {
|
|
514
|
+
// Wait between popups
|
|
515
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
516
|
+
// Use dynamic path generation based on actual network parameters
|
|
517
|
+
this.setHdPath(coin, Number(index) || 0, slip44);
|
|
518
|
+
const response = await connect_webextension_1.default.ethereumSignTransaction({
|
|
519
|
+
path: this.hdPath,
|
|
520
|
+
transaction: tx,
|
|
521
|
+
});
|
|
522
|
+
if (response.success) {
|
|
523
|
+
return response;
|
|
524
|
+
}
|
|
525
|
+
throw new Error(response.payload.error);
|
|
526
|
+
}, 'signEthTransaction');
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* This sign message.
|
|
530
|
+
*
|
|
531
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
532
|
+
* @param slip44 - network slip44 number
|
|
533
|
+
* @param message - message to be signed. Example: 'Test message'
|
|
534
|
+
* @param index - index of account for path derivation
|
|
535
|
+
* @returns signature object
|
|
536
|
+
*/
|
|
537
|
+
async signMessage({ index, message, coin, slip44, address, }) {
|
|
538
|
+
return this.executeWithRetry(async () => {
|
|
539
|
+
// Wait between popups
|
|
540
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
541
|
+
if ((0, derivation_paths_1.isEvmCoin)(coin, slip44) && `${index ? index : 0}` && message) {
|
|
542
|
+
return this._signEthPersonalMessage(Number(index), message, address);
|
|
543
|
+
}
|
|
544
|
+
return this._signUtxoPersonalMessage({ coin, index, slip44, message });
|
|
545
|
+
}, 'signMessage');
|
|
546
|
+
}
|
|
547
|
+
async _signUtxoPersonalMessage({ coin, index, slip44, message, }) {
|
|
548
|
+
try {
|
|
549
|
+
// Use dynamic path generation instead of hardcoded switch
|
|
550
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
551
|
+
const { success, payload } = await connect_webextension_1.default.signMessage({
|
|
552
|
+
path: this.hdPath,
|
|
553
|
+
coin: coin,
|
|
554
|
+
message: message,
|
|
555
|
+
});
|
|
556
|
+
if (success) {
|
|
557
|
+
return { success, payload };
|
|
558
|
+
}
|
|
559
|
+
return { success: false, payload };
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
return { error };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// For personal_sign, we need to prefix the message:
|
|
566
|
+
async _signEthPersonalMessage(index, message, address) {
|
|
567
|
+
return new Promise((resolve, reject) => {
|
|
568
|
+
setTimeout(async () => {
|
|
569
|
+
try {
|
|
570
|
+
this.setHdPath('eth', index, 60);
|
|
571
|
+
connect_webextension_1.default.ethereumSignMessage({
|
|
572
|
+
path: this.hdPath,
|
|
573
|
+
message: (0, ethereumjs_util_1.stripHexPrefix)(message),
|
|
574
|
+
hex: true,
|
|
575
|
+
})
|
|
576
|
+
.then((response) => {
|
|
577
|
+
if (response.success) {
|
|
578
|
+
if (address &&
|
|
579
|
+
response.payload.address.toLowerCase() !==
|
|
580
|
+
address.toLowerCase()) {
|
|
581
|
+
reject(new Error('signature doesnt match the right address'));
|
|
582
|
+
}
|
|
583
|
+
const signature = `0x${response.payload.signature}`;
|
|
584
|
+
resolve({ signature, success: true });
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
reject(
|
|
588
|
+
// @ts-ignore
|
|
589
|
+
new Error(response.payload.error || 'Unknown error'));
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
.catch((e) => {
|
|
593
|
+
reject(new Error(e.toString() || 'Unknown error'));
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
reject(error);
|
|
598
|
+
}
|
|
599
|
+
// This is necessary to avoid popup collision
|
|
600
|
+
// between the unlock & sign trezor popups
|
|
601
|
+
}, DELAY_BETWEEN_POPUPS);
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
_sanitizeData(data) {
|
|
605
|
+
switch (Object.prototype.toString.call(data)) {
|
|
606
|
+
case '[object Object]': {
|
|
607
|
+
const entries = Object.keys(data).map((k) => [
|
|
608
|
+
k,
|
|
609
|
+
this._sanitizeData(data[k]),
|
|
610
|
+
]);
|
|
611
|
+
return Object.fromEntries(entries);
|
|
612
|
+
}
|
|
613
|
+
case '[object Array]':
|
|
614
|
+
return data.map((v) => this._sanitizeData(v));
|
|
615
|
+
case '[object BigInt]':
|
|
616
|
+
return data.toString();
|
|
617
|
+
default:
|
|
618
|
+
return data;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* EIP-712 Sign Typed Data
|
|
623
|
+
*/
|
|
624
|
+
async signTypedData({ version, address, data, index, }) {
|
|
625
|
+
return this.executeWithRetry(async () => {
|
|
626
|
+
// Wait between popups
|
|
627
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
628
|
+
this.setHdPath('eth', index, 60);
|
|
629
|
+
// Use dynamic path generation for ETH (EVM) - typed data is only used for EVM
|
|
630
|
+
const dataWithHashes = this._transformTypedData(data, version === 'V4');
|
|
631
|
+
// set default values for signTypedData
|
|
632
|
+
// Trezor is stricter than @metamask/eth-sig-util in what it accepts
|
|
633
|
+
const { types, message = {}, domain = {}, primaryType,
|
|
634
|
+
// snake_case since Trezor uses Protobuf naming conventions here
|
|
635
|
+
domain_separator_hash, // eslint-disable-line camelcase
|
|
636
|
+
message_hash, // eslint-disable-line camelcase
|
|
637
|
+
} = dataWithHashes;
|
|
638
|
+
// This is necessary to avoid popup collision
|
|
639
|
+
// between the unlock & sign trezor popups
|
|
640
|
+
const response = await connect_webextension_1.default.ethereumSignTypedData({
|
|
641
|
+
path: this.hdPath,
|
|
642
|
+
data: {
|
|
643
|
+
types: {
|
|
644
|
+
...types,
|
|
645
|
+
EIP712Domain: types.EIP712Domain ? types.EIP712Domain : [],
|
|
646
|
+
},
|
|
647
|
+
message,
|
|
648
|
+
domain,
|
|
649
|
+
primaryType: primaryType,
|
|
650
|
+
},
|
|
651
|
+
metamask_v4_compat: true,
|
|
652
|
+
// Trezor 1 only supports blindly signing hashes
|
|
653
|
+
domain_separator_hash,
|
|
654
|
+
message_hash: message_hash ? message_hash : '',
|
|
655
|
+
});
|
|
656
|
+
if (response.success) {
|
|
657
|
+
if (address !== response.payload.address) {
|
|
658
|
+
throw new Error('signature doesnt match the right address');
|
|
659
|
+
}
|
|
660
|
+
return response.payload.signature;
|
|
661
|
+
}
|
|
662
|
+
// @ts-ignore
|
|
663
|
+
throw new Error(response.payload.error || 'Unknown error');
|
|
664
|
+
}, 'signTypedData');
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Verify UTXO address by displaying it on the Trezor device
|
|
668
|
+
* @param accountIndex - The account index
|
|
669
|
+
* @param currency - The currency (coin type)
|
|
670
|
+
* @param slip44 - The slip44 value for the network
|
|
671
|
+
* @returns The verified address
|
|
672
|
+
*/
|
|
673
|
+
async verifyUtxoAddress(accountIndex, currency, slip44) {
|
|
674
|
+
return this.executeWithRetry(async () => {
|
|
675
|
+
const fullPath = (0, derivation_paths_1.getAddressDerivationPath)(currency, slip44, accountIndex, false, // Not a change address
|
|
676
|
+
0);
|
|
677
|
+
try {
|
|
678
|
+
const { payload, success } = await connect_webextension_1.default.getAddress({
|
|
679
|
+
path: fullPath,
|
|
680
|
+
coin: currency,
|
|
681
|
+
showOnTrezor: true, // This displays the address on device for verification
|
|
682
|
+
});
|
|
683
|
+
if (success) {
|
|
684
|
+
return payload.address;
|
|
685
|
+
}
|
|
686
|
+
throw new Error('Address verification cancelled by user');
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
throw error;
|
|
690
|
+
}
|
|
691
|
+
}, 'verifyUtxoAddress');
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Check Trezor status
|
|
695
|
+
*/
|
|
696
|
+
getStatus() {
|
|
697
|
+
return this.hardwareWalletManager
|
|
698
|
+
.getStatus()
|
|
699
|
+
.find((s) => s.type === hardware_wallet_manager_1.HardwareWalletType.TREZOR);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Clean up resources
|
|
703
|
+
*/
|
|
704
|
+
async destroy() {
|
|
705
|
+
try {
|
|
706
|
+
// First dispose Trezor Connect
|
|
707
|
+
this.dispose();
|
|
708
|
+
// Then destroy hardware wallet manager
|
|
709
|
+
await this.hardwareWalletManager.destroy();
|
|
710
|
+
this.initialized = false;
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
console.error('Error destroying Trezor keyring:', error);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
exports.TrezorKeyring = TrezorKeyring;
|
|
718
|
+
//# sourceMappingURL=index.js.map
|