smartledger-bsv 3.3.2 → 3.3.4
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 +220 -79
- package/README.md +283 -71
- package/bsv-covenant.min.js +26 -3
- package/bsv-gdaf.min.js +11 -9
- package/bsv-ltp.min.js +10 -8
- package/bsv-mnemonic.min.js +4 -4
- package/bsv-script-helper.min.js +2 -2
- package/bsv-security.min.js +3 -24
- package/bsv-shamir.min.js +2 -2
- package/bsv-smartcontract.min.js +10 -8
- package/bsv.bundle.js +9 -9
- package/bsv.min.js +10 -8
- package/build/webpack.bundle.config.js +2 -2
- package/build/webpack.config.js +2 -2
- package/build/webpack.covenant.config.js +2 -2
- package/build/webpack.gdaf.config.js +6 -43
- package/build/webpack.script-helper.config.js +2 -2
- package/build/webpack.security.config.js +2 -2
- package/build/webpack.smartcontract.config.js +2 -2
- package/bundle-entry.js +1 -341
- package/covenant-entry.js +1 -44
- package/demos/README.md +188 -0
- package/{architecture_demo.js → demos/architecture_demo.js} +2 -2
- package/demos/bsv_wallet_demo.js +242 -0
- package/{complete_ltp_demo.js → demos/complete_ltp_demo.js} +1 -1
- 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/{shamir_demo.js → demos/shamir_demo.js} +1 -1
- package/{simple_demo.js → demos/simple_demo.js} +1 -1
- 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 +295 -0
- package/docs/FIX_CREATEHMAC_ISSUE.md +91 -0
- package/docs/MODULE_REFERENCE_COMPLETE.md +330 -0
- package/docs/README.md +107 -79
- 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/LEGAL_TOKEN_PROTOCOL.md +411 -0
- package/docs/advanced/SMART_CONTRACT_GUIDE.md +1255 -0
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +851 -0
- package/docs/api/LTP.md +334 -0
- package/docs/getting-started/INSTALLATION.md +410 -0
- package/docs/getting-started/QUICK_START.md +180 -0
- package/docs/migration/FROM_BSV_1_5_6.md +260 -0
- package/docs/technical/GDAF_DEVELOPER_INTERFACE.md +187 -0
- package/docs/technical/GDAF_IMPLEMENTATION_COMPLETE.md +190 -0
- package/docs/technical/SHAMIR_INTEGRATION_SUMMARY.md +165 -0
- package/docs/technical/roadmap.md +1250 -0
- package/docs/technical/trust_law.md +142 -0
- 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/gdaf-entry.js +2 -54
- package/index.js +32 -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/ltp-entry.js +2 -92
- package/package.json +21 -8
- package/script-helper-entry.js +1 -49
- package/security-entry.js +1 -70
- package/shamir-entry.js +1 -173
- package/smartcontract-entry.js +1 -133
- 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
- package/tests/test_builtin_verify.js +117 -0
- package/tests/test_debug_integration.js +71 -0
- package/tests/test_ecdsa_little.js +70 -0
- package/tests/test_smartverify_der.js +110 -0
- package/utilities/blockchain-state.js +155 -155
- package/utilities/blockchain-state.json +103293 -5244
- package/utilities/miner-simulator.js +354 -358
- package/utilities/mock-utxo-generator.js +54 -54
- package/utilities/raw-tx-examples.js +120 -122
- package/utilities/success-demo.js +104 -105
- package/utilities/transaction-examples.js +188 -188
- package/utilities/utxo-manager.js +91 -91
- package/utilities/wallet-setup.js +79 -80
- package/utilities/working-signature-demo.js +108 -110
- package/SECURITY.md +0 -75
- package/build/bsv-covenant.min.js +0 -10
- package/build/bsv-script-helper.min.js +0 -10
- package/build/bsv-security.min.js +0 -31
- package/build/bsv-smartcontract.min.js +0 -39
- package/build/bsv.bundle.js +0 -39
- package/build/bsv.min.js +0 -39
- package/validation_test.js +0 -97
- /package/docs/{ADVANCED_COVENANT_DEVELOPMENT.md → advanced/ADVANCED_COVENANT_DEVELOPMENT.md} +0 -0
- /package/docs/{CUSTOM_SCRIPT_DEVELOPMENT.md → advanced/CUSTOM_SCRIPT_DEVELOPMENT.md} +0 -0
- /package/docs/{block.md → api/BLOCKS.md} +0 -0
- /package/docs/{ecies.md → api/ECIES.md} +0 -0
- /package/docs/{networks.md → api/NETWORKS.md} +0 -0
- /package/docs/{script.md → api/SCRIPTS.md} +0 -0
- /package/docs/{transaction.md → api/TRANSACTIONS.md} +0 -0
- /package/docs/{unspentoutput.md → api/UTXO.md} +0 -0
- /package/{test_shamir.js → tests/test_shamir.js} +0 -0
- /package/{test_standalone_shamir.html → tests/test_standalone_shamir.html} +0 -0
|
@@ -0,0 +1,740 @@
|
|
|
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.3 -->
|
|
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.3 • 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.3'}` : 'SmartLedger BSV v3.3.3'
|
|
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>
|