hd-wallet-ui 1.0.0 → 1.1.2
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 +11 -3
- package/src/app.js +290 -1
- package/src/template.js +117 -6
- package/styles/main.css +278 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hd-wallet-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"bip39": "^3.1.0",
|
|
35
35
|
"buffer": "^6.0.3",
|
|
36
36
|
"flatbuffers": "^25.9.23",
|
|
37
|
-
"hd-wallet-wasm": "
|
|
37
|
+
"hd-wallet-wasm": "^1.1.2",
|
|
38
38
|
"qrcode": "^1.5.3",
|
|
39
39
|
"vcard-cryptoperson": "^1.1.10"
|
|
40
40
|
},
|
|
@@ -55,5 +55,13 @@
|
|
|
55
55
|
"ethereum",
|
|
56
56
|
"solana"
|
|
57
57
|
],
|
|
58
|
-
"license": "Apache-2.0"
|
|
58
|
+
"license": "Apache-2.0",
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "git+https://github.com/DigitalArsenal/hd-wallet-wasm.git"
|
|
62
|
+
},
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/DigitalArsenal/hd-wallet-wasm/issues"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://digitalarsenal.github.io/hd-wallet-wasm/"
|
|
59
67
|
}
|
package/src/app.js
CHANGED
|
@@ -19,6 +19,10 @@ import QRCode from 'qrcode';
|
|
|
19
19
|
import { Buffer } from 'buffer';
|
|
20
20
|
import { createV3 } from 'vcard-cryptoperson';
|
|
21
21
|
|
|
22
|
+
// SpaceDataStandards EME (Encrypted Message Envelope)
|
|
23
|
+
import { EME, EMET } from '@sds/lib/js/ts/EME/EME.js';
|
|
24
|
+
import * as flatbuffers from 'flatbuffers';
|
|
25
|
+
|
|
22
26
|
// Make Buffer available globally for various crypto libraries
|
|
23
27
|
window.Buffer = Buffer;
|
|
24
28
|
|
|
@@ -34,6 +38,7 @@ import {
|
|
|
34
38
|
coinTypeToConfig,
|
|
35
39
|
buildSigningPath,
|
|
36
40
|
buildEncryptionPath,
|
|
41
|
+
PKI_STORAGE_KEY,
|
|
37
42
|
} from './constants.js';
|
|
38
43
|
|
|
39
44
|
import {
|
|
@@ -1626,7 +1631,7 @@ async function updateAdversarialSecurity() {
|
|
|
1626
1631
|
// Update account header total value
|
|
1627
1632
|
const accountTotalEl = $('account-total-value');
|
|
1628
1633
|
if (accountTotalEl) {
|
|
1629
|
-
accountTotalEl.textContent = '
|
|
1634
|
+
accountTotalEl.textContent = 'Bond: ' + formatCurrencyValue(totalConverted, currency);
|
|
1630
1635
|
}
|
|
1631
1636
|
|
|
1632
1637
|
// Update account address dropdown values
|
|
@@ -2772,14 +2777,17 @@ function setupMainAppHandlers() {
|
|
|
2772
2777
|
$('hd-coin')?.addEventListener('change', () => {
|
|
2773
2778
|
updatePathDisplay();
|
|
2774
2779
|
deriveAndDisplayAddress();
|
|
2780
|
+
updateEncryptionTab();
|
|
2775
2781
|
});
|
|
2776
2782
|
$('hd-account')?.addEventListener('input', () => {
|
|
2777
2783
|
updatePathDisplay();
|
|
2778
2784
|
deriveAndDisplayAddress();
|
|
2785
|
+
updateEncryptionTab();
|
|
2779
2786
|
});
|
|
2780
2787
|
$('hd-index')?.addEventListener('input', () => {
|
|
2781
2788
|
updatePathDisplay();
|
|
2782
2789
|
deriveAndDisplayAddress();
|
|
2790
|
+
updateEncryptionTab();
|
|
2783
2791
|
});
|
|
2784
2792
|
|
|
2785
2793
|
// PKI clear keys
|
|
@@ -3129,6 +3137,287 @@ function setupTrustHandlers() {
|
|
|
3129
3137
|
// Expose start/stop for login/logout
|
|
3130
3138
|
state._startTrustScanning = startTrustScanning;
|
|
3131
3139
|
state._stopTrustScanning = stopTrustScanning;
|
|
3140
|
+
|
|
3141
|
+
// =========================================================================
|
|
3142
|
+
// Encryption Tab Handlers (ECIES: ECDH + HKDF + AES-256-GCM)
|
|
3143
|
+
// =========================================================================
|
|
3144
|
+
|
|
3145
|
+
function updateEncryptionTab() {
|
|
3146
|
+
if (!state.hdRoot || !state.hdWalletModule) return;
|
|
3147
|
+
const coin = $('hd-coin')?.value || '0';
|
|
3148
|
+
const account = $('hd-account')?.value || '0';
|
|
3149
|
+
const index = $('hd-index')?.value || '0';
|
|
3150
|
+
const encPath = buildEncryptionPath(coin, account, index);
|
|
3151
|
+
const encKey = deriveHDKey(encPath);
|
|
3152
|
+
const pubKey = encKey.publicKey();
|
|
3153
|
+
const pubHex = toHexCompact(pubKey);
|
|
3154
|
+
|
|
3155
|
+
const senderPubEl = $('encrypt-sender-pubkey');
|
|
3156
|
+
const senderPathEl = $('encrypt-sender-path');
|
|
3157
|
+
if (senderPubEl) senderPubEl.textContent = pubHex;
|
|
3158
|
+
if (senderPathEl) senderPathEl.textContent = encPath;
|
|
3159
|
+
|
|
3160
|
+
const encryptBtn = $('encrypt-btn');
|
|
3161
|
+
if (encryptBtn) encryptBtn.disabled = false;
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
// Update encryption tab when it becomes active or HD controls change
|
|
3165
|
+
$qa('.modal-tab[data-modal-tab="encrypt-tab-content"]').forEach(tab => {
|
|
3166
|
+
tab.addEventListener('click', () => {
|
|
3167
|
+
if (state.hdRoot) updateEncryptionTab();
|
|
3168
|
+
});
|
|
3169
|
+
});
|
|
3170
|
+
|
|
3171
|
+
// "Self" button - fill recipient with own public key for testing
|
|
3172
|
+
$('encrypt-use-self')?.addEventListener('click', () => {
|
|
3173
|
+
const senderPub = $('encrypt-sender-pubkey')?.textContent;
|
|
3174
|
+
if (senderPub && senderPub !== '--') {
|
|
3175
|
+
$('encrypt-recipient-pubkey').value = senderPub;
|
|
3176
|
+
}
|
|
3177
|
+
});
|
|
3178
|
+
|
|
3179
|
+
// ---- EME state for current encryption result ----
|
|
3180
|
+
let currentEME = null; // EMET instance
|
|
3181
|
+
let currentFormat = 'json';
|
|
3182
|
+
|
|
3183
|
+
function emeToJSON(eme) {
|
|
3184
|
+
return JSON.stringify({
|
|
3185
|
+
ENCRYPTED_BLOB: eme.ENCRYPTED_BLOB,
|
|
3186
|
+
EPHEMERAL_PUBLIC_KEY: eme.EPHEMERAL_PUBLIC_KEY,
|
|
3187
|
+
MAC: eme.MAC,
|
|
3188
|
+
NONCE: eme.NONCE,
|
|
3189
|
+
TAG: eme.TAG,
|
|
3190
|
+
IV: eme.IV,
|
|
3191
|
+
SALT: eme.SALT,
|
|
3192
|
+
PUBLIC_KEY_IDENTIFIER: eme.PUBLIC_KEY_IDENTIFIER,
|
|
3193
|
+
CIPHER_SUITE: eme.CIPHER_SUITE,
|
|
3194
|
+
KDF_PARAMETERS: eme.KDF_PARAMETERS,
|
|
3195
|
+
ENCRYPTION_ALGORITHM_PARAMETERS: eme.ENCRYPTION_ALGORITHM_PARAMETERS,
|
|
3196
|
+
}, null, 2);
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
function emeToFlatBuffer(eme) {
|
|
3200
|
+
const builder = new flatbuffers.Builder(1);
|
|
3201
|
+
const packed = eme.pack(builder);
|
|
3202
|
+
builder.finishSizePrefixed(packed, '$EME');
|
|
3203
|
+
return builder.asUint8Array();
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
function emeToFlatBufferBase64(eme) {
|
|
3207
|
+
return Buffer.from(emeToFlatBuffer(eme)).toString('base64');
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
function updateBundleDisplay() {
|
|
3211
|
+
if (!currentEME) return;
|
|
3212
|
+
const textarea = $('encrypt-bundle');
|
|
3213
|
+
if (!textarea) return;
|
|
3214
|
+
if (currentFormat === 'json') {
|
|
3215
|
+
textarea.value = emeToJSON(currentEME);
|
|
3216
|
+
} else {
|
|
3217
|
+
textarea.value = emeToFlatBufferBase64(currentEME);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
// Format toggle buttons
|
|
3222
|
+
$qa('.encrypt-fmt-btn').forEach(btn => {
|
|
3223
|
+
btn.addEventListener('click', () => {
|
|
3224
|
+
$qa('.encrypt-fmt-btn').forEach(b => b.classList.remove('active'));
|
|
3225
|
+
btn.classList.add('active');
|
|
3226
|
+
currentFormat = btn.dataset.format;
|
|
3227
|
+
const label = $('encrypt-format-label');
|
|
3228
|
+
if (label) label.textContent = currentFormat === 'json'
|
|
3229
|
+
? 'EME (Encrypted Message Envelope) — SpaceDataStandards.org'
|
|
3230
|
+
: 'EME FlatBuffer binary (base64-encoded)';
|
|
3231
|
+
updateBundleDisplay();
|
|
3232
|
+
});
|
|
3233
|
+
});
|
|
3234
|
+
|
|
3235
|
+
// Encrypt button
|
|
3236
|
+
$('encrypt-btn')?.addEventListener('click', () => {
|
|
3237
|
+
const w = state.hdWalletModule;
|
|
3238
|
+
if (!w || !state.hdRoot) return;
|
|
3239
|
+
|
|
3240
|
+
const recipientHex = $('encrypt-recipient-pubkey')?.value?.trim();
|
|
3241
|
+
const plainStr = $('encrypt-plaintext')?.value;
|
|
3242
|
+
if (!recipientHex || !plainStr) {
|
|
3243
|
+
alert('Please enter both a recipient public key and a message.');
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
try {
|
|
3248
|
+
const coin = $('hd-coin')?.value || '0';
|
|
3249
|
+
const account = $('hd-account')?.value || '0';
|
|
3250
|
+
const index = $('hd-index')?.value || '0';
|
|
3251
|
+
const encPath = buildEncryptionPath(coin, account, index);
|
|
3252
|
+
const senderKey = deriveHDKey(encPath);
|
|
3253
|
+
const senderPriv = senderKey.privateKey();
|
|
3254
|
+
const senderPub = senderKey.publicKey();
|
|
3255
|
+
|
|
3256
|
+
// Parse recipient public key from hex
|
|
3257
|
+
const recipientPub = new Uint8Array(recipientHex.match(/.{1,2}/g).map(b => parseInt(b, 16)));
|
|
3258
|
+
|
|
3259
|
+
// 1. ECDH shared secret
|
|
3260
|
+
const shared = w.curves.secp256k1.ecdh(senderPriv, recipientPub);
|
|
3261
|
+
|
|
3262
|
+
// 2. HKDF: derive 32-byte AES key from shared secret
|
|
3263
|
+
const salt = w.utils.getRandomBytes(32);
|
|
3264
|
+
const info = new TextEncoder().encode('ecies-secp256k1-aes256gcm');
|
|
3265
|
+
const aesKey = w.utils.hkdf(shared, salt, info, 32);
|
|
3266
|
+
|
|
3267
|
+
// 3. AES-256-GCM encrypt
|
|
3268
|
+
const iv = w.utils.generateIv();
|
|
3269
|
+
const plaintext = new TextEncoder().encode(plainStr);
|
|
3270
|
+
const { ciphertext, tag } = w.utils.aesGcm.encrypt(aesKey, plaintext, iv);
|
|
3271
|
+
|
|
3272
|
+
// Display field-level results
|
|
3273
|
+
$('encrypt-out-ciphertext').textContent = toHexCompact(ciphertext);
|
|
3274
|
+
$('encrypt-out-tag').textContent = toHexCompact(tag);
|
|
3275
|
+
$('encrypt-out-iv').textContent = toHexCompact(iv);
|
|
3276
|
+
$('encrypt-out-salt').textContent = toHexCompact(salt);
|
|
3277
|
+
$('encrypt-out-sender-pub').textContent = toHexCompact(senderPub);
|
|
3278
|
+
// Build EME (Encrypted Message Envelope) standard object
|
|
3279
|
+
currentEME = new EMET(
|
|
3280
|
+
Array.from(ciphertext), // ENCRYPTED_BLOB
|
|
3281
|
+
toHexCompact(senderPub), // EPHEMERAL_PUBLIC_KEY
|
|
3282
|
+
null, // MAC (not used, tag covers it)
|
|
3283
|
+
null, // NONCE (we use IV field instead)
|
|
3284
|
+
toHexCompact(tag), // TAG
|
|
3285
|
+
toHexCompact(iv), // IV
|
|
3286
|
+
toHexCompact(salt), // SALT
|
|
3287
|
+
null, // PUBLIC_KEY_IDENTIFIER
|
|
3288
|
+
'aes-256-gcm', // CIPHER_SUITE
|
|
3289
|
+
'hkdf-sha256', // KDF_PARAMETERS
|
|
3290
|
+
'secp256k1', // ENCRYPTION_ALGORITHM_PARAMETERS
|
|
3291
|
+
);
|
|
3292
|
+
|
|
3293
|
+
updateBundleDisplay();
|
|
3294
|
+
|
|
3295
|
+
// Switch to result step
|
|
3296
|
+
$('encrypt-step-compose').style.display = 'none';
|
|
3297
|
+
$('encrypt-step-result').style.display = 'block';
|
|
3298
|
+
} catch (err) {
|
|
3299
|
+
console.error('Encryption failed:', err);
|
|
3300
|
+
alert('Encryption failed: ' + err.message);
|
|
3301
|
+
}
|
|
3302
|
+
});
|
|
3303
|
+
|
|
3304
|
+
// Copy bundle
|
|
3305
|
+
$('encrypt-copy-bundle')?.addEventListener('click', () => {
|
|
3306
|
+
const bundle = $('encrypt-bundle')?.value;
|
|
3307
|
+
if (bundle) {
|
|
3308
|
+
navigator.clipboard.writeText(bundle).catch(() => {});
|
|
3309
|
+
}
|
|
3310
|
+
});
|
|
3311
|
+
|
|
3312
|
+
// Download bundle
|
|
3313
|
+
$('encrypt-download-bundle')?.addEventListener('click', () => {
|
|
3314
|
+
if (!currentEME) return;
|
|
3315
|
+
let blob, filename;
|
|
3316
|
+
if (currentFormat === 'json') {
|
|
3317
|
+
blob = new Blob([emeToJSON(currentEME)], { type: 'application/json' });
|
|
3318
|
+
filename = 'message.eme.json';
|
|
3319
|
+
} else {
|
|
3320
|
+
const buf = emeToFlatBuffer(currentEME);
|
|
3321
|
+
blob = new Blob([buf], { type: 'application/octet-stream' });
|
|
3322
|
+
filename = 'message.eme.fbs';
|
|
3323
|
+
}
|
|
3324
|
+
const url = URL.createObjectURL(blob);
|
|
3325
|
+
const a = document.createElement('a');
|
|
3326
|
+
a.href = url;
|
|
3327
|
+
a.download = filename;
|
|
3328
|
+
a.click();
|
|
3329
|
+
URL.revokeObjectURL(url);
|
|
3330
|
+
});
|
|
3331
|
+
|
|
3332
|
+
// Parse EME from input (JSON or base64 FlatBuffer)
|
|
3333
|
+
function parseEMEPayload(input) {
|
|
3334
|
+
const trimmed = input.trim();
|
|
3335
|
+
// Try JSON first
|
|
3336
|
+
if (trimmed.startsWith('{')) {
|
|
3337
|
+
return JSON.parse(trimmed);
|
|
3338
|
+
}
|
|
3339
|
+
// Try base64 FlatBuffer (size-prefixed)
|
|
3340
|
+
const buf = new Uint8Array(Buffer.from(trimmed, 'base64'));
|
|
3341
|
+
const bb = new flatbuffers.ByteBuffer(buf);
|
|
3342
|
+
const root = EME.getSizePrefixedRootAsEME(bb);
|
|
3343
|
+
const eme = root.unpack();
|
|
3344
|
+
return eme;
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// Decrypt button
|
|
3348
|
+
$('decrypt-btn')?.addEventListener('click', () => {
|
|
3349
|
+
const w = state.hdWalletModule;
|
|
3350
|
+
if (!w || !state.hdRoot) return;
|
|
3351
|
+
|
|
3352
|
+
const payloadStr = $('decrypt-payload')?.value?.trim();
|
|
3353
|
+
if (!payloadStr) {
|
|
3354
|
+
alert('Paste an EME payload to decrypt.');
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
try {
|
|
3359
|
+
const payload = parseEMEPayload(payloadStr);
|
|
3360
|
+
const fromHex = (h) => new Uint8Array(h.match(/.{1,2}/g).map(b => parseInt(b, 16)));
|
|
3361
|
+
|
|
3362
|
+
const senderPub = fromHex(payload.EPHEMERAL_PUBLIC_KEY);
|
|
3363
|
+
const tag = fromHex(payload.TAG);
|
|
3364
|
+
const iv = fromHex(payload.IV);
|
|
3365
|
+
const salt = fromHex(payload.SALT);
|
|
3366
|
+
|
|
3367
|
+
// ENCRYPTED_BLOB can be a number array (from EMET) or hex string
|
|
3368
|
+
let ciphertext;
|
|
3369
|
+
if (Array.isArray(payload.ENCRYPTED_BLOB)) {
|
|
3370
|
+
ciphertext = new Uint8Array(payload.ENCRYPTED_BLOB);
|
|
3371
|
+
} else {
|
|
3372
|
+
ciphertext = fromHex(payload.ENCRYPTED_BLOB);
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
const coin = $('hd-coin')?.value || '0';
|
|
3376
|
+
const account = $('hd-account')?.value || '0';
|
|
3377
|
+
const index = $('hd-index')?.value || '0';
|
|
3378
|
+
const encPath = buildEncryptionPath(coin, account, index);
|
|
3379
|
+
const recipientKey = deriveHDKey(encPath);
|
|
3380
|
+
const recipientPriv = recipientKey.privateKey();
|
|
3381
|
+
|
|
3382
|
+
// 1. ECDH shared secret (using sender's public key)
|
|
3383
|
+
const shared = w.curves.secp256k1.ecdh(recipientPriv, senderPub);
|
|
3384
|
+
|
|
3385
|
+
// 2. HKDF: derive same AES key
|
|
3386
|
+
const info = new TextEncoder().encode('ecies-secp256k1-aes256gcm');
|
|
3387
|
+
const aesKey = w.utils.hkdf(shared, salt, info, 32);
|
|
3388
|
+
|
|
3389
|
+
// 3. AES-256-GCM decrypt
|
|
3390
|
+
const decrypted = w.utils.aesGcm.decrypt(aesKey, ciphertext, tag, iv);
|
|
3391
|
+
const decStr = new TextDecoder().decode(decrypted);
|
|
3392
|
+
|
|
3393
|
+
$('decrypt-result-value').textContent = decStr;
|
|
3394
|
+
|
|
3395
|
+
// Switch to result step
|
|
3396
|
+
$('decrypt-step-input').style.display = 'none';
|
|
3397
|
+
$('decrypt-step-result').style.display = 'block';
|
|
3398
|
+
} catch (err) {
|
|
3399
|
+
console.error('Decryption failed:', err);
|
|
3400
|
+
alert('Decryption failed: ' + err.message);
|
|
3401
|
+
}
|
|
3402
|
+
});
|
|
3403
|
+
|
|
3404
|
+
// Enable decrypt button when payload is pasted
|
|
3405
|
+
$('decrypt-payload')?.addEventListener('input', () => {
|
|
3406
|
+
const btn = $('decrypt-btn');
|
|
3407
|
+
if (btn) btn.disabled = !$('decrypt-payload')?.value?.trim();
|
|
3408
|
+
});
|
|
3409
|
+
|
|
3410
|
+
// Back button: encrypt result -> compose
|
|
3411
|
+
$('encrypt-back-btn')?.addEventListener('click', () => {
|
|
3412
|
+
$('encrypt-step-result').style.display = 'none';
|
|
3413
|
+
$('encrypt-step-compose').style.display = 'block';
|
|
3414
|
+
});
|
|
3415
|
+
|
|
3416
|
+
// Back button: decrypt result -> input
|
|
3417
|
+
$('decrypt-back-btn')?.addEventListener('click', () => {
|
|
3418
|
+
$('decrypt-step-result').style.display = 'none';
|
|
3419
|
+
$('decrypt-step-input').style.display = 'block';
|
|
3420
|
+
});
|
|
3132
3421
|
}
|
|
3133
3422
|
|
|
3134
3423
|
// =============================================================================
|
package/src/template.js
CHANGED
|
@@ -4,13 +4,15 @@ export function getModalHTML() {
|
|
|
4
4
|
<div id="keys-modal" class="modal">
|
|
5
5
|
<div class="modal-glass modal-wide">
|
|
6
6
|
<div class="modal-header"><div class="account-header-info"><div class="account-header-top"><h3>Account</h3><h3 class="account-total-value" id="account-total-value"></h3></div><div class="account-address-row"><select id="account-address-select" class="glass-select compact account-address-select"><option value="xpub">xpub</option></select><code class="account-address-display" id="account-address-display"></code><button class="account-address-copy" id="account-address-copy" title="Copy address"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button><span class="account-address-value" id="account-address-value"></span></div></div><button class="modal-close">×</button></div>
|
|
7
|
+
<div class="modal-tabs">
|
|
8
|
+
<button class="modal-tab active" data-modal-tab="vcard-tab-content">Identity</button>
|
|
9
|
+
<button class="modal-tab" data-modal-tab="keys-tab-content">Keys</button>
|
|
10
|
+
<button class="modal-tab" data-modal-tab="trust-tab-content">Trust Map</button>
|
|
11
|
+
<button class="modal-tab" data-modal-tab="bond-tab-content">Bond</button>
|
|
12
|
+
<button class="modal-tab" data-modal-tab="encrypt-tab-content">Encrypt</button>
|
|
13
|
+
<button class="modal-tab" data-modal-tab="decrypt-tab-content">Decrypt</button>
|
|
14
|
+
</div>
|
|
7
15
|
<div class="modal-body">
|
|
8
|
-
<div class="modal-tabs">
|
|
9
|
-
<button class="modal-tab active" data-modal-tab="vcard-tab-content">Identity</button>
|
|
10
|
-
<button class="modal-tab" data-modal-tab="keys-tab-content">Keys</button>
|
|
11
|
-
<button class="modal-tab" data-modal-tab="trust-tab-content">Trust Map</button>
|
|
12
|
-
<button class="modal-tab" data-modal-tab="bond-tab-content">Security Bond</button>
|
|
13
|
-
</div>
|
|
14
16
|
<div id="keys-tab-content" class="modal-tab-content">
|
|
15
17
|
<div id="memory-info-box" class="memory-info-box" style="display:none"><div class="memory-info-content"><p><strong>Your keys never leave your device.</strong></p></div><button class="wallet-info-close" id="memory-info-close" title="Close">×</button></div>
|
|
16
18
|
<div class="xpub-section">
|
|
@@ -271,6 +273,115 @@ export function getModalHTML() {
|
|
|
271
273
|
</div>
|
|
272
274
|
</div>
|
|
273
275
|
|
|
276
|
+
<!-- Encrypt Tab -->
|
|
277
|
+
<div id="encrypt-tab-content" class="modal-tab-content">
|
|
278
|
+
<!-- Step 1: Compose -->
|
|
279
|
+
<div id="encrypt-step-compose" class="encrypt-step">
|
|
280
|
+
<div class="encrypt-tab-intro">
|
|
281
|
+
<h4 class="section-label">Encrypt a Message</h4>
|
|
282
|
+
<p>ECDH key agreement + HKDF + AES-256-GCM (ECIES)</p>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div class="encrypt-keys-section">
|
|
286
|
+
<div class="encrypt-key-row">
|
|
287
|
+
<div class="encrypt-key-card glass-card">
|
|
288
|
+
<div class="encrypt-key-header">
|
|
289
|
+
<span class="encrypt-role-badge sender">Sender (You)</span>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="encrypt-key-detail">
|
|
292
|
+
<label>Encryption Public Key</label>
|
|
293
|
+
<code id="encrypt-sender-pubkey" class="truncate">--</code>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="encrypt-key-detail">
|
|
296
|
+
<label>Derivation Path</label>
|
|
297
|
+
<code id="encrypt-sender-path">--</code>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
<div class="encrypt-key-card glass-card">
|
|
301
|
+
<div class="encrypt-key-header">
|
|
302
|
+
<span class="encrypt-role-badge recipient">Recipient</span>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="encrypt-key-detail">
|
|
305
|
+
<label>Recipient Public Key (hex)</label>
|
|
306
|
+
<div class="encrypt-recipient-input-row">
|
|
307
|
+
<input type="text" id="encrypt-recipient-pubkey" class="glass-input compact" placeholder="Paste recipient's secp256k1 public key (hex)">
|
|
308
|
+
<button id="encrypt-use-self" class="glass-btn small" title="Use your own key (for testing)">Self</button>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div class="encrypt-message-section">
|
|
316
|
+
<div class="encrypt-input-group">
|
|
317
|
+
<label class="section-label">Message</label>
|
|
318
|
+
<textarea id="encrypt-plaintext" class="glass-input glass-textarea" rows="3" placeholder="Enter a message to encrypt..."></textarea>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="encrypt-actions">
|
|
321
|
+
<button id="encrypt-btn" class="glass-btn primary" disabled>Encrypt</button>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<!-- Step 2: Result (replaces compose) -->
|
|
327
|
+
<div id="encrypt-step-result" class="encrypt-step" style="display:none;">
|
|
328
|
+
<div class="encrypt-step-header">
|
|
329
|
+
<button id="encrypt-back-btn" class="glass-btn small encrypt-back-btn"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> Back</button>
|
|
330
|
+
<h4 class="section-label">Encrypted Payload</h4>
|
|
331
|
+
</div>
|
|
332
|
+
<div class="encrypt-output-fields">
|
|
333
|
+
<div class="encrypt-field"><label>Ciphertext</label><code id="encrypt-out-ciphertext" class="encrypt-out-value truncate"></code><button class="copy-btn" data-copy="encrypt-out-ciphertext" title="Copy"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button></div>
|
|
334
|
+
<div class="encrypt-field"><label>Auth Tag</label><code id="encrypt-out-tag" class="encrypt-out-value truncate"></code></div>
|
|
335
|
+
<div class="encrypt-field"><label>IV (nonce)</label><code id="encrypt-out-iv" class="encrypt-out-value truncate"></code></div>
|
|
336
|
+
<div class="encrypt-field"><label>HKDF Salt</label><code id="encrypt-out-salt" class="encrypt-out-value truncate"></code></div>
|
|
337
|
+
<div class="encrypt-field"><label>Sender Public Key</label><code id="encrypt-out-sender-pub" class="encrypt-out-value truncate"></code></div>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="encrypt-bundle-group">
|
|
340
|
+
<div class="encrypt-format-toggle">
|
|
341
|
+
<label class="section-label">Payload Bundle</label>
|
|
342
|
+
<div class="encrypt-format-btns">
|
|
343
|
+
<button class="glass-btn small encrypt-fmt-btn active" data-format="json">JSON</button>
|
|
344
|
+
<button class="glass-btn small encrypt-fmt-btn" data-format="flatbuffer">FlatBuffer</button>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="encrypt-format-info">
|
|
348
|
+
<span id="encrypt-format-label" class="encrypt-format-label">EME (Encrypted Message Envelope) — SpaceDataStandards.org</span>
|
|
349
|
+
</div>
|
|
350
|
+
<textarea id="encrypt-bundle" class="glass-input glass-textarea" rows="4" readonly></textarea>
|
|
351
|
+
<div class="encrypt-bundle-actions">
|
|
352
|
+
<button class="glass-btn small" id="encrypt-copy-bundle">Copy</button>
|
|
353
|
+
<button class="glass-btn small" id="encrypt-download-bundle">Download</button>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<!-- Decrypt Tab -->
|
|
360
|
+
<div id="decrypt-tab-content" class="modal-tab-content">
|
|
361
|
+
<!-- Step 1: Input -->
|
|
362
|
+
<div id="decrypt-step-input" class="encrypt-step">
|
|
363
|
+
<div class="encrypt-tab-intro">
|
|
364
|
+
<h4 class="section-label">Decrypt a Message</h4>
|
|
365
|
+
<p>Paste an EME payload (JSON or base64 FlatBuffer) to decrypt with your key.</p>
|
|
366
|
+
</div>
|
|
367
|
+
<textarea id="decrypt-payload" class="glass-input glass-textarea" rows="6" placeholder='Paste EME JSON or base64 FlatBuffer here...'></textarea>
|
|
368
|
+
<div class="encrypt-actions">
|
|
369
|
+
<button id="decrypt-btn" class="glass-btn primary" disabled>Decrypt</button>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<!-- Step 2: Result (replaces input) -->
|
|
374
|
+
<div id="decrypt-step-result" class="encrypt-step" style="display:none;">
|
|
375
|
+
<div class="encrypt-step-header">
|
|
376
|
+
<button id="decrypt-back-btn" class="glass-btn small encrypt-back-btn"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> Back</button>
|
|
377
|
+
<h4 class="section-label">Decrypted Message</h4>
|
|
378
|
+
</div>
|
|
379
|
+
<div class="decrypt-result">
|
|
380
|
+
<div class="decrypt-result-value" id="decrypt-result-value"></div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
274
385
|
</div>
|
|
275
386
|
</div>
|
|
276
387
|
</div>
|
package/styles/main.css
CHANGED
|
@@ -616,39 +616,42 @@ body:has(.modal.active) .nav-bar {
|
|
|
616
616
|
display: block;
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
-
/* Modal Tabs (Account modal - Keys / vCard) */
|
|
619
|
+
/* Modal Tabs (Account modal - Keys / vCard) - Bootstrap-style nav tabs */
|
|
620
620
|
.modal-tabs {
|
|
621
621
|
display: flex;
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
border-radius: var(--radius-sm);
|
|
622
|
+
gap: 0;
|
|
623
|
+
margin: 0;
|
|
624
|
+
padding: 0 24px;
|
|
625
|
+
background: transparent;
|
|
626
|
+
border-bottom: 2px solid var(--white-10, rgba(255,255,255,0.1));
|
|
628
627
|
}
|
|
629
628
|
|
|
630
629
|
.modal-tab {
|
|
631
|
-
|
|
632
|
-
padding: 8px 4px;
|
|
630
|
+
padding: 10px 16px;
|
|
633
631
|
background: transparent;
|
|
634
632
|
border: none;
|
|
635
|
-
border-
|
|
633
|
+
border-bottom: 2px solid transparent;
|
|
634
|
+
margin-bottom: -2px;
|
|
636
635
|
color: var(--white-60);
|
|
637
636
|
font-family: var(--font-sans);
|
|
638
637
|
font-size: 13px;
|
|
639
638
|
font-weight: 500;
|
|
640
639
|
line-height: 1;
|
|
641
|
-
display: flex;
|
|
642
|
-
align-items: center;
|
|
643
|
-
justify-content: center;
|
|
644
640
|
cursor: pointer;
|
|
645
|
-
transition:
|
|
641
|
+
transition: color 0.2s, border-color 0.2s;
|
|
646
642
|
text-align: center;
|
|
643
|
+
white-space: nowrap;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.modal-tab:hover {
|
|
647
|
+
color: var(--white, #fff);
|
|
648
|
+
border-bottom-color: var(--white-30, rgba(255,255,255,0.3));
|
|
647
649
|
}
|
|
648
650
|
|
|
649
651
|
.modal-tab.active {
|
|
650
|
-
|
|
651
|
-
color: var(--
|
|
652
|
+
color: var(--white, #fff);
|
|
653
|
+
border-bottom-color: var(--accent, #00dc82);
|
|
654
|
+
background: transparent;
|
|
652
655
|
}
|
|
653
656
|
|
|
654
657
|
.modal-tab-content {
|
|
@@ -3991,46 +3994,35 @@ body:has(.modal.active) .nav-bar {
|
|
|
3991
3994
|
min-width: 0;
|
|
3992
3995
|
}
|
|
3993
3996
|
|
|
3994
|
-
.modal-tabs {
|
|
3995
|
-
flex-shrink: 0;
|
|
3996
|
-
position: sticky;
|
|
3997
|
-
top: 0;
|
|
3998
|
-
z-index: 10;
|
|
3999
|
-
background: var(--glass-bg);
|
|
4000
|
-
backdrop-filter: var(--glass-blur);
|
|
4001
|
-
border-bottom: 1px solid var(--glass-border);
|
|
4002
|
-
margin: -24px -24px 0 -24px;
|
|
4003
|
-
padding: 12px 24px;
|
|
4004
|
-
}
|
|
4005
3997
|
|
|
4006
|
-
.modal-tab-content {
|
|
3998
|
+
.modal-wide .modal-tab-content {
|
|
4007
3999
|
flex: 1;
|
|
4008
4000
|
overflow-y: auto;
|
|
4009
4001
|
overflow-x: hidden;
|
|
4010
|
-
|
|
4002
|
+
|
|
4011
4003
|
scrollbar-width: thin;
|
|
4012
4004
|
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
|
4013
4005
|
}
|
|
4014
4006
|
|
|
4015
|
-
.modal-tab-content::-webkit-scrollbar {
|
|
4007
|
+
.modal-wide .modal-tab-content::-webkit-scrollbar {
|
|
4016
4008
|
width: 3px;
|
|
4017
4009
|
}
|
|
4018
4010
|
|
|
4019
|
-
.modal-tab-content::-webkit-scrollbar-track {
|
|
4011
|
+
.modal-wide .modal-tab-content::-webkit-scrollbar-track {
|
|
4020
4012
|
background: transparent;
|
|
4021
4013
|
}
|
|
4022
4014
|
|
|
4023
|
-
.modal-tab-content::-webkit-scrollbar-thumb {
|
|
4015
|
+
.modal-wide .modal-tab-content::-webkit-scrollbar-thumb {
|
|
4024
4016
|
background: rgba(255, 255, 255, 0.2);
|
|
4025
4017
|
border-radius: 3px;
|
|
4026
4018
|
}
|
|
4027
4019
|
|
|
4028
|
-
.modal-tab-content::-webkit-scrollbar-thumb:hover {
|
|
4020
|
+
.modal-wide .modal-tab-content::-webkit-scrollbar-thumb:hover {
|
|
4029
4021
|
background: rgba(255, 255, 255, 0.3);
|
|
4030
4022
|
}
|
|
4031
4023
|
|
|
4032
4024
|
/* Ensure content doesn't get cut off */
|
|
4033
|
-
.modal-tab-content.active {
|
|
4025
|
+
.modal-wide .modal-tab-content.active {
|
|
4034
4026
|
display: block;
|
|
4035
4027
|
}
|
|
4036
4028
|
|
|
@@ -4142,6 +4134,7 @@ body:has(.modal.active) .nav-bar {
|
|
|
4142
4134
|
padding: 80px 24px;
|
|
4143
4135
|
position: relative;
|
|
4144
4136
|
z-index: 1;
|
|
4137
|
+
overflow: hidden;
|
|
4145
4138
|
}
|
|
4146
4139
|
|
|
4147
4140
|
.page-section {
|
|
@@ -4163,6 +4156,7 @@ html {
|
|
|
4163
4156
|
.section-container {
|
|
4164
4157
|
max-width: 1100px;
|
|
4165
4158
|
margin: 0 auto;
|
|
4159
|
+
width: 100%;
|
|
4166
4160
|
}
|
|
4167
4161
|
|
|
4168
4162
|
.section-container.section-sm {
|
|
@@ -4237,6 +4231,8 @@ html {
|
|
|
4237
4231
|
border: 1px solid var(--glass-border);
|
|
4238
4232
|
border-radius: var(--radius-sm);
|
|
4239
4233
|
overflow: hidden;
|
|
4234
|
+
width: 100%;
|
|
4235
|
+
min-width: 0;
|
|
4240
4236
|
}
|
|
4241
4237
|
|
|
4242
4238
|
.code-block-header {
|
|
@@ -4277,6 +4273,8 @@ html {
|
|
|
4277
4273
|
font-size: 0.8125rem;
|
|
4278
4274
|
line-height: 1.7;
|
|
4279
4275
|
color: var(--white-70);
|
|
4276
|
+
width: 100%;
|
|
4277
|
+
min-width: 0;
|
|
4280
4278
|
}
|
|
4281
4279
|
|
|
4282
4280
|
.chains-grid {
|
|
@@ -4288,6 +4286,17 @@ html {
|
|
|
4288
4286
|
.chain-card {
|
|
4289
4287
|
padding: 20px;
|
|
4290
4288
|
text-align: center;
|
|
4289
|
+
display: flex;
|
|
4290
|
+
flex-direction: column;
|
|
4291
|
+
align-items: center;
|
|
4292
|
+
text-decoration: none;
|
|
4293
|
+
color: inherit;
|
|
4294
|
+
transition: transform 0.15s ease, border-color 0.15s ease;
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
a.chain-card:hover {
|
|
4298
|
+
transform: translateY(-2px);
|
|
4299
|
+
border-color: var(--white-20);
|
|
4291
4300
|
}
|
|
4292
4301
|
|
|
4293
4302
|
.chain-card strong {
|
|
@@ -4519,3 +4528,238 @@ html {
|
|
|
4519
4528
|
.example-code.active {
|
|
4520
4529
|
display: block;
|
|
4521
4530
|
}
|
|
4531
|
+
|
|
4532
|
+
@media (max-width: 600px) {
|
|
4533
|
+
.code-block pre {
|
|
4534
|
+
padding: 14px;
|
|
4535
|
+
font-size: 0.7rem;
|
|
4536
|
+
line-height: 1.6;
|
|
4537
|
+
}
|
|
4538
|
+
.example-tab {
|
|
4539
|
+
padding: 8px 8px;
|
|
4540
|
+
font-size: 0.75rem;
|
|
4541
|
+
}
|
|
4542
|
+
.section-sm {
|
|
4543
|
+
padding-left: 8px;
|
|
4544
|
+
padding-right: 8px;
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
/* =============================================================================
|
|
4549
|
+
Encryption Tab
|
|
4550
|
+
============================================================================= */
|
|
4551
|
+
|
|
4552
|
+
.encrypt-tab-intro {
|
|
4553
|
+
margin-bottom: 20px;
|
|
4554
|
+
}
|
|
4555
|
+
.encrypt-tab-intro p {
|
|
4556
|
+
font-size: 0.85rem;
|
|
4557
|
+
color: var(--white-60);
|
|
4558
|
+
margin: 4px 0 0;
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4561
|
+
.encrypt-key-row {
|
|
4562
|
+
display: grid;
|
|
4563
|
+
grid-template-columns: 1fr 1fr;
|
|
4564
|
+
gap: 12px;
|
|
4565
|
+
margin-bottom: 20px;
|
|
4566
|
+
}
|
|
4567
|
+
@media (max-width: 640px) {
|
|
4568
|
+
.encrypt-key-row {
|
|
4569
|
+
grid-template-columns: 1fr;
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
.encrypt-key-card {
|
|
4574
|
+
padding: 14px;
|
|
4575
|
+
border-radius: var(--radius-sm);
|
|
4576
|
+
}
|
|
4577
|
+
.encrypt-key-header {
|
|
4578
|
+
margin-bottom: 10px;
|
|
4579
|
+
}
|
|
4580
|
+
.encrypt-role-badge {
|
|
4581
|
+
font-size: 0.7rem;
|
|
4582
|
+
text-transform: uppercase;
|
|
4583
|
+
letter-spacing: 0.08em;
|
|
4584
|
+
font-weight: 600;
|
|
4585
|
+
padding: 3px 8px;
|
|
4586
|
+
border-radius: 6px;
|
|
4587
|
+
}
|
|
4588
|
+
.encrypt-role-badge.sender {
|
|
4589
|
+
background: rgba(59, 130, 246, 0.2);
|
|
4590
|
+
color: #60a5fa;
|
|
4591
|
+
}
|
|
4592
|
+
.encrypt-role-badge.recipient {
|
|
4593
|
+
background: rgba(16, 185, 129, 0.2);
|
|
4594
|
+
color: #34d399;
|
|
4595
|
+
}
|
|
4596
|
+
.encrypt-key-detail {
|
|
4597
|
+
margin-bottom: 8px;
|
|
4598
|
+
}
|
|
4599
|
+
.encrypt-key-detail label {
|
|
4600
|
+
display: block;
|
|
4601
|
+
font-size: 0.7rem;
|
|
4602
|
+
text-transform: uppercase;
|
|
4603
|
+
letter-spacing: 0.06em;
|
|
4604
|
+
color: var(--white-40);
|
|
4605
|
+
margin-bottom: 4px;
|
|
4606
|
+
}
|
|
4607
|
+
.encrypt-key-detail code {
|
|
4608
|
+
font-size: 0.8rem;
|
|
4609
|
+
color: var(--white-70);
|
|
4610
|
+
word-break: break-all;
|
|
4611
|
+
}
|
|
4612
|
+
.encrypt-recipient-input-row {
|
|
4613
|
+
display: flex;
|
|
4614
|
+
gap: 8px;
|
|
4615
|
+
align-items: center;
|
|
4616
|
+
}
|
|
4617
|
+
.encrypt-recipient-input-row input {
|
|
4618
|
+
flex: 1;
|
|
4619
|
+
min-width: 0;
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
.encrypt-message-section {
|
|
4623
|
+
margin-bottom: 20px;
|
|
4624
|
+
}
|
|
4625
|
+
.encrypt-actions {
|
|
4626
|
+
display: flex;
|
|
4627
|
+
gap: 8px;
|
|
4628
|
+
margin-top: 10px;
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
.encrypt-step-header {
|
|
4632
|
+
display: flex;
|
|
4633
|
+
align-items: center;
|
|
4634
|
+
gap: 12px;
|
|
4635
|
+
margin-bottom: 16px;
|
|
4636
|
+
}
|
|
4637
|
+
|
|
4638
|
+
.encrypt-step-header .section-label {
|
|
4639
|
+
margin: 0;
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
.encrypt-back-btn {
|
|
4643
|
+
display: inline-flex;
|
|
4644
|
+
align-items: center;
|
|
4645
|
+
gap: 4px;
|
|
4646
|
+
flex-shrink: 0;
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
.encrypt-output-section {
|
|
4650
|
+
margin-bottom: 24px;
|
|
4651
|
+
padding: 16px;
|
|
4652
|
+
background: var(--glass-bg);
|
|
4653
|
+
border: 1px solid var(--glass-border);
|
|
4654
|
+
border-radius: var(--radius-sm);
|
|
4655
|
+
}
|
|
4656
|
+
.encrypt-output-fields {
|
|
4657
|
+
display: flex;
|
|
4658
|
+
flex-direction: column;
|
|
4659
|
+
gap: 8px;
|
|
4660
|
+
margin-bottom: 16px;
|
|
4661
|
+
}
|
|
4662
|
+
.encrypt-field {
|
|
4663
|
+
display: flex;
|
|
4664
|
+
align-items: center;
|
|
4665
|
+
gap: 8px;
|
|
4666
|
+
}
|
|
4667
|
+
.encrypt-field label {
|
|
4668
|
+
font-size: 0.7rem;
|
|
4669
|
+
text-transform: uppercase;
|
|
4670
|
+
letter-spacing: 0.06em;
|
|
4671
|
+
color: var(--white-40);
|
|
4672
|
+
white-space: nowrap;
|
|
4673
|
+
min-width: 100px;
|
|
4674
|
+
}
|
|
4675
|
+
.encrypt-out-value {
|
|
4676
|
+
font-size: 0.8rem;
|
|
4677
|
+
color: var(--white-70);
|
|
4678
|
+
overflow: hidden;
|
|
4679
|
+
text-overflow: ellipsis;
|
|
4680
|
+
white-space: nowrap;
|
|
4681
|
+
flex: 1;
|
|
4682
|
+
min-width: 0;
|
|
4683
|
+
}
|
|
4684
|
+
.encrypt-field .copy-btn {
|
|
4685
|
+
background: none;
|
|
4686
|
+
border: none;
|
|
4687
|
+
padding: 4px;
|
|
4688
|
+
cursor: pointer;
|
|
4689
|
+
color: var(--white-30);
|
|
4690
|
+
flex-shrink: 0;
|
|
4691
|
+
transition: color 0.2s;
|
|
4692
|
+
}
|
|
4693
|
+
.encrypt-field .copy-btn:hover {
|
|
4694
|
+
color: var(--white);
|
|
4695
|
+
}
|
|
4696
|
+
.encrypt-bundle-group {
|
|
4697
|
+
margin-top: 12px;
|
|
4698
|
+
}
|
|
4699
|
+
.encrypt-format-toggle {
|
|
4700
|
+
display: flex;
|
|
4701
|
+
align-items: center;
|
|
4702
|
+
justify-content: space-between;
|
|
4703
|
+
margin-bottom: 6px;
|
|
4704
|
+
}
|
|
4705
|
+
.encrypt-format-btns {
|
|
4706
|
+
display: flex;
|
|
4707
|
+
gap: 4px;
|
|
4708
|
+
}
|
|
4709
|
+
.encrypt-fmt-btn {
|
|
4710
|
+
font-size: 0.72rem !important;
|
|
4711
|
+
padding: 4px 10px !important;
|
|
4712
|
+
opacity: 0.5;
|
|
4713
|
+
transition: opacity 0.2s;
|
|
4714
|
+
}
|
|
4715
|
+
.encrypt-fmt-btn.active {
|
|
4716
|
+
opacity: 1;
|
|
4717
|
+
border-color: var(--accent, #00dc82);
|
|
4718
|
+
color: var(--accent, #00dc82);
|
|
4719
|
+
}
|
|
4720
|
+
.encrypt-format-info {
|
|
4721
|
+
margin-bottom: 8px;
|
|
4722
|
+
}
|
|
4723
|
+
.encrypt-format-label {
|
|
4724
|
+
font-size: 0.72rem;
|
|
4725
|
+
color: var(--white-40);
|
|
4726
|
+
font-style: italic;
|
|
4727
|
+
}
|
|
4728
|
+
.encrypt-bundle-group textarea {
|
|
4729
|
+
font-size: 0.78rem;
|
|
4730
|
+
font-family: 'SF Mono', monospace;
|
|
4731
|
+
}
|
|
4732
|
+
.encrypt-bundle-actions {
|
|
4733
|
+
display: flex;
|
|
4734
|
+
gap: 8px;
|
|
4735
|
+
margin-top: 8px;
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
.encrypt-decrypt-section {
|
|
4739
|
+
margin-bottom: 16px;
|
|
4740
|
+
}
|
|
4741
|
+
.encrypt-decrypt-info {
|
|
4742
|
+
font-size: 0.85rem;
|
|
4743
|
+
color: var(--white-60);
|
|
4744
|
+
margin: 4px 0 10px;
|
|
4745
|
+
}
|
|
4746
|
+
.decrypt-result {
|
|
4747
|
+
margin-top: 12px;
|
|
4748
|
+
padding: 12px;
|
|
4749
|
+
background: rgba(16, 185, 129, 0.1);
|
|
4750
|
+
border: 1px solid rgba(16, 185, 129, 0.25);
|
|
4751
|
+
border-radius: var(--radius-sm);
|
|
4752
|
+
}
|
|
4753
|
+
.decrypt-result label {
|
|
4754
|
+
display: block;
|
|
4755
|
+
font-size: 0.7rem;
|
|
4756
|
+
text-transform: uppercase;
|
|
4757
|
+
letter-spacing: 0.06em;
|
|
4758
|
+
color: #34d399;
|
|
4759
|
+
margin-bottom: 6px;
|
|
4760
|
+
}
|
|
4761
|
+
.decrypt-result-value {
|
|
4762
|
+
font-size: 0.95rem;
|
|
4763
|
+
color: var(--white);
|
|
4764
|
+
word-break: break-word;
|
|
4765
|
+
}
|