usawa

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

commit b454c4ee09998b1eb7895a122bb5586951be787d
parent 7d87306f7050dbcbc9674f1f507bfe49d2b6b8c2
Author: lash <dev@holbrook.no>
Date:   Mon, 19 Jan 2026 18:48:32 +0000

Add intro, fill out optional elements

Diffstat:
Adummy/README | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Tdummy/README.md | 0
Mdummy/doc/accounts.texi | 1+
Mdummy/doc/appendix.texi | 30++++++++++++++++++++++++------
Mdummy/doc/assets.texi | 32++++++++++++++++++++++++++++++++
Mdummy/doc/identity.texi | 7+++++++
Mdummy/doc/index.texi | 2++
Mdummy/doc/intro.texi | 42+++++++++++++++++++++++++++++++++++++++++-
Adummy/doc/protocol.texi | 19+++++++++++++++++++
Mdummy/doc/resolver.texi | 29+++++++++++++++++++++++++++++
Adummy/doc/store.texi | 2++
Mdummy/doc/unit.texi | 25+++++++++++++++++++++++++
Mdummy/doc/xml.texi | 10+++++-----
Mdummy/usawa/data/schema.xsd | 22++++++++++++++++++++--
14 files changed, 396 insertions(+), 14 deletions(-)

diff --git a/dummy/README b/dummy/README @@ -0,0 +1,189 @@ +# Usawa + +A local-first, authenticated and immutable accounting schema for individuals and small businesses. + + +## Documentation + +Please refer to the documentation in `doc/`. If is vanilla GNU texinfo, and can easily be built with: + +``` +cd doc/ +makeinfo --html index.texi +``` + +The HTML entry point for local browsing will be in `doc/index_html/index.html` + + +## Dependencies + +OS dependencies upon which python dependencies rely: + +* [libsodium](https://libsodium.net/) +* [valkey](https://valkey.io/) (server running) + +Note that `valkey` is implemented for the POC, but even this POC library allows for any key-value store that satisfies the [whee](https://pypi.org/project/wheepy/) interface. + + +## Trying out the tools + +First install requirements in a virtual environment: + +``` +python -mvenv .venv +. .venv/bin/activate +pip install .[valkey] +``` + +### Creating a ledger + +The topic may be any value, and will be stored under its digest. Prefix with `0x` to define a literal hexadecimal value. + +`python usawa/runnable/create.py -t <sometopic> -o start.xml` + +Once completed, the contents of `start.xml` will be something like this: + + +``` +<?xml version="1.0"?> +<ledger xmlns="http://usawa.defalsify.org/" version="1"> + <topic xmlns:ns="http://usawa.defalsify.org/">7092e69f8179a45f673f8e4b85cf7de2a09d5f3e43f801fd2c1a47c6dcd9f14a0137c7a1244e7e625057850f3e0c4132bbd23877c2ede5f49f12e646bb5cca7b</topic> + <generated xmlns:ns="http://usawa.defalsify.org/">2026-01-19T18:19:57Z</generated> + <src xmlns:ns="http://usawa.defalsify.org/"/> + <units xmlns:ns="http://usawa.defalsify.org/" base="BTC"> + <unit sym="BTC"> + <precision>2</precision> + <exchange>1000000000</exchange> + </unit> + </units> + <identity xmlns:ns="http://usawa.defalsify.org/" keyid="3b54648d60bb8a5b9e84fa0057f79b3a5996e511682e80176dc948dcbff5a4fc" didtype="usawa"/> + <incoming xmlns:ns="http://usawa.defalsify.org/" serial="0"> + <real unit="BTC"> + <asset>0</asset> + <liability>0</liability> + </real> + <digest algo="sha512">00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</digest> + <sig keyid="3b54648d60bb8a5b9e84fa0057f79b3a5996e511682e80176dc948dcbff5a4fc" type="ed25519">3615f40538a11d625400ac0195f803c68c7b53b93f095753dcca93c5eb2851004c2b28ca067fd6bd298e73ebe1fa02eb1392e0ff83c87a5c421516951c33c30b</sig> + </incoming> +</ledger> +``` + + +### Adding a ledger entry + +Using the initial state of the ledger, the `add.py` tool can be used to interactively specify a new entry in the ledger. + +`python usawa/runnable/add.py -i start.xml -o first.xml` + +If correctly specified, this will store an entry in the `valkey` database, and output the new _truncated state_ of the ledger to `first.xml`. + +Another entry may be added using the new _truncated state_: + +`python usawa/runnable/add.py -i first.xml -o second.xml` + +... and so on. + + +### Viewing the ledger + +An XML representation of the ledger and its entries may be generated using the corresponding ledger state. + +Using states from the precending examples: + +``` +# Will output the zero-state ledger header, aswell as two entries +python usawa/runnable/view.py start.xml +# Will output the ledger header after the first entry, aswell as one entry. +python usawa/runnable/view.py first.xml +# Will output the ledger header after the second entry with no entries. +python usawa/runnable/view.py second.xml +``` + +Example output of the first command: + +``` +<?xml version="1.0"?> +<ledger xmlns="http://usawa.defalsify.org/" version="1"> + <topic xmlns:ns="http://usawa.defalsify.org/">59e0bb093d185a9f8e677b1fb76f84d829709561ca03f3c1b53dd9cc275b26f106777ea96998a85803e6fd582173c0f631c626c2725126405e969ba0459c73ea</topic> + <generated xmlns:ns="http://usawa.defalsify.org/">2026-01-19T18:42:17Z</generated> + <src xmlns:ns="http://usawa.defalsify.org/">defalsify.org</src> + <units xmlns:ns="http://usawa.defalsify.org/" base="BTC"> + <unit sym="BTC"> + <precision>2</precision> + <exchange>1000000000</exchange> + </unit> + </units> + <identity xmlns:ns="http://usawa.defalsify.org/" keyid="3b54648d60bb8a5b9e84fa0057f79b3a5996e511682e80176dc948dcbff5a4fc" didtype="usawa"/> + <identity xmlns:ns="http://usawa.defalsify.org/" keyid="3b54648d60bb8a5b9e84fa0057f79b3a5996e511682e80176dc948dcbff5a4fc" didtype="usawa"/> + <incoming xmlns:ns="http://usawa.defalsify.org/" serial="0"> + <real unit="BTC"> + <asset>0</asset> + <liability>0</liability> + </real> + <digest algo="sha512">00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</digest> + </incoming> + <entry> + <data> + <parent>00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</parent> + <ref>a844baa7-074d-4654-8513-a0510ead7f41</ref> + <serial>1</serial> + <date>2026-01-19</date> + <dateTimeRegistered>2026-01-19T18:41:37Z</dateTimeRegistered> + <description>aergiuaheorg</description> + <debit type="expense"> + <unit>BTC</unit> + <account>general</account> + <amount>-1212500</amount> + </debit> + <credit type="asset"> + <unit>BTC</unit> + <account>general</account> + <amount>1212500</amount> + </credit> + </data> + <sig type="ed25519" keyid="3b54648d60bb8a5b9e84fa0057f79b3a5996e511682e80176dc948dcbff5a4fc">6811b639e6679dee7e4e229ed1bc836eca6c0731556f47f6ce9c4829221fdbc2472949f968f60d16e51c56d781f0f168fbfcc12e2366b1cd29e793a611e48a0f</sig> + </entry> + <entry> + <data> + <parent>00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</parent> + <ref>450956b1-99e3-4011-af32-3b88723ab233</ref> + <serial>2</serial> + <date>2026-01-19</date> + <dateTimeRegistered>2026-01-19T18:41:57Z</dateTimeRegistered> + <description>eqriguheqighq3ghoq3hgqhrg</description> + <debit type="expense"> + <unit>BTC</unit> + <account>general</account> + <amount>-4968347600</amount> + </debit> + <credit type="asset"> + <unit>BTC</unit> + <account>general</account> + <amount>4968347600</amount> + </credit> + </data> + <sig type="ed25519" keyid="3b54648d60bb8a5b9e84fa0057f79b3a5996e511682e80176dc948dcbff5a4fc">c8d728ecc17cc1ac0d11dd24f35025f133e3fd91be6037d35df056b9a6ab26e67b7028826069b3f99b37258176921c45f0b333d8e950e36a7e5f729994cbd60c</sig> + </entry> +</ledger> + +``` + + +## State of development + +This software is to be considered proof-of-concept and not safe for use in production in any way. + +Shortcomings include: + +* Media assets and attachments are not yet implemented. +* No PKI functions are available to manage public key lists. +* A single, unencrypted private key entry in the key-value store is used for signatures. + + +## Legals + +Copyright 2026 by Louis Holbrook, all rights reserved. + +All software licensed under [GNU Affero General Public License 3.0](https://www.gnu.org/licenses/agpl-3.0.en.html). + +All documentation licensed under [Creative Commons Attribution-Sharealike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/). diff --git a/dummy/README.md b/dummy/README.md diff --git a/dummy/doc/accounts.texi b/dummy/doc/accounts.texi @@ -12,6 +12,7 @@ There are four account types: @item @strong{liability}, representing value owed in the ledger. @end itemize +@anchor{account_path} Under each type, a path-like identifier may be freely chosen to subdivide the context under each type. This context can in turn be used to obtain granular balances and totals. For example, @code{rents/Apartment A} could subdivide an @code{income} account type to separate it from @code{rents/Apartment B} diff --git a/dummy/doc/appendix.texi b/dummy/doc/appendix.texi @@ -31,7 +31,7 @@ <xs:element name="digest" type="Digest" minOccurs="1" maxOccurs="1"/> <xs:element name="sig" type="Signature" minOccurs="1" maxOccurs="unbounded" /> </xs:sequence> - <xs:attribute name="serial" type="xs:positiveInteger" /> + <xs:attribute name="serial" type="xs:nonNegativeInteger" /> </xs:complexType> <xs:complexType name="Balance"> @@ -51,6 +51,14 @@ <xs:attribute name="base" type="xs:string" /> </xs:complexType> + <xs:complexType name="Lookup"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="method" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="Digest"> <xs:simpleContent> <xs:extension base="xs:string"> @@ -59,6 +67,7 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="Signature"> <xs:simpleContent> <xs:extension base="xs:string"> @@ -124,21 +133,21 @@ <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="ref" type="xs:string" /> <xs:element name="serial" type="xs:positiveInteger" /> - <xs:element name="unit" type="xs:string" /> <xs:element name="date" type="xs:date" /> <xs:element name="dateTimeRegistered" type="xs:dateTime" /> - <xs:element name="src" type="EntryPart"/> - <xs:element name="dst" type="EntryPart"/> + <xs:element name="debit" type="EntryPart" minOccurs="1" maxOccurs="unbounded" /> + <xs:element name="credit" type="EntryPart" minOccurs="1" maxOccurs="unbounded" /> <xs:element name="attachment" type="Attachment" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> <xs:complexType name="EntryPart"> <xs:sequence> + <xs:element name="unit" type="xs:string" /> <xs:element name="account" type="xs:string" /> <xs:element name="amount" type="xs:nonNegativeInteger" /> </xs:sequence> - <xs:attribute name="type" type="xs:string" /> + <xs:attribute name="type" type="accountType" /> </xs:complexType> <xs:complexType name="Attachment"> @@ -146,10 +155,19 @@ <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="slug" type="xs:string" /> <xs:element name="description" type="xs:string" /> - <xs:element name="digest" type="Digest" /> + <xs:element name="lookup" type="Lookup" /> </xs:sequence> <xs:attribute name="mime" type="xs:string" /> </xs:complexType> + + <xs:simpleType name="accountType" final="restriction"> + <xs:restriction base="xs:string"> + <xs:enumeration value="income" /> + <xs:enumeration value="expense" /> + <xs:enumeration value="asset" /> + <xs:enumeration value="liability" /> + </xs:restriction> + </xs:simpleType> </xs:schema> @end example diff --git a/dummy/doc/assets.texi b/dummy/doc/assets.texi @@ -1 +1,33 @@ @anchor{assets} +@chapter Media assets + +Any entry may contain one or more media assets as @emph{attachments}. These allow for detailed documentation of the transaction the entry represents. + +Attachments are not limited to any specific type of content, but are usually a @emph{receipt}, an @emph{invoice} or a @emph{proof of payment}. + + +@section Structure + +The attachment is defined by the following data: + +@itemize +@item A @strong{slug}, specifically a filesystem-safe string appropriate for use as a filename stem. (i.e. the substring @emph{"picture"} from @file{/foo/bar/picture.png}) +@item A (preferably succint) @strong{description} of what the asset represents. +@item One or more @strong{lookup} elements, specifying the @emph{lookup key} and the @xref{method} used to generate the lookup key. +@end itemize + + +@anchor{method} +@subsection Lookup methods + +The method may or may not be verifiable from the content. + + +At a minimum, any implementation should accommodate the following methods: + +@itemize +@item @code{sha256} and @code{sha512}, for which the @emph{lookup value} is the corresponding digest according to those algorithms. +@item @code{uuid}@footnote{Note that uuid should not be used alone, as the value domain may carry the risk of collisions}, for which the key is a @emph{uuid string} that may or may not be related to a @emph{content digest}. +@end itemize + +Any implementation is free to define any method it wishes, but should refrain from using well-known method strings like the above. diff --git a/dummy/doc/identity.texi b/dummy/doc/identity.texi @@ -1 +1,8 @@ @anchor{identity} +@chapter Identities + +Ledger headers and entry chains may be signed be more than one private key. + +Each entry signature element is bundled with a @code{keyid} attribute. @code{identity} elements matching the @code{keyid} may be defined in the ledger header, mapping them to a @emph{did URI}@footnote{DID v1.0: @url{https://www.w3.org/TR/did-1.0/}}. + +The default @emph{did method} is @code{usawa}, which refers to a resolution using the usawa store mechanism directly. @ref{store_identity, Identity entries in the store chapter} for details. diff --git a/dummy/doc/index.texi b/dummy/doc/index.texi @@ -19,12 +19,14 @@ Documentation released 2026 under CC-BY-SA @end ifnottex @include intro.texi +@include protocol.texi @include xml.texi @include ledger.texi @include accounts.texi @include unit.texi @include identity.texi @include time.texi +@include store.texi @include resolver.texi @include assets.texi @include internals.texi diff --git a/dummy/doc/intro.texi b/dummy/doc/intro.texi @@ -1,3 +1,43 @@ -@chapter Introduction +@chapter Overview +The @strong{usawa} data format specification is an attempt at creating a portable, immutable and authenticated accounting schema. +It is a @emph{decentralized ledger} project without being a @ref{blockchain_compare,blockchain}. + +At its heart, the specification is little more than a chained of entries, where each new entry points to the previous and is cryptographically signed. + + +@section Design goals + +@itemize +@item Enable publishing of ledger entries regardless of technology of platform. +@item Primarily target an offline, local-first environment. +@item Natively accommodate use of multiple currencies and units of accounts. +@item Make receipts and other documentation aswell as conversion rates part of the immutable chain. +@item Tailored for individuals and small businesses. +@end itemize + + +@anchor{blockchain_compare} +@section Comparison with blockchain + +Although this specification may seem similar to a blockchain concept, there are some important differences. The difference can best be defined as what @strong{usawa} does @emph{not} seek to provide. + + +@subsection No double-spend prevention + +The primary innovation of blockchains and the peer-to-peer software that support them is to solve the double-spend problem of decentralized transactions. + +@strong{usawa} is an @emph{accounting} project, and does not seek to solve such problems. + + +@subsection No built-in finality and consensus. + +The @strong{usawa} specification only concentrates on @emph{how} to describe an accounting ledger and its evolving state. + +It leaves it up the implementer to create protocols and procedures to make sure that enough information of state is known to enough parties so that immutability can be enforced. + + +@subsection No bundled dissemination. + +There is no node software specific to nor any protocol proposal for @strong{usawa} that defines how to make the state of the ledger known to all relevant parties at any one time. diff --git a/dummy/doc/protocol.texi b/dummy/doc/protocol.texi @@ -0,0 +1,19 @@ +@chapter Protocol + +The @strong{usawa} project does not specify a strict protocol. + +However, to guide the implementation to fulfill the design goals, some recommendations are given in the following. + + +@section Publishing and dissemination + +All parties that wish to follow the ledger must be given clear rules on how to receive new entries. + +There should be more than one channel through which entries are published, and ideally these "channels" should also include different platforms. + + +@subsection Dissemination example + +All participants could be asked to follow both a @strong{nostr}@footnote{nostr is an ownerless, client- and server-agnostic protocol for publishing arbitrary content ("notes and other stuff"): @url{https://nostr.com/}} and an @emph{email mailist list}, that both would publish the full entry data as clearly defined data structures. + +Also, all participants would be encouraged to follow an @emph{EVM chain}@footnote{A list of EVM chains: @url{https://chainlist.org/}} that could publish the latest ledger state digest in the data field of a transaction. diff --git a/dummy/doc/resolver.texi b/dummy/doc/resolver.texi @@ -1 +1,30 @@ @anchor{resolver} +@chapter Resolvers + + +The resolver element describes endpoints that can be used as key-value stores to expand content. + +Content to resolve may include: + +@itemize +@item Ledger entries +@item +@end itemize + +Typically, they are content-addressed stores where a digest is used to retrieve content corresponding to the digest from a @xref{resolver}. + +Resolvers are defined per lookup key algorithm, and any algorithm may have several resolvers defined. Defining more than one resolver per algorithm enables redundancy in conent retrieval. + + +@section Defining a resolver + +The endpoint is defined as a combination of protocol for retrieval, location for retrieval and algorithms for content lookup key. An example for a @code{sha256} resolver could be defined as: + +@example +<resolver algo="sha256" proto="https">foo.com/content/</resolver> +@end example + + +@section Security + +Note that even if an endpoint is trusted, it is always up to the client to verify that any digest used as a lookup key actually matches the content returned. diff --git a/dummy/doc/store.texi b/dummy/doc/store.texi @@ -0,0 +1,2 @@ +@anchor{store} +@anchor{store_identity} diff --git a/dummy/doc/unit.texi b/dummy/doc/unit.texi @@ -1 +1,26 @@ @anchor{unit_index} +@chapter Unit Index + +The account unit index defines a base reference unit for all transactions in the ledger, aswell as a means to translate between units. + +For each known unit, it defines the level of decimal precision a currency denominates itself. + +Additionally, it defines conversion rates between different units and the base unit of account. These conversion rates may be defined statically, fixed to dates, or fixed to dates and time. This approach accommodates both pegged and floating unit conversions. + + +@section Structure + +A unit index defines a base unit of account, to which all conversion rates relates to. The base unit of account will have an effective conversion rate of 1.0. + + +@subsection Real vs virtual + +The XML representation of the unit index defines two different elements; @code{real} and @code{virt}. + +Units using the @code{real} element are assets that are in @emph{custody} of the ledger owner, without an intermediary. This may include cash, metals or even self-custodied crypto. + +The @code{virt} element is used to define unit balances that are in some way @emph{derivatives} of real assets, and/or in custody by another party than the legder owner. + +Any asset may have both a @emph{real} and a @emph{virtual} entry. + +The classification does not differentiate between levels of derivation of virtualization. diff --git a/dummy/doc/xml.texi b/dummy/doc/xml.texi @@ -4,7 +4,7 @@ The canonical ledger data format is XML. The XML schema is designed to allow embedding of all data necessary to verify integrity signatures, including exchange rates between units of account. -Order of elements are important in the XML document. The schema is cited in @xref{xml_schema, appendix A}. +Order of elements are important in the XML document. The schema is cited in @ref{xml_schema, appendix A}. @section Ledger header @@ -19,8 +19,8 @@ It includes: @item Generation date and time of the XML document. @item Index of units of account used in the following entries. @ref{unit_index,Unix Index} @item The public key identities whose signatures appear in the document. @ref{identity,Public Key Identities}. -@item Details on resolver services that can be used to expand assets references by digests. See @xref{resolver, Resolvers} and @xref{assets, Referencing assets}. -@item Ledger state, under the element @code{incoming}, defining the digest of the position of the entry chain the ledger document starts at. @ref{incoming, Ledger State}. +@item Details on resolver services that can be used to expand assets references by digests. @xref{resolver, Resolvers} and @ref{assets, Referencing assets}. +@item Ledger state, under the element @code{incoming}, defining the digest of the position of the entry chain the ledger document starts at. @xref{incoming, Ledger State}. @item Signatures that may authenticate the ledger state. @end itemize @@ -29,7 +29,7 @@ It includes: An important feature of the header is to allow only a portion of the ledger's entries to appear in the document. -This is accomplished by populating the @code{digest} and @code{serial} values in the ledger state with those of the parent of the first entry listed. @ref{xml_example_trunc, Truncated XML example} for more details. +This is accomplished by populating the @code{digest} and @code{serial} values in the ledger state with those of the parent of the first entry listed. @xref{xml_example_trunc, Truncated XML example} for more details. In order to list all entries in a ledger, the @code{digest} value should be the zero-digest (64 zero-value bytes), and with a @code{serial} of @code{0}. @@ -56,7 +56,7 @@ The entry representation also defines data necessary for the immutability chain @item The digest of the parent. @item The serial number of this entry in the ledger. @item The external reference of the entry, e.g. a blockchain transaction specificer or tax invoice uuid. -@item The date or datetime when the transaction took place. @ref{entry_timing, Entry timing}. +@item The date or datetime when the transaction took place. @xref{entry_timing, Entry timing}. @item The datetime when the transaction was registered in the ledger. @item A free-text description for the entry. @end itemize diff --git a/dummy/usawa/data/schema.xsd b/dummy/usawa/data/schema.xsd @@ -47,6 +47,14 @@ <xs:attribute name="base" type="xs:string" /> </xs:complexType> + <xs:complexType name="Lookup"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="method" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="Digest"> <xs:simpleContent> <xs:extension base="xs:string"> @@ -55,6 +63,7 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="Signature"> <xs:simpleContent> <xs:extension base="xs:string"> @@ -134,7 +143,7 @@ <xs:element name="account" type="xs:string" /> <xs:element name="amount" type="xs:nonNegativeInteger" /> </xs:sequence> - <xs:attribute name="type" type="xs:string" /> + <xs:attribute name="type" type="accountType" /> </xs:complexType> <xs:complexType name="Attachment"> @@ -142,8 +151,17 @@ <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="slug" type="xs:string" /> <xs:element name="description" type="xs:string" /> - <xs:element name="digest" type="Digest" /> + <xs:element name="lookup" type="Lookup" /> </xs:sequence> <xs:attribute name="mime" type="xs:string" /> </xs:complexType> + + <xs:simpleType name="accountType" final="restriction"> + <xs:restriction base="xs:string"> + <xs:enumeration value="income" /> + <xs:enumeration value="expense" /> + <xs:enumeration value="asset" /> + <xs:enumeration value="liability" /> + </xs:restriction> + </xs:simpleType> </xs:schema>