{"id":34092117,"url":"https://github.com/k98kurz/reverse-entropy-clocks","last_synced_at":"2026-04-05T18:32:26.511Z","repository":{"id":55637264,"uuid":"523166460","full_name":"k98kurz/reverse-entropy-clocks","owner":"k98kurz","description":"Logical clock with reverse entropy mechanism; generalization of hashlocks and logical clocks.","archived":false,"fork":false,"pushed_at":"2025-02-04T13:53:26.000Z","size":103,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-16T18:36:40.236Z","etag":null,"topics":["hashlock","logical-clocks"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/k98kurz.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"polar":"pycelium","github":"k98kurz"}},"created_at":"2022-08-10T01:55:01.000Z","updated_at":"2025-02-04T13:53:29.000Z","dependencies_parsed_at":"2025-02-04T14:44:56.844Z","dependency_job_id":null,"html_url":"https://github.com/k98kurz/reverse-entropy-clocks","commit_stats":null,"previous_names":["k98kurz/reverse-entropy-clocks"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/k98kurz/reverse-entropy-clocks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k98kurz%2Freverse-entropy-clocks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k98kurz%2Freverse-entropy-clocks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k98kurz%2Freverse-entropy-clocks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k98kurz%2Freverse-entropy-clocks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k98kurz","download_url":"https://codeload.github.com/k98kurz/reverse-entropy-clocks/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k98kurz%2Freverse-entropy-clocks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31446524,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T15:22:31.103Z","status":"ssl_error","status_checked_at":"2026-04-05T15:22:00.205Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hashlock","logical-clocks"],"created_at":"2025-12-14T15:00:13.481Z","updated_at":"2026-04-05T18:32:26.505Z","avatar_url":"https://github.com/k98kurz.png","language":"Python","funding_links":["https://polar.sh/pycelium","https://github.com/sponsors/k98kurz"],"categories":[],"sub_categories":[],"readme":"# Reverse Entropy Clocks\n\nA reverse entropy clock is a logical clock that uses one-way functions to\npre-compute a causality chain that is revealed in reverse order, thus enabling\nlater timestamps to be verified using just the public UUID of the clock, the\ninteger timestamp, and a 32 byte proof. It is \"reverse entropy\" in the sense\nthat the causal chain of events within the clock mechanism is reversed compared\nto other logical clocks using one-way functions, e.g. blockchains.\n\nThis package provides two types of reverse entropy logical clocks:\n- Hash-based, in which a seed value is recursively hashed to produce the UUID\n- Ed25519-based, in which a seed secret value is used as the seed for a private\nkey, then the public key is recursively hashed and added to a point derived from\nthat hash (as a scalar) using Ed25519 point addition to produce the UUID\n\nThis module uses sha256 from hashlib to implement reverse entropy hash clocks;\ni.e. hash lock clocks. This is a generalization of the hash lock used in Bitcoin\nUTXO locking scripts and logical clocks as invented by Leslie Lamport. A given\nhash clock can only be updated by the node that created it or any nodes with\nwhich it shares the seed and max_time values, and it can be used in a\ndistributed system to order events.\n\nThis module uses sha256 from hashlib and ed25519 via the PyNaCl package to\nimplement reverse entropy signature clocks. This takes advantage of a\ncombination of Ed25519's homomorphic one-way feature of point derivation and\nsha256's one-way feature, and it can produce verifiable attestations. The clock\nowner needs only to keep a 32 byte seed value and the lifetime; clock observers\nneed the 32 byte uuid.\n\nTo the author's knowledge, these are novel constructions.\n\n## Status\n\n- [x] Readme\n- [x] Interfaces\n- [x] HashClock and HashClockUpdater\n- [x] Optimization refactor\n- [x] VectorHashClock\n- [x] PointClock and PointClockUpdater\n- [x] VectorPointClock\n\n## Installation\n\n```bash\npip install reclocks\n```\n\n## Overview\n\nIn general, a reverse entropy clock uses a cryptographic one-way function to\npre-compute a causality chain. Intermediate states are then revealed in reverse\norder to move the clock forward. The causal chains have finite length, and so\nthe clocks have a finite life span. Because the functions are one-way, they\ncannot be reversed, so only the holder of the seed value can issue updates.\n\nFull documentation can be found\n[here](https://github.com/k98kurz/reverse-entropy-clocks/blob/main/dox.md). Dox\nwere automagically generated by [autodox](https://pypi.org/project/autodox/).\n\n### Reverse Entropy Hash Clock\n\nThe idea is that a message digest can form a lock that is opened by the preimage\nkey. These can be chained by using digests as preimages for the next digest.\nThis can be used to create a logical clock where the final digest becomes the\nclock state; each next preimage released is verified and added to the state; the\ntime of the clock is the number of state items minus 1. Thus, as causally prior\nstates are revealed, the timer increases; hence, it is a Reverse Entropy Clock\n(aka HashClock).\n\nThis library uses sha256 as the hash algorithm to generate `HashClock`s that can\nprovably terminate if default options are used. Clocks that do not provably\nterminate can be setup with `clock.setup(n, seed_size=32)`.\n\nThe `setup(max_time: int, seed_size: int = 16) -\u003e HashClockUpdater` method will\nset up the hash lock chain, set the final digest as the state, and return a\nHashClockUpdater. This HashClockUpdater is then used to advance the clock by\nrecursively hashing the seed `max_time - time` times and returning a tuple of\n`(time, state)`. It is technically a state-based CRDT counter and can be thought\nof as a permissioned CRDT since only those who know the seed and max_time can\nadvance the clock.\n\nThe VectorHashClock class can be used to create vector clocks using the\nHashClock mechanism underneath.Additionally, a VectorPointClock class can be\nused to create vector clocks using the PointClock mechanism underneath.\n\nIf there is sufficient interest, perhaps I will make a MapHashClock class to\nextend the VectorHashClock idea to not require all node IDs to be included in\nthe setup; i.e. new nodes would be able to join the MapHashClock after setup by\nissuing an update referencing the clock's uuid, the node id, and the node's\nHashLock state tuple. A further optimization could be made in a ChainHashClock\nclass to implement the chain clock as described by Agarwal and Garg in their\npaper \"Efficient Dependency Tracking for Relevant Events in Concurrent Systems\".\n\n### Reverse Entropy Ed25519 Clock\n\nSimilarly, we can construct a reverse entropy clock by generating a secret seed\nvalue, generating an ed25519 private key from it, then using the public key as\nthe base of the causality chain. The chain is then constructed by recursively\nmultiplying the public key point by its sha256 digest clamped to the ed22519\nscalar field.\n\nThis library uses the ed25519 bindings from the PyNaCl package to create\n`PointClock`s. Unlike the `HachClock`, the `PointClock` does not provably\nterminate, regardless of the seed size.\n\nThe mechanism is as follows: given `derive(scalar) -\u003e G * scalar` for generating\ned25519 points from scalars, `add(scalar) -\u003e scalar + scalar` for adding ed25519\nprivate key scalars, `mult(point, scalar) -\u003e point + derive(scalar)` for adding\nan ed25519 public key point with the point dervied from an ed25519 scalar,\n`next_p(point) -\u003e mult(point, hash(point))` for generating the next point in the\nchain, `next_s(scalar) -\u003e scalar + hash(derive(scalar))` for generating the next\n(secret) scalar value in the chain,\n`recursive_next_p(pnt, cnt) -\u003e pnt if cnt is 0 else recursive_next_p(next_p(pnt, pnt), cnt-1)`\nfor recursively calling `next_p` to get the point for a given time step, and\n`recursive_next_s(sclr, cnt) -\u003e sclr if cnt is 0 else recursive_next_s(next_s(sclr, pnt), cnt-1)`\nfor recursively calling `next_s` to get the scalar for a given time step,\nwe can set `clock.uuid = recursive_next_p(pubkey, clock.lifetime)`, and have\ntimestamp tuple `(ts, state = recursive_next_p(pubkey, clock.lifetime - ts), msg, sig)`\nwhere the signature `sig` is made using `recursive_next_s(prvkey, clock.lifetime - ts)`\nas the signing key. Verification is then\n`verify(ts, state, msg, sig) -\u003e recursive_next_p(state, ts) == clock.uuid and ed25519_verify(msg, sig, state)`.\n\n## Classes and Interfaces\n\n### Interfaces\n\n- ClockProtocol(Protocol)\n- ClockUpdaterProtocol(Protocol)\n- VectorClockProtocol(Protocol)\n\n### Classes\n\n- HashClock(ClockProtocol)\n- HashClockUpdater(ClockUpdaterProtocol)\n- VectorHashClock(VectorClockProtocol)\n- PointClock(ClockProtocol)\n- PointClockUpdater(ClockUpdaterProtocol)\n- VectorPointClock(VectorClockProtocol)\n\n## Usage Examples\n\n### HashClock and HashClockUpdater\n\n```python\nfrom reclocks import HashClock, HashClockUpdater\n\nmax_clock_life = 420 # arbitrary but meaningful choice\n\n# setup clock\nhclock = HashClock()\nhc_updater = hclock.setup(420)\n\n# simulate sending public data elsewhere\nhclock2 = HashClock(uuid=hclock.uuid)\nts1 = hclock2.read()\n\n# advance the clock\nhcu1 = hc_updater.advance(68)\nhclock.update(hcu1)\n\n# verify update and synchronize\nassert hclock2.verify_timestamp(hcu1)\nhclock2.update(hcu1)\nts2 = hclock2.read()\n\n# prove causality\nassert hclock2.happens_before(ts1, ts2)\n\n# serialize, deserialize, and verify internal state\npacked = hclock2.pack()\nunpacked = HashClock.unpack(packed)\nassert unpacked.verify()\n\n# serialiaze and deserialize updater\npacked = hc_updater.pack()\nunpacked = HashClockUpdater.unpack(packed)\n```\n\n### PointClock and PointClockUpdater\n\n```python\nfrom reclocks import PointClock, PointClockUpdater\n\nmax_clock_life = 420 # arbitrary but meaningful choice\n\n# setup clock\npclock = PointClock()\npc_updater = pclock.setup(420)\n\n# simulate sending public data elsewhere\npclock2 = PointClock(uuid=pclock.uuid)\nts1 = pclock2.read()\n\n# advance the clock\npcu1 = pc_updater.advance(68)\npclock.update(pcu1)\n\n# verify and synchronize\nassert pclock2.verify_timestamp(pcu1)\npclock2.update(pcu1)\nts2 = pclock2.read()\n\n# additional signature feature of the PointClock\npcu2 = pc_updater.advance_and_sign(69, b'nice')\npclock.update(pcu2)\n\n# verify before synchronizing\nassert pclock2.verify_signed_timestamp(pcu2, b'nice')\npclock2.update(pcu2)\nts3 = pclock2.read()\n\n# prove causality\nassert pclock2.happens_before(ts1, ts2)\nassert pclock2.happens_before(ts2, ts3)\nassert pclock2.happens_before(ts1, ts3) # transitive\n\n# serialize, deserialize, and verify internal state\npacked = pclock2.pack()\nunpacked = PointClock.unpack(packed)\nassert unpacked.verify()\n\n# serialiaze and deserialize updater\npacked = pc_updater.pack()\nunpacked = PointClockUpdater.unpack(packed)\n```\n\n### VectorHashClock\n\n```python\nfrom reclocks import HashClock, VectorHashClock\nfrom reclocks.misc import hexify\nnode_ids = [b'node0', b'node1']\nvhc0 = VectorHashClock().setup(node_ids)\nhc0, hc1 = HashClock(), HashClock()\nhcu0, hcu1 = hc0.setup(1), hc1.setup(3)\n\n# initial timestamp where both are time=-1\ntsneg1 = vhc0.read()\nprint(hexify(tsneg1))\n\n# setup each HashClock at initial state\nvhc0 = vhc0.update(vhc0.advance(node_ids[0], hcu0.advance(0)))\nvhc0 = vhc0.update(vhc0.advance(node_ids[1], hcu1.advance(0)))\n\n# next timestamp where both are time=0\nts0 = vhc0.read()\nprint(hexify(ts0))\n\n# advance the clocks separately\nupdate0 = vhc0.advance(node_ids[0], hcu0.advance(1))\nupdate1 = vhc0.advance(node_ids[1], hcu1.advance(1))\npacked = vhc0.pack()\nvhc1 = VectorHashClock.unpack(packed)\n\nvhc0 = vhc0.update(update0)\nvhc1 = vhc1.update(update1)\n\nts0, ts1 = vhc0.read(), vhc1.read()\n\nprint(hexify(ts0))\nprint(hexify(ts1))\nprint(f'{VectorHashClock.are_concurrent(ts0, ts1)=}')\n\n# converge by swapping updates\nvhc1 = vhc1.update(update0)\nvhc0 = vhc0.update(update1)\nprint(hexify(vhc0.read()))\nprint(hexify(vhc1.read()))\n\nfor c in vhc0.clocks:\n    print(repr(vhc0.clocks[c]))\n```\n\n### VectorPointClock\n\nE2e example from the test suite:\n\n```python\nfrom hashlib import sha256\nfrom reclocks import PointClock, VectorPointClock\n\n# simulate setting up clocks independently\nclocks = [PointClock() for _ in range(5)]\nupdaters = [clock.setup(256) for clock in clocks]\n\n# compile the ids\nnode_ids = [clock.uuid for clock in clocks]\nuuids = { nid: nid for nid in node_ids }\nroot_uuid = sha256(b''.join(node_ids)).digest()\n\n# simulate creating a vector clock at each node\nvectorclocks = [\n    VectorPointClock(root_uuid).setup(node_ids, uuids)\n    for _ in node_ids\n]\n\n# make timestamps\nts0 = [vc.read() for vc in vectorclocks]\nassert all([ts0[0] == ts for ts in ts0]), 'timestamps should be the same'\n\n# create some updates\nmessage = b'hello world'\nupdates = [\n    vectorclocks[i].advance(\n        updaters[i].uuid,\n        updaters[i].advance_and_sign(1, message)\n    )\n    for i in range(len(node_ids))\n]\n\n# verify every update at each node and then update\nfor u in updates:\n    for vc in vectorclocks:\n        assert vc.verify_signed_timestamp(u, message)\n        _ = vc.update(u)\n\n# check timestamps are all the same\nts1 = [vc.read() for vc in vectorclocks]\nassert all([ts1[0] == ts for ts in ts1]), 'timestamps should be the same'\n\n# ensure that time moved forward\nassert vectorclocks[0].happens_before(ts0[0], ts1[0]), 'time should move forward'\n```\n\nNote: in theory, an application should advance its own clock after receiving\nupdates for other clocks in the vector; each receipt of a message that updates\none of the clocks should represent a causal happens-before relationship to any\nupdates that occur after. However, allowing the clocks to tick independently may\nbe useful for some applications where synchronization/desynchronization levels\nneed to be tracked and managed.\n\n## Tests\n\nTo setup, clone the repository and run the following in the seed directory:\n\n```bash\npython -m venv venv\nsource venv/bin/activate\npip install -r requirements.txt\n```\n\nThen to run the tests:\n\n```bash\npython tests/test_classes.py\n```\n\nThere are 75 tests ensuring algorithmic correctness. Examples of intended and\ndisallowed behaviors are contained in the tests. Reading through them may be\nhelpful when reasoning about the clocks' mechanisms.\n\n## Bugs and Contributions\n\nIf you encounter a bug, please submit an issue on GitHub. If you would like to\ncontribute to any of my projects, discuss new projects, or collaborate on\nexperiments, join the [Pycelium discord server](https://discord.gg/b2QFEJDX69).\n\n## ISC License\n\nCopyleft (c) 2024 Jonathan Voss\n\nPermission to use, copy, modify, and/or distribute this software\nfor any purpose with or without fee is hereby granted, provided\nthat the above copyleft notice and this permission notice appear in\nall copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\nWARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\nAUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR\nCONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\nNEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\nCONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk98kurz%2Freverse-entropy-clocks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk98kurz%2Freverse-entropy-clocks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk98kurz%2Freverse-entropy-clocks/lists"}