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 +2 -2
- package/src/app.js +59 -1
- package/src/wallet-storage.js +13 -17
- package/styles/main.css +42 -0
- package/styles/widget.css +42 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hd-wallet-ui",
|
|
3
|
-
"version": "1.4.
|
|
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.
|
|
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
|
// =============================================================================
|
package/src/wallet-storage.js
CHANGED
|
@@ -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
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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
|
|
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;
|