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/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))
}