灵符lingfu
L

ling-wallet

↓ 0 downloads

Ling Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG

Windows · x86-64 ✕Linux · x86-64 ✕LingOS · x86-64LingOS · Pi 5 ARM64 ✕
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-wallet
LicenseMIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (116)

ling/mock.ling
# mock.ling — a simulated backend so the UI runs without a node. Sync,
# heights, peer uptimes, hashrate and the HyperTorus trace move with time;
# address lookups and signing resolve after short delays; sends land in the
# history and confirm as blocks arrive. Same contract as backend.ling, with
# every function prefixed mock_ instead of be_.

use "api.ling"

bind MOCK_COIN = 100000000
bind MOCK_U32 = 4294967296
bind MOCK_WORDS = ["orbit", "candy", "lattice", "violet", "helix", "torus", "amber", "signal", "quiet", "harbor", "nebula", "copper", "meadow", "prism", "falcon", "ember", "glacier", "summit", "velvet", "anchor", "cipher", "lantern", "mosaic", "tundra"]
bind MOCK_ADDR = "lng1v7k2m9qf8x3h5d6n4p0t2wz8yj7c5sralg3ue"

fn mock_hash(seed) {
    bind hexd = "0123456789abcdef"
    bind x = (seed * 7919 + 104729) % 2147483647
    bind out = ""
    bind i = 0
    while i < 32 {
        bind x = (x * 48271) % 2147483647
        bind b = floor(x / 256) % 256
        bind out = out + substr(hexd, floor(b / 16), 1) + substr(hexd, b % 16, 1)
        bind i = i + 1
    }
    return out
}

fn mock_elapsed() { return time_now() - st_get("mk_t0", 0) }
fn mock_height() { return 1242 + floor(mock_elapsed() / 120) }

fn mock_init() {
    st_set("mk_t0", time_now())
    st_set("mk_state", "nowallet")
    st_set("mk_pw", "")
    st_set("mk_mining", true)
    st_set("mk_threads", 1)
    st_set("mk_intensity", 35)
    st_set("mk_pause", true)
    st_set("mk_next_id", 1)
    st_set("mk_walk", [305419896, 2596069104, 253635900, 1264216440, 0])
    bind now = now_unix()
    bind h = 1242
    st_set("mk_history", [
        TxRecord(mock_hash(3), "out", 7 * MOCK_COIN, 120000, h - 6, 0, now - 700, "lng1q8w3x5ffm2kzv7d4hn3j9t0yqe6c2rplx4sa8gu", "", true),
        TxRecord(mock_hash(2), "in", 25 * MOCK_COIN, 0, h - 41, 0, now - 4900, "", "order #12", false),
        TxRecord(mock_hash(1), "mined", 50 * MOCK_COIN, 0, h - 62, 0, now - 7400, "", "", false)
    ])
    st_set("mk_spent", 0)
}
fn mock_tick() { return () }

fn mock_state() { return st_get("mk_state", "nowallet") }

fn mock_create(password) {
    if str_len(password) < 10 { return bad("Use a password of at least 10 characters.") }
    st_set("mk_state", "unlocked")
    st_set("mk_pw", password)
    return ok(MOCK_WORDS)
}

fn mock_restore(phrase, password) {
    bind words = []
    bind parts = str_split(str_trim(phrase), " ")
    bind i = 0
    while i < len(parts) {
        if list_get(parts, i) != "" { bind words = list_push(words, list_get(parts, i)) }
        bind i = i + 1
    }
    if len(words) != 24 { return bad("A recovery phrase has exactly 24 words.") }
    if str_len(password) < 10 { return bad("Use a password of at least 10 characters.") }
    st_set("mk_state", "unlocked")
    st_set("mk_pw", password)
    return ok(())
}

fn mock_unlock(password) {
    if mock_state() == "nowallet" { return bad("No wallet yet.") }
    if password != st_get("mk_pw", "") { return bad("Wrong password.") }
    st_set("mk_state", "unlocked")
    return ok(())
}

fn mock_lock() {
    if mock_state() == "unlocked" { st_set("mk_state", "locked") }
}

fn mock_summary() {
    if mock_state() != "unlocked" { return bad("locked") }
    bind e = mock_elapsed()
    bind kind = "synced"
    bind sh = mock_height()
    if e < 6 {
        bind kind = "connecting"
        bind sh = 0
    } else if e < 16 {
        bind kind = "syncing"
        bind sh = 900 + floor((e - 6) * 34)
    }
    bind spent = st_get("mk_spent", 0)
    return ok(Summary(68 * MOCK_COIN - 120000 - spent, 2 * MOCK_COIN, MOCK_ADDR, mock_hash(42), kind, sh, mock_height(), NET_NAME))
}

fn mock_history(limit) {
    bind all = st_get("mk_history", [])
    bind h = mock_height()
    bind out = []
    bind i = 0
    while i < len(all) && i < limit {
        bind r = list_get(all, i)
        bind conf = 0
        if r.height >= 0 { bind conf = h - r.height + 1 }
        bind out = list_push(out, TxRecord(r.txid, r.direction, r.amount, r.fee, r.height, conf, r.timestamp, r.counterparty, r.memo, r.reduced))
        bind i = i + 1
    }
    return out
}

fn mock_lookup(address) {
    if !str_starts_with(address, "lng1") || str_len(address) < 20 {
        return ["failed", "That isn't an LNG address (they start with lng1)."]
    }
    bind key = "mk_look:" + address
    if st_has(key) < 1 { st_set(key, time_now()) }
    if time_now() - st_get(key, 0) < 1.2 { return ["pending"] }
    return ["verified", address, mock_hash(str_len(address) * 7919), "3fa9-c210-77be-04d1"]
}

fn mock_prepare(to, amount, memo) {
    if amount <= 0 { return bad("Enter an amount.") }
    if amount > 68 * MOCK_COIN - st_get("mk_spent", 0) { return bad("Not enough unlocked funds.") }
    bind id = st_get("mk_next_id", 1)
    st_set("mk_next_id", id + 1)
    bind p = Prepared(id, to, mock_hash(str_len(to) * 7919), amount, 118400, 3, 5, 341220, 11, false)
    st_set("mk_send:" + num_str(id), p)
    st_set("mk_send_memo:" + num_str(id), memo)
    st_set("mk_send_t:" + num_str(id), 0 - 1)
    return ok(p)
}

fn mock_confirm(id, password) {
    if password != st_get("mk_pw", "") { return bad("Wrong password.") }
    bind key = "mk_send:" + num_str(id)
    if st_has(key) < 1 { return bad("That send expired. Prepare it again.") }
    st_set("mk_send_t:" + num_str(id), time_now())
    return ok(())
}

fn mock_progress(id) {
    bind ks = num_str(id)
    if st_has("mk_send:" + ks) < 1 { return ["none"] }
    bind t = st_get("mk_send_t:" + ks, 0 - 1)
    if t < 0 { return ["none"] }
    if time_now() - t < 1.5 { return ["signing"] }
    bind txid = mock_hash(id + 1000)
    if st_has("mk_sent:" + ks) < 1 {
        st_set("mk_sent:" + ks, 1)
        bind p = st_get("mk_send:" + ks, ())
        st_set("mk_spent", st_get("mk_spent", 0) + p.amount + p.fee)
        bind rec = TxRecord(txid, "out", p.amount, p.fee, 0 - 1, 0, now_unix(), p.to, st_get("mk_send_memo:" + ks, ""), p.reduced)
        bind hist = st_get("mk_history", [])
        bind nh = [rec]
        bind i = 0
        while i < len(hist) {
            bind nh = list_push(nh, list_get(hist, i))
            bind i = i + 1
        }
        st_set("mk_history", nh)
    }
    return ["sent", txid]
}

fn mock_export_bundle() {
    if mock_state() != "unlocked" { return "" }
    return "lngkey1" + str_repeat("QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", 40)
}

fn mock_reveal(password) {
    if password != st_get("mk_pw", "") { return bad("Wrong password.") }
    return ok(MOCK_WORDS)
}

fn mock_change_password(old, new) {
    if old != st_get("mk_pw", "") { return bad("Wrong password.") }
    if str_len(new) < 10 { return bad("Use a password of at least 10 characters.") }
    st_set("mk_pw", new)
    return ok(())
}

# A toy 4-torus walk (64 steps per call) so the visualizer has something to draw.
fn mock_walk() {
    bind w = st_get("mk_walk", [0, 0, 0, 0, 0])
    bind x = list_get(w, 0)
    bind y = list_get(w, 1)
    bind z = list_get(w, 2)
    bind q = list_get(w, 3)
    bind tw = list_get(w, 4)
    bind trace = []
    bind i = 0
    while i < 64 {
        bind nx = x + 26442335 + (i * 2654435761) % MOCK_U32
        bind cx = sel(nx >= MOCK_U32, 1, 0)
        bind x = nx % MOCK_U32
        bind y = (y * 1103515 + x + i) % MOCK_U32
        bind nz = z + floor(x / 8) + y % 65536
        bind cz = sel(nz >= MOCK_U32, 2, 0)
        bind z = nz % MOCK_U32
        bind q = (q * 69069 + z) % MOCK_U32
        bind tw = (tw + cx + cz) % 4
        bind trace = list_push(trace, [x, y, z, q, tw])
        bind i = i + 1
    }
    st_set("mk_walk", [x, y, z, q, tw])
    return trace
}

fn mock_mining() {
    bind on = st_get("mk_mining", true)
    bind th = st_get("mk_threads", 1)
    bind it = st_get("mk_intensity", 35)
    bind rate = 0
    bind trace = []
    if on {
        bind e = mock_elapsed()
        bind jitter = 1 + 0.06 * sin(e * 1.7) + 0.03 * sin(e * 5.3)
        bind rate = 9.7 * th * it / 100 * jitter
        bind trace = mock_walk()
    }
    return Mining(on, th, 8, it, rate, 4, "2430118", trace, st_get("mk_pause", true), "")
}

fn mock_set_mining(enabled, threads, intensity, pause_when_busy) {
    st_set("mk_mining", enabled)
    st_set("mk_threads", clampf(threads, 1, 8))
    st_set("mk_intensity", clampf(intensity, 5, 100))
    st_set("mk_pause", pause_when_busy)
}

fn mock_network() {
    bind e = floor(mock_elapsed())
    bind peers = [
        ["127.0.0.1:18781", false, "a41c-09ef-5d32-77b0", true, 3620 + e],
        ["127.0.0.1:18782", false, "0be7-31aa-c94f-2e18", false, 2210 + e],
        ["127.0.0.1:52114", true, "ephemeral", false, 140 + e]
    ]
    bind mem = 1 + floor(e / 7) % 4
    return NetStatus(peers, "Direct", true, mock_height(), "2430118", mem, "c7d1-58e2-0a94-bb3f")
}

fn mock_audit() {
    return [[50 * MOCK_COIN, 1206, 311], [20 * MOCK_COIN, 402, 97], [5 * MOCK_COIN, 655, 288], [MOCK_COIN, 1310, 702]]
}

# Rows for the About page: [name, status, detail]. Status is "standard",
# "novel" or "experimental"; rows with status "limitation" are known limits.
fn mock_about() {
    return [
        ["ML-DSA-87", "standard", "FIPS 204 signatures for identities, address bundles and node handshakes."],
        ["ML-KEM-1024 + X25519", "standard", "FIPS 203 hybrid key exchange for one-time output keys and peer links."],
        ["XChaCha20-Poly1305", "standard", "Authenticated encryption for the wallet file and peer frames."],
        ["SHA-3 / SHAKE256", "standard", "FIPS 202 hashing; every proof-of-work result ends in SHAKE256."],
        ["Argon2id", "standard", "RFC 9106 password hashing for the wallet file (256 MiB, 3 passes)."],
        ["BIP-39 phrase", "standard", "24-word English encoding of the master seed."],
        ["Dandelion++", "standard", "Transactions travel a random stem before they are broadcast."],
        ["DICE-42 framing", "novel", "Outer layer around encrypted peer frames; padded to fixed sizes."],
        ["HyperTorus PoW", "novel", "Memory-hard proof-of-work designed for this project."],
        ["LRS-87 ring signatures", "experimental", "Lattice ring signatures over 11 outputs; no independent cryptanalysis yet."],
        ["Unaudited coin", "limitation", "LNG is new and has had no external security review."],
        ["Sender tracing", "limitation", "Whoever paid you can tell when you later spend those coins."],
        ["Linkability gap", "limitation", "A strong lattice attacker might double-spend one output; the supply audit bounds it."],
        ["Small rings while usage is low", "limitation", "Spends hide among few same-denomination outputs; such spends are flagged."],
        ["Public amounts", "limitation", "Each output's denomination is visible on chain."],
        ["Malware", "limitation", "A compromised computer can read keys as you type your password."]
    ]
}

← Back to index