OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-wallet commits / 8607764

Fix copy address + Add EIP-55 validation + Debug logging
Deployment: testing (hyper-wallet-test) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8607764dbed9dfdcb2fd904978d2c6e39d42d667
by plankatron <fleeky@gmail.com> · 2026-01-27T22:13:35.000Z

Changed files (6) +140 -13

.claude/tmux-registry.json +2 -2
……@@ -30,9 +30,9 @@
3030 "persistent": true,
3131 "channel": "hyper-wallet-test",
3232 "pear_key": "pear://6bafdga5tc9wpts69y478sgnsght4wsuhthroa1a3oxn1xcbxqmo",
33 "latest_version": "7665",
33 "latest_version": "7669",
3434 "last_deployed": "2026-01-27",
35 "features": "Ethereum validation fixed + Add/Remove wallets to contacts + Multi-address support",
35 "features": "Fixed: Change password (pbkdf2) + Encryption exports + Ethereum validation + Add/Remove wallets",
3636 "notes": "Deploy here first with --testing flag, verify, then promote to production (window 0)"
3737 }
3838 }
lib/address-validator.cjs +30 -6
……@@ -17,11 +17,12 @@
1717 *
1818 * VALIDATION:
1919 * - Bitcoin: Base58Check (P2PKH, P2SH) and Bech32/Bech32m (SegWit)
20 * - Ethereum: EIP-55 mixed-case checksum
20 * - Ethereum: EIP-55 mixed-case checksum (keccak256)
2121 * - Returns: { valid: boolean, reason?: string, warning?: string }
2222 */
2323
2424const crypto = require('bare-crypto')
25const { keccak_256 } = require('@noble/hashes/sha3')
2526
2627/**
2728 * Bitcoin Base58 alphabet (no 0, O, I, l to avoid confusion)
……@@ -241,11 +242,34 @@ function validateEthereumAddress(address) {
241242 return { valid: true, warning: 'No EIP-55 checksum (address not checksummed)' }
242243 }
243244
244 // TEMPORARY FIX: EIP-55 checksum validation disabled
245 // bare-crypto doesn't support keccak256 hash algorithm
246 // TODO: Add keccak library (@noble/hashes or @ethereumjs/util) for proper checksum validation
247 console.warn('⚠️ EIP-55 checksum validation skipped (keccak256 not available in bare-crypto)')
248 return { valid: true, warning: 'EIP-55 checksum validation skipped (bare-crypto limitation)' }
245 // Verify EIP-55 checksum using keccak256
246 try {
247 // Hash the lowercase address
248 const hash = keccak_256(addressNoPrefix.toLowerCase())
249 const hashHex = Buffer.from(hash).toString('hex')
250
251 // Check each character
252 for (let i = 0; i < 40; i++) {
253 const char = addressNoPrefix[i]
254 const hashChar = hashHex[i]
255
256 // If hash digit >= 8, char should be uppercase; otherwise lowercase
257 if (parseInt(hashChar, 16) >= 8) {
258 if (char !== char.toUpperCase()) {
259 return { valid: false, reason: 'Invalid EIP-55 checksum (character case mismatch)' }
260 }
261 } else {
262 if (char !== char.toLowerCase()) {
263 return { valid: false, reason: 'Invalid EIP-55 checksum (character case mismatch)' }
264 }
265 }
266 }
267
268 return { valid: true }
269 } catch (err) {
270 console.error('EIP-55 checksum validation error:', err)
271 return { valid: true, warning: 'EIP-55 checksum validation failed, accepting address' }
272 }
249273}
250274
251275/**
package-lock.json +76 -3
……@@ -1,13 +1,14 @@
11{
2 "name": "wdk-bare-test",
2 "name": "hyper-wallet",
33 "version": "0.1.0",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
8 "name": "wdk-bare-test",
8 "name": "hyper-wallet",
99 "hasInstallScript": true,
1010 "dependencies": {
11 "@noble/hashes": "^2.0.1",
1112 "@tetherto/wdk": "^1.0.0-beta.5",
1213 "@tetherto/wdk-wallet-btc": "^1.0.0-beta.4",
1314 "@tetherto/wdk-wallet-evm": "^1.0.0-beta.4",
……@@ -121,7 +122,7 @@
121122 "url": "https://paulmillr.com/funding/"
122123 }
123124 },
124 "node_modules/@noble/hashes": {
125 "node_modules/@noble/curves/node_modules/@noble/hashes": {
125126 "version": "1.8.0",
126127 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
127128 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
……@@ -133,6 +134,18 @@
133134 "url": "https://paulmillr.com/funding/"
134135 }
135136 },
137 "node_modules/@noble/hashes": {
138 "version": "2.0.1",
139 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
140 "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
141 "license": "MIT",
142 "engines": {
143 "node": ">= 20.19.0"
144 },
145 "funding": {
146 "url": "https://paulmillr.com/funding/"
147 }
148 },
136149 "node_modules/@noble/secp256k1": {
137150 "version": "2.2.3",
138151 "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.2.3.tgz",
……@@ -223,6 +236,18 @@
223236 "url": "https://paulmillr.com/funding/"
224237 }
225238 },
239 "node_modules/@tetherto/wdk-wallet-evm/node_modules/@noble/hashes": {
240 "version": "1.8.0",
241 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
242 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
243 "license": "MIT",
244 "engines": {
245 "node": "^14.21.3 || >=16"
246 },
247 "funding": {
248 "url": "https://paulmillr.com/funding/"
249 }
250 },
226251 "node_modules/@types/node": {
227252 "version": "22.7.5",
228253 "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
……@@ -1288,6 +1313,18 @@
12881313 "node": ">=6.0.0"
12891314 }
12901315 },
1316 "node_modules/bip32/node_modules/@noble/hashes": {
1317 "version": "1.8.0",
1318 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
1319 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
1320 "license": "MIT",
1321 "engines": {
1322 "node": "^14.21.3 || >=16"
1323 },
1324 "funding": {
1325 "url": "https://paulmillr.com/funding/"
1326 }
1327 },
12911328 "node_modules/bip39": {
12921329 "version": "3.1.0",
12931330 "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz",
……@@ -1308,6 +1345,18 @@
13081345 "sodium-universal": "^5.0.1"
13091346 }
13101347 },
1348 "node_modules/bip39/node_modules/@noble/hashes": {
1349 "version": "1.8.0",
1350 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
1351 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
1352 "license": "MIT",
1353 "engines": {
1354 "node": "^14.21.3 || >=16"
1355 },
1356 "funding": {
1357 "url": "https://paulmillr.com/funding/"
1358 }
1359 },
13111360 "node_modules/bip66": {
13121361 "version": "1.1.5",
13131362 "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
……@@ -1343,6 +1392,18 @@
13431392 "node": ">=8.0.0"
13441393 }
13451394 },
1395 "node_modules/bitcoinjs-lib/node_modules/@noble/hashes": {
1396 "version": "1.8.0",
1397 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
1398 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
1399 "license": "MIT",
1400 "engines": {
1401 "node": "^14.21.3 || >=16"
1402 },
1403 "funding": {
1404 "url": "https://paulmillr.com/funding/"
1405 }
1406 },
13461407 "node_modules/bitcoinjs-message": {
13471408 "version": "2.2.0",
13481409 "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.2.0.tgz",
……@@ -1475,6 +1536,18 @@
14751536 "bs58": "^5.0.0"
14761537 }
14771538 },
1539 "node_modules/bs58check/node_modules/@noble/hashes": {
1540 "version": "1.8.0",
1541 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
1542 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
1543 "license": "MIT",
1544 "engines": {
1545 "node": "^14.21.3 || >=16"
1546 },
1547 "funding": {
1548 "url": "https://paulmillr.com/funding/"
1549 }
1550 },
14781551 "node_modules/buffer-equals": {
14791552 "version": "1.0.4",
14801553 "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz",
package.json +1 -0
……@@ -30,6 +30,7 @@
3030 "postinstall": "bash scripts/setup-pear-compat.sh"
3131 },
3232 "dependencies": {
33 "@noble/hashes": "^2.0.1",
3334 "@tetherto/wdk": "^1.0.0-beta.5",
3435 "@tetherto/wdk-wallet-btc": "^1.0.0-beta.4",
3536 "@tetherto/wdk-wallet-evm": "^1.0.0-beta.4",
ui/app.js +11 -1
……@@ -184,6 +184,16 @@ document.addEventListener('DOMContentLoaded', () => {
184184 confirmBtn.disabled = !e.target.checked
185185 })
186186 }
187
188 // Event delegation for copying contact addresses
189 document.addEventListener('click', (e) => {
190 if (e.target.classList.contains('copyable-address')) {
191 const address = e.target.getAttribute('data-address')
192 if (address) {
193 copyToClipboard(address, 'Address copied!')
194 }
195 }
196 })
187197})
188198
189199function confirmMnemonicBackup() {
……@@ -958,7 +968,7 @@ async function loadContactsList() {
958968 <div class="contact-address-row">
959969 <div class="contact-address-info">
960970 <span class="chain-icon" title="${addr.chain}">${chainIcon}</span>
961 <code class="contact-address" onclick="copyToClipboard('${addr.address}', 'Address copied!')" title="Click to copy">${shortAddr}</code>
971 <code class="contact-address copyable-address" data-address="${escapeHtml(addr.address)}" title="Click to copy full address">${shortAddr}</code>
962972 <span class="chain-badge chain-badge-${addr.chain}">${addr.chain}</span>
963973 </div>
964974 <div class="contact-address-actions">
wallet-server.cjs +20 -1
……@@ -1011,24 +1011,33 @@ const routes = {
10111011
10121012 // POST /api/contacts/:id/addresses - Add address to contact
10131013 'POST /api/contacts/': requireAuth(async (req, res, id, ...rest) => {
1014 console.log('📝 POST /api/contacts/ route hit:', { id, rest })
1015
10141016 // Check if this is the /addresses sub-route
10151017 if (rest[0] === 'addresses') {
1018 console.log('✅ /addresses sub-route detected')
10161019 try {
10171020 if (!id) {
1021 console.log('❌ No contact ID provided')
10181022 return sendError(res, 'Contact ID required', 400)
10191023 }
10201024
10211025 const body = await parseBody(req)
1026 console.log('📦 Request body:', { chain: body.chain, address: body.address, network: body.network })
1027
10221028 let { chain, address, network } = body
10231029
10241030 if (!chain || !address) {
1031 console.log('❌ Missing required fields:', { chain, address })
10251032 return sendError(res, 'Missing required fields: chain, address', 400)
10261033 }
10271034
10281035 // Validate chain
10291036 try {
10301037 chain = validateChain(chain)
1038 console.log('✅ Chain validated:', chain)
10311039 } catch (err) {
1040 console.log('❌ Chain validation error:', err.message)
10321041 return sendError(res, err.message, 400)
10331042 }
10341043
……@@ -1037,20 +1046,28 @@ const routes = {
10371046 const config = getConfig()
10381047 network = config.network || 'testnet'
10391048 }
1049 console.log('🌐 Network:', network)
10401050
10411051 // Validate address
1052 console.log('🔍 Validating address:', { address, chain, network })
10421053 const addressValidation = validateAddress(address, chain, network)
1054 console.log('📋 Address validation result:', addressValidation)
1055
10431056 if (!addressValidation.valid) {
1057 console.log('❌ Address validation failed:', addressValidation.reason)
10441058 return sendError(res, `Invalid address: ${addressValidation.reason}`, 400)
10451059 }
10461060 if (addressValidation.warning) {
10471061 console.warn(`⚠️ Address validation warning: ${addressValidation.warning}`)
10481062 }
10491063
1064 console.log('💾 Loading identity and initializing storage...')
10501065 const identity = await loadIdentity()
10511066 await initializeStorage(identity)
10521067
1068 console.log('➕ Adding address to contact...')
10531069 const contact = await addAddressToContact(id, { chain, address, network })
1070 console.log('✅ Address added successfully:', contact.id)
10541071
10551072 sendJSON(res, {
10561073 success: true,
……@@ -1058,13 +1075,15 @@ const routes = {
10581075 contact
10591076 })
10601077 } catch (err) {
1061 console.error('Add address to contact error:', err)
1078 console.error('❌ Add address to contact error:', err)
1079 console.error('Stack trace:', err.stack)
10621080 sendError(res, err.message)
10631081 }
10641082 return
10651083 }
10661084
10671085 // If not /addresses, return 404
1086 console.log('❌ Not /addresses sub-route, returning 404')
10681087 sendError(res, 'Not found', 404)
10691088 }),
10701089