hd-wallet-ui 1.2.6 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/package.json +10 -5
- package/src/app.js +703 -182
- package/src/template.js +24 -0
- package/src/wallet-storage.js +148 -35
- package/styles/main.css +89 -0
- package/styles/widget.css +6089 -0
package/src/template.js
CHANGED
|
@@ -410,6 +410,26 @@ export function getModalHTML() {
|
|
|
410
410
|
|
|
411
411
|
<!-- Messaging Tab (Encrypt + Decrypt) -->
|
|
412
412
|
<div id="messaging-tab-content" class="modal-tab-content">
|
|
413
|
+
<div class="glass-card messaging-key-config">
|
|
414
|
+
<div class="messaging-key-config-grid">
|
|
415
|
+
<div class="messaging-key-config-item">
|
|
416
|
+
<label>Key Type</label>
|
|
417
|
+
<select id="messaging-key-type" class="glass-input compact">
|
|
418
|
+
<option value="btc">Bitcoin (BTC) - secp256k1</option>
|
|
419
|
+
<option value="eth">Ethereum (ETH) - secp256k1</option>
|
|
420
|
+
<option value="sol">Solana (SOL) - X25519</option>
|
|
421
|
+
</select>
|
|
422
|
+
</div>
|
|
423
|
+
<div class="messaging-key-config-item">
|
|
424
|
+
<label>HD Path</label>
|
|
425
|
+
<div class="messaging-path-row">
|
|
426
|
+
<input type="text" id="messaging-hd-path" class="glass-input compact" value="m/44'/0'/0'/1/0" spellcheck="false" autocomplete="off">
|
|
427
|
+
<button id="messaging-hd-path-default" class="glass-btn small" title="Reset to default path">Default</button>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="messaging-key-hint">Example: m/44'/60'/0'/1/0</div>
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
413
433
|
<div class="messaging-sub-tabs">
|
|
414
434
|
<button class="messaging-sub-tab active" data-messaging-sub="encrypt-sub">Encrypt</button>
|
|
415
435
|
<button class="messaging-sub-tab" data-messaging-sub="decrypt-sub">Decrypt</button>
|
|
@@ -435,6 +455,10 @@ export function getModalHTML() {
|
|
|
435
455
|
<label>Derivation Path</label>
|
|
436
456
|
<code id="encrypt-sender-path">--</code>
|
|
437
457
|
</div>
|
|
458
|
+
<div class="encrypt-key-detail">
|
|
459
|
+
<label>Key Algorithm</label>
|
|
460
|
+
<code id="encrypt-sender-algo">--</code>
|
|
461
|
+
</div>
|
|
438
462
|
</div>
|
|
439
463
|
<div class="encrypt-key-card glass-card">
|
|
440
464
|
<div class="encrypt-key-header">
|
package/src/wallet-storage.js
CHANGED
|
@@ -22,7 +22,15 @@ const ENCRYPTED_DATA_KEY = `${STORAGE_PREFIX}encrypted`;
|
|
|
22
22
|
const PASSKEY_CREDENTIAL_KEY = `${STORAGE_PREFIX}passkey_credential`;
|
|
23
23
|
|
|
24
24
|
// Version for future migrations
|
|
25
|
-
const STORAGE_VERSION =
|
|
25
|
+
const STORAGE_VERSION = 3;
|
|
26
|
+
|
|
27
|
+
// AES-GCM standard IV size (96-bit)
|
|
28
|
+
const AES_GCM_IV_LENGTH = 12;
|
|
29
|
+
|
|
30
|
+
// 6-digit PINs have low entropy; PBKDF2 must be expensive to slow offline brute force.
|
|
31
|
+
// (This is still not a substitute for rate-limiting when an attacker can query online.)
|
|
32
|
+
const PIN_PBKDF2_ITERATIONS = 600000;
|
|
33
|
+
const LEGACY_PIN_PBKDF2_ITERATIONS = 100000;
|
|
26
34
|
|
|
27
35
|
// =============================================================================
|
|
28
36
|
// Storage Method Enum
|
|
@@ -113,9 +121,9 @@ async function hkdfDerive(inputKeyMaterial, salt, info, length) {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
/**
|
|
116
|
-
* Derive encryption key
|
|
124
|
+
* Derive encryption key from key material (HKDF).
|
|
117
125
|
*/
|
|
118
|
-
async function
|
|
126
|
+
async function deriveEncryptionKey(keyMaterial, context) {
|
|
119
127
|
const salt = new TextEncoder().encode(`wallet-storage-v${STORAGE_VERSION}`);
|
|
120
128
|
|
|
121
129
|
const encryptionKey = await hkdfDerive(
|
|
@@ -125,13 +133,27 @@ async function deriveKeyAndIV(keyMaterial, context) {
|
|
|
125
133
|
32
|
|
126
134
|
);
|
|
127
135
|
|
|
136
|
+
return encryptionKey;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Legacy: v1/v2 storage used a deterministic IV derived via HKDF.
|
|
141
|
+
* This exists only to decrypt and upgrade old stored blobs.
|
|
142
|
+
*/
|
|
143
|
+
async function deriveLegacyKeyAndIV(keyMaterial, context, version) {
|
|
144
|
+
const salt = new TextEncoder().encode(`wallet-storage-v${version}`);
|
|
145
|
+
const encryptionKey = await hkdfDerive(
|
|
146
|
+
keyMaterial,
|
|
147
|
+
salt,
|
|
148
|
+
`${context}-encryption-key`,
|
|
149
|
+
32
|
|
150
|
+
);
|
|
128
151
|
const iv = await hkdfDerive(
|
|
129
152
|
keyMaterial,
|
|
130
153
|
salt,
|
|
131
154
|
`${context}-encryption-iv`,
|
|
132
|
-
|
|
155
|
+
AES_GCM_IV_LENGTH
|
|
133
156
|
);
|
|
134
|
-
|
|
135
157
|
return { encryptionKey, iv };
|
|
136
158
|
}
|
|
137
159
|
|
|
@@ -143,7 +165,7 @@ async function deriveKeyAndIV(keyMaterial, context) {
|
|
|
143
165
|
* Derive key material from a 6-digit PIN
|
|
144
166
|
* Uses PBKDF2 for additional security against brute-force attacks
|
|
145
167
|
*/
|
|
146
|
-
async function deriveKeyFromPIN(pin, storedSalt) {
|
|
168
|
+
async function deriveKeyFromPIN(pin, storedSalt, iterations = PIN_PBKDF2_ITERATIONS) {
|
|
147
169
|
if (!/^\d{6}$/.test(pin)) {
|
|
148
170
|
throw new Error('PIN must be exactly 6 digits');
|
|
149
171
|
}
|
|
@@ -169,7 +191,7 @@ async function deriveKeyFromPIN(pin, storedSalt) {
|
|
|
169
191
|
name: 'PBKDF2',
|
|
170
192
|
hash: 'SHA-256',
|
|
171
193
|
salt: salt,
|
|
172
|
-
iterations
|
|
194
|
+
iterations
|
|
173
195
|
},
|
|
174
196
|
keyMaterial,
|
|
175
197
|
256
|
|
@@ -299,15 +321,15 @@ export async function registerPasskey(options = {}) {
|
|
|
299
321
|
let hasPRF = false;
|
|
300
322
|
|
|
301
323
|
if (prfResult && prfResult.byteLength > 0) {
|
|
302
|
-
// PRF is supported - use the PRF output
|
|
303
324
|
keyMaterial = new Uint8Array(prfResult);
|
|
304
325
|
hasPRF = true;
|
|
305
326
|
} else {
|
|
306
|
-
// PRF not
|
|
307
|
-
// This is less secure but provides fallback functionality
|
|
327
|
+
// PRF not available — derive key material from credential ID + a fixed salt.
|
|
308
328
|
const rawId = new Uint8Array(credential.rawId);
|
|
309
|
-
const
|
|
310
|
-
|
|
329
|
+
const salt = new TextEncoder().encode('wallet-storage-credid-fallback-v1');
|
|
330
|
+
const base = await crypto.subtle.importKey('raw', rawId, 'HKDF', false, ['deriveBits']);
|
|
331
|
+
const bits = await crypto.subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt, info: new Uint8Array(0) }, base, 256);
|
|
332
|
+
keyMaterial = new Uint8Array(bits);
|
|
311
333
|
}
|
|
312
334
|
|
|
313
335
|
return {
|
|
@@ -365,10 +387,12 @@ export async function authenticatePasskey(credentialId) {
|
|
|
365
387
|
keyMaterial = new Uint8Array(prfResult);
|
|
366
388
|
hasPRF = true;
|
|
367
389
|
} else {
|
|
368
|
-
//
|
|
390
|
+
// PRF not available — derive key material from credential ID + a fixed salt.
|
|
369
391
|
const rawId = new Uint8Array(assertion.rawId);
|
|
370
|
-
const
|
|
371
|
-
|
|
392
|
+
const salt = new TextEncoder().encode('wallet-storage-credid-fallback-v1');
|
|
393
|
+
const base = await crypto.subtle.importKey('raw', rawId, 'HKDF', false, ['deriveBits']);
|
|
394
|
+
const bits = await crypto.subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt, info: new Uint8Array(0) }, base, 256);
|
|
395
|
+
keyMaterial = new Uint8Array(bits);
|
|
372
396
|
}
|
|
373
397
|
|
|
374
398
|
return { keyMaterial, hasPRF };
|
|
@@ -381,9 +405,10 @@ export async function authenticatePasskey(credentialId) {
|
|
|
381
405
|
/**
|
|
382
406
|
* Encrypt data using AES-256-GCM
|
|
383
407
|
*/
|
|
384
|
-
async function encryptData(data, encryptionKey,
|
|
408
|
+
async function encryptData(data, encryptionKey, aad) {
|
|
385
409
|
const encoder = new TextEncoder();
|
|
386
410
|
const plaintext = encoder.encode(JSON.stringify(data));
|
|
411
|
+
const iv = generateRandomBytes(AES_GCM_IV_LENGTH);
|
|
387
412
|
|
|
388
413
|
const cryptoKey = await crypto.subtle.importKey(
|
|
389
414
|
'raw',
|
|
@@ -393,19 +418,22 @@ async function encryptData(data, encryptionKey, iv) {
|
|
|
393
418
|
['encrypt']
|
|
394
419
|
);
|
|
395
420
|
|
|
421
|
+
const alg = { name: 'AES-GCM', iv };
|
|
422
|
+
if (aad) alg.additionalData = aad;
|
|
423
|
+
|
|
396
424
|
const ciphertext = await crypto.subtle.encrypt(
|
|
397
|
-
|
|
425
|
+
alg,
|
|
398
426
|
cryptoKey,
|
|
399
427
|
plaintext
|
|
400
428
|
);
|
|
401
429
|
|
|
402
|
-
return new Uint8Array(ciphertext);
|
|
430
|
+
return { iv, ciphertext: new Uint8Array(ciphertext) };
|
|
403
431
|
}
|
|
404
432
|
|
|
405
433
|
/**
|
|
406
434
|
* Decrypt data using AES-256-GCM
|
|
407
435
|
*/
|
|
408
|
-
async function decryptData(ciphertext, encryptionKey, iv) {
|
|
436
|
+
async function decryptData(ciphertext, encryptionKey, iv, aad) {
|
|
409
437
|
const cryptoKey = await crypto.subtle.importKey(
|
|
410
438
|
'raw',
|
|
411
439
|
encryptionKey,
|
|
@@ -414,8 +442,11 @@ async function decryptData(ciphertext, encryptionKey, iv) {
|
|
|
414
442
|
['decrypt']
|
|
415
443
|
);
|
|
416
444
|
|
|
445
|
+
const alg = { name: 'AES-GCM', iv };
|
|
446
|
+
if (aad) alg.additionalData = aad;
|
|
447
|
+
|
|
417
448
|
const plaintext = await crypto.subtle.decrypt(
|
|
418
|
-
|
|
449
|
+
alg,
|
|
419
450
|
cryptoKey,
|
|
420
451
|
ciphertext
|
|
421
452
|
);
|
|
@@ -428,6 +459,12 @@ async function decryptData(ciphertext, encryptionKey, iv) {
|
|
|
428
459
|
// High-Level Storage API
|
|
429
460
|
// =============================================================================
|
|
430
461
|
|
|
462
|
+
function getAadForMethod(method) {
|
|
463
|
+
// Small AAD to bind ciphertexts to this module + method.
|
|
464
|
+
// (Replay protection against localStorage rollback is not achievable without an external monotonic anchor.)
|
|
465
|
+
return new TextEncoder().encode(`wallet-storage|v${STORAGE_VERSION}|${method}`);
|
|
466
|
+
}
|
|
467
|
+
|
|
431
468
|
/**
|
|
432
469
|
* Get storage metadata
|
|
433
470
|
* @returns {Object|null} Storage metadata or null if no wallet stored
|
|
@@ -477,14 +514,16 @@ export function getStorageMethod() {
|
|
|
477
514
|
export async function storeWithPIN(pin, walletData) {
|
|
478
515
|
// Derive key from PIN
|
|
479
516
|
const { keyMaterial, salt } = await deriveKeyFromPIN(pin);
|
|
480
|
-
const
|
|
517
|
+
const encryptionKey = await deriveEncryptionKey(keyMaterial, 'pin');
|
|
481
518
|
|
|
482
519
|
// Encrypt wallet data
|
|
483
|
-
const
|
|
520
|
+
const aad = getAadForMethod(StorageMethod.PIN);
|
|
521
|
+
const { ciphertext, iv } = await encryptData(walletData, encryptionKey, aad);
|
|
484
522
|
|
|
485
523
|
// Store encrypted data
|
|
486
524
|
const encryptedData = {
|
|
487
525
|
ciphertext: arrayBufferToBase64(ciphertext),
|
|
526
|
+
iv: arrayBufferToBase64(iv),
|
|
488
527
|
salt: arrayBufferToBase64(salt)
|
|
489
528
|
};
|
|
490
529
|
localStorage.setItem(ENCRYPTED_DATA_KEY, JSON.stringify(encryptedData));
|
|
@@ -520,13 +559,37 @@ export async function retrieveWithPIN(pin) {
|
|
|
520
559
|
const encryptedData = JSON.parse(encryptedJson);
|
|
521
560
|
const salt = base64ToUint8Array(encryptedData.salt);
|
|
522
561
|
const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
|
|
562
|
+
const iv = encryptedData.iv ? base64ToUint8Array(encryptedData.iv) : null;
|
|
523
563
|
|
|
524
564
|
// Derive key from PIN with stored salt
|
|
525
|
-
const { keyMaterial } = await deriveKeyFromPIN(pin, salt);
|
|
526
|
-
const
|
|
565
|
+
const { keyMaterial } = await deriveKeyFromPIN(pin, salt, iv ? PIN_PBKDF2_ITERATIONS : LEGACY_PIN_PBKDF2_ITERATIONS);
|
|
566
|
+
const aad = getAadForMethod(StorageMethod.PIN);
|
|
527
567
|
|
|
528
568
|
try {
|
|
529
|
-
|
|
569
|
+
let walletData;
|
|
570
|
+
if (iv) {
|
|
571
|
+
const encryptionKey = await deriveEncryptionKey(keyMaterial, 'pin');
|
|
572
|
+
walletData = await decryptData(ciphertext, encryptionKey, iv, aad);
|
|
573
|
+
} else {
|
|
574
|
+
// Legacy v1/v2 deterministic-IV storage. Decrypt and upgrade in-place.
|
|
575
|
+
const legacyVersion = metadata.version || 2;
|
|
576
|
+
const { encryptionKey: legacyKey, iv: legacyIv } = await deriveLegacyKeyAndIV(keyMaterial, 'pin', legacyVersion);
|
|
577
|
+
walletData = await decryptData(ciphertext, legacyKey, legacyIv);
|
|
578
|
+
|
|
579
|
+
// Upgrade stored blob to v3 (random IV) after successful decrypt.
|
|
580
|
+
const newKey = await deriveEncryptionKey(keyMaterial, 'pin');
|
|
581
|
+
const { ciphertext: newCt, iv: newIv } = await encryptData(walletData, newKey, aad);
|
|
582
|
+
localStorage.setItem(ENCRYPTED_DATA_KEY, JSON.stringify({
|
|
583
|
+
ciphertext: arrayBufferToBase64(newCt),
|
|
584
|
+
iv: arrayBufferToBase64(newIv),
|
|
585
|
+
salt: arrayBufferToBase64(salt)
|
|
586
|
+
}));
|
|
587
|
+
localStorage.setItem(METADATA_KEY, JSON.stringify({
|
|
588
|
+
...metadata,
|
|
589
|
+
version: STORAGE_VERSION
|
|
590
|
+
}));
|
|
591
|
+
}
|
|
592
|
+
return walletData;
|
|
530
593
|
} catch (e) {
|
|
531
594
|
throw new Error('Invalid PIN or corrupted data');
|
|
532
595
|
}
|
|
@@ -540,14 +603,39 @@ export async function retrieveWithPIN(pin) {
|
|
|
540
603
|
* @returns {Promise<boolean>}
|
|
541
604
|
*/
|
|
542
605
|
export async function storeWithPasskey(walletData, options = {}) {
|
|
543
|
-
//
|
|
544
|
-
|
|
606
|
+
// Prefer reusing an existing stored passkey credential to avoid creating duplicates.
|
|
607
|
+
let credentialId = null;
|
|
608
|
+
let keyMaterial = null;
|
|
609
|
+
let hasPRF = false;
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const existing = localStorage.getItem(PASSKEY_CREDENTIAL_KEY);
|
|
613
|
+
if (existing) {
|
|
614
|
+
const parsed = JSON.parse(existing);
|
|
615
|
+
if (parsed?.id) {
|
|
616
|
+
credentialId = parsed.id;
|
|
617
|
+
const auth = await authenticatePasskey(credentialId);
|
|
618
|
+
keyMaterial = auth.keyMaterial;
|
|
619
|
+
hasPRF = auth.hasPRF;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} catch {
|
|
623
|
+
// Ignore and fall back to registration below.
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (!keyMaterial) {
|
|
627
|
+
const reg = await registerPasskey(options);
|
|
628
|
+
credentialId = reg.credentialId;
|
|
629
|
+
keyMaterial = reg.keyMaterial;
|
|
630
|
+
hasPRF = reg.hasPRF;
|
|
631
|
+
}
|
|
545
632
|
|
|
546
633
|
// Derive encryption key
|
|
547
|
-
const
|
|
634
|
+
const encryptionKey = await deriveEncryptionKey(keyMaterial, 'passkey');
|
|
548
635
|
|
|
549
636
|
// Encrypt wallet data
|
|
550
|
-
const
|
|
637
|
+
const aad = getAadForMethod(StorageMethod.PASSKEY);
|
|
638
|
+
const { ciphertext, iv } = await encryptData(walletData, encryptionKey, aad);
|
|
551
639
|
|
|
552
640
|
// Store credential info
|
|
553
641
|
const credentialData = {
|
|
@@ -558,7 +646,8 @@ export async function storeWithPasskey(walletData, options = {}) {
|
|
|
558
646
|
|
|
559
647
|
// Store encrypted data
|
|
560
648
|
const encryptedData = {
|
|
561
|
-
ciphertext: arrayBufferToBase64(ciphertext)
|
|
649
|
+
ciphertext: arrayBufferToBase64(ciphertext),
|
|
650
|
+
iv: arrayBufferToBase64(iv)
|
|
562
651
|
};
|
|
563
652
|
localStorage.setItem(ENCRYPTED_DATA_KEY, JSON.stringify(encryptedData));
|
|
564
653
|
|
|
@@ -584,7 +673,6 @@ export async function retrieveWithPasskey() {
|
|
|
584
673
|
if (!metadata || metadata.method !== StorageMethod.PASSKEY) {
|
|
585
674
|
throw new Error('No passkey-encrypted wallet found');
|
|
586
675
|
}
|
|
587
|
-
|
|
588
676
|
const credentialJson = localStorage.getItem(PASSKEY_CREDENTIAL_KEY);
|
|
589
677
|
if (!credentialJson) {
|
|
590
678
|
throw new Error('Passkey credential not found');
|
|
@@ -599,15 +687,38 @@ export async function retrieveWithPasskey() {
|
|
|
599
687
|
|
|
600
688
|
const encryptedData = JSON.parse(encryptedJson);
|
|
601
689
|
const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
|
|
690
|
+
const iv = encryptedData.iv ? base64ToUint8Array(encryptedData.iv) : null;
|
|
602
691
|
|
|
603
692
|
// Authenticate with passkey and get key material
|
|
604
693
|
const { keyMaterial } = await authenticatePasskey(credentialData.id);
|
|
605
694
|
|
|
606
695
|
// Derive encryption key
|
|
607
|
-
const
|
|
696
|
+
const aad = getAadForMethod(StorageMethod.PASSKEY);
|
|
608
697
|
|
|
609
698
|
try {
|
|
610
|
-
|
|
699
|
+
let walletData;
|
|
700
|
+
if (iv) {
|
|
701
|
+
const encryptionKey = await deriveEncryptionKey(keyMaterial, 'passkey');
|
|
702
|
+
walletData = await decryptData(ciphertext, encryptionKey, iv, aad);
|
|
703
|
+
} else {
|
|
704
|
+
// Legacy v1/v2 deterministic-IV storage. Decrypt and upgrade in-place.
|
|
705
|
+
const legacyVersion = metadata.version || 2;
|
|
706
|
+
const { encryptionKey: legacyKey, iv: legacyIv } = await deriveLegacyKeyAndIV(keyMaterial, 'passkey', legacyVersion);
|
|
707
|
+
walletData = await decryptData(ciphertext, legacyKey, legacyIv);
|
|
708
|
+
|
|
709
|
+
const newKey = await deriveEncryptionKey(keyMaterial, 'passkey');
|
|
710
|
+
const { ciphertext: newCt, iv: newIv } = await encryptData(walletData, newKey, aad);
|
|
711
|
+
localStorage.setItem(ENCRYPTED_DATA_KEY, JSON.stringify({
|
|
712
|
+
ciphertext: arrayBufferToBase64(newCt),
|
|
713
|
+
iv: arrayBufferToBase64(newIv)
|
|
714
|
+
}));
|
|
715
|
+
localStorage.setItem(METADATA_KEY, JSON.stringify({
|
|
716
|
+
...metadata,
|
|
717
|
+
version: STORAGE_VERSION,
|
|
718
|
+
hasPRF: true
|
|
719
|
+
}));
|
|
720
|
+
}
|
|
721
|
+
return walletData;
|
|
611
722
|
} catch (e) {
|
|
612
723
|
throw new Error('Passkey authentication failed or data corrupted');
|
|
613
724
|
}
|
|
@@ -636,7 +747,7 @@ export function migrateStorage() {
|
|
|
636
747
|
if (getStorageMetadata() !== null) return;
|
|
637
748
|
if (!oldPinWallet && !oldPasskeyCredential) return;
|
|
638
749
|
|
|
639
|
-
console.log('Migrating wallet storage from
|
|
750
|
+
console.log('Migrating wallet storage from legacy format...');
|
|
640
751
|
|
|
641
752
|
if (oldPasskeyCredential && oldPasskeyWallet) {
|
|
642
753
|
// Migrate passkey storage
|
|
@@ -656,7 +767,9 @@ export function migrateStorage() {
|
|
|
656
767
|
localStorage.setItem(METADATA_KEY, JSON.stringify({
|
|
657
768
|
method: StorageMethod.PASSKEY,
|
|
658
769
|
timestamp: credential.timestamp || wallet.timestamp || Date.now(),
|
|
659
|
-
version
|
|
770
|
+
// Preserve legacy version so decrypt can derive the correct legacy HKDF salt/IV,
|
|
771
|
+
// then upgrade in-place on first successful retrieval.
|
|
772
|
+
version: credential.version || wallet.version || 2,
|
|
660
773
|
hasPRF: credential.hasPRF || false
|
|
661
774
|
}));
|
|
662
775
|
|
package/styles/main.css
CHANGED
|
@@ -2704,6 +2704,48 @@ body:has(.modal.active) .nav-bar {
|
|
|
2704
2704
|
justify-content: center;
|
|
2705
2705
|
}
|
|
2706
2706
|
|
|
2707
|
+
.wallet-receive-peer-section {
|
|
2708
|
+
margin: 0 auto 16px;
|
|
2709
|
+
max-width: 320px;
|
|
2710
|
+
text-align: left;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
.wallet-receive-field {
|
|
2714
|
+
display: flex;
|
|
2715
|
+
align-items: baseline;
|
|
2716
|
+
gap: 8px;
|
|
2717
|
+
padding: 8px 0;
|
|
2718
|
+
border-bottom: 1px solid var(--white-05);
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
.wallet-receive-field:last-child {
|
|
2722
|
+
border-bottom: none;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
.wallet-receive-field-label {
|
|
2726
|
+
flex-shrink: 0;
|
|
2727
|
+
width: 48px;
|
|
2728
|
+
font-size: 10px;
|
|
2729
|
+
text-transform: uppercase;
|
|
2730
|
+
letter-spacing: 0.05em;
|
|
2731
|
+
color: var(--white-50);
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
.wallet-receive-field-value {
|
|
2735
|
+
flex: 1;
|
|
2736
|
+
font-size: 11px;
|
|
2737
|
+
color: var(--white-60);
|
|
2738
|
+
word-break: break-all;
|
|
2739
|
+
line-height: 1.4;
|
|
2740
|
+
font-family: var(--font-mono);
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
.wallet-receive-field .glass-btn.small {
|
|
2744
|
+
flex-shrink: 0;
|
|
2745
|
+
padding: 4px 10px;
|
|
2746
|
+
font-size: 11px;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2707
2749
|
.trust-summary {
|
|
2708
2750
|
margin-top: 16px;
|
|
2709
2751
|
padding: 24px;
|
|
@@ -5247,6 +5289,53 @@ body:has(.modal.active) .nav-bar {
|
|
|
5247
5289
|
display: block;
|
|
5248
5290
|
}
|
|
5249
5291
|
|
|
5292
|
+
/* Messaging key config (derivation path + algorithm selection) */
|
|
5293
|
+
.messaging-key-config {
|
|
5294
|
+
padding: 14px;
|
|
5295
|
+
margin-bottom: 16px;
|
|
5296
|
+
}
|
|
5297
|
+
|
|
5298
|
+
.messaging-key-config-grid {
|
|
5299
|
+
display: grid;
|
|
5300
|
+
grid-template-columns: 1fr 2fr;
|
|
5301
|
+
gap: 12px;
|
|
5302
|
+
align-items: start;
|
|
5303
|
+
}
|
|
5304
|
+
|
|
5305
|
+
.messaging-key-config-item label {
|
|
5306
|
+
display: block;
|
|
5307
|
+
font-size: 0.7rem;
|
|
5308
|
+
text-transform: uppercase;
|
|
5309
|
+
letter-spacing: 0.06em;
|
|
5310
|
+
color: var(--white-40);
|
|
5311
|
+
margin-bottom: 6px;
|
|
5312
|
+
}
|
|
5313
|
+
|
|
5314
|
+
.messaging-path-row {
|
|
5315
|
+
display: flex;
|
|
5316
|
+
gap: 8px;
|
|
5317
|
+
align-items: center;
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
.messaging-path-row input {
|
|
5321
|
+
flex: 1;
|
|
5322
|
+
min-width: 0;
|
|
5323
|
+
font-family: var(--font-mono);
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5326
|
+
.messaging-key-hint {
|
|
5327
|
+
margin-top: 6px;
|
|
5328
|
+
font-size: 0.7rem;
|
|
5329
|
+
color: var(--white-40);
|
|
5330
|
+
font-family: var(--font-mono);
|
|
5331
|
+
}
|
|
5332
|
+
|
|
5333
|
+
@media (max-width: 640px) {
|
|
5334
|
+
.messaging-key-config-grid {
|
|
5335
|
+
grid-template-columns: 1fr;
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
|
|
5250
5339
|
@media (max-width: 640px) {
|
|
5251
5340
|
.identity-card {
|
|
5252
5341
|
grid-template-columns: 1fr;
|