qr-secure-send 1.6.7 → 1.7.1

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.
Files changed (2) hide show
  1. package/index.html +87 -43
  2. package/package.json +1 -1
package/index.html CHANGED
@@ -159,7 +159,7 @@
159
159
  <header>
160
160
  <h1>QR Secure Send</h1>
161
161
  <p>Encrypt and transfer secrets via QR code</p>
162
- <p style="font-size:0.7rem;color:#484f58;margin-top:0.2rem">v1.6.7</p>
162
+ <p style="font-size:0.7rem;color:#484f58;margin-top:0.2rem">v1.7.1</p>
163
163
  </header>
164
164
 
165
165
  <div class="tabs">
@@ -186,15 +186,14 @@
186
186
  <div class="wallet-section">
187
187
  <label class="wallet-toggle">
188
188
  <input type="checkbox" id="send-wallet-toggle">
189
- Require receiver's crypto wallet (MetaMask)
189
+ Encrypt with crypto wallet (MetaMask)
190
190
  </label>
191
191
  <div id="send-wallet-fields" class="wallet-fields">
192
- <label for="send-wallet-address">Receiver Wallet Address</label>
193
- <input type="text" id="send-wallet-address" placeholder="0x..." autocomplete="off"
194
- style="font-family:'SF Mono','Fira Code',monospace;font-size:0.85rem;">
195
- <p class="info" style="margin-top:-0.5rem;text-align:left">
196
- The receiver must connect this exact wallet to decrypt. The address is mixed into the encryption key.
192
+ <p class="info" style="text-align:left;margin-bottom:0.5rem">
193
+ Your wallet's private key will be used to encrypt. Only the same wallet can decrypt.
197
194
  </p>
195
+ <button class="wallet-btn" id="send-wallet-connect-btn">Connect MetaMask</button>
196
+ <div id="send-wallet-status" class="wallet-address" style="display:none"></div>
198
197
  </div>
199
198
  </div>
200
199
 
@@ -223,9 +222,9 @@
223
222
  </div>
224
223
  <div id="recv-wallet-section" class="wallet-section" style="display:none">
225
224
  <p style="font-size:0.82rem;color:#d29922;margin-bottom:0.5rem;">
226
- This secret requires wallet authentication. Connect the designated wallet to decrypt.
225
+ This secret was encrypted with a wallet. Connect the same wallet to decrypt.
227
226
  </p>
228
- <button class="wallet-btn" id="recv-wallet-connect-btn">Connect MetaMask</button>
227
+ <button class="wallet-btn" id="recv-wallet-connect-btn">Connect & Sign with MetaMask</button>
229
228
  <a class="wallet-btn" id="recv-wallet-deeplink" style="display:none;text-decoration:none;text-align:center" target="_blank">Open in MetaMask App</a>
230
229
  <div id="recv-wallet-status" class="wallet-address" style="display:none"></div>
231
230
  </div>
@@ -1017,12 +1016,24 @@
1017
1016
  // PAYLOAD HELPERS
1018
1017
  // =========================================================================
1019
1018
 
1020
- const APP_VERSION = "1.6.7";
1019
+ const APP_VERSION = "1.7.1";
1021
1020
  const GH_PAGES_URL = "https://degenddy.github.io/qr-secure-send/?v=" + APP_VERSION;
1022
1021
  const METAMASK_DEEP_URL = "https://link.metamask.io/dapp/degenddy.github.io/qr-secure-send/?v=" + APP_VERSION;
1023
1022
 
1024
- function buildKeyInput(passphrase, walletAddress) {
1025
- return walletAddress ? passphrase + ':' + walletAddress.toLowerCase() : passphrase;
1023
+ const WALLET_SIGN_MSG = "QR Secure Send: generate encryption key";
1024
+
1025
+ // Ask MetaMask to sign a deterministic message. Returns the signature (hex string).
1026
+ // Same wallet + same message = same signature every time (RFC 6979).
1027
+ async function getWalletSignature(account) {
1028
+ const sig = await window.ethereum.request({
1029
+ method: 'personal_sign',
1030
+ params: [WALLET_SIGN_MSG, account]
1031
+ });
1032
+ return sig;
1033
+ }
1034
+
1035
+ function buildKeyInput(passphrase, walletSignature) {
1036
+ return walletSignature ? passphrase + ':' + walletSignature : passphrase;
1026
1037
  }
1027
1038
 
1028
1039
  // Extract encrypted data from raw or URL form. Returns { data, wallet } or null.
@@ -1077,12 +1088,35 @@
1077
1088
  document.getElementById("send-wallet-fields").classList.toggle("active", e.target.checked);
1078
1089
  });
1079
1090
 
1091
+ // Send wallet connect
1092
+ let sendWalletSignature = null;
1093
+
1094
+ document.getElementById("send-wallet-connect-btn").addEventListener("click", async () => {
1095
+ const errEl = document.getElementById("send-error");
1096
+ const statusEl = document.getElementById("send-wallet-status");
1097
+ if (typeof window.ethereum === 'undefined') {
1098
+ errEl.textContent = "MetaMask not detected. Install the extension or use MetaMask mobile.";
1099
+ return;
1100
+ }
1101
+ try {
1102
+ const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
1103
+ if (!accounts.length) { errEl.textContent = "No accounts returned."; return; }
1104
+ const account = accounts[0];
1105
+ sendWalletSignature = await getWalletSignature(account);
1106
+ statusEl.textContent = "Signed with: " + account.toLowerCase();
1107
+ statusEl.style.display = "block";
1108
+ document.getElementById("send-wallet-connect-btn").textContent = "Wallet Connected";
1109
+ document.getElementById("send-wallet-connect-btn").disabled = true;
1110
+ } catch (e) {
1111
+ errEl.textContent = "Wallet connection/signing failed: " + (e.message || "User rejected.");
1112
+ }
1113
+ });
1114
+
1080
1115
  // QR Generation
1081
1116
  document.getElementById("generate-btn").addEventListener("click", async () => {
1082
1117
  const secret = document.getElementById("secret").value.trim();
1083
1118
  const passphrase = document.getElementById("send-passphrase").value;
1084
1119
  const useWallet = document.getElementById("send-wallet-toggle").checked;
1085
- const walletAddress = useWallet ? document.getElementById("send-wallet-address").value.trim() : null;
1086
1120
  const errEl = document.getElementById("send-error");
1087
1121
  const output = document.getElementById("qr-output");
1088
1122
  const info = document.getElementById("qr-info");
@@ -1103,15 +1137,13 @@
1103
1137
  }
1104
1138
  }
1105
1139
 
1106
- if (useWallet) {
1107
- if (!walletAddress || !/^0x[0-9a-fA-F]{40}$/.test(walletAddress)) {
1108
- errEl.textContent = "Enter a valid Ethereum wallet address (0x + 40 hex characters).";
1109
- return;
1110
- }
1140
+ if (useWallet && !sendWalletSignature) {
1141
+ errEl.textContent = "Connect and sign with MetaMask first.";
1142
+ return;
1111
1143
  }
1112
1144
 
1113
1145
  try {
1114
- const keyInput = buildKeyInput(passphrase, walletAddress);
1146
+ const keyInput = buildKeyInput(passphrase, useWallet ? sendWalletSignature : null);
1115
1147
  const encrypted = await encryptSecret(secret, keyInput);
1116
1148
  const prefix = useWallet ? "QRSECW:" : "QRSEC:";
1117
1149
  const payload = GH_PAGES_URL + "#" + prefix + encrypted;
@@ -1137,14 +1169,14 @@
1137
1169
  "loaded from the internet when you start the camera.";
1138
1170
  }
1139
1171
 
1140
- // Wallet connect (receive side)
1141
- let connectedWalletAddress = null;
1172
+ // Wallet connect & sign (receive side)
1173
+ let recvWalletSignature = null;
1142
1174
 
1143
1175
  document.getElementById("recv-wallet-connect-btn").addEventListener("click", async () => {
1144
1176
  const statusEl = document.getElementById("recv-wallet-status");
1145
1177
  const errEl = document.getElementById("recv-error");
1146
1178
  if (typeof window.ethereum === 'undefined') {
1147
- errEl.textContent = "MetaMask not detected. Install the MetaMask extension, or use the button below to open in the MetaMask mobile app.";
1179
+ errEl.textContent = "MetaMask not detected. Install the extension, or use the button below to open in the MetaMask mobile app.";
1148
1180
  const deepEl = document.getElementById("recv-wallet-deeplink");
1149
1181
  deepEl.href = METAMASK_DEEP_URL + location.hash;
1150
1182
  deepEl.style.display = "inline-block";
@@ -1152,30 +1184,33 @@
1152
1184
  }
1153
1185
  try {
1154
1186
  const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
1155
- if (!accounts.length) { errEl.textContent = "No accounts returned. Please unlock MetaMask."; return; }
1156
- connectedWalletAddress = accounts[0].toLowerCase();
1157
- statusEl.textContent = "Connected: " + connectedWalletAddress;
1187
+ if (!accounts.length) { errEl.textContent = "No accounts returned."; return; }
1188
+ const account = accounts[0];
1189
+ recvWalletSignature = await getWalletSignature(account);
1190
+ statusEl.textContent = "Signed with: " + account.toLowerCase();
1158
1191
  statusEl.style.display = "block";
1159
- document.getElementById("recv-wallet-connect-btn").textContent = "Wallet Connected";
1192
+ document.getElementById("recv-wallet-connect-btn").textContent = "Wallet Signed";
1160
1193
  document.getElementById("recv-wallet-connect-btn").disabled = true;
1161
1194
  } catch (e) {
1162
- errEl.textContent = "Wallet connection failed: " + (e.message || "User rejected request.");
1195
+ errEl.textContent = "Wallet connection/signing failed: " + (e.message || "User rejected.");
1163
1196
  }
1164
1197
  });
1165
1198
 
1166
- // Update wallet address if user switches account in MetaMask
1199
+ // Re-sign if user switches account in MetaMask
1167
1200
  if (typeof window.ethereum !== 'undefined') {
1168
- window.ethereum.on('accountsChanged', (accounts) => {
1201
+ window.ethereum.on('accountsChanged', async (accounts) => {
1169
1202
  if (accounts.length) {
1170
- connectedWalletAddress = accounts[0].toLowerCase();
1171
- const statusEl = document.getElementById("recv-wallet-status");
1172
- statusEl.textContent = "Connected: " + connectedWalletAddress;
1173
- statusEl.style.display = "block";
1203
+ try {
1204
+ recvWalletSignature = await getWalletSignature(accounts[0]);
1205
+ const statusEl = document.getElementById("recv-wallet-status");
1206
+ statusEl.textContent = "Signed with: " + accounts[0].toLowerCase();
1207
+ statusEl.style.display = "block";
1208
+ } catch (_) { recvWalletSignature = null; }
1174
1209
  } else {
1175
- connectedWalletAddress = null;
1210
+ recvWalletSignature = null;
1176
1211
  document.getElementById("recv-wallet-status").style.display = "none";
1177
1212
  const btn = document.getElementById("recv-wallet-connect-btn");
1178
- btn.textContent = "Connect MetaMask"; btn.disabled = false;
1213
+ btn.textContent = "Connect & Sign with MetaMask"; btn.disabled = false;
1179
1214
  }
1180
1215
  });
1181
1216
  }
@@ -1205,13 +1240,13 @@
1205
1240
  }
1206
1241
  if (payload.wallet) {
1207
1242
  document.getElementById("recv-wallet-section").style.display = "block";
1208
- if (!connectedWalletAddress) {
1209
- errEl.textContent = "This secret requires wallet authentication. Connect your wallet first, then scan again.";
1243
+ if (!recvWalletSignature) {
1244
+ errEl.textContent = "This secret requires wallet signing. Connect & sign with MetaMask first, then scan again.";
1210
1245
  return false;
1211
1246
  }
1212
1247
  }
1213
1248
  try {
1214
- const keyInput = buildKeyInput(passphrase, payload.wallet ? connectedWalletAddress : null);
1249
+ const keyInput = buildKeyInput(passphrase, payload.wallet ? recvWalletSignature : null);
1215
1250
  const secret = await decryptSecret(payload.data, keyInput);
1216
1251
  document.getElementById("recv-value").textContent = secret;
1217
1252
  resultBox.style.display = "block";
@@ -1258,14 +1293,14 @@
1258
1293
  const payload = extractPayload(location.hash.slice(1));
1259
1294
  if (!payload) { errEl.textContent = "No encrypted data found in URL."; return; }
1260
1295
 
1261
- if (payload.wallet && !connectedWalletAddress) {
1262
- errEl.textContent = "This secret requires wallet authentication. Connect your wallet first.";
1296
+ if (payload.wallet && !recvWalletSignature) {
1297
+ errEl.textContent = "This secret requires wallet signing. Connect & sign with MetaMask first.";
1263
1298
  document.getElementById("recv-wallet-section").style.display = "block";
1264
1299
  return;
1265
1300
  }
1266
1301
 
1267
1302
  try {
1268
- const keyInput = buildKeyInput(passphrase, payload.wallet ? connectedWalletAddress : null);
1303
+ const keyInput = buildKeyInput(passphrase, payload.wallet ? recvWalletSignature : null);
1269
1304
  const secret = await decryptSecret(payload.data, keyInput);
1270
1305
  document.getElementById("recv-value").textContent = secret;
1271
1306
  resultBox.style.display = "block";
@@ -1278,6 +1313,15 @@
1278
1313
 
1279
1314
  // Check URL hash for incoming encrypted data (link-based receive flow)
1280
1315
  if (location.hash.startsWith("#QRSEC:") || location.hash.startsWith("#QRSECW:")) {
1316
+ // Wallet mode + no MetaMask → redirect to MetaMask deep link (mobile)
1317
+ if (location.hash.startsWith("#QRSECW:") && typeof window.ethereum === 'undefined') {
1318
+ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
1319
+ if (isMobile) {
1320
+ window.location.href = METAMASK_DEEP_URL + location.hash;
1321
+ // stop further execution; page will redirect
1322
+ }
1323
+ }
1324
+
1281
1325
  document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
1282
1326
  document.querySelectorAll(".panel").forEach(p => p.classList.remove("active"));
1283
1327
  document.querySelector('[data-tab="receive"]').classList.add("active");
@@ -1288,8 +1332,8 @@
1288
1332
  if (location.hash.startsWith("#QRSECW:")) {
1289
1333
  document.getElementById("recv-wallet-section").style.display = "block";
1290
1334
  document.getElementById("link-notice").textContent =
1291
- "Encrypted data received via link. This secret requires wallet authentication. " +
1292
- "Connect your wallet, enter the passphrase, and click Decrypt.";
1335
+ "Encrypted data received via link. This secret requires wallet signing. " +
1336
+ "Connect & sign with your wallet, enter the passphrase (if set), and click Decrypt.";
1293
1337
  }
1294
1338
  }
1295
1339
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qr-secure-send",
3
- "version": "1.6.7",
3
+ "version": "1.7.1",
4
4
  "description": "Encrypt and transfer secrets via QR code",
5
5
  "keywords": ["qr", "qrcode", "encryption", "password", "secure", "transfer"],
6
6
  "author": "",