usawa

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

commit fe8a2b761ae29eabb35da1b7b92a6746aba14d34
parent f9524dceb62b126823d9efdfe37bac85e186b1ac
Author: lash <dev@holbrook.no>
Date:   Thu, 15 Jan 2026 13:23:17 +0000

Use ACL to generate ledger xml identities

Diffstat:
Mdummy/usawa/crypto.py | 51+++++++++++++++++++++++++++++++++++++++++++++------
Mdummy/usawa/ledger.py | 30++++++++++++++++++++----------
Mdummy/usawa/runnable/create.py | 5+++--
Mdummy/usawa/unit.py | 38+++++++++++++++++++++++++++++++++-----
Adummy/usawa/util.py | 5+++++
5 files changed, 106 insertions(+), 23 deletions(-)

diff --git a/dummy/usawa/crypto.py b/dummy/usawa/crypto.py @@ -5,9 +5,27 @@ import nacl.signing AXX_ALL = 0xffffffff AXX_ANY = 0x01 +DEFAULT_DID = 'usawa' + logg = logging.getLogger('crypto') +class DID: + + def __init__(self, v='_', method=DEFAULT_DID): + self.v = v + self.m = method + + + def method(self): + return self.m + + + def __str__(self): + return 'did:' + self.m + ':' + self.v + + + class Wallet: """Wallet is an unimplemented class defining the interface for wallet operations. """ @@ -17,16 +35,22 @@ class Wallet: :returns: DID URI :rtype: str """ - def __init__(self, did_type='usawalocal'): - self.did_type = did_type + def __init__(self, did=None): + if did == None: + did = DID() + self.didval = did def did(self): - return self.did_type + return self.didval + + + def did_method(self): + return self.didval.method() def did_uri(self): - return 'did:' + self.did_type + ':' + self.address() + return str(self.didval) def address(self): @@ -97,6 +121,7 @@ class DemoWallet(Wallet): else: publickey = nacl.signing.VerifyKey(publickey) self.pubk = publickey + self.didval = DID(v=self.pubkey().hex()) def sign(self, v): @@ -134,9 +159,21 @@ class ACL: def __init__(self): self.axx = {} self.rev = {} + self.dids = {} + + @staticmethod + def from_wallet(wallet, what=None, label=None): + o = ACL() + o.add(wallet.pubkey(), what=what, label=label, did=wallet.did()) + return o - def add(self, who, what=None, label=None): + + def did(self, v): + return self.dids[v] + + + def add(self, who, what=None, label=None, did=DEFAULT_DID): """Add a public key to the trusted list of keys. :param who: Binary or hexadecimal public key data. @@ -152,9 +189,10 @@ class ACL: label = who if what == None: what = AXX_ALL - logg.info('add acl line "{}" ({}): {}'.format(label, who, what)) + logg.info('add acl line "{}" ({}): {} did {}'.format(label, who, what, did)) self.axx[label] = (who, what,) self.rev[who] = label + self.dids[label] = did def have(self, who): @@ -206,3 +244,4 @@ class ACL: v = self.axx[self.rev[v]][0] r.append(v) return r + diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py @@ -11,10 +11,12 @@ from .crypto import DemoWallet from .xml import nsmap, XML_FORMAT_VERSION from .constant import NSPREFIX, DEFAULTPARENT from .entry import Entry +from .util import to_datestring logg = logging.getLogger('usawa.ledger') + class RunningTotal: """RunningTotal is used by the Ledger object to keep track of running totals in all asset and transaction categories of a given unit symbol. @@ -174,7 +176,7 @@ class Ledger: self.src = src self.topic = topic self.acl = acl - self.wallet = wallet + self.dt = None if self.topic == None: self.topic = os.urandom(64) if base == None: @@ -186,6 +188,7 @@ class Ledger: self.reset() self.serial = self.base_serial self.cur = base + self.wallet = wallet logg.debug('ledger base {} serial {} from topic {}'.format(self.base.hex(), self.serial, self.topic.hex())) @@ -237,7 +240,7 @@ class Ledger: :rtype: None :todo: swapping tree keeps two trees in memory, perhaps it can be more efficient """ - def reset(self, src=None, topic=None, wallet=None): + def reset(self, src=None, topic=None, acl=None, wallet=None): if wallet != None: self.set_wallet(wallet) self.serial = self.base_serial @@ -255,7 +258,8 @@ class Ledger: topic = self.topic.hex() o.text = topic o = lxml.etree.SubElement(tree, NSPREFIX + 'retrieved', nsmap=nsmap()) - o.text = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%H:%M:%SZ') + self.dt = datetime.datetime.now() + o.text = to_datestring(self.dt) #self.tree.append(o) o = lxml.etree.SubElement(tree, NSPREFIX + 'src', nsmap=nsmap()) if src == None: @@ -293,13 +297,14 @@ class Ledger: identity.set('keyid', tree_identity.get('keyid')) identity.set('didtype', tree_identity.get('didtype')) - if wallet != None: - v = wallet.address() - if wallet.pubkey() not in identities: - identities.append(v) - identity = lxml.etree.SubElement(tree, NSPREFIX + 'identity', nsmap=nsmap()) - identity.set('keyid', v.hex()) - identity.set('didtype', wallet.did()) + if acl != None: + for v in acl.pubkeys(binary=False): + #v = wallet.address() + if v not in identities: + identities.append(v) + identity = lxml.etree.SubElement(tree, NSPREFIX + 'identity', nsmap=nsmap()) + identity.set('keyid', v) + identity.set('didtype', acl.did(v).method()) if len(identities) == 0: logg.warning('no identities in xml, need at least one to validate against schema') @@ -608,8 +613,13 @@ class Ledger: :rtype: str """ def serialize(self, ledger): + ts = int(self.dt.timestamp()) + ts_bytes = ts.to_bytes(4, byteorder='big') + units = self.uidx.serialize() d = [ self.topic, + ts_bytes, + units, ] logg.debug('serialize ledger {}'.format(d)) return rencode.dumps(d) diff --git a/dummy/usawa/runnable/create.py b/dummy/usawa/runnable/create.py @@ -5,7 +5,7 @@ import urllib.parse import argparse import datetime -from usawa import Ledger, DemoWallet, UnitIndex +from usawa import Ledger, DemoWallet, UnitIndex, ACL from usawa.store import LedgerStore from whee.valkey import ValkeyStore @@ -149,6 +149,7 @@ if wallet == None: wallet = DemoWallet(privatekey=pk) logg.info('loaded existing key. {}'.format(wallet.pubkey().hex())) -ledger.reset(topic=ctx.topic, src=ctx.uri, wallet=wallet) +acl = ACL.from_wallet(wallet) +ledger.reset(topic=ctx.topic, src=ctx.uri, acl=acl) ctx.f.write(ledger.to_string()) ctx.close() diff --git a/dummy/usawa/unit.py b/dummy/usawa/unit.py @@ -1,5 +1,7 @@ import logging +import rencode + from .constant import NSPREFIX from .xml import nsmap @@ -25,20 +27,24 @@ class UnitIndex: def __init__(self, base, precision=2): self.base = base self.detail = {base: precision} - self.exchange = {base: 1} + self.exchange = {base: 1000000000} """Add a unit to the index. + The exchange rate is stored as an integer with nano precision. If a float is passed as value, it will be correspondingly converted to an integer. + :param sym: The symbol name of the unit. :type sym: str :param precision: The decimal precision of the base unit. Default is 2. :type precision: int - :param ex: The exchange rate of the unit, relative to the base unit. Default is 1.0. - :type ex: float + :param ex: The exchange rate of the unit, relative to the base unit. Default is 1000000000 (1.0). + :type ex: int or float """ - def add(self, sym, precision=2, ex=1.0): + def add(self, sym, precision=2, ex=1000000000): self.detail[sym] = precision + if isinstance(ex, float): + ex = int(ex*1000000000) # nano resolution self.exchange[sym] = ex @@ -98,9 +104,11 @@ class UnitIndex: """Retrieve the exchange rate for the unit. + The value represents a decimal number with nano precision. For example, a value of 4200000000 corresponds to a float value of 4.2. + :raises: KeyError if symbol not found. :returns: Rate - :rtype: float + :rtype: int """ def ex(self, k): return self.exchange[k] @@ -187,3 +195,23 @@ class UnitIndex: if neg: r *= -1 return int(r) + + + def serialize(self): + syms = list(self.base) + syms.sort() + units = [] + for v in syms: + precision = self.detail[v].to_bytes(1) + exchange = self.exchange[v].to_bytes(8, byteorder='big') + units.append((v, precision, exchange,)) + d = [ + self.base, + units, + ] + return rencode.dumps(d) + + + @staticmethod + def deserialize(v): + pass diff --git a/dummy/usawa/util.py b/dummy/usawa/util.py @@ -0,0 +1,5 @@ +import datetime + + +def to_datestring(v): + return datetime.datetime.strftime(v, '%Y-%m-%dT%H:%M:%SZ')