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:
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')