commit b7ae7b3d91fda1296ba615bea6dfcd7888e3017f
parent 8d2bcaf34de48c2dc16847b9bc9abb3b6c217509
Author: lash <dev@holbrook.no>
Date: Wed, 5 Nov 2025 16:10:59 +0000
Make verifier example
Diffstat:
3 files changed, 252 insertions(+), 21 deletions(-)
diff --git a/dummy/demo.py b/dummy/demo.py
@@ -0,0 +1,230 @@
+import sys
+import logging
+import datetime
+
+from lxml import etree
+
+logging.basicConfig(level=logging.DEBUG)
+logg = logging.getLogger()
+
+
+class NoopSigVerifier:
+
+ def verify(self, msg, key, sig):
+ logg.warning('using noop verifier')
+ return True
+
+
+class UnitIndex:
+
+ def __init__(self, base):
+ self.base = base
+ self.detail = {}
+ self.exchange = {}
+
+
+ @staticmethod
+ def from_tree(tree):
+ r = UnitIndex(tree.get('base'))
+ for o in tree.iter('unit'):
+ r.detail[o.get('sym')] = int(o.find('precision').text)
+ r.exchange[o.get('sym')] = int(o.find('ex').text)
+ r.check()
+ return r
+
+
+ def check(self):
+ self.get(self.base)
+ return self
+
+
+ def get(self, k):
+ self.detail[k]
+ return k
+
+
+ def to_floatstring(self, sym, v):
+ c = self.detail[sym]
+ i = c * -1
+ s = str(v)
+ l = len(s)
+ if l < c:
+ ss = '0' * c
+ s = '0' + ss[:c-l] + s
+ return s[:i] + '.' + s[i:]
+
+
+class Entry:
+
+ def __init__(self, typ, amount, unit, serial, account, dt, description=None, parent=None):
+ self.typ = typ
+ self.parent = parent
+ self.amount = amount
+ self.unit = unit
+ self.serial = serial
+ self.account = account
+ self.dt = dt
+ self.attachment = []
+ self.sigs = {}
+
+
+ def attach(self, mime, algo, digest, description=None, slug=None):
+ self.attachment.append((mime, algo, digest, description, slug,))
+
+
+ def add_signature(self, keyid, sigdata):
+ self.sigs[keyid] = sigdata
+
+
+ @staticmethod
+ def from_tree(tree, unitindex):
+ o = tree.find('data')
+ amount = int(o.find('amount').text)
+ unit = unitindex.get(o.find('unit').text)
+ serial = int(o.find('serial').text)
+ account = o.find('account').text
+ dt = datetime.date.fromisoformat(o.find('date').text)
+ r = Entry(tree.get('type'), amount, unit, serial, account, dt)
+ return r
+
+
+class RunningTotal:
+
+ def __init__(self, sym, unitindex, asset=0, liability=0, income=0, expense=0):
+ self.sym = sym
+ self.asset = asset
+ self.liability = liability
+ self.income = income
+ self.expense = expense
+ self.unitindex = unitindex
+
+
+ def get_balance(self):
+ return self.asset - self.liability
+
+
+ def get_result(self):
+ return self.income - self.expense
+
+
+ def income_delta(self, v):
+ self.income += v
+ self.asset += v
+
+
+ def expense_delta(self, v):
+ self.expense += v
+ self.asset -= v
+
+
+ def asset_delta(self, v):
+ self.asset += v
+
+
+ def liability_delta(self, v):
+ self.liability += v
+
+
+ def apply_entry(self, entry):
+ fn = getattr(self, entry.typ + '_delta')
+ fn(entry.amount)
+ logg.debug('applied entry {} typ {} amount {} total {} balance {}'.format(entry.serial, entry.typ, entry.amount, getattr(self, entry.typ), self.unitindex.to_floatstring(self.sym, self.get_balance())))
+
+
+ def __str__(self):
+ return 'running total {}: income {} expense {} asset {} liability {}'.format(self.sym, self.income, self.expense, self.asset, self.liability)
+
+
+class Ledger:
+
+ def __init__(self, base, unitindex, verifier=None):
+ self.uidx = unitindex
+ self.base = bytes.fromhex(base)
+ self.sigs = {}
+ if verifier == None:
+ verifier = NoopSigVerifier()
+ self.verifier = verifier
+ self.entries = {}
+ self.running = {}
+
+
+ def add_entry_from_tree(self, tree):
+ o = tree.find('data/parent')
+
+
+ def add_signature(self, sigdata, identity):
+ self.verifier.verify(self.base, identity, sigdata)
+ self.sigs[identity] = sigdata
+ logg.debug('add sig from key{}: {}'.format(identity, sigdata))
+
+
+ @staticmethod
+ def from_tree(tree, unitindex, verifier=None):
+ o = tree.get('digest')
+ r = Ledger(o, unitindex, verifier=verifier)
+ for sig in tree.iter('sig'):
+ keyid = sig.get('keyid')
+ digest = sig.text
+ r.add_signature(digest, keyid)
+
+ o = tree.find('real')
+ asset = int(o.find('asset').text)
+ liability = int(o.find('liability').text)
+ r.real = RunningTotal('.', unitindex, asset=asset, liability=liability)
+ logg.debug(r.real)
+
+ for v in tree.iter('virt'):
+ income = int(v.find('income').text)
+ expense = int(v.find('expense').text)
+ asset = int(v.find('asset').text)
+ liability = int(v.find('liability').text)
+ sym = v.get('symbol')
+ r.running[sym] = RunningTotal(sym, unitindex, income=income, expense=expense, asset=asset, liability=liability)
+ logg.debug(r.running[sym])
+
+ return r.check()
+
+
+ def apply_tree(self, tree):
+ for v in tree.iter('entry'):
+ o = Entry.from_tree(v, self.uidx)
+ self.entries[o.serial] = o
+ self.running[o.unit].apply_entry(o)
+
+
+ def check(self):
+ return self
+
+
+ def __str__(self):
+ return "state: " + self.base.hex()
+
+
+def init_ledger(tree, units):
+ o = tree.find('incoming')
+ return Ledger.from_tree(o, units)
+
+
+def get_units(tree):
+ o = tree.find('units')
+ return UnitIndex.from_tree(o)
+
+
+def load(fp):
+ f = open(fp, 'r')
+ tree = etree.parse(f)
+ f.close()
+ return tree.getroot()
+
+
+if __name__ == '__main__':
+ fp = 'running.xml'
+ try:
+ fp = sys.argv[1]
+ except IndexError:
+ pass
+ root = load(fp)
+ un = get_units(root)
+ st = init_ledger(root, un)
+ st.apply_tree(root)
+ print(st)
diff --git a/dummy/present.xsl b/dummy/present.xsl
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
-<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:template match="/">
<html>
<head>
@@ -12,11 +12,10 @@
</xsl:template>
<xsl:template match="/ledger">
- <p>Document retrieved: <xsl:value-of select="retrieved" /></p>
- <xsl:apply-templates select="entry" />
+ <p>Document retrieved: <xsl:value-of select="retrieved" /> from <xsl:value-of select="src" /></p>
+ <xsl:for-each select="entry">
+ <p><xsl:value-of select="data/serial" />:<xsl:value-of select="data/date" />:<xsl:value-of select="data/ref" /></p>
+ </xsl:for-each>
</xsl:template>
- <xsl:template match="entry">
- <p>ref: <xsl:value-of select="data/ref" /></p>
- </xsl:template>
</xsl:stylesheet>
diff --git a/dummy/running.xml b/dummy/running.xml
@@ -3,28 +3,25 @@
<ledger>
<retrieved>2025-11-02T13:09:55Z</retrieved>
<src>playalastunas.org</src>
- <units vs="BTC">
- <unit>
- <sym>USD</sym>
+ <units base="BTC">
+ <unit sym="USD">
<precision>2</precision>
<ex>10999310</ex>
</unit>
- <unit>
- <sym>BTC</sym>
+ <unit sym="BTC">
<precision>8</precision>
<ex>1</ex>
</unit>
</units>
- <incoming serial="231">
- <state>
- <serial>231</serial>
- <digest>b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c</digest>
- <sig pubkey="58851ae2166b3f1454193e0a7e821402dd1c1a91">1f74a9c0196a025f27d4e940c4abedfa2d37504f268aea359659cb65b85bd4d7974369507950006964c93391d3d4580ab8064f6d30a62908468ef771be952e95</sig>
- <sig pubkey="566c38287d3f31c7e50836cae58e426c6bccc52d">117a57c72ed210b91469307a1c2e73fe2d5ee306cd8ccf1a9db4ecb15d38ecbbfc97d62fec4ab8aadb08c531f2d1ede34cb6e4d3987bcba63322a0767e532e13</sig>
- </state>
+ <resolver algo="sha256" proto="https">g33k.holbrook.no</resolver>
+ <identity keyid="f1d2d2f924e986ac86fdf7b36c94bcdf32beec15" didtype="web">nondominium.org/lash/</identity>
+ <identity keyid="f1d2d2f924e986ac86fdf7b36c94bcdf32beec15" didtype="web">holbrook.no</identity>
+ <incoming serial="231" digest="b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c">
+ <sig keyid="58851ae2166b3f1454193e0a7e821402dd1c1a91">1f74a9c0196a025f27d4e940c4abedfa2d37504f268aea359659cb65b85bd4d7974369507950006964c93391d3d4580ab8064f6d30a62908468ef771be952e95</sig>
+ <sig keyid="566c38287d3f31c7e50836cae58e426c6bccc52d">117a57c72ed210b91469307a1c2e73fe2d5ee306cd8ccf1a9db4ecb15d38ecbbfc97d62fec4ab8aadb08c531f2d1ede34cb6e4d3987bcba63322a0767e532e13</sig>
<real unit="BTC">
<asset>6323141</asset>
- <liabilities>0</liabilities>
+ <liability>0</liability>
</real>
<virt symbol="USD">
<income>4213</income>
@@ -49,8 +46,13 @@
<account>Donations</account>
<description>Alice</description>
<amount>524</amount>
+ <attachment mime="image/jpeg">
+ <slug>troll</slug>
+ <description>The face of a troll</description>
+ <digest algo="sha256">777b30c8fc40aea3c717777831a05c9f29c7b6735f1573e9b0b55373c264f6f3</digest>
+ </attachment>
</data>
- <sig pubkey="e242ed3bffccdf271b7fbaf34ed72d089537b42f">0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6</sig>
+ <sig keyid="e242ed3bffccdf271b7fbaf34ed72d089537b42f">0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6/</sig>
</entry>
<entry type="liability">
<data>
@@ -63,6 +65,6 @@
<description>Install AC</description>
<amount>1000000</amount>
</data>
- <sig pubkey="f1d2d2f924e986ac86fdf7b36c94bcdf32beec15">cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1a60ed9cabdb8cac6d24242dac4063</sig>
+ <sig keyid="f1d2d2f924e986ac86fdf7b36c94bcdf32beec15">cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1a60ed9cabdb8cac6d24242dac4063</sig>
</entry>
</ledger>