L
ling-wallet
↓ 0 downloadsLing Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-walletCode / Files (116)
ling/ui/security.ling
# security.ling — SECURITY: password-gated recovery phrase (hides itself),
# password change, auto-lock timer and keyboard shortcuts.
bind REVEAL_SECS = 60
fn sec_wipe() {
st_set("sec_words", [])
st_set("sec_reveal_t", 0 - 1)
tf_clear("sec_pw")
tf_clear("sec_old")
tf_clear("sec_new")
tf_clear("sec_new2")
st_set("sec_err", "")
st_set("sec_msg", "")
}
fn sec_hide() {
st_set("sec_words", [])
st_set("sec_reveal_t", 0 - 1)
}
# Numbered placeholder grid: hatched bars instead of words.
fn veiled_grid(x, y, w) {
bind gap = 8
bind cw = floor((w - 3 * gap) / 4)
bind i = 0
while i < 24 {
bind cx = x + (i % 4) * (cw + gap)
bind cy = y + floor(i / 4) * 48
fill(cx, cy, cw, 40, C_PANEL)
outline(cx, cy, cw, 40, C_LINE_FAINT, 1)
t_center(F_MONO, 18, cx, cy, 30, 40, pad2(i + 1), C_TEXT_FAINT)
bind bw = 40 + ((i * 37) % 5) * 10
cross(cx + 42, cy + 15, min(bw, cw - 54), 10, C_LINE, 3, 0.8)
bind i = i + 1
}
}
fn security_draw(ax, ay, aw, ah) {
bind lw = floor(aw * 52 / 100)
bind rec_h = 404
bind inn = panel(ax, ay, lw, rec_h, "RECOVERY PHRASE", C_VIOLET)
bind x = list_get(inn, 0)
bind y = list_get(inn, 1)
bind iw = list_get(inn, 2)
bind gy = y + 8
bind gh = 6 * 48 - 8
bind t0 = st_get("sec_reveal_t", 0 - 1)
if t0 >= 0 && u_now() - t0 > REVEAL_SECS && st_get("shot", 0) < 1 { sec_hide() }
bind words = st_get("sec_words", [])
if len(words) > 0 {
word_grid(x, gy, iw, words, C_VIOLET)
bind left = max(0, REVEAL_SECS - (u_now() - st_get("sec_reveal_t", 0)))
if st_get("shot", 0) > 0 { bind left = REVEAL_SECS }
bind by = gy + gh + 24
bind bw = iw - 160
fill(x, by + 4, bw, 6, C_LINE_FAINT)
fill(x, by + 4, floor(bw * left / REVEAL_SECS), 6, C_VIOLET)
t_text(F_UI, 15, x, by + 32, "Hides automatically in " + num_str(ceil(left)) + " s", C_TEXT_DIM)
if button(x + iw - 140, by - 8, 140, 40, "Hide now", C_VIOLET, "plain", true, true, "") { sec_hide() }
} else {
veiled_grid(x, gy, iw)
bind ox = x + 40
bind ow = iw - 80
bind oy = gy + floor(gh / 2) - 64
fill(ox, oy, ow, 128, C_PANEL)
outline(ox, oy, ow, 128, C_LINE_SOFT, 1)
brackets(ox, oy, ow, 128, 8, C_VIOLET, 1)
icon_draw("eye", ox + 20, oy + 26, C_VIOLET)
t_text(F_UI, 18, ox + 48, oy + 32, "Hidden. Enter your password to reveal.", C_TEXT_STRONG)
bind fw = ow - 40 - 132
bind r = field("sec_pw", ox + 20, oy + 60, fw, 44, "password", C_VIOLET, "password", 0)
if list_get(r, 0) { st_set("sec_err", "") }
if button(ox + 20 + fw + 12, oy + 60, 120, 44, "Reveal", C_VIOLET, "primary", true, false, "") || list_get(r, 1) {
bind res = be_reveal(tf_get("sec_pw"))
match res {
ok(ws) => sec_revealed(ws),
bad(e) => sec_reveal_bad(e),
}
}
bind by = gy + gh + 24
if st_get("sec_err", "") != "" {
t_text(F_UI, 16, x, by + 18, st_get("sec_err", ""), C_RED)
} else {
t_text(F_UI, 15, x, by + 18, "Make sure no one can see your screen. The words hide after 60 s.", C_TEXT_FAINT)
}
}
bind py = ay + rec_h + 32
bind ph = ah - rec_h - 32
bind inp = panel(ax, py, lw, ph, "CHANGE PASSWORD", C_VIOLET)
bind x = list_get(inp, 0)
bind y = list_get(inp, 1) + 28
bind iw = list_get(inp, 2)
bind fw = floor((iw - 32) / 3)
bind a = labeled("sec_old", x, y, fw, 44, "Current", "", C_VIOLET, "password")
bind b = labeled("sec_new", x + fw + 16, y, fw, 44, "New", "", C_VIOLET, "password")
bind c = labeled("sec_new2", x + 2 * (fw + 16), y, fw, 44, "Confirm new", "", C_VIOLET, "password")
if list_get(a, 0) || list_get(b, 0) || list_get(c, 0) { st_set("sec_msg", "") }
strength_meter(x + fw + 16, y + 60, 2 * fw + 16, 8, tf_get("sec_new"))
bind by = py + ph - 20 - 40
bind mism = tf_get("sec_new2") != "" && tf_get("sec_new") != tf_get("sec_new2")
bind good = tf_get("sec_old") != "" && pw_strength(tf_get("sec_new")) >= 1 && !mism && tf_get("sec_new2") != ""
bind mw = iw - 216
bind msg = st_get("sec_msg", "")
if msg != "" {
t_para(F_UI, 16, x, by + 4, mw, 19, msg, sel(st_get("sec_msg_ok", 0) > 0, C_GREEN, C_RED))
} else if mism {
t_para(F_UI, 16, x, by + 4, mw, 19, "New passwords don't match.", C_RED)
} else {
t_para(F_UI, 15, x, by + 2, mw, 19, "Re-encrypts the wallet file. Your recovery phrase stays the same.", C_TEXT_FAINT)
}
bind submit = list_get(a, 1) || list_get(b, 1) || list_get(c, 1)
if button(x + iw - 200, by, 200, 40, "Change password", C_VIOLET, "plain", good, false, "") || (submit && good) {
bind res = be_change_password(tf_get("sec_old"), tf_get("sec_new"))
match res {
ok(v) => sec_pw_changed(),
bad(e) => sec_pw_msg(e, 0),
}
}
bind rx = ax + lw + 24
bind rw = aw - lw - 24
bind ina = panel(rx, ay, rw, 132, "AUTO LOCK", C_VIOLET)
bind x = list_get(ina, 0)
bind y = list_get(ina, 1)
t_text(F_UI, 16, x, y + 10, "Lock after this long without input:", C_TEXT_DIM)
bind pick = segmented(x, y + 28, list_get(ina, 2), ["1 min", "5 min", "10 min", "30 min", "Never"], st_get("autolock", 2), C_VIOLET)
if pick >= 0 { st_set("autolock", pick) }
bind ky = ay + 132 + 32
bind ink = panel(rx, ky, rw, ah - 132 - 32, "SHORTCUTS", C_VIOLET)
bind x = list_get(ink, 0)
bind y = list_get(ink, 1) + 4
bind iw = list_get(ink, 2)
bind keys = ["Ctrl+1 … 7", "Ctrl+L", "Tab / Shift+Tab", "Enter", "Esc", "Ctrl+V", "Ctrl+A"]
bind what = ["Switch section", "Lock now", "Next / previous field", "Confirm the focused step", "Go back a step", "Paste into a field", "Select a field's text"]
bind i = 0
while i < len(keys) {
t_text(F_MONO, 20, x, y + 20, list_get(keys, i), C_TEXT_STRONG)
t_right(F_UI, 16, x + iw, y + 20, list_get(what, i), C_TEXT_DIM)
hline(x, y + 32, iw, C_LINE_FAINT, 1)
bind y = y + 40
bind i = i + 1
}
}
fn sec_revealed(ws) {
st_set("sec_words", ws)
st_set("sec_reveal_t", u_now())
tf_clear("sec_pw")
}
fn sec_reveal_bad(e) {
st_set("sec_err", e)
st_set("tf_all", "sec_pw")
}
fn sec_pw_changed() {
tf_clear("sec_old")
tf_clear("sec_new")
tf_clear("sec_new2")
sec_pw_msg("Password changed.", 1)
}
fn sec_pw_msg(m, good) {
st_set("sec_msg", m)
st_set("sec_msg_ok", good)
}