@smartledger/bsv 3.4.3 → 3.4.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +367 -0
  2. package/README.md +72 -72
  3. package/SECURITY.md +88 -0
  4. package/bin/cli.js +13 -8
  5. package/bsv-covenant.min.js +4 -4
  6. package/bsv-gdaf.min.js +5 -5
  7. package/bsv-ltp.min.js +7 -7
  8. package/bsv-smartcontract.min.js +5 -5
  9. package/bsv.bundle.js +5 -5
  10. package/bsv.d.ts +486 -9
  11. package/bsv.min.js +5 -5
  12. package/docs/COVENANT_DEVELOPMENT_RESOLVED.md +2 -2
  13. package/docs/MODULE_REFERENCE_COMPLETE.md +60 -57
  14. package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
  15. package/docs/getting-started/INSTALLATION.md +30 -30
  16. package/docs/getting-started/QUICK_START.md +14 -14
  17. package/docs/migration/FROM_BSV_1_5_6.md +5 -5
  18. package/gdaf-entry.js +1 -2
  19. package/index.js +20 -7
  20. package/lib/script/script.js +19 -0
  21. package/lib/smart_contract/covenant.js +10 -1
  22. package/lib/smartutxo.js +20 -12
  23. package/lib/transaction/input/publickeyhash.js +6 -1
  24. package/lib/transaction/transaction.js +12 -1
  25. package/ltp-entry.js +1 -2
  26. package/package.json +3 -3
  27. package/utilities/blockchain-state.js +32 -23
  28. package/demos/README.md +0 -188
  29. package/demos/architecture_demo.js +0 -247
  30. package/demos/browser-test.html +0 -1208
  31. package/demos/bsv_wallet_demo.js +0 -242
  32. package/demos/complete_ltp_demo.js +0 -511
  33. package/demos/debug_tools_demo.js +0 -87
  34. package/demos/demo_features.js +0 -123
  35. package/demos/easy_interface_demo.js +0 -109
  36. package/demos/ecies_demo.js +0 -182
  37. package/demos/gdaf_demo.js +0 -237
  38. package/demos/ltp_demo.js +0 -361
  39. package/demos/ltp_primitives_demo.js +0 -403
  40. package/demos/message_demo.js +0 -209
  41. package/demos/preimage_separation_demo.js +0 -383
  42. package/demos/script_helper_demo.js +0 -289
  43. package/demos/security_demo.js +0 -287
  44. package/demos/shamir_demo.js +0 -121
  45. package/demos/simple_demo.js +0 -204
  46. package/demos/simple_p2pkh_demo.js +0 -169
  47. package/demos/simple_utxo_preimage_demo.js +0 -196
  48. package/demos/smart_contract_demo.html +0 -1347
  49. package/demos/smart_contract_demo.js +0 -910
  50. package/demos/utxo_generator_demo.js +0 -244
  51. package/demos/validation_pipeline_demo.js +0 -155
  52. package/demos/web3keys.html +0 -740
  53. package/examples/README.md +0 -200
  54. package/examples/basic/transaction-creation.js +0 -534
  55. package/examples/basic/transaction_signature_api_gap.js +0 -178
  56. package/examples/complete_workflow_demo.js +0 -783
  57. package/examples/covenants/advanced_covenant_demo.js +0 -219
  58. package/examples/covenants/covenant_interface_demo.js +0 -270
  59. package/examples/covenants/covenant_manual_signature_resolved.js +0 -212
  60. package/examples/covenants/covenant_signature_template.js +0 -117
  61. package/examples/covenants2/covenant_bidirectional_example.js +0 -262
  62. package/examples/covenants2/covenant_utils_demo.js +0 -120
  63. package/examples/covenants2/preimage_covenant_utils.js +0 -287
  64. package/examples/covenants2/production_integration.js +0 -256
  65. package/examples/data/covenant_utxos.json +0 -28
  66. package/examples/data/utxos.json +0 -26
  67. package/examples/definitive_working_demo.js +0 -261
  68. package/examples/final_working_contracts.js +0 -338
  69. package/examples/legacy/README.md +0 -11
  70. package/examples/legacy/smart_contract_test_integration.js +0 -269
  71. package/examples/legacy/test_builtin_verify.js +0 -117
  72. package/examples/legacy/test_debug_integration.js +0 -71
  73. package/examples/legacy/test_ecdsa_little.js +0 -70
  74. package/examples/legacy/test_shamir.js +0 -221
  75. package/examples/legacy/test_smartverify_der.js +0 -110
  76. package/examples/preimage/README.md +0 -178
  77. package/examples/preimage/extract_preimage_bidirectional.js +0 -421
  78. package/examples/preimage/generate_sample_preimage.js +0 -208
  79. package/examples/preimage/generate_sighash_examples.js +0 -152
  80. package/examples/preimage/parse_preimage.js +0 -117
  81. package/examples/preimage/test_preimage_extractor.js +0 -53
  82. package/examples/preimage/test_varint_extraction.js +0 -95
  83. package/examples/scripts/custom_script_helper_example.js +0 -273
  84. package/examples/scripts/script_interpreter.js +0 -193
  85. package/examples/smart_contract/complete_workflow_demo.js +0 -343
  86. package/examples/smart_contract/covenant_builder_demo.js +0 -176
  87. package/examples/smart_contract/script_testing_integration.js +0 -198
  88. package/examples/smart_contract_templates.js +0 -718
  89. package/examples/working_smart_contracts.js +0 -348
@@ -1,740 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Web3Keys Playground – All-in-One SPA (No Server)</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <!-- SmartLedger BSV Security-Hardened Library - Local Fixed v3.3.4 -->
9
- <script src="../bsv.min.js"></script>
10
- <script src="../bsv-mnemonic.min.js"></script>
11
- <script>
12
- tailwind.config = {
13
- theme: {
14
- extend: {
15
- colors: {
16
- primary: '#6B46C1',
17
- primaryDark: '#4C1D95',
18
- primaryLight: '#8B5CF6',
19
- accent: '#A855F7',
20
- }
21
- }
22
- }
23
- }
24
- </script>
25
- <style>
26
- .mono{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}
27
- .mask { -webkit-text-security: disc; text-security: disc; }
28
- </style>
29
- </head>
30
- <body class="bg-gradient-to-br from-purple-100 via-purple-50 to-indigo-100 min-h-screen">
31
- <main class="max-w-5xl mx-auto py-8 px-4">
32
- <div class="bg-white rounded-2xl shadow-xl border border-purple-200 overflow-hidden">
33
- <!-- Header -->
34
- <header class="bg-gradient-to-r from-primaryDark to-primary p-6 text-white">
35
- <div class="flex items-center justify-between">
36
- <h1 class="text-2xl md:text-3xl font-bold flex items-center">
37
- <svg class="w-8 h-8 mr-3 text-primaryLight" fill="none" stroke="currentColor" viewBox="0 0 24 24">
38
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
39
- </svg>
40
- Web3Keys Playground
41
- </h1>
42
- <div class="text-sm opacity-90">Pure browser demo • No server • Local-only crypto</div>
43
- </div>
44
- <p class="mt-2 text-purple-200">Explore SmartLedger BSV (security-hardened): generate/import mnemonics, derive keys, sign/verify, hash data, and encrypt/decrypt — all client-side with zero vulnerabilities.</p>
45
- </header>
46
-
47
- <!-- Status -->
48
- <div id="status" class="m-4 text-sm p-3 rounded-lg border bg-purple-50 text-purple-800 border-purple-200" aria-live="polite"></div>
49
-
50
- <!-- Nav -->
51
- <nav class="px-4 pb-4 border-b border-purple-200">
52
- <div class="flex flex-wrap gap-2">
53
- <button data-tab="keys" class="tab px-3 py-2 rounded-lg bg-primary text-white">1) Keys</button>
54
- <button data-tab="derive" class="tab px-3 py-2 rounded-lg hover:bg-purple-50">2) Derive</button>
55
- <button data-tab="sign" class="tab px-3 py-2 rounded-lg hover:bg-purple-50">3) Sign / Verify</button>
56
- <button data-tab="encrypt" class="tab px-3 py-2 rounded-lg hover:bg-purple-50">4) Encrypt / Decrypt</button>
57
- <button data-tab="hash" class="tab px-3 py-2 rounded-lg hover:bg-purple-50">5) Hash</button>
58
- <button data-tab="backup" class="tab px-3 py-2 rounded-lg hover:bg-purple-50">6) Backup / Restore</button>
59
- <button data-tab="debug" class="tab px-3 py-2 rounded-lg hover:bg-purple-50">Debug</button>
60
- </div>
61
- </nav>
62
-
63
- <section class="p-6 space-y-8">
64
- <!-- KEYS -->
65
- <div id="tab-keys" class="tab-panel space-y-4">
66
- <h2 class="text-xl font-semibold text-primaryDark">Step 1: Generate or Import Keys</h2>
67
- <div class="grid md:grid-cols-2 gap-4">
68
- <div class="space-y-3">
69
- <button id="gen-mnemonic" class="w-full px-4 py-3 bg-gradient-to-r from-primary to-primaryDark text-white rounded-lg shadow hover:from-primaryDark hover:to-primary">Generate 24‑word mnemonic (256-bit)</button>
70
- <div id="mnemonic-display" class="hidden space-y-2">
71
- <label class="text-sm font-medium text-green-700">Generated/Imported Mnemonic (save securely!):</label>
72
- <textarea id="mnemonic-out" class="w-full p-3 border-2 border-yellow-300 bg-yellow-50 rounded-lg mono text-sm" rows="3" readonly></textarea>
73
- </div>
74
- </div>
75
- <div class="space-y-3">
76
- <button id="toggle-import" class="w-full px-4 py-3 bg-purple-100 text-primaryDark rounded-lg border border-purple-300 hover:bg-purple-200">Import existing mnemonic</button>
77
- <div id="import-wrap" class="hidden space-y-2">
78
- <textarea id="mnemonic-in" rows="3" placeholder="Enter your 12/24-word mnemonic" class="w-full p-3 border-2 border-purple-300 rounded-lg"></textarea>
79
- <button id="import-mnemonic" class="px-4 py-2 bg-primary text-white rounded-lg">Import</button>
80
- </div>
81
- </div>
82
- </div>
83
-
84
- <div class="grid md:grid-cols-2 gap-4">
85
- <div class="space-y-2">
86
- <label class="text-sm font-medium">Password (used only locally to encrypt secrets)</label>
87
- <input id="password" type="password" class="w-full p-3 border-2 border-purple-300 rounded-lg" placeholder="Strong password" />
88
- </div>
89
- <div class="space-y-2">
90
- <label class="text-sm font-medium">Identity Derivation Path</label>
91
- <input id="id-path" value="m/44'/236'/0'/0/0" class="w-full p-3 border-2 border-purple-300 rounded-lg mono" />
92
- </div>
93
- </div>
94
-
95
- <div class="grid md:grid-cols-3 gap-4">
96
- <button id="derive-identity" class="px-4 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700">Derive Identity</button>
97
- <button id="lock-secrets" class="px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Encrypt & store locally</button>
98
- <button id="clear-state" class="px-4 py-3 bg-red-100 text-red-700 rounded-lg border border-red-300 hover:bg-red-200">Reset All</button>
99
- </div>
100
-
101
- <div id="identity-box" class="hidden bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4 space-y-2">
102
- <div class="text-sm">Public Key (hex)</div>
103
- <div id="pubkey" class="mono text-xs break-all"></div>
104
- <div class="text-sm mt-2">Address (BSV)</div>
105
- <div id="address" class="mono text-xs break-all"></div>
106
- <details class="text-xs mt-2">
107
- <summary class="cursor-pointer">Sensitive (WIF / PrivateKey) – click to reveal</summary>
108
- <div class="mt-2 space-y-1">
109
- <div class="text-xs">Private Key (WIF)</div>
110
- <div id="wif" class="mono text-xs break-all"></div>
111
- </div>
112
- </details>
113
- </div>
114
- </div>
115
-
116
- <!-- DERIVE -->
117
- <div id="tab-derive" class="tab-panel hidden space-y-4">
118
- <h2 class="text-xl font-semibold text-primaryDark">Derivation Playground</h2>
119
- <div class="grid md:grid-cols-3 gap-4">
120
- <div class="space-y-2 md:col-span-2">
121
- <label class="text-sm font-medium">Derivation Path</label>
122
- <input id="path-input" value="m/44'/236'/0'/0/0" class="w-full p-3 border-2 border-purple-300 rounded-lg mono" />
123
- </div>
124
- <div class="flex items-end">
125
- <button id="derive-path" class="w-full px-4 py-3 bg-primary text-white rounded-lg">Derive</button>
126
- </div>
127
- </div>
128
- <div class="grid md:grid-cols-2 gap-4">
129
- <div>
130
- <div class="text-sm">Public Key (hex)</div>
131
- <div id="derive-pub" class="mono text-xs break-all p-3 bg-white border rounded"></div>
132
- </div>
133
- <div>
134
- <div class="text-sm">Address (BSV)</div>
135
- <div id="derive-addr" class="mono text-xs break-all p-3 bg-white border rounded"></div>
136
- </div>
137
- </div>
138
- </div>
139
-
140
- <!-- SIGN / VERIFY -->
141
- <div id="tab-sign" class="tab-panel hidden space-y-4">
142
- <h2 class="text-xl font-semibold text-primaryDark">Message Signing & Verification</h2>
143
- <div class="space-y-2">
144
- <label class="text-sm font-medium">Message</label>
145
- <textarea id="msg" rows="3" class="w-full p-3 border-2 border-purple-300 rounded-lg" placeholder="Hello, Web3Keys!"></textarea>
146
- </div>
147
- <div class="grid md:grid-cols-3 gap-4">
148
- <div class="space-y-2">
149
- <label class="text-sm font-medium">Use WIF (optional). Leave blank to sign with current identity.</label>
150
- <input id="sign-wif" class="w-full p-3 border-2 border-purple-300 rounded-lg" placeholder="WIF for signing (optional)" />
151
- </div>
152
- <div class="flex items-end"><button id="sign-msg" class="w-full px-4 py-3 bg-emerald-600 text-white rounded-lg">Sign</button></div>
153
- <div class="flex items-end"><button id="verify-msg" class="w-full px-4 py-3 bg-blue-600 text-white rounded-lg">Verify</button></div>
154
- </div>
155
- <div class="grid md:grid-cols-2 gap-4">
156
- <div>
157
- <div class="text-sm">Signature (base64)</div>
158
- <textarea id="sig" class="w-full p-3 border rounded mono text-xs" rows="3" readonly></textarea>
159
- </div>
160
- <div class="space-y-2">
161
- <label class="text-sm font-medium">Public Key (hex) for verification (optional). Blank → use identity.</label>
162
- <textarea id="verify-pub" class="w-full p-3 border rounded mono text-xs" rows="3" placeholder="Hex public key"></textarea>
163
- <div id="verify-result" class="text-sm"></div>
164
- </div>
165
- </div>
166
- </div>
167
-
168
- <!-- ENCRYPT / DECRYPT -->
169
- <div id="tab-encrypt" class="tab-panel hidden space-y-4">
170
- <h2 class="text-xl font-semibold text-primaryDark">Encrypt / Decrypt (Password-based)</h2>
171
- <div class="grid md:grid-cols-2 gap-4">
172
- <div class="space-y-2">
173
- <label class="text-sm font-medium">Plaintext JSON</label>
174
- <textarea id="plain" rows="6" class="w-full p-3 border-2 border-purple-300 rounded-lg mono">{"hello":"world"}</textarea>
175
- <button id="do-encrypt" class="px-4 py-2 bg-primary text-white rounded">Encrypt with password</button>
176
- </div>
177
- <div class="space-y-2">
178
- <label class="text-sm font-medium">Ciphertext (paste here to decrypt)</label>
179
- <textarea id="cipher" rows="6" class="w-full p-3 border-2 border-purple-300 rounded-lg mono"></textarea>
180
- <button id="do-decrypt" class="px-4 py-2 bg-primaryDark text-white rounded">Decrypt with password</button>
181
- </div>
182
- </div>
183
- <div>
184
- <label class="text-sm font-medium">Result</label>
185
- <pre id="enc-result" class="p-3 bg-gray-50 border rounded mono text-xs overflow-auto"></pre>
186
- </div>
187
- </div>
188
-
189
- <!-- HASH -->
190
- <div id="tab-hash" class="tab-panel hidden space-y-4">
191
- <h2 class="text-xl font-semibold text-primaryDark">Hash Utilities</h2>
192
- <div class="grid md:grid-cols-2 gap-4">
193
- <div class="space-y-2">
194
- <label class="text-sm font-medium">Input (string)</label>
195
- <textarea id="hash-input" rows="4" class="w-full p-3 border-2 border-purple-300 rounded-lg" placeholder="Any text…"></textarea>
196
- <div class="flex flex-wrap gap-2">
197
- <button data-algo="SHA256" class="hash-btn px-3 py-2 bg-primary text-white rounded">SHA-256</button>
198
- <button data-algo="SHA512" class="hash-btn px-3 py-2 bg-primaryDark text-white rounded">SHA-512</button>
199
- <button data-algo="RIPEMD160" class="hash-btn px-3 py-2 bg-purple-200 rounded">RIPEMD-160</button>
200
- </div>
201
- </div>
202
- <div class="space-y-2">
203
- <label class="text-sm font-medium">Output (hex)</label>
204
- <textarea id="hash-out" rows="6" class="w-full p-3 border rounded mono text-xs" readonly></textarea>
205
- </div>
206
- </div>
207
- </div>
208
-
209
- <!-- BACKUP / RESTORE -->
210
- <div id="tab-backup" class="tab-panel hidden space-y-4">
211
- <h2 class="text-xl font-semibold text-primaryDark">Backup / Restore (Local)</h2>
212
- <p class="text-sm text-gray-700">Create an encrypted JSON bundle containing your mnemonic and current identity WIF. Keep your password safe; it cannot be recovered.</p>
213
- <div class="flex flex-wrap gap-3">
214
- <button id="export-backup" class="px-4 py-2 bg-emerald-600 text-white rounded">Export encrypted backup</button>
215
- <label class="px-4 py-2 bg-blue-600 text-white rounded cursor-pointer">
216
- Import backup JSON<input id="import-backup" type="file" accept="application/json" class="hidden" />
217
- </label>
218
- </div>
219
- <div class="space-y-2">
220
- <label class="text-sm font-medium">Preview</label>
221
- <pre id="backup-preview" class="p-3 bg-gray-50 border rounded mono text-xs overflow-auto"></pre>
222
- </div>
223
- </div>
224
-
225
- <!-- DEBUG -->
226
- <div id="tab-debug" class="tab-panel hidden space-y-4">
227
- <h2 class="text-xl font-semibold text-primaryDark">Debug</h2>
228
- <div class="space-y-4">
229
- <div class="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
230
- <h3 class="font-semibold text-yellow-800">CDN createHmac Issue Information</h3>
231
- <p class="text-sm text-yellow-700 mt-2">If you're seeing "createHmac is not a function" errors, this is because the CDN version uses Node.js crypto modules that don't exist in browsers.</p>
232
- <p class="text-sm text-yellow-700 mt-1"><strong>Solution:</strong> The CDN bundles need browser-compatible PBKDF2 implementation using BSV's crypto modules instead of Node.js crypto.</p>
233
- </div>
234
- <div class="grid md:grid-cols-2 gap-4">
235
- <div>
236
- <div class="text-sm">Local Storage (truncated)</div>
237
- <pre id="ls-state" class="p-3 bg-gray-50 border rounded mono text-xs"></pre>
238
- </div>
239
- <div>
240
- <div class="text-sm">SDK Info</div>
241
- <pre id="sdk-info" class="p-3 bg-gray-50 border rounded mono text-xs"></pre>
242
- </div>
243
- </div>
244
- <div>
245
- <div class="text-sm">Crypto Availability Test</div>
246
- <button id="test-crypto" class="px-3 py-2 bg-blue-600 text-white rounded">Test Crypto Functions</button>
247
- <pre id="crypto-test" class="p-3 bg-gray-50 border rounded mono text-xs mt-2"></pre>
248
- </div>
249
- </div>
250
- </div>
251
- </section>
252
-
253
- <footer class="px-6 py-5 bg-gradient-to-r from-primaryDark to-primary text-white flex flex-col md:flex-row md:items-center md:justify-between gap-2">
254
- <div class="text-sm opacity-90">Powered by <a href="https://smartledger.technology" class="underline hover:text-primaryLight">SmartLedger Technology</a> BSV v3.3.4 • Security-Hardened</div>
255
- <div class="text-sm">Client-side only • No network calls • Zero vulnerabilities</div>
256
- </footer>
257
- </div>
258
- </main>
259
-
260
- <script>
261
- // Define Buffer using BSV's implementation for cross-compatibility
262
- const Buffer = bsv.deps.Buffer;
263
- const bsvHash = bsv.crypto.Hash;
264
- console.log('bsvHash:', bsvHash);
265
- console.log('Buffer:', Buffer);
266
- // ---------- Helpers ----------
267
- const $ = (id) => document.getElementById(id);
268
- const qs = (sel) => document.querySelector(sel);
269
- const qsa = (sel) => Array.from(document.querySelectorAll(sel));
270
-
271
- const setStatus = (msg, good=true) => {
272
- const el = $('status');
273
- el.textContent = msg || '';
274
- el.className = 'm-4 text-sm p-3 rounded-lg border ' + (good ? 'bg-green-50 text-green-700 border-green-200' : 'bg-red-50 text-red-700 border-red-200');
275
- if (msg) setTimeout(()=> setStatus(''), 5000);
276
- };
277
-
278
- const canonicalStringify = (obj) => JSON.stringify(sortKeysDeep(obj));
279
- function sortKeysDeep(v){
280
- if (Array.isArray(v)) return v.map(sortKeysDeep);
281
- if (v && typeof v === 'object') return Object.keys(v).sort().reduce((a,k)=> (a[k]=sortKeysDeep(v[k]), a),{});
282
- return v;
283
- }
284
-
285
- // ---------- SDK Convenience ----------
286
- const SL = {
287
- genMnemonic: () => {
288
- // Generate 24-word mnemonic using BSV Mnemonic module
289
- const MnemonicClass = window.bsvMnemonic || window.Mnemonic || (window.bsv && window.bsv.Mnemonic);
290
- if (!MnemonicClass) throw new Error('Mnemonic module not loaded');
291
- // Use 256 bits of entropy for 24 words (default is 128 bits for 12 words)
292
- return MnemonicClass.fromRandom(256).phrase;
293
- },
294
- validateMnemonic: (m) => {
295
- try {
296
- const MnemonicClass = window.bsvMnemonic || window.Mnemonic || (window.bsv && window.bsv.Mnemonic);
297
- if (!MnemonicClass) return false;
298
- new MnemonicClass(m);
299
- return true;
300
- } catch {
301
- return false;
302
- }
303
- },
304
- derivePath: (m, path) => {
305
- // Derive key from mnemonic and path
306
- const MnemonicClass = window.bsvMnemonic || window.Mnemonic || (window.bsv && window.bsv.Mnemonic);
307
- if (!MnemonicClass) throw new Error('Mnemonic module not loaded');
308
-
309
- const mnemonic = new MnemonicClass(m);
310
- const hdPrivateKey = bsv.HDPrivateKey.fromSeed(mnemonic.toSeed());
311
- const derived = hdPrivateKey.deriveChild(path);
312
- return {
313
- wif: derived.privateKey.toWIF(),
314
- publicKey: derived.privateKey.toPublicKey().toString('hex'),
315
- address: derived.privateKey.toAddress().toString()
316
- };
317
- },
318
- encrypt: async (data, password) => {
319
- // Simple password-based encryption using Web Crypto API
320
- const encoder = new TextEncoder();
321
- const decoder = new TextDecoder();
322
- const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey']);
323
- const salt = crypto.getRandomValues(new Uint8Array(16));
324
- const key = await crypto.subtle.deriveKey(
325
- { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' },
326
- keyMaterial,
327
- { name: 'AES-GCM', length: 256 },
328
- false,
329
- ['encrypt']
330
- );
331
- const iv = crypto.getRandomValues(new Uint8Array(12));
332
- const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, encoder.encode(data));
333
- const result = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
334
- result.set(salt, 0);
335
- result.set(iv, salt.length);
336
- result.set(new Uint8Array(encrypted), salt.length + iv.length);
337
- return btoa(String.fromCharCode(...result));
338
- },
339
- decrypt: async (encData, password) => {
340
- // Decrypt using Web Crypto API
341
- const encoder = new TextEncoder();
342
- const decoder = new TextDecoder();
343
- const data = new Uint8Array(atob(encData).split('').map(c => c.charCodeAt(0)));
344
- const salt = data.slice(0, 16);
345
- const iv = data.slice(16, 28);
346
- const encrypted = data.slice(28);
347
- const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey']);
348
- const key = await crypto.subtle.deriveKey(
349
- { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' },
350
- keyMaterial,
351
- { name: 'AES-GCM', length: 256 },
352
- false,
353
- ['decrypt']
354
- );
355
- const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, encrypted);
356
- return decoder.decode(decrypted);
357
- },
358
- hash: async (data, algo) => {
359
- // Use BSV crypto functions directly since they're available
360
- if (window.bsv && bsv.crypto && bsv.crypto.Hash) {
361
- try {
362
- // Create a proper buffer using BSV's Buffer (defined at top of script)
363
- const buffer = Buffer.from(data, 'utf8');
364
-
365
- let hashResult;
366
- switch(algo) {
367
- case 'SHA256':
368
- hashResult = bsv.crypto.Hash.sha256(buffer);
369
- break;
370
- case 'SHA512':
371
- hashResult = bsv.crypto.Hash.sha512(buffer);
372
- break;
373
- case 'RIPEMD160':
374
- hashResult = bsv.crypto.Hash.ripemd160(buffer);
375
- break;
376
- default:
377
- throw new Error('Unsupported hash algorithm: ' + algo);
378
- }
379
-
380
- // Convert result to hex string
381
- if (hashResult && typeof hashResult.toString === 'function') {
382
- return hashResult.toString('hex');
383
- } else if (hashResult && hashResult.length) {
384
- // Handle array-like results
385
- return Array.from(hashResult).map(b => b.toString(16).padStart(2, '0')).join('');
386
- } else {
387
- throw new Error('Unexpected hash result format');
388
- }
389
-
390
- } catch (e) {
391
- console.error('BSV hash failed:', e);
392
- throw new Error(`${algo} hashing failed: ${e.message}`);
393
- }
394
- } else {
395
- // Fallback to Web Crypto API for SHA algorithms only
396
- const encoder = new TextEncoder();
397
- const dataBuffer = encoder.encode(data);
398
-
399
- switch(algo) {
400
- case 'SHA256': {
401
- const hash = await crypto.subtle.digest('SHA-256', dataBuffer);
402
- return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
403
- }
404
- case 'SHA512': {
405
- const hash = await crypto.subtle.digest('SHA-512', dataBuffer);
406
- return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
407
- }
408
- case 'RIPEMD160':
409
- throw new Error('RIPEMD160 not supported - BSV library not available');
410
- default:
411
- throw new Error('Unsupported hash algorithm: ' + algo);
412
- }
413
- }
414
- },
415
- signMessage: async (msg, wif) => {
416
- const privateKey = bsv.PrivateKey.fromWIF(wif);
417
- return bsv.Message(msg).sign(privateKey);
418
- },
419
- verifySignature: async (msg, sig, pubHex) => {
420
- try {
421
- const publicKey = bsv.PublicKey.fromString(pubHex);
422
- const address = bsv.Address.fromPublicKey(publicKey);
423
- return bsv.Message(msg).verify(address, sig);
424
- } catch {
425
- return false;
426
- }
427
- },
428
- bsv: () => window.bsv,
429
- };
430
-
431
- // ---------- State ----------
432
- const LS = {
433
- encMnemonic: 'w3k_encMnemonic',
434
- encWif: 'w3k_encWif',
435
- pubKey: 'w3k_pubKey',
436
- addr: 'w3k_address',
437
- idPath: 'w3k_idPath'
438
- };
439
-
440
- let mnemonic = null;
441
- let identity = null; // { wif, publicKey, address }
442
-
443
- function saveState(){
444
- if (identity?.publicKey) localStorage.setItem(LS.pubKey, identity.publicKey);
445
- if (identity?.address) localStorage.setItem(LS.addr, identity.address);
446
- if ($('id-path').value) localStorage.setItem(LS.idPath, $('id-path').value);
447
- }
448
- function refreshDebug(){
449
- const obj = {};
450
- Object.values(LS).forEach(k=>{
451
- const v = localStorage.getItem(k);
452
- if (v) obj[k] = v.slice(0,64) + (v.length>64?'…':'');
453
- });
454
- $('ls-state').textContent = JSON.stringify(obj, null, 2);
455
- $('sdk-info').textContent = JSON.stringify({
456
- bsvPresent: !!window.bsv,
457
- hasMessage: !!(window.bsv && bsv.Message),
458
- hasMnemonic: !!(window.bsvMnemonic || window.Mnemonic || (window.bsv && bsv.Mnemonic)),
459
- hasSmartLedger: !!(window.bsv && bsv.SmartLedger),
460
- mnemonicModule: window.bsvMnemonic ? 'bsvMnemonic' : window.Mnemonic ? 'Mnemonic' : 'bsv.Mnemonic',
461
- mnemonicAvailable: {
462
- bsvMnemonic: !!window.bsvMnemonic,
463
- globalMnemonic: !!window.Mnemonic,
464
- bsvInternal: !!(window.bsv && bsv.Mnemonic)
465
- },
466
- version: window.bsv ? `SmartLedger BSV v${bsv.version || '3.3.4'}` : 'SmartLedger BSV v3.3.4'
467
- }, null, 2);
468
- }
469
-
470
- function showTab(name){
471
- qsa('.tab').forEach(b=> b.classList.remove('bg-primary','text-white'));
472
- qsa('.tab').find(b=> b.dataset.tab===name)?.classList.add('bg-primary','text-white');
473
- qsa('.tab-panel').forEach(p=> p.classList.add('hidden'));
474
- $('tab-'+name).classList.remove('hidden');
475
- }
476
-
477
- // ---------- Key / Identity ops ----------
478
- async function deriveIdentity(){
479
- if (!mnemonic) throw new Error('Generate or import a mnemonic first.');
480
- const path = $('id-path').value.trim();
481
- const k = await SL.derivePath(mnemonic, path);
482
- const bsv = SL.bsv();
483
- const priv = bsv.PrivateKey.fromWIF(k.wif);
484
- const pubHex = priv.toPublicKey().toString('hex');
485
- const address = bsv.Address.fromPrivateKey(priv).toString();
486
- identity = { wif: k.wif, publicKey: pubHex, address };
487
- $('pubkey').textContent = pubHex;
488
- $('address').textContent = address;
489
- $('wif').textContent = k.wif;
490
- $('identity-box').classList.remove('hidden');
491
- setStatus('Identity derived.');
492
- saveState();
493
- }
494
-
495
- // ---------- Event wiring ----------
496
- document.addEventListener('DOMContentLoaded', () => {
497
- // Tabs
498
- qsa('.tab').forEach(b=> b.addEventListener('click', ()=> showTab(b.dataset.tab)));
499
-
500
- // Restore path
501
- const savedPath = localStorage.getItem(LS.idPath);
502
- if (savedPath) $('id-path').value = savedPath;
503
-
504
- // Keys
505
- $('gen-mnemonic').onclick = async () => {
506
- mnemonic = await SL.genMnemonic();
507
- $('mnemonic-out').value = mnemonic;
508
- $('mnemonic-display').classList.remove('hidden');
509
- setStatus('Mnemonic generated – write it down and store offline.');
510
- };
511
- $('toggle-import').onclick = () => $('import-wrap').classList.toggle('hidden');
512
- $('import-mnemonic').onclick = () => {
513
- const m = $('mnemonic-in').value.trim();
514
- if (!SL.validateMnemonic(m)) return setStatus('Invalid mnemonic', false);
515
- mnemonic = m;
516
- $('mnemonic-out').value = mnemonic;
517
- $('mnemonic-display').classList.remove('hidden');
518
- $('import-wrap').classList.add('hidden');
519
- setStatus('Mnemonic imported.');
520
- };
521
-
522
- $('derive-identity').onclick = async () => {
523
- try { await deriveIdentity(); } catch(e){ setStatus(e.message, false); }
524
- };
525
-
526
- $('lock-secrets').onclick = async () => {
527
- try {
528
- const pw = $('password').value;
529
- if (!pw) return setStatus('Enter a password first.', false);
530
- if (!mnemonic) return setStatus('Generate/import mnemonic first.', false);
531
- if (!identity?.wif) await deriveIdentity();
532
- const encMnemonic = await SL.encrypt(mnemonic, pw);
533
- const encWif = await SL.encrypt(identity.wif, pw);
534
- localStorage.setItem(LS.encMnemonic, encMnemonic);
535
- localStorage.setItem(LS.encWif, encWif);
536
- saveState();
537
- refreshDebug();
538
- setStatus('Secrets encrypted and stored locally.');
539
- } catch(e){ setStatus(e.message||'Encrypt/store failed', false); }
540
- };
541
-
542
- $('clear-state').onclick = () => {
543
- mnemonic = null; identity = null;
544
- Object.values(LS).forEach(k=> localStorage.removeItem(k));
545
- $('mnemonic-out').value=''; $('wif').textContent=''; $('pubkey').textContent=''; $('address').textContent='';
546
- $('identity-box').classList.add('hidden');
547
- refreshDebug();
548
- setStatus('All local state cleared.');
549
- };
550
-
551
- // Derivation playground
552
- $('derive-path').onclick = async () => {
553
- try {
554
- if (!mnemonic) return setStatus('Generate/import mnemonic first.', false);
555
- const path = $('path-input').value.trim();
556
- const k = await SL.derivePath(mnemonic, path);
557
- const bsv = SL.bsv();
558
- const addr = bsv.Address.fromPrivateKey(bsv.PrivateKey.fromWIF(k.wif)).toString();
559
- $('derive-pub').textContent = bsv.PrivateKey.fromWIF(k.wif).toPublicKey().toString('hex');
560
- $('derive-addr').textContent = addr;
561
- setStatus('Path derived.');
562
- } catch(e){ setStatus(e.message, false); }
563
- };
564
-
565
- // Sign/verify
566
- $('sign-msg').onclick = async () => {
567
- try {
568
- const msg = $('msg').value;
569
- if (!msg) return setStatus('Enter a message to sign.', false);
570
- let wif = $('sign-wif').value.trim();
571
- if (!wif){
572
- if (!identity?.wif) await deriveIdentity();
573
- wif = identity.wif;
574
- }
575
- const sig = await SL.signMessage(msg, wif);
576
- $('sig').value = sig;
577
- setStatus('Message signed.');
578
- } catch(e){ setStatus(e.message, false); }
579
- };
580
-
581
- $('verify-msg').onclick = async () => {
582
- try {
583
- const msg = $('msg').value;
584
- const sig = $('sig').value.trim();
585
- let pub = $('verify-pub').value.trim();
586
- if (!pub){
587
- if (!identity?.publicKey) await deriveIdentity();
588
- pub = identity.publicKey;
589
- }
590
- const ok = await SL.verifySignature(msg, sig, pub);
591
- $('verify-result').textContent = ok ? '✅ Valid signature' : '❌ Invalid signature';
592
- $('verify-result').className = ok ? 'text-green-700' : 'text-red-700';
593
- setStatus('Verification complete.');
594
- } catch(e){ setStatus(e.message, false); }
595
- };
596
-
597
- // Encrypt / Decrypt
598
- $('do-encrypt').onclick = async () => {
599
- try {
600
- const pw = $('password').value;
601
- if (!pw) return setStatus('Enter password (top of page) to encrypt.', false);
602
- const json = $('plain').value;
603
- const enc = await SL.encrypt(json, pw);
604
- $('cipher').value = enc;
605
- $('enc-result').textContent = 'Encrypted.';
606
- setStatus('Encryption complete.');
607
- } catch(e){ setStatus(e.message, false); }
608
- };
609
- $('do-decrypt').onclick = async () => {
610
- try {
611
- const pw = $('password').value;
612
- if (!pw) return setStatus('Enter password to decrypt.', false);
613
- const enc = $('cipher').value;
614
- const dec = await SL.decrypt(enc, pw);
615
- $('enc-result').textContent = dec;
616
- setStatus('Decryption complete.');
617
- } catch(e){ setStatus(e.message||'Decryption failed', false); }
618
- };
619
-
620
- // Hash
621
- qsa('.hash-btn').forEach(b=> b.onclick = async () => {
622
- try {
623
- const algo = b.dataset.algo;
624
- const v = $('hash-input').value || '';
625
- const out = await SL.hash(v, algo);
626
- $('hash-out').value = out;
627
- setStatus(`${algo} computed.`);
628
- } catch(e){ setStatus(e.message, false); }
629
- });
630
-
631
- // Backup/Restore
632
- $('export-backup').onclick = async () => {
633
- try {
634
- const pw = $('password').value;
635
- if (!pw) return setStatus('Enter password first.', false);
636
- if (!mnemonic) return setStatus('Generate/import mnemonic first.', false);
637
- if (!identity?.wif) await deriveIdentity();
638
- const bundle = {
639
- createdAt: new Date().toISOString(),
640
- idPath: $('id-path').value.trim(),
641
- encMnemonic: await SL.encrypt(mnemonic, pw),
642
- encWif: await SL.encrypt(identity.wif, pw),
643
- pubKey: identity.publicKey,
644
- address: identity.address
645
- };
646
- const blob = new Blob([canonicalStringify(bundle)], { type: 'application/json' });
647
- const url = URL.createObjectURL(blob);
648
- const a = document.createElement('a');
649
- a.href = url; a.download = 'web3keys-backup.json'; a.click();
650
- URL.revokeObjectURL(url);
651
- $('backup-preview').textContent = canonicalStringify(bundle);
652
- setStatus('Encrypted backup exported.');
653
- } catch(e){ setStatus(e.message, false); }
654
- };
655
-
656
- $('import-backup').onchange = async (ev) => {
657
- try {
658
- const file = ev.target.files?.[0];
659
- if (!file) return;
660
- const text = await file.text();
661
- const json = JSON.parse(text);
662
- $('backup-preview').textContent = canonicalStringify(json);
663
- // Do not auto-decrypt; user can copy enc values into Encrypt/Decrypt tab to test with their password.
664
- setStatus('Backup loaded (preview shown). Use your password in Encrypt/Decrypt to open fields.');
665
- } catch(e){ setStatus(e.message, false); }
666
- };
667
-
668
- // Crypto test
669
- $('test-crypto').onclick = () => {
670
- let result = 'Testing crypto availability...\n\n';
671
-
672
- try {
673
- // Test Node.js crypto
674
- result += 'Node.js crypto module: ';
675
- if (typeof require !== 'undefined') {
676
- try {
677
- const crypto = require('crypto');
678
- result += crypto ? 'Available\n' : 'Not available\n';
679
- result += ` createHmac: ${typeof crypto.createHmac}\n`;
680
- } catch (e) {
681
- result += `Error: ${e.message}\n`;
682
- }
683
- } else {
684
- result += 'require() not available (browser environment)\n';
685
- }
686
-
687
- // Test BSV crypto
688
- result += '\nBSV crypto module: ';
689
- if (window.bsv && bsv.crypto && bsv.crypto.Hash) {
690
- result += 'Available\n';
691
- result += ` Hash.sha256: ${typeof bsv.crypto.Hash.sha256}\n`;
692
- result += ` Hash.sha512: ${typeof bsv.crypto.Hash.sha512}\n`;
693
- result += ` Hash.sha256hmac: ${typeof bsv.crypto.Hash.sha256hmac}\n`;
694
- result += ` Hash.sha512hmac: ${typeof bsv.crypto.Hash.sha512hmac}\n`;
695
-
696
- // Test HMAC functionality
697
- try {
698
- const testData = Buffer.from('test');
699
- const testKey = Buffer.from('key');
700
- const hmacResult = bsv.crypto.Hash.sha512hmac(testData, testKey);
701
- result += ` HMAC test: Success (${hmacResult.length} bytes)\n`;
702
- } catch (e) {
703
- result += ` HMAC test: Error - ${e.message}\n`;
704
- }
705
- } else {
706
- result += 'Not available\n';
707
- }
708
-
709
- // Test mnemonic PBKDF2
710
- result += '\nMnemonic PBKDF2 test: ';
711
- try {
712
- const MnemonicClass = window.bsvMnemonic || window.Mnemonic || (window.bsv && window.bsv.Mnemonic);
713
- if (MnemonicClass) {
714
- const mnemonic = MnemonicClass.fromRandom(128); // Use 128 for faster test
715
- result += 'Success\n';
716
- result += ` Generated: ${mnemonic.phrase.split(' ').slice(0, 3).join(' ')}...\n`;
717
- } else {
718
- result += 'Mnemonic class not available\n';
719
- }
720
- } catch (e) {
721
- result += `Error - ${e.message}\n`;
722
- if (e.message.includes('createHmac')) {
723
- result += ' ^ This is the createHmac issue!\n';
724
- }
725
- }
726
-
727
- } catch (e) {
728
- result += `\nUnexpected error: ${e.message}\n`;
729
- }
730
-
731
- $('crypto-test').textContent = result;
732
- };
733
-
734
- // Defaults
735
- showTab('keys');
736
- refreshDebug();
737
- });
738
- </script>
739
- </body>
740
- </html>