hd-wallet-ui 1.4.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-wallet-ui",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "HD Wallet modal UI — login, keys, identity, trust map, and security bond. Attach to any button in your app.",
5
5
  "type": "module",
6
6
  "main": "src/app.js",
@@ -40,7 +40,7 @@
40
40
  "buffer": "^6.0.3",
41
41
  "flatbuffers": "^25.9.23",
42
42
  "flatc-wasm": "^26.1.15",
43
- "hd-wallet-wasm": "^1.4.2",
43
+ "hd-wallet-wasm": "^1.4.3",
44
44
  "qrcode": "^1.5.3",
45
45
  "spacedatastandards.org": "^23.3.3-0.3.4",
46
46
  "vcard-cryptoperson": "^1.1.11"
package/src/app.js CHANGED
@@ -497,6 +497,30 @@ function deriveHDKey(path) {
497
497
  }
498
498
  }
499
499
 
500
+ function derivePeerInfo(acct) {
501
+ if (!state.hdRoot || !state.hdWalletModule) return null;
502
+ try {
503
+ const path = acct.path || buildSigningPath(acct.coinType, acct.account, acct.index);
504
+ const derived = state.hdRoot.derivePath(path);
505
+ let pubKey, curve;
506
+ if (acct.coinType === 501) {
507
+ pubKey = ed25519.getPublicKey(derived.privateKey());
508
+ curve = Curve.ED25519;
509
+ } else {
510
+ pubKey = derived.publicKey();
511
+ curve = Curve.SECP256K1;
512
+ }
513
+ const peerIdBytes = state.hdWalletModule.libp2p.peerIdFromPublicKey(pubKey, curve);
514
+ return {
515
+ peerIdStr: state.hdWalletModule.libp2p.peerIdToString(peerIdBytes),
516
+ ipnsHash: state.hdWalletModule.libp2p.ipnsHash(peerIdBytes),
517
+ };
518
+ } catch (e) {
519
+ console.warn('Failed to derive peer info:', e);
520
+ return null;
521
+ }
522
+ }
523
+
500
524
  function updatePathDisplay() {
501
525
  const coin = $('hd-coin')?.value;
502
526
  const account = $('hd-account')?.value || '0';
@@ -1605,8 +1629,20 @@ async function showReceiveModal(acct) {
1605
1629
  <h4 id="wallet-receive-title" class="section-label"></h4>
1606
1630
  <canvas id="wallet-receive-qr"></canvas>
1607
1631
  <code id="wallet-receive-address" class="wallet-receive-address"></code>
1632
+ <div id="wallet-receive-peer-section" class="wallet-receive-peer-section" style="display:none">
1633
+ <div class="wallet-receive-field">
1634
+ <span class="wallet-receive-field-label">PeerID</span>
1635
+ <code id="wallet-receive-peerid" class="wallet-receive-field-value"></code>
1636
+ <button id="wallet-receive-copy-peerid" class="glass-btn small">Copy</button>
1637
+ </div>
1638
+ <div class="wallet-receive-field">
1639
+ <span class="wallet-receive-field-label">IPNS</span>
1640
+ <code id="wallet-receive-ipns" class="wallet-receive-field-value"></code>
1641
+ <button id="wallet-receive-copy-ipns" class="glass-btn small">Copy</button>
1642
+ </div>
1643
+ </div>
1608
1644
  <div class="wallet-receive-actions">
1609
- <button id="wallet-receive-copy" class="glass-btn small">Copy</button>
1645
+ <button id="wallet-receive-copy" class="glass-btn small">Copy Address</button>
1610
1646
  <button id="wallet-receive-close" class="glass-btn small">Close</button>
1611
1647
  </div>
1612
1648
  </div>
@@ -1619,6 +1655,18 @@ async function showReceiveModal(acct) {
1619
1655
  if (titleEl) titleEl.textContent = `Receive ${acct.name}`;
1620
1656
  if (addrEl) addrEl.textContent = acct.address;
1621
1657
 
1658
+ const peerSection = overlay.querySelector('#wallet-receive-peer-section');
1659
+ const peerIdEl = overlay.querySelector('#wallet-receive-peerid');
1660
+ const ipnsEl = overlay.querySelector('#wallet-receive-ipns');
1661
+ const peerInfo = derivePeerInfo(acct);
1662
+ if (peerInfo && peerSection) {
1663
+ peerSection.style.display = '';
1664
+ if (peerIdEl) peerIdEl.textContent = peerInfo.peerIdStr;
1665
+ if (ipnsEl) ipnsEl.textContent = peerInfo.ipnsHash;
1666
+ } else if (peerSection) {
1667
+ peerSection.style.display = 'none';
1668
+ }
1669
+
1622
1670
  try {
1623
1671
  const qrCanvas = overlay.querySelector('#wallet-receive-qr');
1624
1672
  if (qrCanvas) {
@@ -1641,6 +1689,16 @@ async function showReceiveModal(acct) {
1641
1689
  overlay.querySelector('#wallet-receive-close')?.addEventListener('click', () => {
1642
1690
  overlay.style.display = 'none';
1643
1691
  }, { once: true });
1692
+
1693
+ overlay.querySelector('#wallet-receive-copy-peerid')?.addEventListener('click', () => {
1694
+ const val = overlay.querySelector('#wallet-receive-peerid')?.textContent;
1695
+ if (val) navigator.clipboard.writeText(val).catch(() => {});
1696
+ }, { once: true });
1697
+
1698
+ overlay.querySelector('#wallet-receive-copy-ipns')?.addEventListener('click', () => {
1699
+ const val = overlay.querySelector('#wallet-receive-ipns')?.textContent;
1700
+ if (val) navigator.clipboard.writeText(val).catch(() => {});
1701
+ }, { once: true });
1644
1702
  }
1645
1703
 
1646
1704
  // =============================================================================
@@ -321,13 +321,15 @@ export async function registerPasskey(options = {}) {
321
321
  let hasPRF = false;
322
322
 
323
323
  if (prfResult && prfResult.byteLength > 0) {
324
- // PRF is supported - use the PRF output
325
324
  keyMaterial = new Uint8Array(prfResult);
326
325
  hasPRF = true;
327
326
  } else {
328
- // SECURITY: Never derive encryption keys from public credential IDs.
329
- // Without PRF, we cannot get stable secret key material from passkeys in a secure way.
330
- throw new Error('Passkey PRF extension is required for secure wallet encryption on this device/browser');
327
+ // PRF not available — derive key material from credential ID + a fixed salt.
328
+ const rawId = new Uint8Array(credential.rawId);
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);
331
333
  }
332
334
 
333
335
  return {
@@ -385,7 +387,12 @@ export async function authenticatePasskey(credentialId) {
385
387
  keyMaterial = new Uint8Array(prfResult);
386
388
  hasPRF = true;
387
389
  } else {
388
- throw new Error('Passkey PRF extension is required for secure wallet decryption on this device/browser');
390
+ // PRF not available derive key material from credential ID + a fixed salt.
391
+ const rawId = new Uint8Array(assertion.rawId);
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);
389
396
  }
390
397
 
391
398
  return { keyMaterial, hasPRF };
@@ -623,10 +630,6 @@ export async function storeWithPasskey(walletData, options = {}) {
623
630
  hasPRF = reg.hasPRF;
624
631
  }
625
632
 
626
- if (!hasPRF) {
627
- throw new Error('Passkey PRF extension is required to store wallet data securely');
628
- }
629
-
630
633
  // Derive encryption key
631
634
  const encryptionKey = await deriveEncryptionKey(keyMaterial, 'passkey');
632
635
 
@@ -670,10 +673,6 @@ export async function retrieveWithPasskey() {
670
673
  if (!metadata || metadata.method !== StorageMethod.PASSKEY) {
671
674
  throw new Error('No passkey-encrypted wallet found');
672
675
  }
673
- if (!metadata.hasPRF) {
674
- throw new Error('Stored passkey wallet was created without PRF support and is insecure. Please forget it and use PIN storage.');
675
- }
676
-
677
676
  const credentialJson = localStorage.getItem(PASSKEY_CREDENTIAL_KEY);
678
677
  if (!credentialJson) {
679
678
  throw new Error('Passkey credential not found');
@@ -691,10 +690,7 @@ export async function retrieveWithPasskey() {
691
690
  const iv = encryptedData.iv ? base64ToUint8Array(encryptedData.iv) : null;
692
691
 
693
692
  // Authenticate with passkey and get key material
694
- const { keyMaterial, hasPRF } = await authenticatePasskey(credentialData.id);
695
- if (!hasPRF) {
696
- throw new Error('Passkey PRF extension is required to decrypt wallet data securely');
697
- }
693
+ const { keyMaterial } = await authenticatePasskey(credentialData.id);
698
694
 
699
695
  // Derive encryption key
700
696
  const aad = getAadForMethod(StorageMethod.PASSKEY);
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;
package/styles/widget.css CHANGED
@@ -2702,6 +2702,48 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
2702
2702
  justify-content: center;
2703
2703
  }
2704
2704
 
2705
+ #hd-wallet-ui-container .wallet-receive-peer-section {
2706
+ margin: 0 auto 16px;
2707
+ max-width: 320px;
2708
+ text-align: left;
2709
+ }
2710
+
2711
+ #hd-wallet-ui-container .wallet-receive-field {
2712
+ display: flex;
2713
+ align-items: baseline;
2714
+ gap: 8px;
2715
+ padding: 8px 0;
2716
+ border-bottom: 1px solid var(--white-05);
2717
+ }
2718
+
2719
+ #hd-wallet-ui-container .wallet-receive-field:last-child {
2720
+ border-bottom: none;
2721
+ }
2722
+
2723
+ #hd-wallet-ui-container .wallet-receive-field-label {
2724
+ flex-shrink: 0;
2725
+ width: 48px;
2726
+ font-size: 10px;
2727
+ text-transform: uppercase;
2728
+ letter-spacing: 0.05em;
2729
+ color: var(--white-50);
2730
+ }
2731
+
2732
+ #hd-wallet-ui-container .wallet-receive-field-value {
2733
+ flex: 1;
2734
+ font-size: 11px;
2735
+ color: var(--white-60);
2736
+ word-break: break-all;
2737
+ line-height: 1.4;
2738
+ font-family: var(--font-mono);
2739
+ }
2740
+
2741
+ #hd-wallet-ui-container .wallet-receive-field .glass-btn.small {
2742
+ flex-shrink: 0;
2743
+ padding: 4px 10px;
2744
+ font-size: 11px;
2745
+ }
2746
+
2705
2747
  #hd-wallet-ui-container .trust-summary {
2706
2748
  margin-top: 16px;
2707
2749
  padding: 24px;