灵符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/core/codec.ling
# codec.ling — canonical encoding of transactions and blocks (DESIGN §7.3,
# §7.4), as hex strings.
#
# Decoding uses a small cursor kept in the isolate-local st_* store
# ("rd_buf", "rd_pos", "rd_err"): every rd_* call is a no-op once an error is
# recorded, and callers check rd_failed() once at the end. That keeps the
# decoders readable in a language without exceptions, and every read is
# bounded, so hostile input can only ever produce an error.

use "params.ling"

form Input { denom, ring, key_image }
form Output { denom, onetime, kem_ct, memo }
# coinbase: true ⇒ `height` is the coinbase height and inputs/sigs are empty;
# otherwise `ref_height` applies (DESIGN §7.4 rule 0).
form Tx { version, coinbase, height, ref_height, inputs, outputs, fee, extra, sigs }
form Header { version, network, height, prev_id, timestamp, merkle_root, nonce }
form Block { header, txs }

# ── reader ────────────────────────────────────────────────────────────────

fn rd_begin(hex) {
    st_set("rd_buf", hex)
    st_set("rd_pos", 0)
    st_set("rd_err", "")
    st_set("rd_len", hex_len(hex))
    return 0
}

fn rd_failed() {
    return st_get("rd_err", "") != ""
}

fn rd_fail(msg) {
    if st_get("rd_err", "") == "" {
        st_set("rd_err", msg)
    }
    return 0
}

fn rd_remaining() {
    return st_get("rd_len", 0) - st_get("rd_pos", 0)
}

fn rd_fixed(n) {
    if rd_failed() {
        return ""
    }
    if rd_remaining() < n {
        rd_fail("unexpected end of input")
        return ""
    }
    bind pos = st_get("rd_pos", 0)
    st_set("rd_pos", pos + n)
    return hex_slice(st_get("rd_buf", ""), pos, n)
}

fn rd_num(width) {
    if rd_failed() {
        return 0
    }
    bind pos = st_get("rd_pos", 0)
    if rd_remaining() < width {
        rd_fail("unexpected end of input")
        return 0
    }
    bind r = if width == 1 { hex_u8(st_get("rd_buf", ""), pos) } else { if width == 4 { hex_u32le(st_get("rd_buf", ""), pos) } else { hex_u64le(st_get("rd_buf", ""), pos) } }
    return match r {
        ok(v) => rd_advance(width, v),
        bad(e) => rd_fail(e),
    }
}

fn rd_advance(width, v) {
    st_set("rd_pos", st_get("rd_pos", 0) + width)
    return v
}

fn rd_u8() {
    return rd_num(1)
}

fn rd_u64() {
    return rd_num(8)
}

fn rd_varint() {
    if rd_failed() {
        return 0
    }
    return match hex_varint(st_get("rd_buf", ""), st_get("rd_pos", 0)) {
        ok(pair) => rd_set_pos(list_get(pair, 1), list_get(pair, 0)),
        bad(e) => rd_fail(e),
    }
}

fn rd_set_pos(pos, v) {
    st_set("rd_pos", pos)
    return v
}

# A varint count that must not exceed `max` (caps allocations).
fn rd_count(max) {
    bind n = rd_varint()
    if n > max {
        rd_fail("count exceeds limit")
        return 0
    }
    return n
}

fn rd_end() {
    if rd_failed() == false {
        if rd_remaining() != 0 {
            rd_fail("trailing bytes")
        }
    }
    return 0
}

# ── transactions ──────────────────────────────────────────────────────────

fn enc_output(o) {
    return hex_concat([u64le_hex(o.denom), o.onetime, o.kem_ct, o.memo])
}

fn enc_prefix(tx) {
    bind parts = [u8_hex(tx.version)]
    if tx.coinbase {
        bind parts = list_push(parts, varint_hex(0))
        bind parts = list_push(parts, u64le_hex(tx.height))
    } else {
        bind parts = list_push(parts, varint_hex(len(tx.inputs)))
        bind parts = list_push(parts, u64le_hex(tx.ref_height))
        bind i = 0
        while i < len(tx.inputs) {
            bind inp = list_get(tx.inputs, i)
            bind parts = list_push(parts, u64le_hex(inp.denom))
            bind parts = list_push(parts, varint_hex(len(inp.ring)))
            bind prev = 0
            bind k = 0
            while k < len(inp.ring) {
                bind g = list_get(inp.ring, k)
                bind delta = if k == 0 { g } else { g - prev }
                bind parts = list_push(parts, varint_hex(delta))
                bind prev = g
                bind k = k + 1
            }
            bind parts = list_push(parts, inp.key_image)
            bind i = i + 1
        }
    }
    bind parts = list_push(parts, varint_hex(len(tx.outputs)))
    bind j = 0
    while j < len(tx.outputs) {
        bind parts = list_push(parts, enc_output(list_get(tx.outputs, j)))
        bind j = j + 1
    }
    bind parts = list_push(parts, u64le_hex(tx.fee))
    bind parts = list_push(parts, varint_hex(hex_len(tx.extra)))
    bind parts = list_push(parts, tx.extra)
    return hex_concat(parts)
}

fn enc_tx(tx) {
    if tx.coinbase {
        return enc_prefix(tx)
    }
    return hex_concat(list_push([enc_prefix(tx)], hex_concat(tx.sigs)))
}

fn tx_prefix_hash(tx) {
    return lw_h256("LW1/tx/prefix", [enc_prefix(tx)])
}

fn tx_id(tx) {
    return lw_h256("LW1/tx/id", [enc_tx(tx)])
}

fn tx_size(tx) {
    return hex_len(enc_tx(tx))
}

fn dec_output() {
    bind denom = rd_u64()
    bind onetime = rd_fixed(PK_LEN)
    bind kem_ct = rd_fixed(KEM_CT_LEN)
    bind memo = rd_fixed(MEMO_LEN)
    if rd_failed() == false {
        if lw_pk_valid(onetime) == false {
            rd_fail("bad one-time key")
        }
    }
    return Output(denom, onetime, kem_ct, memo)
}

# Decode one tx at the current cursor (shared by blocks).
fn dec_tx_here() {
    bind version = rd_u8()
    bind n_in = rd_count(MAX_TX_INPUTS)
    bind coinbase = n_in == 0
    bind height = 0
    bind ref_height = 0
    bind inputs = []
    if coinbase {
        bind height = rd_u64()
    } else {
        bind ref_height = rd_u64()
        bind i = 0
        while i < n_in {
            bind denom = rd_u64()
            bind n = rd_count(MAX_RING)
            if n == 0 {
                rd_fail("empty ring")
            }
            bind ring = []
            bind prev = 0
            bind k = 0
            while k < n {
                bind d = rd_varint()
                if k > 0 {
                    if d == 0 {
                        rd_fail("ring not strictly increasing")
                    }
                }
                bind g = if k == 0 { d } else { prev + d }
                # ring offsets are real output indices: they stay well under 2^53, where f64 is still exact and the
                # varint re-encoder accepts the value. A sum that climbs past that is a crafted tx, not a real one.
                if g >= 4503599627370496 {
                    rd_fail("ring index out of range")
                }
                bind ring = list_push(ring, g)
                bind prev = g
                bind k = k + 1
            }
            bind ki = rd_fixed(KI_LEN)
            bind inputs = list_push(inputs, Input(denom, ring, ki))
            bind i = i + 1
        }
    }
    bind n_out = rd_count(MAX_TX_OUTPUTS)
    bind outputs = []
    bind j = 0
    while j < n_out {
        bind outputs = list_push(outputs, dec_output())
        bind j = j + 1
    }
    bind fee = rd_u64()
    bind extra_len = rd_count(MAX_EXTRA_BYTES)
    bind extra = rd_fixed(extra_len)
    bind sigs = []
    if coinbase == false {
        bind i = 0
        while i < len(inputs) {
            bind ring_len = len(list_get(inputs, i).ring)
            bind sigs = list_push(sigs, rd_fixed(32 + ring_len * Z_LEN))
            bind i = i + 1
        }
    }
    return Tx(version, coinbase, height, ref_height, inputs, outputs, fee, extra, sigs)
}

fn dec_tx(hex) {
    rd_begin(hex)
    bind tx = dec_tx_here()
    rd_end()
    if rd_failed() {
        return bad(st_get("rd_err", "decode error"))
    }
    return ok(tx)
}

# ── blocks ────────────────────────────────────────────────────────────────

fn enc_header(h) {
    return hex_concat([u8_hex(h.version), u8_hex(h.network), u64le_hex(h.height), h.prev_id, u64le_hex(h.timestamp), h.merkle_root, u64le_hex(h.nonce)])
}

fn block_id(h) {
    return lw_h256("LW1/block", [enc_header(h)])
}

fn with_nonce(h, nonce) {
    return Header(h.version, h.network, h.height, h.prev_id, h.timestamp, h.merkle_root, nonce)
}

fn with_merkle(h, root) {
    return Header(h.version, h.network, h.height, h.prev_id, h.timestamp, root, h.nonce)
}

fn merkle_root(txids) {
    if len(txids) == 0 {
        return lw_h256("LW1/merkle/empty", [])
    }
    bind level = []
    bind i = 0
    while i < len(txids) {
        bind level = list_push(level, lw_h256("LW1/merkle/leaf", [list_get(txids, i)]))
        bind i = i + 1
    }
    while len(level) > 1 {
        bind next = []
        bind k = 0
        while k < len(level) {
            if k + 1 < len(level) {
                bind next = list_push(next, lw_h256("LW1/merkle/node", [list_get(level, k), list_get(level, k + 1)]))
            } else {
                bind next = list_push(next, list_get(level, k))
            }
            bind k = k + 2
        }
        bind level = next
    }
    return list_get(level, 0)
}

fn block_merkle(b) {
    bind ids = []
    bind i = 0
    while i < len(b.txs) {
        bind ids = list_push(ids, tx_id(list_get(b.txs, i)))
        bind i = i + 1
    }
    return merkle_root(ids)
}

fn enc_block(b) {
    bind parts = [enc_header(b.header), varint_hex(len(b.txs))]
    bind i = 0
    while i < len(b.txs) {
        bind parts = list_push(parts, enc_tx(list_get(b.txs, i)))
        bind i = i + 1
    }
    return hex_concat(parts)
}

fn dec_header_here() {
    return Header(rd_u8(), rd_u8(), rd_u64(), rd_fixed(32), rd_u64(), rd_fixed(32), rd_u64())
}

fn dec_block(hex) {
    if hex_len(hex) > MAX_BLOCK_BYTES {
        return bad("block too large")
    }
    rd_begin(hex)
    bind header = dec_header_here()
    bind n = rd_count(MAX_BLOCK_BYTES / 4000 + 1)
    bind txs = []
    bind i = 0
    while i < n {
        bind txs = list_push(txs, dec_tx_here())
        bind i = i + 1
    }
    rd_end()
    if rd_failed() {
        return bad(st_get("rd_err", "decode error"))
    }
    return ok(Block(header, txs))
}

← Back to index