commit 986c41b48325c35d09f406e4f66ffb362711beb1
parent fe8a2b761ae29eabb35da1b7b92a6746aba14d34
Author: lash <dev@holbrook.no>
Date: Thu, 15 Jan 2026 14:11:35 +0000
Add ledger signature and serialization
Diffstat:
4 files changed, 85 insertions(+), 6 deletions(-)
diff --git a/dummy/tests/ledger.py b/dummy/tests/ledger.py
@@ -7,7 +7,7 @@ import copy
import lxml.etree
from whee.mem import MemStore
-from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet
+from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet, ACL
from usawa.store import LedgerStore
logging.basicConfig(level=logging.DEBUG)
@@ -41,6 +41,7 @@ class TestLedger(unittest.TestCase):
def test_ledger_firstfew(self):
s = 'FOO'
uidx = UnitIndex(s)
+ uidx.add('USD')
o = Ledger(uidx)
store = LedgerStore(self.store, ledger=o)
store.start()
@@ -60,5 +61,16 @@ class TestLedger(unittest.TestCase):
o.add_entry(v)
+ def test_serialize(self):
+ wallet = DemoWallet()
+ acl = ACL.from_wallet(wallet)
+
+ s = 'FOO'
+ uidx = UnitIndex(s)
+ o = Ledger(uidx, acl=acl, wallet=wallet)
+ #b = o.serialize()
+ r = o.sign()
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/dummy/usawa/crypto.py b/dummy/usawa/crypto.py
@@ -1,5 +1,7 @@
import logging
+import rencode
+
import nacl.signing
AXX_ALL = 0xffffffff
@@ -245,3 +247,12 @@ class ACL:
r.append(v)
return r
+
+ def serialize(self):
+ keys = list(self.rev.keys())
+ keys.sort()
+ r = []
+ for k in keys:
+ v = self.axx[self.rev[k]][1]
+ r.append((k, v,))
+ return rencode.dumps(r)
diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py
@@ -134,6 +134,21 @@ class RunningTotal:
return RunningTotal(unit, asset=asset, liability=liability)
+ """
+ :todo: Use varint in serialization, consider leb128
+ """
+ def serialize(self):
+ d = [
+ self.sym,
+ self.income.to_bytes(8, byteorder='big'),
+ self.expense.to_bytes(8, byteorder='big'),
+ self.asset.to_bytes(8, byteorder='big'),
+ self.liability.to_bytes(8, byteorder='big'),
+ ]
+ return rencode.dumps(d)
+
+
+
def __str__(self):
return 'running total {}: income {} expense {} asset {} liability {}'.format(self.sym, self.income, self.expense, self.asset, self.liability)
@@ -170,6 +185,7 @@ class Ledger:
self.sigs = {}
self.entries = {}
self.running = {}
+ self.resolvers = {}
self.tree = tree
self.base = base
self.base_serial = serial
@@ -239,6 +255,7 @@ class Ledger:
:type topic: bytes
:rtype: None
:todo: swapping tree keeps two trees in memory, perhaps it can be more efficient
+ :todo: add permissions array to the identities elements
"""
def reset(self, src=None, topic=None, acl=None, wallet=None):
if wallet != None:
@@ -342,6 +359,12 @@ class Ledger:
o = lxml.etree.SubElement(real, NSPREFIX + 'liability', nsmap=nsmap())
o.text = '0'
+ for k in self.uidx.syms():
+ if self.running.get(k) != None:
+ continue
+ logg.debug('add new runningtotal for {}'.format(k))
+ self.running[k] = RunningTotal(k, self.uidx)
+
o = lxml.etree.SubElement(incoming, NSPREFIX + 'digest', nsmap=nsmap())
o.attrib['algo'] = 'sha512'
o.text = self.base.hex()
@@ -398,7 +421,12 @@ class Ledger:
o = lxml.etree.Element(NSPREFIX + 'resolver', nsmap=nsmap())
o.attrib['algo'] = algo
o.attrib['proto'] = proto
- o.text = path
+ o.text = location
+
+ if self.resolvers.get(algo) == None:
+ self.resolvers[algo] = []
+ self.resolvers[algo].append((proto, location,))
+
tree.addnext(o)
"""Check signature on individual ledger entry.
@@ -471,7 +499,7 @@ class Ledger:
"""
def add_signature(self, sigdata, identity):
self.sigs[identity] = sigdata
- logg.debug('add sig from key{}: {}'.format(identity, sigdata))
+ logg.debug('add sig from key {}: {}'.format(identity.hex(), sigdata.hex()))
"""Create a new ledger from a parsed XML document.
@@ -501,7 +529,7 @@ class Ledger:
for sig in part.iter(NSPREFIX + 'sig'):
keyid = sig.get('keyid')
digest = sig.text
- r.add_signature(digest, keyid)
+ r.add_signature(bytes.fromhex(digest), bytes.fromhex(keyid))
o = part.find('real', namespaces=nsmap())
asset = int(o.find('asset', namespaces=nsmap()).text)
@@ -612,19 +640,47 @@ class Ledger:
:returns: String representation of the entry, in rencode format.
:rtype: str
"""
- def serialize(self, ledger):
+ def serialize(self):
ts = int(self.dt.timestamp())
ts_bytes = ts.to_bytes(4, byteorder='big')
units = self.uidx.serialize()
+ identities = self.acl.serialize()
+ totals = []
+ v = self.running[self.uidx.base].serialize()
+ totals.append(v)
+ for k in self.running.keys():
+ if k == self.uidx.base:
+ continue
+ v = self.running[k].serialize()
+ totals.append(v)
d = [
self.topic,
+ self.serial.to_bytes(8, byteorder='big'),
+ self.cur,
ts_bytes,
units,
+ identities,
+ totals,
]
logg.debug('serialize ledger {}'.format(d))
return rencode.dumps(d)
+ """
+
+ :raises AttributeError: Ledger is missing wallet
+ """
+ def sign(self):
+ if self.wallet == None:
+ raise AttributeError()
+ v = self.serialize()
+ r = self.wallet.sign(v)
+ k = self.wallet.pubkey()
+ self.add_signature(r, k)
+ return r
+
+
+
"""Create a ledger object from serialized data.
:param data: rencoded ledger object, as produced by the serialize() method.
diff --git a/dummy/usawa/unit.py b/dummy/usawa/unit.py
@@ -198,7 +198,7 @@ class UnitIndex:
def serialize(self):
- syms = list(self.base)
+ syms = list(self.detail.keys())
syms.sort()
units = []
for v in syms: