@smartledger/bsv 3.3.3 → 3.3.5
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 +50 -28
- package/README.md +55 -36
- package/bsv-covenant.min.js +6 -6
- package/bsv-gdaf.min.js +6 -6
- package/bsv-ltp.min.js +6 -6
- package/bsv-mnemonic.min.js +4 -4
- package/bsv-smartcontract.min.js +5 -5
- package/bsv.bundle.js +5 -5
- package/bsv.min.js +5 -5
- package/demos/README.md +188 -0
- package/demos/architecture_demo.js +247 -0
- package/demos/browser-test.html +1208 -0
- package/demos/bsv_wallet_demo.js +242 -0
- package/demos/complete_ltp_demo.js +511 -0
- package/demos/debug_tools_demo.js +87 -0
- package/demos/demo_features.js +123 -0
- package/demos/easy_interface_demo.js +109 -0
- package/demos/ecies_demo.js +182 -0
- package/demos/gdaf_core_test.js +131 -0
- package/demos/gdaf_demo.js +237 -0
- package/demos/ltp_demo.js +361 -0
- package/demos/ltp_primitives_demo.js +403 -0
- package/demos/message_demo.js +209 -0
- package/demos/preimage_separation_demo.js +383 -0
- package/demos/script_helper_demo.js +289 -0
- package/demos/security_demo.js +287 -0
- package/demos/shamir_demo.js +121 -0
- package/demos/simple_demo.js +204 -0
- package/demos/simple_p2pkh_demo.js +169 -0
- package/demos/simple_utxo_preimage_demo.js +196 -0
- package/demos/smart_contract_demo.html +1347 -0
- package/demos/smart_contract_demo.js +910 -0
- package/demos/utxo_generator_demo.js +244 -0
- package/demos/validation_pipeline_demo.js +155 -0
- package/demos/web3keys.html +740 -0
- package/docs/BUNDLE_UPDATE_SUMMARY.md +40 -0
- package/docs/DOCUMENTATION_REVIEW_REPORT.md +11 -11
- package/docs/FIX_CREATEHMAC_ISSUE.md +91 -0
- package/docs/MODULE_REFERENCE_COMPLETE.md +28 -28
- package/docs/SMARTLEDGER_BSV_USAGE_ANSWERS.md +477 -0
- package/docs/SMARTLEDGER_BSV_USAGE_EXAMPLES.js +372 -0
- package/docs/SMARTLEDGER_BSV_USAGE_GUIDE.md +555 -0
- package/docs/SMART_CONTRACT_DEVELOPMENT_GUIDE.md +1459 -0
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +2 -2
- package/docs/getting-started/INSTALLATION.md +25 -25
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/examples/complete_workflow_demo.js +783 -0
- package/examples/definitive_working_demo.js +261 -0
- package/examples/final_working_contracts.js +338 -0
- package/examples/smart_contract_templates.js +718 -0
- package/examples/working_smart_contracts.js +348 -0
- package/index.js +7 -0
- package/lib/browser-utxo-manager-es5.js +316 -0
- package/lib/browser-utxo-manager.js +533 -0
- package/lib/mnemonic/pbkdf2.browser.js +69 -0
- package/lib/mnemonic/pbkdf2.js +2 -68
- package/lib/mnemonic/pbkdf2.node.js +68 -0
- package/package.json +18 -7
- package/tests/browser-compatibility/README.md +35 -0
- package/tests/browser-compatibility/test-cdn-vs-local.html +186 -0
- package/tests/browser-compatibility/test-pbkdf2.html +51 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser-Compatible UTXO Manager
|
|
5
|
+
* Lightweight UTXO management for browser environments with configurable storage
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Storage types available for browser UTXO management
|
|
10
|
+
*/
|
|
11
|
+
var STORAGE_TYPES = {
|
|
12
|
+
MEMORY: 'memory', // In-memory only (lost on page reload)
|
|
13
|
+
SESSION: 'session', // sessionStorage (lost when tab closes)
|
|
14
|
+
LOCAL: 'local' // localStorage (persists until cleared)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Browser-compatible UTXO Manager
|
|
19
|
+
* Provides UTXO tracking and management for browser applications
|
|
20
|
+
*/
|
|
21
|
+
function BrowserUTXOManager(options) {
|
|
22
|
+
options = options || {}
|
|
23
|
+
/**
|
|
24
|
+
* Create a new browser UTXO manager
|
|
25
|
+
* @param {Object} options - Configuration options
|
|
26
|
+
* @param {string} options.storage - Storage type: 'memory', 'session', or 'local' (default: 'memory')
|
|
27
|
+
* @param {string} options.storageKey - Key for browser storage (default: 'smartledger-utxos')
|
|
28
|
+
* @param {boolean} options.autoSave - Auto-save after each operation (default: true)
|
|
29
|
+
* @param {number} options.maxUTXOs - Maximum UTXOs to store (default: 1000)
|
|
30
|
+
*/
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
this.options = {
|
|
33
|
+
storage: options.storage || STORAGE_TYPES.MEMORY,
|
|
34
|
+
storageKey: options.storageKey || 'smartledger-utxos',
|
|
35
|
+
autoSave: options.autoSave !== false,
|
|
36
|
+
maxUTXOs: options.maxUTXOs || 1000,
|
|
37
|
+
...options
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Validate storage type
|
|
41
|
+
if (!Object.values(STORAGE_TYPES).includes(this.options.storage)) {
|
|
42
|
+
throw new Error(`Invalid storage type: ${this.options.storage}. Must be one of: ${Object.values(STORAGE_TYPES).join(', ')}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Initialize storage
|
|
46
|
+
this.utxos = new Map() // Main UTXO store: key = "txid:vout", value = utxo object
|
|
47
|
+
this.addressIndex = new Map() // Address index: key = address, value = Set of utxo keys
|
|
48
|
+
this.spentUTXOs = new Map() // Spent UTXO tracking
|
|
49
|
+
this.metadata = {
|
|
50
|
+
totalUTXOs: 0,
|
|
51
|
+
totalValue: 0,
|
|
52
|
+
createdAt: new Date().toISOString(),
|
|
53
|
+
lastUpdated: new Date().toISOString()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Load existing data
|
|
57
|
+
this.loadFromStorage()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get storage interface based on configuration
|
|
62
|
+
* @returns {Object} Storage interface (memory, sessionStorage, or localStorage)
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
_getStorage() {
|
|
66
|
+
switch (this.options.storage) {
|
|
67
|
+
case STORAGE_TYPES.MEMORY:
|
|
68
|
+
return null // Memory storage handled by class properties
|
|
69
|
+
case STORAGE_TYPES.SESSION:
|
|
70
|
+
return typeof sessionStorage !== 'undefined' ? sessionStorage : null
|
|
71
|
+
case STORAGE_TYPES.LOCAL:
|
|
72
|
+
return typeof localStorage !== 'undefined' ? localStorage : null
|
|
73
|
+
default:
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Load UTXOs from configured storage
|
|
80
|
+
*/
|
|
81
|
+
loadFromStorage() {
|
|
82
|
+
try {
|
|
83
|
+
if (this.options.storage === STORAGE_TYPES.MEMORY) {
|
|
84
|
+
// Memory storage - nothing to load, start fresh
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const storage = this._getStorage()
|
|
89
|
+
if (!storage) {
|
|
90
|
+
console.warn('BrowserUTXOManager: Storage not available, using memory mode')
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const stored = storage.getItem(this.options.storageKey)
|
|
95
|
+
if (!stored) {
|
|
96
|
+
return // No existing data
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const data = JSON.parse(stored)
|
|
100
|
+
|
|
101
|
+
// Restore UTXOs
|
|
102
|
+
if (data.utxos) {
|
|
103
|
+
data.utxos.forEach(utxoData => {
|
|
104
|
+
const key = `${utxoData.txid}:${utxoData.vout}`
|
|
105
|
+
this.utxos.set(key, utxoData)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Restore address index
|
|
110
|
+
if (data.addressIndex) {
|
|
111
|
+
Object.entries(data.addressIndex).forEach(([address, utxoKeys]) => {
|
|
112
|
+
this.addressIndex.set(address, new Set(utxoKeys))
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Restore spent UTXOs
|
|
117
|
+
if (data.spentUTXOs) {
|
|
118
|
+
data.spentUTXOs.forEach(spentData => {
|
|
119
|
+
const key = `${spentData.txid}:${spentData.vout}`
|
|
120
|
+
this.spentUTXOs.set(key, spentData)
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Restore metadata
|
|
125
|
+
if (data.metadata) {
|
|
126
|
+
this.metadata = { ...this.metadata, ...data.metadata }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this._updateMetadata()
|
|
130
|
+
console.log(`✅ BrowserUTXOManager: Loaded ${this.utxos.size} UTXOs from ${this.options.storage} storage`)
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('BrowserUTXOManager: Error loading from storage:', error.message)
|
|
134
|
+
// Continue with empty state
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Save UTXOs to configured storage
|
|
140
|
+
*/
|
|
141
|
+
saveToStorage() {
|
|
142
|
+
try {
|
|
143
|
+
if (this.options.storage === STORAGE_TYPES.MEMORY) {
|
|
144
|
+
return // Memory storage - nothing to persist
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const storage = this._getStorage()
|
|
148
|
+
if (!storage) {
|
|
149
|
+
return // Storage not available
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Prepare data for serialization
|
|
153
|
+
const data = {
|
|
154
|
+
utxos: Array.from(this.utxos.values()),
|
|
155
|
+
addressIndex: {},
|
|
156
|
+
spentUTXOs: Array.from(this.spentUTXOs.values()),
|
|
157
|
+
metadata: this.metadata
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Convert address index to serializable format
|
|
161
|
+
this.addressIndex.forEach((utxoKeys, address) => {
|
|
162
|
+
data.addressIndex[address] = Array.from(utxoKeys)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
storage.setItem(this.options.storageKey, JSON.stringify(data))
|
|
166
|
+
console.log(`💾 BrowserUTXOManager: Saved ${this.utxos.size} UTXOs to ${this.options.storage} storage`)
|
|
167
|
+
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('BrowserUTXOManager: Error saving to storage:', error.message)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Add a UTXO to the manager
|
|
175
|
+
* @param {Object} utxo - UTXO object {txid, vout, address, satoshis, script}
|
|
176
|
+
* @returns {boolean} - true if added, false if already exists or limit exceeded
|
|
177
|
+
*/
|
|
178
|
+
addUTXO(utxo) {
|
|
179
|
+
try {
|
|
180
|
+
// Validate UTXO
|
|
181
|
+
if (!utxo.txid || typeof utxo.vout !== 'number' || !utxo.address || typeof utxo.satoshis !== 'number') {
|
|
182
|
+
throw new Error('Invalid UTXO: missing required fields (txid, vout, address, satoshis)')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const key = `${utxo.txid}:${utxo.vout}`
|
|
186
|
+
|
|
187
|
+
// Check if already exists
|
|
188
|
+
if (this.utxos.has(key)) {
|
|
189
|
+
console.log(`⚠️ UTXO already exists: ${key}`)
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check limits
|
|
194
|
+
if (this.utxos.size >= this.options.maxUTXOs) {
|
|
195
|
+
console.warn(`⚠️ Maximum UTXO limit reached (${this.options.maxUTXOs})`)
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Add timestamp
|
|
200
|
+
const utxoWithMeta = {
|
|
201
|
+
...utxo,
|
|
202
|
+
addedAt: new Date().toISOString()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Store UTXO
|
|
206
|
+
this.utxos.set(key, utxoWithMeta)
|
|
207
|
+
|
|
208
|
+
// Update address index
|
|
209
|
+
if (!this.addressIndex.has(utxo.address)) {
|
|
210
|
+
this.addressIndex.set(utxo.address, new Set())
|
|
211
|
+
}
|
|
212
|
+
this.addressIndex.get(utxo.address).add(key)
|
|
213
|
+
|
|
214
|
+
this._updateMetadata()
|
|
215
|
+
|
|
216
|
+
if (this.options.autoSave) {
|
|
217
|
+
this.saveToStorage()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(`✅ UTXO added: ${key} (${utxo.satoshis} sats)`)
|
|
221
|
+
return true
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('BrowserUTXOManager: Error adding UTXO:', error.message)
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get all UTXOs for a specific address
|
|
231
|
+
* @param {string} address - Bitcoin address
|
|
232
|
+
* @returns {Array} Array of UTXO objects
|
|
233
|
+
*/
|
|
234
|
+
getUTXOsForAddress(address) {
|
|
235
|
+
try {
|
|
236
|
+
const utxoKeys = this.addressIndex.get(address)
|
|
237
|
+
if (!utxoKeys) {
|
|
238
|
+
return []
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const utxos = []
|
|
242
|
+
utxoKeys.forEach(key => {
|
|
243
|
+
const utxo = this.utxos.get(key)
|
|
244
|
+
if (utxo) {
|
|
245
|
+
utxos.push(utxo)
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
return utxos.sort((a, b) => b.satoshis - a.satoshis) // Sort by value descending
|
|
250
|
+
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error('BrowserUTXOManager: Error getting UTXOs for address:', error.message)
|
|
253
|
+
return []
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get total balance for an address
|
|
259
|
+
* @param {string} address - Bitcoin address
|
|
260
|
+
* @returns {number} Total satoshis
|
|
261
|
+
*/
|
|
262
|
+
getBalance(address) {
|
|
263
|
+
const utxos = this.getUTXOsForAddress(address)
|
|
264
|
+
return utxos.reduce((total, utxo) => total + utxo.satoshis, 0)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Spend UTXOs (mark as spent and remove from available set)
|
|
269
|
+
* @param {Array} inputs - Array of input objects {txid, vout} or {txid, vout, spentInTx}
|
|
270
|
+
* @param {string} spentInTx - Optional transaction ID where UTXOs were spent
|
|
271
|
+
* @returns {Array} Array of spent UTXO objects
|
|
272
|
+
*/
|
|
273
|
+
spendUTXOs(inputs, spentInTx = 'browser-spend') {
|
|
274
|
+
const spentUTXOs = []
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
inputs.forEach(input => {
|
|
278
|
+
const key = `${input.txid}:${input.vout}`
|
|
279
|
+
const utxo = this.utxos.get(key)
|
|
280
|
+
|
|
281
|
+
if (!utxo) {
|
|
282
|
+
console.warn(`⚠️ UTXO not found: ${key}`)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Mark as spent
|
|
287
|
+
const spentUTXO = {
|
|
288
|
+
...utxo,
|
|
289
|
+
spentAt: new Date().toISOString(),
|
|
290
|
+
spentInTx: input.spentInTx || spentInTx
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.spentUTXOs.set(key, spentUTXO)
|
|
294
|
+
spentUTXOs.push(spentUTXO)
|
|
295
|
+
|
|
296
|
+
// Remove from available UTXOs
|
|
297
|
+
this.utxos.delete(key)
|
|
298
|
+
|
|
299
|
+
// Update address index
|
|
300
|
+
const addressSet = this.addressIndex.get(utxo.address)
|
|
301
|
+
if (addressSet) {
|
|
302
|
+
addressSet.delete(key)
|
|
303
|
+
if (addressSet.size === 0) {
|
|
304
|
+
this.addressIndex.delete(utxo.address)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log(`❌ UTXO spent: ${key} in ${spentUTXO.spentInTx}`)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
this._updateMetadata()
|
|
312
|
+
|
|
313
|
+
if (this.options.autoSave) {
|
|
314
|
+
this.saveToStorage()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error('BrowserUTXOManager: Error spending UTXOs:', error.message)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return spentUTXOs
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Check if a UTXO is available (unspent)
|
|
326
|
+
* @param {string} txid - Transaction ID
|
|
327
|
+
* @param {number} vout - Output index
|
|
328
|
+
* @returns {boolean} True if UTXO is available
|
|
329
|
+
*/
|
|
330
|
+
isUTXOAvailable(txid, vout) {
|
|
331
|
+
const key = `${txid}:${vout}`
|
|
332
|
+
return this.utxos.has(key)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get UTXO details
|
|
337
|
+
* @param {string} txid - Transaction ID
|
|
338
|
+
* @param {number} vout - Output index
|
|
339
|
+
* @returns {Object|null} UTXO object or null if not found
|
|
340
|
+
*/
|
|
341
|
+
getUTXO(txid, vout) {
|
|
342
|
+
const key = `${txid}:${vout}`
|
|
343
|
+
|
|
344
|
+
// Check if available
|
|
345
|
+
if (this.utxos.has(key)) {
|
|
346
|
+
return { status: 'available', utxo: this.utxos.get(key) }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check if spent
|
|
350
|
+
if (this.spentUTXOs.has(key)) {
|
|
351
|
+
return { status: 'spent', utxo: this.spentUTXOs.get(key) }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return { status: 'not_found', utxo: null }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Create mock UTXOs for testing (browser-compatible)
|
|
359
|
+
* @param {string} address - Target address
|
|
360
|
+
* @param {number} count - Number of UTXOs to create
|
|
361
|
+
* @param {number} satoshis - Satoshis per UTXO
|
|
362
|
+
* @returns {Array} Array of created UTXOs
|
|
363
|
+
*/
|
|
364
|
+
createMockUTXOs(address, count = 5, satoshis = 100000) {
|
|
365
|
+
const mockUTXOs = []
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
for (let i = 0; i < count; i++) {
|
|
369
|
+
// Generate random txid using Web Crypto API
|
|
370
|
+
const txidArray = new Uint8Array(32)
|
|
371
|
+
if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
|
|
372
|
+
window.crypto.getRandomValues(txidArray)
|
|
373
|
+
} else {
|
|
374
|
+
// Fallback for environments without Web Crypto
|
|
375
|
+
for (let j = 0; j < 32; j++) {
|
|
376
|
+
txidArray[j] = Math.floor(Math.random() * 256)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const txid = Array.from(txidArray).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
381
|
+
const script = `76a914${Array.from(new Uint8Array(20)).map(b => Math.floor(Math.random() * 256).toString(16).padStart(2, '0')).join('')}88ac`
|
|
382
|
+
|
|
383
|
+
const utxo = {
|
|
384
|
+
txid,
|
|
385
|
+
vout: i,
|
|
386
|
+
address,
|
|
387
|
+
satoshis,
|
|
388
|
+
script,
|
|
389
|
+
isMock: true
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (this.addUTXO(utxo)) {
|
|
393
|
+
mockUTXOs.push(utxo)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error('BrowserUTXOManager: Error creating mock UTXOs:', error.message)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return mockUTXOs
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get manager statistics
|
|
406
|
+
* @returns {Object} Statistics object
|
|
407
|
+
*/
|
|
408
|
+
getStats() {
|
|
409
|
+
const addresses = Array.from(this.addressIndex.keys())
|
|
410
|
+
const balancesByAddress = {}
|
|
411
|
+
|
|
412
|
+
addresses.forEach(address => {
|
|
413
|
+
balancesByAddress[address] = this.getBalance(address)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
totalUTXOs: this.utxos.size,
|
|
418
|
+
totalSpent: this.spentUTXOs.size,
|
|
419
|
+
totalValue: this.metadata.totalValue,
|
|
420
|
+
totalAddresses: addresses.length,
|
|
421
|
+
storageType: this.options.storage,
|
|
422
|
+
storageKey: this.options.storageKey,
|
|
423
|
+
balancesByAddress,
|
|
424
|
+
createdAt: this.metadata.createdAt,
|
|
425
|
+
lastUpdated: this.metadata.lastUpdated
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Clear all UTXOs and reset state
|
|
431
|
+
* @param {boolean} clearStorage - Also clear browser storage (default: true)
|
|
432
|
+
*/
|
|
433
|
+
reset(clearStorage = true) {
|
|
434
|
+
this.utxos.clear()
|
|
435
|
+
this.addressIndex.clear()
|
|
436
|
+
this.spentUTXOs.clear()
|
|
437
|
+
|
|
438
|
+
this.metadata = {
|
|
439
|
+
totalUTXOs: 0,
|
|
440
|
+
totalValue: 0,
|
|
441
|
+
createdAt: new Date().toISOString(),
|
|
442
|
+
lastUpdated: new Date().toISOString()
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (clearStorage && this.options.storage !== STORAGE_TYPES.MEMORY) {
|
|
446
|
+
const storage = this._getStorage()
|
|
447
|
+
if (storage) {
|
|
448
|
+
storage.removeItem(this.options.storageKey)
|
|
449
|
+
console.log(`🔄 Cleared ${this.options.storage} storage`)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log('🔄 BrowserUTXOManager reset complete')
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Update internal metadata
|
|
458
|
+
* @private
|
|
459
|
+
*/
|
|
460
|
+
_updateMetadata() {
|
|
461
|
+
this.metadata.totalUTXOs = this.utxos.size
|
|
462
|
+
this.metadata.totalValue = Array.from(this.utxos.values())
|
|
463
|
+
.reduce((total, utxo) => total + utxo.satoshis, 0)
|
|
464
|
+
this.metadata.lastUpdated = new Date().toISOString()
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Export UTXOs as JSON
|
|
469
|
+
* @returns {string} JSON string of all data
|
|
470
|
+
*/
|
|
471
|
+
exportData() {
|
|
472
|
+
const data = {
|
|
473
|
+
utxos: Array.from(this.utxos.values()),
|
|
474
|
+
spentUTXOs: Array.from(this.spentUTXOs.values()),
|
|
475
|
+
metadata: this.metadata,
|
|
476
|
+
exportedAt: new Date().toISOString()
|
|
477
|
+
}
|
|
478
|
+
return JSON.stringify(data, null, 2)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Import UTXOs from JSON
|
|
483
|
+
* @param {string} jsonData - JSON string to import
|
|
484
|
+
* @param {boolean} merge - Merge with existing data (default: false)
|
|
485
|
+
* @returns {boolean} Success status
|
|
486
|
+
*/
|
|
487
|
+
importData(jsonData, merge = false) {
|
|
488
|
+
try {
|
|
489
|
+
const data = JSON.parse(jsonData)
|
|
490
|
+
|
|
491
|
+
if (!merge) {
|
|
492
|
+
this.reset(false) // Don't clear storage yet
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Import UTXOs
|
|
496
|
+
if (data.utxos && Array.isArray(data.utxos)) {
|
|
497
|
+
data.utxos.forEach(utxo => {
|
|
498
|
+
this.addUTXO(utxo)
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Import spent UTXOs
|
|
503
|
+
if (data.spentUTXOs && Array.isArray(data.spentUTXOs)) {
|
|
504
|
+
data.spentUTXOs.forEach(spentUTXO => {
|
|
505
|
+
const key = `${spentUTXO.txid}:${spentUTXO.vout}`
|
|
506
|
+
this.spentUTXOs.set(key, spentUTXO)
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
this._updateMetadata()
|
|
511
|
+
|
|
512
|
+
if (this.options.autoSave) {
|
|
513
|
+
this.saveToStorage()
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
console.log('✅ BrowserUTXOManager: Imported ' + (data.utxos && data.utxos.length || 0) + ' UTXOs')
|
|
517
|
+
return true
|
|
518
|
+
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error('BrowserUTXOManager: Error importing data:', error.message)
|
|
521
|
+
return false
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Export for both CommonJS and ES modules
|
|
527
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
528
|
+
module.exports = BrowserUTXOManager
|
|
529
|
+
module.exports.STORAGE_TYPES = STORAGE_TYPES
|
|
530
|
+
} else if (typeof window !== 'undefined') {
|
|
531
|
+
window.BrowserUTXOManager = BrowserUTXOManager
|
|
532
|
+
window.BrowserUTXOManager.STORAGE_TYPES = STORAGE_TYPES
|
|
533
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser-compatible PBKDF2 implementation using BSV crypto
|
|
5
|
+
* Credit to: https://github.com/stayradiated/pbkdf2-sha512
|
|
6
|
+
* Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Import BSV crypto instead of Node.js crypto
|
|
10
|
+
var Hash = require('../crypto/hash')
|
|
11
|
+
|
|
12
|
+
function pbkdf2 (key, salt, iterations, dkLen) {
|
|
13
|
+
var hLen = 64 // SHA512 Mac length
|
|
14
|
+
if (dkLen > (Math.pow(2, 32) - 1) * hLen) {
|
|
15
|
+
throw Error('Requested key length too long')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof key !== 'string' && !Buffer.isBuffer(key)) {
|
|
19
|
+
throw new TypeError('key must a string or Buffer')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof salt !== 'string' && !Buffer.isBuffer(salt)) {
|
|
23
|
+
throw new TypeError('salt must a string or Buffer')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof key === 'string') {
|
|
27
|
+
key = Buffer.from(key)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof salt === 'string') {
|
|
31
|
+
salt = Buffer.from(salt)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var DK = Buffer.alloc(dkLen)
|
|
35
|
+
var U = Buffer.alloc(hLen)
|
|
36
|
+
var T = Buffer.alloc(hLen)
|
|
37
|
+
var block1 = Buffer.alloc(salt.length + 4)
|
|
38
|
+
|
|
39
|
+
var l = Math.ceil(dkLen / hLen)
|
|
40
|
+
var r = dkLen - (l - 1) * hLen
|
|
41
|
+
|
|
42
|
+
salt.copy(block1, 0, 0, salt.length)
|
|
43
|
+
for (var i = 1; i <= l; i++) {
|
|
44
|
+
block1[salt.length + 0] = (i >> 24 & 0xff)
|
|
45
|
+
block1[salt.length + 1] = (i >> 16 & 0xff)
|
|
46
|
+
block1[salt.length + 2] = (i >> 8 & 0xff)
|
|
47
|
+
block1[salt.length + 3] = (i >> 0 & 0xff)
|
|
48
|
+
|
|
49
|
+
// Use BSV's browser-compatible HMAC instead of Node.js crypto
|
|
50
|
+
U = Hash.sha512hmac(block1, key)
|
|
51
|
+
U.copy(T, 0, 0, hLen)
|
|
52
|
+
|
|
53
|
+
for (var j = 1; j < iterations; j++) {
|
|
54
|
+
U = Hash.sha512hmac(U, key)
|
|
55
|
+
|
|
56
|
+
for (var k = 0; k < hLen; k++) {
|
|
57
|
+
T[k] ^= U[k]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var destPos = (i - 1) * hLen
|
|
62
|
+
var len = (i === l ? r : hLen)
|
|
63
|
+
T.copy(DK, destPos, 0, len)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return DK
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = pbkdf2
|
package/lib/mnemonic/pbkdf2.js
CHANGED
|
@@ -1,68 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var crypto = require('crypto')
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* PDKBF2
|
|
7
|
-
* Credit to: https://github.com/stayradiated/pbkdf2-sha512
|
|
8
|
-
* Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved
|
|
9
|
-
*/
|
|
10
|
-
function pbkdf2 (key, salt, iterations, dkLen) {
|
|
11
|
-
var hLen = 64 // SHA512 Mac length
|
|
12
|
-
if (dkLen > (Math.pow(2, 32) - 1) * hLen) {
|
|
13
|
-
throw Error('Requested key length too long')
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (typeof key !== 'string' && !Buffer.isBuffer(key)) {
|
|
17
|
-
throw new TypeError('key must a string or Buffer')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (typeof salt !== 'string' && !Buffer.isBuffer(salt)) {
|
|
21
|
-
throw new TypeError('salt must a string or Buffer')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (typeof key === 'string') {
|
|
25
|
-
key = Buffer.from(key)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (typeof salt === 'string') {
|
|
29
|
-
salt = Buffer.from(salt)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
var DK = Buffer.alloc(dkLen)
|
|
33
|
-
|
|
34
|
-
var U = Buffer.alloc(hLen)
|
|
35
|
-
var T = Buffer.alloc(hLen)
|
|
36
|
-
var block1 = Buffer.alloc(salt.length + 4)
|
|
37
|
-
|
|
38
|
-
var l = Math.ceil(dkLen / hLen)
|
|
39
|
-
var r = dkLen - (l - 1) * hLen
|
|
40
|
-
|
|
41
|
-
salt.copy(block1, 0, 0, salt.length)
|
|
42
|
-
for (var i = 1; i <= l; i++) {
|
|
43
|
-
block1[salt.length + 0] = (i >> 24 & 0xff)
|
|
44
|
-
block1[salt.length + 1] = (i >> 16 & 0xff)
|
|
45
|
-
block1[salt.length + 2] = (i >> 8 & 0xff)
|
|
46
|
-
block1[salt.length + 3] = (i >> 0 & 0xff)
|
|
47
|
-
|
|
48
|
-
U = crypto.createHmac('sha512', key).update(block1).digest()
|
|
49
|
-
|
|
50
|
-
U.copy(T, 0, 0, hLen)
|
|
51
|
-
|
|
52
|
-
for (var j = 1; j < iterations; j++) {
|
|
53
|
-
U = crypto.createHmac('sha512', key).update(U).digest()
|
|
54
|
-
|
|
55
|
-
for (var k = 0; k < hLen; k++) {
|
|
56
|
-
T[k] ^= U[k]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
var destPos = (i - 1) * hLen
|
|
61
|
-
var len = (i === l ? r : hLen)
|
|
62
|
-
T.copy(DK, destPos, 0, len)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return DK
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
module.exports = pbkdf2
|
|
1
|
+
if (process.browser) module.exports = require('./pbkdf2.browser')
|
|
2
|
+
else module.exports = require('./pbkdf2.node')
|