Aegis 0.32.1: the PIN pads follow the vault PIN's length

Since Theseus 0.3.80 the vault PIN can be 6 to 8 digits, but Aegis's
pads still submitted at six, so anyone with a 7- or 8-digit PIN got
"wrong length" from every Aegis prompt and had to use the master
password. The lock-screen, transaction and reveal pads now draw as many
dots as the host reports (pinLength) and submit at that length; a
wrong length is explained instead of shown as a wrong PIN. Aegis's own
PIN on older hosts stays at six.
This commit is contained in:
Local Dev 2026-10-05 22:43:05 +02:00
parent 02d8331cbc
commit a1eceb444a
3 changed files with 23 additions and 8 deletions

View file

@ -1,7 +1,7 @@
{
"id": "aegis",
"name": "Aegis Wallet",
"version": "0.32.0",
"version": "0.32.1",
"category": "plugin",
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash and window.wizardconnect on any site; window.tronWeb / window.tronLink / window.ethereum / window.solana too. Every call needs your approval.",
"author": "Silent Mode",

View file

@ -2856,6 +2856,11 @@ function registerPanelMessages(api) {
await refreshHostPin(api);
if (r && r.ok) return { ok: true, masterPassword: mintPinProof() };
if (r && r.code === "no-pin") throw new Error("no PIN is set");
// Not a guess (Theseus spends no strike on it): the pad had the wrong
// number of digits for this PIN.
if (r && r.code === "wrong-length") {
return { ok: false, remaining: Number(r.remaining) || 0, lockedMs: 0, error: `Your PIN has ${Number(r.length) || hostPinCache.length || "a different number of"} digits. Reopen this prompt and try again.` };
}
return { ok: false, remaining: Number(r?.remaining) || 0, lockedMs: Number(r?.lockedMs) || 0, error: r?.error || undefined };
}
const blob = openPinBlob(api);
@ -2981,6 +2986,7 @@ function registerPanelMessages(api) {
pinHardware: blob ? (blob.hw ? "tpm" : "none") : host ? (host.pinSet ? host.hardware || "none" : null) : null,
pinStorable: host ? host.storable !== false : osSealUsable(safeStorageOr(api)),
pinUnified: !!hostPinApi(api),
pinLength: host && host.pinSet && Number(host.length) >= 6 && Number(host.length) <= 8 ? Number(host.length) : 6,
pinLegacyExposure: exposed && !exposed.dismissed ? { at: exposed.at } : null,
};
}

View file

@ -330,6 +330,12 @@ const pinTry = async (pin) => {
async function pinLockoutRemainingMs() {
try { return Number((await S.invoke("pinStatus")).lockedMs) || 0; } catch { return 0; }
}
// How many digits the PIN being entered has: the Theseus vault PIN can be
// 6-8 digits (pinLength from the host), Aegis's own PIN is always 6.
function pinEntryLength() {
const n = Number(securityState && securityState.pinLength);
return n >= 6 && n <= 8 ? n : 6;
}
async function refreshSecurityState() {
try {
securityState = await S.invoke("securityGet");
@ -3375,7 +3381,7 @@ function renderLockScreen(phase) {
body.dataset.mode = "pin";
body.innerHTML = `
<div class="pinpad" id="lockPinPad">
<div class="pindots" id="lockPinDots">${"<span class=\"pindot\"></span>".repeat(6)}</div>
<div class="pindots" id="lockPinDots">${"<span class=\"pindot\"></span>".repeat(pinEntryLength())}</div>
<div class="pinkeys" id="lockPinKeys">
${[1,2,3,4,5,6,7,8,9].map((n) => `<button data-k="${n}">${n}</button>`).join("")}
<button class="util" data-k="clear">Clear</button>
@ -3469,7 +3475,10 @@ function renderLockScreen(phase) {
// are duplicate ids — a global lookup then returns the FIRST one, and this
// function binds a second click handler to the wrong pad's buttons. The
// symptom is a pad that appends two digits per press and resets after three.
// The pad takes its length from the dots it was drawn with: 6 for Aegis's
// own PIN, 6-8 for the Theseus vault PIN (pinLength from the host).
function setupPinPad({ dots, keys, err, onComplete }) {
const len = dots.querySelectorAll(".pindot").length || 6;
let buf = "";
const paint = () => {
const nodes = dots.querySelectorAll(".pindot");
@ -3480,10 +3489,10 @@ function setupPinPad({ dots, keys, err, onComplete }) {
if (err) err.textContent = "";
if (k === "clear") { buf = ""; paint(); return; }
if (k === "back") { buf = buf.slice(0, -1); paint(); return; }
if (buf.length >= 6) return;
if (buf.length >= len) return;
buf += k;
paint();
if (buf.length === 6) {
if (buf.length === len) {
keys.querySelectorAll("button").forEach((x) => x.disabled = true);
let res = "reset";
try { res = await onComplete(buf); }
@ -3512,9 +3521,9 @@ function setupPinPad({ dots, keys, err, onComplete }) {
if (tgt && (tgt.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(tgt.tagName || ""))) return;
if (err) err.textContent = "";
if (/^[0-9]$/.test(e.key)) {
if (buf.length >= 6) return;
if (buf.length >= len) return;
buf += e.key; paint();
if (buf.length === 6) {
if (buf.length === len) {
keys.querySelectorAll("button").forEach((x) => x.disabled = true);
let res = "reset";
try { res = await onComplete(buf); }
@ -5345,7 +5354,7 @@ function capturePinForSecret(subtitle) {
<h2>Confirm with PIN</h2>
<div class="pinsub" id="rsSub">${esc(subtitle || "")}</div>
<div class="pinpad">
<div class="pindots" id="rsDots">${"<span class=\"pindot\"></span>".repeat(6)}</div>
<div class="pindots" id="rsDots">${"<span class=\"pindot\"></span>".repeat(pinEntryLength())}</div>
<div class="pinkeys" id="rsKeys">
${[1,2,3,4,5,6,7,8,9].map((n) => `<button data-k="${n}">${n}</button>`).join("")}
<button class="util" data-k="clear">Clear</button>
@ -5410,7 +5419,7 @@ async function verifyPinInteractivelyOnce(subtitle) {
<h2>Confirm with PIN</h2>
<div class="pinsub" id="vpSub">${esc(subtitle || "")}</div>
<div class="pinpad">
<div class="pindots" id="vpDots">${"<span class=\"pindot\"></span>".repeat(6)}</div>
<div class="pindots" id="vpDots">${"<span class=\"pindot\"></span>".repeat(pinEntryLength())}</div>
<div class="pinkeys" id="vpKeys">
${[1,2,3,4,5,6,7,8,9].map((n) => `<button data-k="${n}">${n}</button>`).join("")}
<button class="util" data-k="clear">Clear</button>