usawa

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | Submodules | LICENSE

commit 986c41b48325c35d09f406e4f66ffb362711beb1
parent fe8a2b761ae29eabb35da1b7b92a6746aba14d34
Author: lash <dev@holbrook.no>
Date:   Thu, 15 Jan 2026 14:11:35 +0000

Add ledger signature and serialization

Diffstat:
Mdummy/tests/ledger.py | 14+++++++++++++-
Mdummy/usawa/crypto.py | 11+++++++++++
Mdummy/usawa/ledger.py | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdummy/usawa/unit.py | 2+-
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: