Skip to main content

Double-entry accounting system with cryptographic audit trail and optional cryptographic transaction verification.

Project description

BookChain

BookChain is an accounting library that provides a cryptographic audit trail of every identity, ledger, account, transaction, and entry to ensure data integrity. It also uses the tapescript package to ensure account security and reject invalid transactions if controls are configured on the involved accounts. Included are tools for establishing correspondent credit relations as well as accomplishing basic bookkeeping tasks.

This package uses sqloquent for database persistence, tapescript for authorization, and packify for deterministic encoding.

Status

All initially planned features have been implemented and tested.

Open issues can be found here.

The async implementation has an upstream issue from the sqloquent dependency that can be tracked here. Once that is fixed, the dependency will be updated, and this notice will be removed.

Overview

This library provides an accounting system using sqloquent for persistence, packify for deterministic encoding, tapescript for authorization, and the classic rules of double-entry bookkeeping. All entries and transactions have deterministic content IDs determined by hashing the relevant contents, and the inclusion of tapescript allows for Bitcoin-style locking and unlocking scripts to encode access controls at the account level.

Class organization

Currency represents a currency/unit of account for a Ledger. It includes the number of subunits to track and optionally the base for conversion to decimal (defaults to 10), FX symbol, prefix symbol, and/or postfix symbol to be used with the format method (e.g. USD.format(1200) could result in "12.00 USD", "$12.00", or "12.00$", respectively). It also includes to_decimal method for formatting int amounts into Decimals, and get_units method for getting a tuple of whole units and remainders.

Identity represents a legal person or other entity that can sign contracts or engage in transactions. It includes the name, details, and optionally public key bytes and private key seed bytes.

Ledger represents a general ledger for a given Identity using a specific Currency. It includes a name, a LedgerType, the Identity id, and the Currency id. LedgerType is an enum representing the valid ledger types, either PRESENT or FUTURE for cash and accrual accounting, respectively, or for other uses the package user may define.

Account represents an account for a given Ledger. It includes a name, a type (one of the AccountType enum options), the Ledger id, an optional locking script for access control, and optional details.

AccountCategory represents a category for Accounts. It includes a name, a LedgerType, and a destination (str description; e.g. "Balance Sheet" or "Profit and Loss").

AccountType is an enum representing the valid account types. The options are DEBIT_BALANCE, ASSET, CONTRA_ASSET, CREDIT_BALANCE, LIABILITY, EQUITY, CONTRA_LIABILITY, and CONTRA_EQUITY.

Entry represents an entry in the general ledger for a given Account. It includes a type (one of the EntryType enum options), an amount, a nonce, the Account id, and optional details.

EntryType is an enum representing the valid entry types. The options are CREDIT and DEBIT.

Transaction represents a transaction made of Entrys. It includes the Entry ids, Ledger ids, a timestamp, details, and the auth script(s). Each Transaction must include entries that balance the number of credits and debits applied to each ledger affected by the transaction. Use Transaction.prepare to prepare a transaction -- it will raise validation errors if the transaction is not valid -- then call .save() on the result to persist it to the database. Transactions in the database can be validated by using .validate(), which will return True if it is valid and False if it is not (it will also raise errors in some situations that require more information about the validation failure).

TxRollup represents a rollup of Transactions. It commits the Transaction ids into a Merkle tree and includes the root hash, timestamp, optional details, aggregate account balance changes of the committed Transactions, the TxRollup id of the parent TxRollup (or None for the genesis TxRollup), the height of the TxRollup (number of TxRollups in the chain), and the Correspondence id or Ledger id. Inclusion proofs for Transactions can be generated using the TxRollup.get_inclusion_proof method and verified using the TxRollup.verify_inclusion_proof method; the latter requires only the tx_root, but the former requires the full list of tx_ids.

ArchivedEntry and ArchivedTransaction are optional classes for storing the trimmed Entrys and Transactions, respectively, after they have been included in a TxRollup. The default behavior of TxRollup.trim is to use these archive classes, but trim(False) can be used to prevent archiving. (The trimmed txns and entries will be retrievable by using TxRollup.trimmed_transactions and TxRollup.trimmed_entries, respectively, but they are not optimized for ease of use.)

Correspondence represents a correspondent credit relationship between several Identitys.

  • Identity has many Ledgers and is within Correspondences
  • Ledger belongs to Identity and Currency, has many Accounts, is within Transactions, and has many TxRollups
  • Account belongs to Ledger and AccountCategory, and has many Entrys
  • AccountCategory has many Accounts
  • Entry belongs to Account and is within Transactions
  • Transaction contains Ledgers and Entrys
  • Correspondence contains Identitys and has many TxRollups
  • TxRollup belongs to Correspondence or Ledger and belongs to and has one TxRollup
  • ArchivedTransaction contains ArchivedEntrys
  • ArchivedEntry belongs to Account and is within ArchivedTransactions

Cryptographic audit trail

Models inherit from sqloquent.HashedModel, so all data is hashed into the ID, guaranteeing a unique, deterministic ID for each unique record.

Whenever something is deleted, it will be encoded and inserted into the deleted_models table to maintain an audit trail.

Accounts may be created with locking scripts, which will require associated Entries to provide valid auth scripts. These scripts are executed using tapescript. If some tapescript runtime values are required for validation, e.g. cache or plugins, they can be saved in Transaction.details and passed to Transaction.validate and Account.validate_script.

TxRollups include a tx_root which is the root hash of the Merkle tree of the Transactions they contain. This allows for efficient inclusion proofs (TxRollup.get_inclusion_proof) and verification of the presence of a Transaction in a TxRollup (TxRollup.verify_inclusion_proof). TxRollups made for a Correspondence will require an auth_script if the Correspondence contains a txru_lock or if the Identitys each have a pubkey (a 2-of-2 multisig lock will be generated as the default txru_lock if one was not set). This system allows for old Transactions to be trimmed from the local database while ensuring that all parties validated the aggregate account balance changes of the trimmed Transactions were preserved.

Installation and Setup

Install with pip install bookchain. If you want to use the async version, instead install with pip install bookchain[asyncql].

Once installed, use the following to setup your project as appropriate:

import bookchain

bookchain.publish_migrations(folder_for_migration_files)

bookchain.automigrate(folder_for_migration_files, db_file_path)

bookchain.set_connection_info(db_file_path)

To use the async version:

import bookchain
import bookchain.asyncql

bookchain.publish_migrations(folder_for_migration_files)

bookchain.automigrate(folder_for_migration_files, db_file_path)

bookchain.asyncql.set_connection_info(db_file_path)

The bookchain.publish_migrations function can be passed a callback that takes the str model name and str migration file contents, and returns the modified str migration file contents. This can be used to modify the migration file contents before they are written to disk. For example, if you wanted to modify the Account migration file to add a unique constraint to the name column, you could do the following:

def migration_callback(name: str, m: str) -> str:
    if name == 'Account':
        return m.replace("t.text('name').index()", "t.text('name').unique()")
    return m

bookchain.publish_migrations(path_to_migrations_folder, migration_callback)

More Resources

Documentation generated by autodox can be found here. Docs for the async version can be found here.

Check out the Pycelium discord server. If you experience a problem, please discuss it on the Discord server. All suggestions for improvement are also welcome, and the best place for that is also Discord. If you experience a bug and do not use Discord, open an issue on Github.

Tests

There are a total of 17 tests (12 e2e tests and 5 tests for miscellaneous tools/features). To run them, clone the repo, set up a virtual environment (e.g. python -m venv venv && source venv/bin/activate), install the dependencies with pip install -r requirements.txt, and then run the following: find tests -name test_*.py -print -exec python {} \;. On Windows, the 9 test files will have to be individually run with the following:

python tests/test_advanced_e2e.py
python tests/test_async_advanced_e2e.py
python tests/test_async_basic_e2e.py
python tests/test_async_correspondences_e2e.py
python tests/test_async_txrollup_e2e.py
python tests/test_basic_e2e.py
python tests/test_correspondences_e2e.py
python tests/test_misc.py
python tests/test_txrollup_e2e.py

Personal, Non-commercial Use License

Copyright (c) 2025 Jonathan Voss (k98kurz)

Permission to use, copy, modify, and/or distribute this software for any personal, non-commercial purpose is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. For other uses, contact the software author.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bookchain-0.3.2.tar.gz (61.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bookchain-0.3.2-py3-none-any.whl (55.5 kB view details)

Uploaded Python 3

File details

Details for the file bookchain-0.3.2.tar.gz.

File metadata

  • Download URL: bookchain-0.3.2.tar.gz
  • Upload date:
  • Size: 61.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for bookchain-0.3.2.tar.gz
Algorithm Hash digest
SHA256 ebc8c3c99357659acd25c22f0bafe7b43c70845ad6e129a4ee818ba037a597e0
MD5 96ab69bbdb7c52eab39957e2e34eea72
BLAKE2b-256 306c63da8ffa66e2211e8d1a5c93defa0997fde8fe21ac900c1bb0d44ea4dab1

See more details on using hashes here.

File details

Details for the file bookchain-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: bookchain-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 55.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for bookchain-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 98e4849876c5da81e9b0f5d8788e78ef544dfdd177a688ee0b368c0f0c5d36e2
MD5 3f1dbe44e65f7d248f93d35986740bea
BLAKE2b-256 8af70c993b059e118bc58eb38f72ca8358f90a276a44ef2957ee8124b2596432

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page