├── CNAME ├── .gitignore ├── 404.md ├── assets ├── images │ ├── share.png │ ├── ngi-logo.png │ ├── 250224-2sm.png │ ├── 250224-dag.png │ ├── adz_sam_glyph.jpg │ ├── eu-flag-logo.png │ ├── 250224-2sm-x3dh.png │ ├── 250827-finality.png │ ├── 250224-group-crdt.png │ ├── 250716-pull-read.png │ ├── 250716-read-write.png │ ├── very_large_phone.jpg │ ├── 250224-key-agreement.png │ ├── 250224-shared-nodes.png │ ├── 250709-parrot-horse.png │ ├── 250827-app-event-api.png │ ├── 250827-app-example-1.png │ ├── 250827-app-example-2.png │ ├── 250827-dependencies.png │ ├── 250827-leaking-keys.png │ ├── 250827-partial-order.png │ ├── 250224-data-encryption.png │ ├── 250224-message-encryption.png │ ├── 250709-stream-controller.png │ ├── 250716-concurrent-removal.png │ ├── 250716-nested-group-graph.jpg │ ├── 250827-centralised-server.png │ ├── 250709-atomic-transactions.png │ ├── 250709-processing-pipeline.png │ ├── 250827-authorization-chain.png │ ├── 250827-concurrent-bubbles-1.png │ ├── 250827-concurrent-bubbles-2.png │ ├── 250709-stacking-system-layers.png │ ├── 250224-verified-group-operations.png │ ├── 250709-system-application-layers.png │ ├── 250716-concurrent-mutual-removal.png │ ├── 250827-without-eventual-consistency.png │ ├── 250716-transitive-invalidation-graph.jpg │ ├── gradient.svg │ ├── p2panda.svg │ ├── nlnet-logo.svg │ └── deepsea-panda.svg ├── fonts │ ├── MetamorBit.woff │ ├── Covik_Sans_Mono-Bold.woff │ ├── Covik_Sans_Mono-Bold.woff2 │ ├── Covik_Sans_Mono-Italic.woff │ ├── Covik_Sans_Mono-Italic.woff2 │ ├── Covik_Sans_Mono-Medium.woff │ ├── Covik_Sans_Mono-Medium.woff2 │ ├── Covik_Sans_Mono-Regular.woff │ ├── IBMPlexMono-Bold-Latin1.woff │ ├── Covik_Sans_Mono-Regular.woff2 │ ├── Covik_Sans_Mono-Semibold.woff │ ├── Covik_Sans_Mono-Semibold.woff2 │ ├── IBMPlexMono-Bold-Latin1.woff2 │ ├── Covik_Sans_Mono-Bold_Italic.woff │ ├── Covik_Sans_Mono-Bold_Italic.woff2 │ ├── IBMPlexMono-Regular-Latin1.woff │ ├── IBMPlexMono-Regular-Latin1.woff2 │ ├── Covik_Sans_Mono-Medium_Italic.woff │ ├── Covik_Sans_Mono-Medium_Italic.woff2 │ ├── Covik_Sans_Mono-Semibold_Italic.woff │ └── Covik_Sans_Mono-Semibold_Italic.woff2 └── styles │ ├── highlight.css │ └── main.css ├── _config.yml ├── _layouts ├── post.html └── default.html ├── Gemfile ├── README.md ├── _posts ├── 2025-01-20-anti-fragile-0.2.0-release.md ├── 2025-03-11-events-sqlite-0.3.0-release.md ├── 2024-12-06-p2panda-release.md ├── 2025-07-09-streams-transactions-crash-resilience.md ├── 2025-08-27-notes-convergent-access-control-crdt.md └── 2025-07-28-access-control.md ├── index.html └── LICENSE /CNAME: -------------------------------------------------------------------------------- 1 | p2panda.org -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.code-workspace 2 | Gemfile.lock 3 | _site 4 | -------------------------------------------------------------------------------- /404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Page not found 4 | --- 5 | 6 | ## Page not found 7 | -------------------------------------------------------------------------------- /assets/images/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/share.png -------------------------------------------------------------------------------- /assets/images/ngi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/ngi-logo.png -------------------------------------------------------------------------------- /assets/fonts/MetamorBit.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/MetamorBit.woff -------------------------------------------------------------------------------- /assets/images/250224-2sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-2sm.png -------------------------------------------------------------------------------- /assets/images/250224-dag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-dag.png -------------------------------------------------------------------------------- /assets/images/adz_sam_glyph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/adz_sam_glyph.jpg -------------------------------------------------------------------------------- /assets/images/eu-flag-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/eu-flag-logo.png -------------------------------------------------------------------------------- /assets/images/250224-2sm-x3dh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-2sm-x3dh.png -------------------------------------------------------------------------------- /assets/images/250827-finality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-finality.png -------------------------------------------------------------------------------- /assets/images/250224-group-crdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-group-crdt.png -------------------------------------------------------------------------------- /assets/images/250716-pull-read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250716-pull-read.png -------------------------------------------------------------------------------- /assets/images/250716-read-write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250716-read-write.png -------------------------------------------------------------------------------- /assets/images/very_large_phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/very_large_phone.jpg -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Bold.woff -------------------------------------------------------------------------------- /assets/images/250224-key-agreement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-key-agreement.png -------------------------------------------------------------------------------- /assets/images/250224-shared-nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-shared-nodes.png -------------------------------------------------------------------------------- /assets/images/250709-parrot-horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250709-parrot-horse.png -------------------------------------------------------------------------------- /assets/images/250827-app-event-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-app-event-api.png -------------------------------------------------------------------------------- /assets/images/250827-app-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-app-example-1.png -------------------------------------------------------------------------------- /assets/images/250827-app-example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-app-example-2.png -------------------------------------------------------------------------------- /assets/images/250827-dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-dependencies.png -------------------------------------------------------------------------------- /assets/images/250827-leaking-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-leaking-keys.png -------------------------------------------------------------------------------- /assets/images/250827-partial-order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-partial-order.png -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Bold.woff2 -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Italic.woff -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Medium.woff -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Medium.woff2 -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Regular.woff -------------------------------------------------------------------------------- /assets/fonts/IBMPlexMono-Bold-Latin1.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/IBMPlexMono-Bold-Latin1.woff -------------------------------------------------------------------------------- /assets/images/250224-data-encryption.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-data-encryption.png -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Regular.woff2 -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Semibold.woff -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Semibold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Semibold.woff2 -------------------------------------------------------------------------------- /assets/fonts/IBMPlexMono-Bold-Latin1.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/IBMPlexMono-Bold-Latin1.woff2 -------------------------------------------------------------------------------- /assets/images/250224-message-encryption.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-message-encryption.png -------------------------------------------------------------------------------- /assets/images/250709-stream-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250709-stream-controller.png -------------------------------------------------------------------------------- /assets/images/250716-concurrent-removal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250716-concurrent-removal.png -------------------------------------------------------------------------------- /assets/images/250716-nested-group-graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250716-nested-group-graph.jpg -------------------------------------------------------------------------------- /assets/images/250827-centralised-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-centralised-server.png -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Bold_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Bold_Italic.woff -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Bold_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Bold_Italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/IBMPlexMono-Regular-Latin1.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/IBMPlexMono-Regular-Latin1.woff -------------------------------------------------------------------------------- /assets/fonts/IBMPlexMono-Regular-Latin1.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/IBMPlexMono-Regular-Latin1.woff2 -------------------------------------------------------------------------------- /assets/images/250709-atomic-transactions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250709-atomic-transactions.png -------------------------------------------------------------------------------- /assets/images/250709-processing-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250709-processing-pipeline.png -------------------------------------------------------------------------------- /assets/images/250827-authorization-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-authorization-chain.png -------------------------------------------------------------------------------- /assets/images/250827-concurrent-bubbles-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-concurrent-bubbles-1.png -------------------------------------------------------------------------------- /assets/images/250827-concurrent-bubbles-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-concurrent-bubbles-2.png -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Medium_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Medium_Italic.woff -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Medium_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Medium_Italic.woff2 -------------------------------------------------------------------------------- /assets/images/250709-stacking-system-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250709-stacking-system-layers.png -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Semibold_Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Semibold_Italic.woff -------------------------------------------------------------------------------- /assets/fonts/Covik_Sans_Mono-Semibold_Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/fonts/Covik_Sans_Mono-Semibold_Italic.woff2 -------------------------------------------------------------------------------- /assets/images/250224-verified-group-operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250224-verified-group-operations.png -------------------------------------------------------------------------------- /assets/images/250709-system-application-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250709-system-application-layers.png -------------------------------------------------------------------------------- /assets/images/250716-concurrent-mutual-removal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250716-concurrent-mutual-removal.png -------------------------------------------------------------------------------- /assets/images/250827-without-eventual-consistency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250827-without-eventual-consistency.png -------------------------------------------------------------------------------- /assets/images/250716-transitive-invalidation-graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p2panda/website/HEAD/assets/images/250716-transitive-invalidation-graph.jpg -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: p2panda 2 | author: p2panda # default author for blog posts 3 | tagline: Building blocks for peer-to-peer applications 4 | baseurl: "" 5 | url: "https://p2panda.org" 6 | 7 | plugins: 8 | - jekyll-feed 9 | -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |

{{ page.title }}

6 | 7 |

✉️ Posted on {{ page.date | date: "%d.%m.%y" }}

8 |

🖊️ Written by {{ page.author }}

9 | 10 | {{ content }} 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "github-pages", "~> 232", group: :jekyll_plugins 4 | 5 | group :jekyll_plugins do 6 | gem "jekyll-feed", "~> 0.17.0" 7 | end 8 | 9 | # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of 10 | # the gem do not have a Java counterpart. 11 | gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] 12 | -------------------------------------------------------------------------------- /assets/images/gradient.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # website 2 | 3 | Official [p2panda](https://p2panda.org) website. 4 | 5 | ## Requirements 6 | 7 | * Ruby `3.2.0` 8 | * Jekyll `3.10.0` 9 | * Bundler `2.4.1` 10 | 11 | ## Development 12 | 13 | ```bash 14 | # Install Ruby dependencies 15 | bundle install 16 | 17 | # Export the gem path if this is your first run 18 | export GEM_HOME=$HOME/.gem 19 | 20 | # Run development web server 21 | jekyll serve 22 | 23 | # If that fails, try this 24 | bundle exec jekyll serve 25 | ``` 26 | 27 | ## License 28 | 29 | [`CC-BY-SA-4.0 License`](LICENSE) 30 | 31 | *This project has received funding from the European Union’s Horizon 2020 32 | research and innovation programme within the framework of the NGI-POINTER 33 | Project funded under grant agreement No 871528, NGI-ASSURE No 957073 and 34 | NGI0-ENTRUST No 101069594.* 35 | -------------------------------------------------------------------------------- /assets/styles/highlight.css: -------------------------------------------------------------------------------- 1 | .highlight { 2 | margin: 0; 3 | border-radius: 10px; 4 | } 5 | 6 | .highlight code { 7 | display: block; 8 | padding: 10px 20px; 9 | background-color: #232323; 10 | } 11 | 12 | .highlight table td { 13 | padding: 5px; 14 | } 15 | 16 | .highlight table pre { 17 | margin: 0; 18 | } 19 | 20 | .highlight, 21 | .highlight .w { 22 | color: #d0d0d0; 23 | background-color: #151515; 24 | } 25 | 26 | .highlight .err { 27 | color: #151515; 28 | background-color: #ac4142; 29 | } 30 | 31 | .highlight .c, 32 | .highlight .ch, 33 | .highlight .cd, 34 | .highlight .cm, 35 | .highlight .cpf, 36 | .highlight .c1, 37 | .highlight .cs { 38 | color: #505050; 39 | } 40 | 41 | .highlight .cp { 42 | color: #f4bf75; 43 | } 44 | 45 | .highlight .nt { 46 | color: #f4bf75; 47 | } 48 | 49 | .highlight .o, 50 | .highlight .ow { 51 | color: #d0d0d0; 52 | } 53 | 54 | .highlight .p, 55 | .highlight .pi { 56 | color: #d0d0d0; 57 | } 58 | 59 | .highlight .gi { 60 | color: #90a959; 61 | } 62 | 63 | .highlight .gd { 64 | color: #ac4142; 65 | } 66 | 67 | .highlight .gh { 68 | color: #6a9fb5; 69 | background-color: #151515; 70 | font-weight: bold; 71 | } 72 | 73 | .highlight .k, 74 | .highlight .kn, 75 | .highlight .kp, 76 | .highlight .kr, 77 | .highlight .kv { 78 | color: #aa759f; 79 | } 80 | 81 | .highlight .kc { 82 | color: #d28445; 83 | } 84 | 85 | .highlight .kt { 86 | color: #d28445; 87 | } 88 | 89 | .highlight .kd { 90 | color: #d28445; 91 | } 92 | 93 | .highlight .s, 94 | .highlight .sb, 95 | .highlight .sc, 96 | .highlight .dl, 97 | .highlight .sd, 98 | .highlight .s2, 99 | .highlight .sh, 100 | .highlight .sx, 101 | .highlight .s1 { 102 | color: #90a959; 103 | } 104 | 105 | .highlight .sa { 106 | color: #aa759f; 107 | } 108 | 109 | .highlight .sr { 110 | color: #75b5aa; 111 | } 112 | 113 | .highlight .si { 114 | color: #8f5536; 115 | } 116 | 117 | .highlight .se { 118 | color: #8f5536; 119 | } 120 | 121 | .highlight .nn { 122 | color: #f4bf75; 123 | } 124 | 125 | .highlight .nc { 126 | color: #f4bf75; 127 | } 128 | 129 | .highlight .no { 130 | color: #f4bf75; 131 | } 132 | 133 | .highlight .na { 134 | color: #6a9fb5; 135 | } 136 | 137 | .highlight .m, 138 | .highlight .mb, 139 | .highlight .mf, 140 | .highlight .mh, 141 | .highlight .mi, 142 | .highlight .il, 143 | .highlight .mo, 144 | .highlight .mx { 145 | color: #90a959; 146 | } 147 | 148 | .highlight .ss { 149 | color: #90a959; 150 | } 151 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | {% capture title %}{% if page.title %}{{ page.title }} ~ {% endif -%}{{ site.title }}{% endcapture -%} 2 | {% capture description %}{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines }}{% else -%}{{ site.tagline }}{% endif -%}{% endcapture -%} 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ title }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |

{{ site.title }}

31 |

{{ site.tagline }}

32 |
33 |
34 | {{ content -}} 35 |
36 | 40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /_posts/2025-01-20-anti-fragile-0.2.0-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Fragile Panda (0.2.0)" 4 | subtitle: "Now with full offline-first networking support" 5 | author: glyph 6 | --- 7 | 8 | Today we're happy to announce a new release of p2panda, one which offers exciting improvements to our networking and discovery capabilities. 9 | 10 | ## More "offline-first", please! 11 | 12 | A major design requirement of p2panda is the ability to operate in an "offline-first" manner. The services we offer should be tolerant of volatile network environments, with the capacity to self-heal when interfaces and connections go down or become available. This stands in contrast to many contemporary networked applications and services which both expect and demand continuous connectivity. 13 | 14 | Our collaboration with several GNOME developers on a local-first GTK text editor (working title: [Aardvark](https://github.com/p2panda/aardvark)) quickly brought to light shortcomings in our "offline-first" resilience; data sync and live-mode would fail to recover after more than 30 seconds of lost connectivity and our mDNS discovery service would throw an error if no interface was available on startup. We've made it a priority to rectify these issues and believe that our `0.2.0` release makes significant improvements. 15 | 16 | **Bidirectional Sync** 17 | 18 | Firstly, we've refactored our log sync protocol to be bidirectional, meaning that both peers exchange their log heights and data in a single session. Our previous implementation required each peer to initiate a separate session for complete synchronisation. The change from a pull-based to push-based approach means that one peer can reset their sync state (e.g. after a network disconnection and reconnection) and initiate a session, resulting in both peers "catching up" on past data. 19 | 20 | **Live-mode State Reset** 21 | 22 | p2panda currently relies on gossip overlays for network-wide topic discovery and "live mode" (ie. broadcast of published data to all peers interested in a given topic). We discovered that these overlays break down after extended loss of connectivity. Our new release rectifies this issue by resetting live-mode state and re-entering gossip overlays when connectivity is regained. 23 | 24 | **mDNS Retries** 25 | 26 | What happens if you register an mDNS discovery service and then start your p2panda-powered network node when your device networking is disabled? In p2panda `0.1.0` this would result in a panic, as no socket was available to be fully configured and bound. Not good! We've refactored the service to (re)bind the socket as needed, ensuring that mDNS discovery can (re)start when interface changes are detected. 27 | 28 | ## Until Next Time 29 | 30 | At the end of the month we'll be heading to Switzerland for [P2P Basel](https://p2p-basel.org/), a weekend workshop which "bring[s] together researchers and software builders to share insights and collaborate towards the sound and sustainable development of efficient eventually-consistent (offline-first) peer-to-peer systems". We can't wait to spend time with new and old friends alike! 31 | 32 | Other than that, we're working hard on the [autonomous coordination toolkit](https://github.com/toolkitties/toolkitty) and designing our group encryption and access control systems. 33 | 34 | We're looking forward to hearing from you as you try out p2panda `0.2.0`! Please consult the [CHANGELOG](https://github.com/p2panda/p2panda/blob/main/CHANGELOG.md) for a full list of changes. 35 | 36 | Remember to subscribe to our [RSS feed](https://p2panda.org/feed.xml) for new upcoming posts on our website or follow us on the Fediverse via [@p2panda@autonomous.zone](https://autonomous.zone/@p2panda) to hear more about upcoming events or news! 37 | -------------------------------------------------------------------------------- /_posts/2025-03-11-events-sqlite-0.3.0-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Event Streams and Persistence (0.3.0)" 4 | subtitle: "Bootstrap your network and listen to events" 5 | author: glyph 6 | --- 7 | 8 | We're excited to share a new release of p2panda, one which is primarily focused on our networking crate but also introduces SQLite persistence and several bug fixes. 9 | 10 | ## Bootstrapping and Discovery 11 | 12 | **Network Events API** 13 | 14 | As a user of our `p2panda-net` crate, it's understandable that you might wish to have some degree of introspection into the peer-related events which are occurring. In this release we've introduced a means of subscribing to a stream of network events by calling `.events()` on `Network`. Events currently include those related to gossip (aka. "live mode"), sync (aka. data replication) and discovery. In this way, it's possible to learn when a sync session has started and finished, when a new peer has been discovered and when a connection has been establish with a new neighbour. We intend to add additional data to these events in the future, such as the amount of bytes synced and the duration of each session. 15 | 16 | **Bootstrap Mode** 17 | 18 | As our collaborators begin working more deeply with our modules, we've been discovering blindspots in our implementations and working on improvements. One such improvement is the introduction of a "bootstrap mode" for network peers. The bootstrap node is one which is started without knowledge of any peers; it serves as the entrypoint into the network for others. This ability is activated by calling `.bootstrap()` on the `NetworkBuilder` during network configuration. The [chat example](https://github.com/p2panda/p2panda/blob/main/p2panda-net/examples/chat.rs) in our `p2panda-net` repository provides a configurable CLI tool to play with various scenarios. 19 | 20 | **Discovery Over the Internet** 21 | 22 | With the bootstrap mode in place, we now have discovery of peers and topics over the internet. Using the power of [iroh](https://www.iroh.computer/) under the hood, all that's needed to join a network is the URL of a relay server (which must be the same amongst peers) and the public key of a bootstrap node or other online peer; the discovery process then occurs automatically. 23 | 24 | ## Filesystem Persistence 25 | 26 | **SQLite Store** 27 | 28 | You might be surprised to learn that up until this point we have only had in-memory persistence for operations and logs, the core data types of p2panda. This reflects our style when it comes to adding new features; we aim to coordinate our efforts to align with the needs of the apps being built by us and our collaborators. The need for filesystem persistence has recently arisen and so this release introduces a SQLite store. It can be accessed through `p2panda-store` with the `sqlite` feature flag enabled. 29 | 30 | ## Until Next Time 31 | 32 | Next week we're on our way to the [Bidston Observatory Artistic Research Centre](https://www.bidstonobservatory.org) (BOARC) near Birkenhead, UK for a team working session! The core team (adz, sam and glyph) will join the Toolkitty team for a contentrated few days of app develpoment. [Toolkitty](https://github.com/toolkitties/toolkitty) is an autonomous coordination toolkit for collectives, organisers and venues to share resources and organise events in a collaborative calendar. We're getting closer to completing the prototype and can't wait to share in the months ahead! 33 | 34 | We're looking forward to hearing from you as you try out p2panda `0.3.0`! Please consult the [CHANGELOG](https://github.com/p2panda/p2panda/blob/main/CHANGELOG.md) for a full list of changes. 35 | 36 | Remember to subscribe to our [RSS feed](https://p2panda.org/feed.xml) for new upcoming posts on our website or follow us on the Fediverse via [@p2panda@autonomous.zone](https://autonomous.zone/@p2panda) to hear more about upcoming events or news! 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |

About

6 |

p2panda aims to provide everything you need to build modern, privacy-respecting and secure local-first applications.

7 |

We have adopted a modular approach—allowing projects the freedom to pick what they need and integrate it with minimal friction. We believe this approach contributes the most to a wider, interoperable p2p ecosystem which outlives “framework lock-in”.

8 |

Many of our Rust crates operate over raw bytes and are fully compatible with your own data types and any CRDT. In case you don't plan on building your own peer-to-peer protocol, we have you covered with all features required to build a mobile or desktop application.

9 |

We're using existing libraries like iroh and well-established standards such as BLAKE3, Ed25519, STUN, CBOR, TLS, QUIC, Double Ratchet and more - as long as they give us the radical offline-first guarantee we need.

10 |

We want collaboration, encryption and access-control to work even when operating over unstable or ephemeral connections. Towards this end, we're actively working alongside researchers to design and implement resilient solutions.

11 |

p2panda is "broadcast-only" at it’s heart, making any data not only offline-first but also compatible with post-internet communication infrastructure, such as shortwave, packet radio, Bluetooth Low Energy, LoRa or simply a USB stick.

12 |

p2panda is a very multifaceted project: We maintain our crates, apply for grants, design protocols and do research in radically distributed data-types. We organise community events and write peer-to-peer applications with our friends and collaborators.

13 |

Libraries

14 | 46 |

News

47 |
48 | {% for post in site.posts %} 49 |
50 | 51 |

✉️ {{ post.title }}

52 |

{{ post.subtitle }}

53 | {{ post.date | date: "%d.%m.%y"}} - {{ post.author }} 54 |
55 |
56 | {% endfor %} 57 |
58 |

Projects

59 |
60 |
61 | 62 |

📝 Reflection (coming mid-2025)

63 |

Local-first, collaborative text editor, based on GTK and Loro CRDT.

64 |

This is a collaboration with friends from GNOME and part of a general research to explore local-first code, UI and UX patterns in GTK applications.

65 |
66 |
67 |
68 | 69 |

📢 Toolkitty (coming mid-2025)

70 |

Autonomous coordination toolkit for collectives, organisers and places to share resources and organise events in a shared calendar.

71 |

Toolkitty is built for iOS, Android and Desktop with Tauri.

72 |
73 |
74 |
75 | 76 |

🐝 Meli Bees App

77 |

Collaborative Android app for sighting and categorisation of Meliponini bee species in the Brazilian Amazon.

78 |

This project is a collaboration between p2panda and Meli and built with Flutter and aquadoggo.

79 |
80 |
81 |
82 | 83 |

📡 rhio

84 |

rhio is a peer-to-peer message router and file sync solution allowing processes to rapidly exchange messages and efficiently replicate large files without any centralised coordination.

85 |

rhio was built in collaboration with HIRO.

86 |
87 |
88 |
89 | 90 |

🐬 aquadoggo

91 |

aquadoggo is a node implementation based on an earlier version of p2panda. It is a intended as a tool for making the design and build of local-first, collaborative p2p applications as simple as possible, and hopefully even a little fun!

92 |
93 |
94 |
95 |

Contact

96 |

Find all source code on GitHub, follow us on the Fediverse, join our official Chat or write us an Email! We'd love to hear from you!

97 | -------------------------------------------------------------------------------- /_posts/2024-12-06-p2panda-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "2024 Updates and Our New Release!" 4 | subtitle: "So much has happened this year and we're so excited to finally tell you about it!" 5 | author: adz 6 | --- 7 | 8 | So much has happened this year and we're so excited to finally tell you about it! 9 | 10 | p2panda has been undergoing some changes; it's lighter, more hackable and much more modular. While this rewrite felt scary at times, we are very proud and excited about the outcome we're releasing today. This post is intended to shed light on our decision and convey why we believe the new p2panda will be useful for the wider "local-first" community! 11 | 12 | ## The "new" p2panda 13 | 14 | We've released our [new crates](https://github.com/p2panda/p2panda) today and with this we're taking a new approach with p2panda: 15 | 16 | **Modular and interoperable** 17 | 18 | p2panda wants to lower the barrier for developers to build modern, privacy-respecting and secure local-first applications for mobile, desktop or web. We've learned that such a toolkit shouldn't come at the cost of a highly abstract "monolithic" API or framework. With that in mind, our new version aims to be as modular as possible—allowing projects the freedom to pick what they need and integrate it with minimal friction. Our networking layer offers a great example of a module which should be useful for many different projects. We believe this approach contributes the most to a wider, interoperable p2p ecosystem which outlives "framework lock-in". 19 | 20 | For projects seeking a more fully-integrated system, it's possible to stack our modules into one efficient, stream-based pipeline providing p2p networking, sync, discovery, gossip, blobs, authentication, ordering, deletion, multi-writer, access control, encryption and so on. 21 | 22 | **Data-type agnostic** 23 | 24 | Some of our new building blocks, such as the sync, discovery and networking layers, do not require any custom p2panda data types. This makes it possible to bring your own data types and develop your protocol on top. We're planning the same for our group encryption implementation which is set to be released next year. 25 | 26 | **Support any CRDT or application data** 27 | 28 | Previous versions of p2panda came with their own approaches to CRDTs and schema validation. While we still believe this is great for future high-level modules, we wanted to offer you the option of combining all p2panda modules with Automerge, Yjs or any other CRDT of your choice. Of course not every application needs a CRDT and at the end of the day the payload is just "raw bytes". 29 | 30 | **Re-use as much existing technology and well-established standards as possible** 31 | 32 | We’re using existing libraries like iroh and well-established standards such as BLAKE3, Ed25519, STUN, ICE, CBOR, TLS, QUIC, UCAN, PlumTree, HyParView, Double Ratchet and more - as long as they give us the radical offline-first guarantee we need. 33 | 34 | **Radically distributed and compatible with any post-internet communication infrastructure** 35 | 36 | We want collaboration, encryption and access-control to work even when we can’t assume any sort of stable connectivity over an extended period. p2panda is "broadcast-only" at it’s heart, making any data not only offline-first but also compatible with post-internet communication infrastructure, such as shortwave, packet radio, Bluetooth Low Energy, LoRa or simply a USB stick. 37 | 38 | **Append-only logs are great!** 39 | 40 | We've chosen to remove Bamboo as a core data type from the p2panda protocol; we realised that we weren't making full use of the features it provides. Still, the new p2panda data type is again an append-only log, just much lighter and more flexible. 41 | 42 | Logs are very efficient data types for exchanging data over challenged communication infrastructure and give developers something they will always need when building a distributed application: ordering (in other words, a knowledge of "what happened before / after or at the same time as x"). Our new data type also includes exciting features and optional extensions, such as writing to multiple logs at the same time, pruning (automatic removal of unused data), prefix-based deletion (delete multiple logs at the same time with a single tombstone) and fork-tolerance. Some of these features are rooted in exciting new research and we're happy to be sharing more about them in a future blog post. 43 | 44 | As already mentioned, not all modules require you to use p2panda data types, but if you want the most features, they are ready with all sorts of extensions you can pull in for your application's needs. 45 | 46 | ## So much news! 47 | 48 | **Collaborations with GNOME and HIRO** 49 | 50 | While working on the new version of p2panda we also embarked on collaborations with two very different teams. We've been exploring code, UX and UI patterns for GTK-based applications with a group of developers from GNOME and together will release the first GTK-based, collaborative, local-first text editor. Our second collaboration has been a project developed together with HIRO, a company based in the Netherlands. Together we designed and implemented a solution named "rhio" to sync large files and messages between micro data centers in a fully distributed manner. 51 | 52 | **Autonomous Coordination App** 53 | 54 | This autumn we started working on an autonomous coordination toolkit with a new team of six people, supported by the [Innovate Creative Catalyst Programme](https://iuk-business-connect.org.uk/programme/creative-catalyst/) in UK. The goal of the project is to develop a mobile and desktop app for collectives, organisers and places to share resources and organise events in a shared calendar. This tool has been a [very old goal](https://media.ccc.de/v/36c3-10756-p2panda) of p2panda and it feels incredibly special to have the release of the first prototype scheduled for Spring of 2025! 55 | 56 | ![Ongoing development of the resource sharing and event organising app with p2panda.](/assets/images/very_large_phone.jpg) 57 | 58 | > Ongoing development of the resource sharing and event organising app with p2panda. 59 | 60 | ## What's next? 61 | 62 | p2panda is a very multifaceted project: We maintain our crates, apply for grants, design protocols and do research in radically distributed data-types. We organise community events and write peer-to-peer applications with our friends and collaborators. There's a lot coming up. 63 | 64 | **Improve!** 65 | 66 | This is our first release of the new p2panda version and we will surely learn more about it's APIs and user requirements in the upcoming months. Our goal is to reach a stable API but for now we need to expect breaking changes as we're adjusting. 67 | 68 | **Group Encryption and Capabilities** 69 | 70 | Next year we'll also be working on an [NLNet](https://nlnet.nl/) NGI Zero Entrust grant to integrate [UCAN](https://github.com/ucan-wg/spec)-based access control and secure group encryption with Post-Compromise-Security and optional Forward-Secrecy, based on research into [decentralised secure group messaging](https://dl.acm.org/doi/10.1145/3460120.3484542) algorithms. Our plan is to implement these as Rust modules which you can pull into your application, independent of p2panda, your choice of data types or networking stack. The DCGKA algorithm we'll be implementing is essentially Signal's Double Ratchet Algorithm with PCS and FS, made fit for offline-first use. 71 | 72 | Together with researchers we'll be publishing our work on fork-tolerant and prunable append-only logs, hopefully in the form of another blog post or even a paper. 73 | 74 | **App releases!** 75 | 76 | For next year we will be releasing the GTK-based text editor in collaboration with GNOME and the first version of the autonomous coordination app (name still pending) and hopefully organise a festival with it sometime :-) 77 | 78 | ## Get involved 79 | 80 | Please subscribe to our [RSS feed](https://p2panda.org/feed.xml) for new upcoming posts on our website or follow us on the Fediverse via [@p2panda@autonomous.zone](https://autonomous.zone/@p2panda) to hear more about upcoming events or news! 81 | 82 | Our crates are ready to be played with and we are more than curious to hear about your ideas or feedback. 83 | 84 | We are very excited to be hearing from you! 85 | 86 | ![Tired but happy p2panda team: adz, sam and glyph in London](/assets/images/adz_sam_glyph.jpg) 87 | 88 | > Tired but happy p2panda team: adz, sam and glyph in London. 89 | -------------------------------------------------------------------------------- /assets/styles/main.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Covik Sans Mono"; 3 | src: url('../fonts/Covik_Sans_Mono-Regular.woff2') format('woff2'), 4 | url('../fonts/Covik_Sans_Mono-Regular.woff') format('woff'); 5 | font-weight: 400; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: "Covik Sans Mono"; 11 | src: url('../fonts/Covik_Sans_Mono-Italic.woff2') format('woff2'), 12 | url('../fonts/Covik_Sans_Mono-Italic.woff') format('woff'); 13 | font-weight: 400; 14 | font-style: italic; 15 | } 16 | 17 | @font-face { 18 | font-family: "Covik Sans Mono"; 19 | src: url('../fonts/Covik_Sans_Mono-Medium.woff2') format('woff2'), 20 | url('../fonts/Covik_Sans_Mono-Medium.woff') format('woff'); 21 | font-weight: 500; 22 | font-style: normal; 23 | } 24 | 25 | @font-face { 26 | font-family: "Covik Sans Mono"; 27 | src: url('../fonts/Covik_Sans_Mono-Medium_Italic.woff2') format('woff2'), 28 | url('../fonts/Covik_Sans_Mono-Medium_Italic.woff') format('woff'); 29 | font-weight: 500; 30 | font-style: italic; 31 | } 32 | 33 | @font-face { 34 | font-family: "Covik Sans Mono"; 35 | src: url('../fonts/Covik_Sans_Mono-Semibold.woff2') format('woff2'), 36 | url('../fonts/Covik_Sans_Mono-Semibold.woff') format('woff'); 37 | font-weight: 600; 38 | font-style: normal; 39 | } 40 | 41 | @font-face { 42 | font-family: "Covik Sans Mono"; 43 | src: url('../fonts/Covik_Sans_Mono-Semibold_Italic.woff2') format('woff2'), 44 | url('../fonts/Covik_Sans_Mono-Semibold_Italic.woff') format('woff'); 45 | font-weight: 600; 46 | font-style: italic; 47 | } 48 | 49 | @font-face { 50 | font-family: "Covik Sans Mono"; 51 | src: url('../fonts/Covik_Sans_Mono-Bold.woff2') format('woff2'), 52 | url('../fonts/Covik_Sans_Mono-Bold.woff') format('woff'); 53 | font-weight: 700; 54 | font-style: normal; 55 | } 56 | 57 | @font-face { 58 | font-family: "Covik Sans Mono"; 59 | src: url('../fonts/Covik_Sans_Mono-Bold_Italic.woff2') format('woff2'), 60 | url('../fonts/Covik_Sans_Mono-Bold_Italic.woff') format('woff'); 61 | font-weight: 700; 62 | font-style: italic; 63 | } 64 | 65 | @font-face { 66 | font-family: "Metamor Bit"; 67 | src: url('../fonts/MetamorBit.woff') format('woff'); 68 | font-weight: 400; 69 | font-style: normal; 70 | } 71 | 72 | @font-face { 73 | font-family: "IBM Plex Mono"; 74 | src: url('../fonts/IBMPlexMono-Regular-Latin1.woff2') format('woff2'), 75 | url('../fonts/IBMPlexMono-Regular-Latin1.woff') format('woff'); 76 | font-weight: 400; 77 | font-style: normal; 78 | } 79 | 80 | @font-face { 81 | font-family: "IBM Plex Mono"; 82 | src: url('../fonts/IBMPlexMono-Bold-Latin1.woff2') format('woff2'), 83 | url('../fonts/IBMPlexMono-Bold-Latin1.woff') format('woff'); 84 | font-weight: 700; 85 | font-style: normal; 86 | } 87 | 88 | :root { 89 | --black: #111111; 90 | --baby-blue: #6666ff; 91 | --peach: #f99a9a; 92 | --pale-yellow: #efefaa; 93 | --gray-light: #434343; 94 | --gray: #ddd; 95 | --gray-dark: #555; 96 | --white: #efefef; 97 | } 98 | 99 | body { 100 | margin: 0; 101 | padding: 0; 102 | font-family: "Covik Sans Mono"; 103 | font-weight: 400; 104 | font-style: normal; 105 | background-color: var(--pale-yellow); 106 | } 107 | 108 | img { 109 | width: 100%; 110 | border-radius: 10px; 111 | } 112 | 113 | p { 114 | margin: 0; 115 | padding: 0; 116 | font-size: 19px; 117 | line-height: 1.6; 118 | } 119 | 120 | p + p { 121 | margin-top: 20px; 122 | } 123 | 124 | p a { 125 | color: var(--baby-blue); 126 | border-bottom: 2px solid var(--baby-blue); 127 | } 128 | 129 | p a:has(> code) { 130 | border-bottom: 0; 131 | } 132 | 133 | strong { 134 | font-weight: 600; 135 | font-style: normal; 136 | } 137 | 138 | a { 139 | color: var(--black); 140 | text-decoration: none; 141 | } 142 | 143 | h1, 144 | h2, 145 | h3, 146 | h4, 147 | h5, 148 | h6 { 149 | padding: 0; 150 | margin: 0; 151 | font-weight: 600; 152 | font-style: normal; 153 | } 154 | 155 | h1, 156 | h2 { 157 | text-decoration-style: wavy; 158 | text-decoration-line: underline; 159 | text-decoration-color: var(--baby-blue); 160 | } 161 | 162 | h1 { 163 | font-size: 50px; 164 | text-decoration-thickness: 3px; 165 | } 166 | 167 | h2 { 168 | font-size: 30px; 169 | margin-top: 70px; 170 | margin-bottom: 30px; 171 | text-decoration-thickness: 2px; 172 | } 173 | 174 | h3 { 175 | margin-top: 30px; 176 | margin-bottom: 10px; 177 | } 178 | 179 | pre, 180 | code { 181 | font-family: "IBM Plex Mono"; 182 | font-weight: 400; 183 | font-size: 19px; 184 | } 185 | 186 | code { 187 | font-size: 16px; 188 | display: inline-block; 189 | background-color: var(--white); 190 | padding: 1px 7px; 191 | border-radius: 10px; 192 | vertical-align: bottom; 193 | } 194 | 195 | blockquote { 196 | margin: 0; 197 | margin-top: 10px; 198 | margin-bottom: 20px; 199 | padding-left: 20px; 200 | border-left: 5px solid var(--gray-dark); 201 | } 202 | 203 | blockquote p { 204 | font-size: 17px; 205 | font-style: italic; 206 | color: var(--gray-dark); 207 | } 208 | 209 | blockquote code { 210 | font-size: 15px; 211 | background-color: var(--white); 212 | padding: 3px; 213 | } 214 | 215 | pre code { 216 | overflow: scroll; 217 | } 218 | 219 | ol, 220 | ul { 221 | padding: 20px; 222 | margin: 0; 223 | } 224 | 225 | li { 226 | font-size: 19px; 227 | line-height: 1.6; 228 | } 229 | 230 | li + li { 231 | margin-top: 13px; 232 | } 233 | 234 | /* Layout */ 235 | 236 | header, 237 | main, 238 | footer { 239 | margin-top: 40px; 240 | margin-left: 20px; 241 | margin-right: 20px; 242 | } 243 | 244 | .container { 245 | margin: 0 auto; 246 | max-width: 800px; 247 | } 248 | 249 | .grid { 250 | display: grid; 251 | gap: 30px; 252 | } 253 | 254 | .rotate-3 { 255 | transform: rotate(-3deg); 256 | } 257 | 258 | .rotate-2 { 259 | transform: rotate(-2deg); 260 | } 261 | 262 | .rotate-1 { 263 | transform: rotate(-1deg); 264 | } 265 | 266 | .rotate1 { 267 | transform: rotate(1deg); 268 | } 269 | 270 | .rotate2 { 271 | transform: rotate(2deg); 272 | } 273 | 274 | .rotate3 { 275 | transform: rotate(3deg); 276 | } 277 | 278 | .note { 279 | font-size: 19px; 280 | margin-bottom: 40px; 281 | margin-top: 20px; 282 | padding-left: 10px; 283 | color: var(--gray-light); 284 | border-left: 5px solid var(--peach); 285 | } 286 | 287 | /* Header */ 288 | 289 | header { 290 | text-align: center; 291 | } 292 | 293 | .logo { 294 | display: inline-block; 295 | margin: 0 auto; 296 | width: 300px; 297 | } 298 | 299 | .title { 300 | margin-bottom: 30px; 301 | } 302 | 303 | .tagline { 304 | font-size: 21px; 305 | max-width: 400px; 306 | margin: 0 auto; 307 | text-align: center; 308 | line-height: 1.2; 309 | } 310 | 311 | /* Libraries */ 312 | 313 | .libraries { 314 | list-style: none; 315 | padding: 0; 316 | } 317 | 318 | .libraries li { 319 | display: block; 320 | margin-bottom: 20px; 321 | } 322 | 323 | .libraries a { 324 | color: var(--baby-blue); 325 | } 326 | 327 | .libraries code { 328 | letter-spacing: 0.5px; 329 | margin-right: 5px; 330 | padding: 4px 10px; 331 | } 332 | 333 | .libraries a:hover code { 334 | background-color: var(--baby-blue); 335 | color: var(--white); 336 | } 337 | 338 | /* Projects */ 339 | 340 | .project { 341 | display: inline-block; 342 | background-color: var(--white); 343 | color: var(--black); 344 | border-radius: 5px; 345 | box-shadow: 5px 5px 0px var(--black); 346 | } 347 | 348 | .project a { 349 | display: block; 350 | padding: 12px 20px; 351 | } 352 | 353 | .project h3 { 354 | font-family: "Metamor Bit"; 355 | font-size: 20px; 356 | letter-spacing: 1px; 357 | margin-bottom: 7px; 358 | margin-top: 0; 359 | } 360 | 361 | .project p { 362 | line-height: 1.3; 363 | } 364 | 365 | .project:hover { 366 | background-color: var(--baby-blue); 367 | color: var(--white); 368 | } 369 | 370 | /* News */ 371 | 372 | .post { 373 | font-family: "IBM Plex Mono"; 374 | font-weight: 400; 375 | display: inline-block; 376 | background-color: var(--baby-blue); 377 | border-radius: 5px; 378 | box-shadow: 5px 5px 0px var(--black); 379 | } 380 | 381 | .post a { 382 | display: block; 383 | padding: 12px 20px; 384 | color: var(--white); 385 | } 386 | 387 | .post h3 { 388 | margin: 0; 389 | } 390 | 391 | .post:hover { 392 | background-color: var(--white); 393 | box-shadow: 5px 5px 0px var(--gray); 394 | } 395 | 396 | .post:hover a { 397 | color: var(--baby-blue); 398 | } 399 | 400 | /* Code */ 401 | 402 | .language-rust code { 403 | line-height: 1.4; 404 | margin-top: 20px; 405 | margin-bottom: 20px; 406 | } 407 | 408 | /* Footer */ 409 | 410 | footer { 411 | margin-bottom: 40px; 412 | } 413 | 414 | footer p { 415 | font-size: 16px; 416 | } 417 | 418 | footer p a { 419 | color: var(--black); 420 | border-bottom: 2px dotted var(--black); 421 | } 422 | -------------------------------------------------------------------------------- /assets/images/p2panda.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /_posts/2025-07-09-streams-transactions-crash-resilience.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Stream Processing, Transactions, Crash Resilience in p2panda" 4 | subtitle: "Strategies we're exploring to make p2p applications resilient to critical failures" 5 | author: adz 6 | --- 7 | 8 | In peer-to-peer applications, processes can crash, fail or be interrupted at any point. This occurs not only as a result of losing connectivity with other peers, abruptly ending a stream of data, but also due to the fact that the process itself doesn't run on a stable, highly-available server but on a mobile phone, laptop or other edge device. Whenever the user decides to "swipe up" and move the app into the background, or the operating system decides not to allocate any more resources to the process, we can't guarantee that whatever we started will end well. And of course, despite our best efforts as developers, bugs can always happen. 9 | 10 | Why are crashes dangerous for applications and especially peer-to-peer ones? 11 | 12 | A classic example: Parrot wants to send one apple to Horse. Parrot starts the transaction and removes an apple from their store. Horse receives the apple and adds it to their store. If one of the processes crashes on either Parrot's or Horse's side, we might end up with a situation where Parrot's state has one less apple and Horse's has none. 13 | 14 | ![Parrot and Horse's transaction example](/assets/images/250709-parrot-horse.png) 15 | 16 | > An example of a failed transaction where Parrot will loose an apple and Horse will never receive it. 17 | 18 | We never want to end up in a situation where a failure like that leads to the app hanging in an invalid state. Trying to recover a "broken" peer is especially hard when doing things without any central coordination. 19 | 20 | This blog post is about the strategies and design ideas we're exploring in p2panda to make p2p applications resilient to critical failures, for both system- and application layers. 21 | 22 | ## Processing system- and application data 23 | 24 | Processes usually change their internal state when receiving new data or input. Peers observe messages on a network and process them based on a set of rules. Message processing results in a new state which then gets moved into a store or database. 25 | 26 | ![Processing pipeline](/assets/images/250709-processing-pipeline.png) 27 | 28 | > Regular processing pipeline. 29 | 30 | What is different from more traditional client-server models is that in peer-to-peer systems *every* single peer needs to process the incoming data and store the *materialised* or indexed state by itself, instead of relying on a server doing the "hard work" and the "lightweight" client cheaply querying the materialised result. 31 | 32 | In p2panda it is possible to model **application data** and the processing of it however you like. We have observed three emergent "patterns" for doing so: 33 | 34 | 1. Peers send **messages** to each other which simply need to be stored in a database, for example chat messages or "social media" posts. Not much processing needs to be done here. 35 | 2. Peers send **State- or Operation-based CRDTs** ([Conflict-free replicated data types](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)) to each other. These "updates" form a new CRDT state which allows all peers to eventually converge to the same state. Applications like [Reflection](https://github.com/p2panda/reflection) follow this pattern. 36 | 3. Peers send **events** to each other which are processed based on a form of "event sourcing" logic. This can also be understood as [event or stream processing](https://www.youtube.com/watch?v=avi-TZI9t2I) (depending on where you come from). By re-processing all events we can re-construct the state again. Applications like [Toolkitty](https://toolkitty.org/) follow this pattern. 37 | 38 | In most application contexts there is underlying **system data** which needs to be processed *before* we process our application data. 39 | 40 | In p2panda we offer different [building blocks](https://p2panda.org/#libraries) as part of the system layer which solve a collection of common challenges in p2p systems, for example: Authentication, Integrity Guarantees, Ordering, Deletion, Garbage Collection, Offline-First, Access Control, Roles Management and Group Encryption. 41 | 42 | Information required to coordinate these system-related data-types or protocols is usually transmitted as part of the **Header** of every p2panda **Operation** being sent. This Header contains information to describe append-only logs, DAGs giving us partial ordering, signatures, pruning mechanisms, key agreement, capabilities etc. Next to the Header the Operation also contains a **Body**; this is where the previously-mentioned application data goes. The exact shape of the data is defined by the application but it must be encoded as bytes for inclusion in the Body. 43 | 44 | ![System- and application layers](/assets/images/250709-system-application-layers.png) 45 | 46 | > Operations processed first on system- then on application layer. The system layer uses data stored in the Header of the Operation for processing, the application layer is mostly interested in what's in the Body. 47 | 48 | Whenever a p2panda Operation arrives at a peer we need to separate the system- and application concerns and handle them one after another. First we look into the Header, process it and adjust our system state accordingly. Has this author been invited to an encrypted group context? Is this author allowed to write to this document? Did this Operation arrive out-of-order and do we need to wait before we can proceed with it? Can we decrypt the Body? 49 | 50 | After all of the system processing and checks have taken place, we can finally "forward" the Operation to the application layer which is controlled by the developers who want to build anything on top. Here we can only guess what will happen, but let's assume that some sort of processing will also be required and whatever comes out of it will land in some application state, potentially persisted in a database like SQLite. 51 | 52 | Our goal is to allow developers to combine and "stack up" different processing flows for their individual application needs. Does your application need permission management? Add `p2panda-auth` to the processing pipeline! 53 | 54 | This is very similar to middleware APIs in classical HTTP frameworks like [Express](https://expressjs.com/) or [Tower](https://docs.rs/tower/latest/tower/) where different "features" can be stacked up on top of each other for every incoming "request". 55 | 56 | ![Stacking system layers with p2panda](/assets/images/250709-stacking-system-layers.png) 57 | 58 | > Stacking system layers like "middlewares" in p2panda. 59 | 60 | While we're building completely p2panda-independent data-types and protocols, for example around access control in [`p2panda-auth`](https://crates.io/crates/p2panda-auth) or group encryption in [`p2panda-encryption`](https://crates.io/crates/p2panda-encryption), we offer a common ground with `p2panda-stream` where these solutions can easily be combined and integrated into every application's pipeline. 61 | 62 | The name and APIs are still in flux but we believe that this gives the most framework-independence and flexibility while allowing application developers to focus primarily on application-level concerns. 63 | 64 | We've experimented with Rust [`Stream`](https://docs.rs/futures-core/latest/futures_core/stream/trait.Stream.html) to express a "flexible" middleware API in our `p2panda-stream` crate but unfortunately Rust's strict type system and exploding complexity and boilerplate around nested async calls and generics makes the [use of it not enjoyable](https://hachyderm.io/@fasterthanlime/112898470217396730), and prone to bugs. Currently we're exploring a more pragmatic approach with slightly less flexibility and significant better readability and ease of usage. 65 | 66 | Why all of this introduction into the p2panda processing pipeline when talking about crash resilience? We see now that there are multiple state changes required before we even arrive at the application layer. In addition, we _only_ really want to commit to the new state if the application finally says: "Yes, this operation was valid and everything is ok". To achieve this guarantee we need to be able to express the processing of an operation across both layers as one single atomic transaction. 67 | 68 | ## Atomic Transactions 69 | 70 | There has already been a lot of thought put into making applications crash-resilient on state changes, for example when writing to a database. This is not an exclusive peer-to-peer problem to have; all applications need to take care of this, it is just a question as to whether or not developers actually do it. 71 | 72 | Databases like SQLite or PostgreSQL try to handle exactly these cases by fulfilling certain properties which can be summarised as [ACID](https://en.wikipedia.org/wiki/ACID). Martin Kleppmann once again gives an excellent [introduction into these properties](https://www.youtube.com/watch?v=5ZjhNTM8XU8) (and how they can be misunderstood). 73 | 74 | The "A" in ACID stands for [Atomicity](https://en.wikipedia.org/wiki/Atomicity_(database_systems)) and is exactly the property we are interested in for our systems. By grouping state changes into one atomic transaction we can guarantee that either *all* get executed *at once*, or *none* of them. If anything fails, the transaction is aborted and all changes are rolled back, as if they never happened. 75 | 76 | Even before our data hits the actual database (like SQLite) we already need to worry about these things like atomicity, this is why building a p2p protocol can sometimes feel like re-inventing databases. 77 | 78 | Here is an example of how to express an atomic transaction in SQL using the `sqlx` Rust crate, following our initial apple-sending example with Parrot and Horse: 79 | 80 | ```rust 81 | // Initiate a single, atomic transaction using sqlx. 82 | let mut tx = connection.begin().await?; 83 | 84 | // Remove one apple from Parrot. 85 | sqlx::query("UPDATE apples SET amount = 2 WHERE user = 'parrot'") 86 | .execute(&mut *tx) 87 | .await?; 88 | 89 | // Give Horse one apple. 90 | sqlx::query("UPDATE apples SET amount = 1 WHERE user = 'horse'") 91 | .execute(&mut *tx) 92 | .await?; 93 | 94 | // Finally "commit" all of these changes. They will be persisted 95 | // from now on if nothing crashed until here. 96 | tx.commit().await?; 97 | ``` 98 | 99 | In p2panda every incoming Operation begins a new atomic transaction which will be used for every state change or *write* to a database during processing. Finally we want to forward the same transaction object to the application layer so developers can continue writing to the database as part of the *same* transaction. 100 | 101 | ![Atomic Transactions](/assets/images/250709-atomic-transactions.png) 102 | 103 | > Atomic transactions in the processing pipeline. 104 | 105 | This solves two important problems: First, we don't want to end up with invalid state when a process crashes. Imagine that a chat message arrives, the system layer decrypts the data using the "Message Encryption" scheme of `p2panda-encryption` with [double ratcheting](https://en.m.wikipedia.org/wiki/Double_Ratchet_Algorithm), moves the ratchet forwards, writes the new encryption state to the database and due to a bug in the application layer everything crashes and we never save the plaintext. Now we potentially end up with a situation where the message will never be read. With this in mind, we don't want to persist any changes to the database until the final application layer "committed" the transaction, making sure that everything until then succeeded. 106 | 107 | The second issue solved is validation: Applications should always have the last say as to whether or not they want to reject an operation based on their own application validation logic and social protocols. Even when nothing fails, an application can choose to *not* commit to the new state. This might occur when the application protocol has been violated due to invalid encoding or offensive content has been published. In such cases, users many not want to persist any operations in our database, nor to commit to any state changes triggered by them. 108 | 109 | ## APIs 110 | 111 | We don't want to burden application developers too much, at the same time we can see that caring about atomic transactions is crucial for rolling out any robust p2p application. 112 | 113 | As part of the `p2panda-store` crate we're currently preparing traits (Rust interfaces) to implement state handling with atomic transactions against any possible database implementation. You can see the [related PR here](https://github.com/p2panda/p2panda/pull/755). 114 | 115 | With this trait design we stay flexible with the final choice concerning what database your application would like to use (in-memory, SQLite, redb, etc.) while keeping the atomicity guarantees. 116 | 117 | The API design clearly separates "read" queries from "writes", as the latter is the only one actually changing state and thus needing to be placed inside an atomic transaction. 118 | 119 | Inspired by `sqlx` we follow a very similar API approach to express writing state to a database inside of transactions and committing them: 120 | 121 | ```rust 122 | // Initialise a concrete store implementation, for example for 123 | // SQLite. This implementation implements the WritableStore 124 | // trait, providing it's native transaction interface. 125 | let mut store = SqliteStore::new(); 126 | 127 | // Establish state, do things with it. 128 | // 129 | // User and Event both implement the WriteToStore trait for the 130 | // concrete store type SqliteStore. 131 | let octopus = User::new("Octopus"); 132 | 133 | // To retrieve state from the database we can read from it via 134 | // a trait interface which was implemented against SqliteStore. 135 | // 136 | // How this trait exactly looks like was defined by whoever 137 | // defined what a "User" is. 138 | let horse = store.find_user("Horse").await?; 139 | 140 | let mut event = Event::new("Ants Research Meetup"); 141 | event.register_attendance(&octopus); 142 | event.register_attendance(&horse); 143 | 144 | // Persist new state in database as part of a transaction. 145 | let mut tx = store.begin().await?; 146 | 147 | octopus.write(&mut tx).await?; 148 | horse.write(&mut tx).await?; 149 | event.write(&mut tx).await?; 150 | 151 | tx.commit().await?; 152 | ``` 153 | 154 | There are different possible approaches to design state handling around transactions and our traits. We're exploring multiple options right now, for example *pure functions*. 155 | 156 | Pure functions are [functions which do not have any side-effects](https://en.wikipedia.org/wiki/Pure_function); they will never write to a database when being called and instead return a new state object. The combination of transactions and Rust's strict borrow checker allows us to express state handling quite neatly (and we did it a lot inside our `p2panda-auth` and `p2panda-encryption` crates), for example: 157 | 158 | ```rust 159 | // Retrieve current group state from database. 160 | let state = store.group_by_id(&group_id).await?; 161 | 162 | // Create a new group state in "pure function style". 163 | let new_state = Group::add_member(state, &member_id).await?; 164 | 165 | // Persist new group state as part of atomic transaction. 166 | new_state.write(&mut tx).await?; 167 | ``` 168 | 169 | ## Replaying operations with a stream controller 170 | 171 | It is important to note that after a process has crashed and restarted, we want to "re-play" any operation which never completed, otherwise our application will not have a chance to recover from the crash and the operation will be lost. 172 | 173 | As part of `p2panda-stream` (the stackable middleware pipeline) we're planning on integrating a **stream controller** which allows re-playing "unprocessed" operations by default and manually re-playing all or a range of operations from a certain point (defined by logical or wall-clock time) or "topic" (grouping operations defined by the application) when required. 174 | 175 | The stream controller can be neatly combined with atomic transactions. Every operation needs to be "acknowledged" by the application layer at the end of every processing. This signals to the controller that the operation can now be marked as "processed". Now we can finally commit the atomic transaction with all state changes caused by that operation and we don't need to re-play it whenever the process starts again. 176 | 177 | Processors usually need to have [idempotency](https://en.wikipedia.org/wiki/Idempotence) guarantees; this can be difficult to reason about when the codebases and data types get complex. Processing an operation twice might lead to invalid state (for example, Horse ending up with two apples when only one was sent). By combining transactions with a stream controller we can guarantee that the state produced by processing an operation is only ever committed *once*. 178 | 179 | A stream controller design is already implemented as part of the [Toolkitty](https://toolkitty.org/) peer-to-peer application and we now want to move the ideas into our official libraries. 180 | 181 | ![Stream Controller](/assets/images/250709-stream-controller.png) 182 | 183 | > Stream controller with atomic transactions in the processing pipeline. 184 | 185 | From our previous experience of releasing peer-to-peer applications like [Meli](https://github.com/p2panda/meli) we can also utilise a stream controller for rolling out app updates with breaking changes or emergency bug fixes. If a schema or data type has changed we might need to wipe some database state, re-play all available operations and re-instantiate the database with the breaking change or bug fix in place. This is a useful feature to have around in case an application ever needs it. 186 | 187 | ## The p2panda node 188 | 189 | p2panda is a project which didn't start by specifying a "perfect system" before building it. We begin by exploring patterns and ideas while vertically integrating them into user applications through collaboration with other teams. At times this makes it challenging to explain *what p2panda is* and *how to get started*, as we're deep down in exchanging with application developers and reasoning about the constantly changing APIs. 190 | 191 | However, in all of this we can see useful patterns emerging, such as clear separation of system- and application concerns, access control and roles, group encryption, multi-device support, message ordering, transactions, stream processing with "stackable" middleware APIs and stream re-plays - and we want to move all of this into one unified `p2panda-node` with an RPC API and a robust p2p networking and sync layer underneath. 192 | 193 | All of this takes place on higher "integration layers", while still keeping all "low-level" code (for example, group encryption) clearly separated, so whoever wants to just use one particular feature built by p2panda will not need to follow the same design. 194 | 195 | For everyone who wants to have a complete peer-to-peer backend with robustness and security guarantees, we're gradually moving towards the release of `p2panda-node`. We will then be able to replace all system-level "custom" integration code with a more unified solution for some of our applications. Stay tuned! 196 | -------------------------------------------------------------------------------- /assets/images/nlnet-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | === 3 | 4 | Creative Commons Corporation ("Creative Commons") is not a law firm and 5 | does not provide legal services or legal advice. Distribution of 6 | Creative Commons public licenses does not create a lawyer-client or 7 | other relationship. Creative Commons makes its licenses and related 8 | information available on an "as-is" basis. Creative Commons gives no 9 | warranties regarding its licenses, any material licensed under their 10 | terms and conditions, or any related information. Creative Commons 11 | disclaims all liability for damages resulting from their use to the 12 | fullest extent possible. 13 | 14 | Using Creative Commons Public Licenses 15 | 16 | Creative Commons public licenses provide a standard set of terms and 17 | conditions that creators and other rights holders may use to share 18 | original works of authorship and other material subject to copyright 19 | and certain other rights specified in the public license below. The 20 | following considerations are for informational purposes only, are not 21 | exhaustive, and do not form part of our licenses. 22 | 23 | Considerations for licensors: Our public licenses are 24 | intended for use by those authorized to give the public 25 | permission to use material in ways otherwise restricted by 26 | copyright and certain other rights. Our licenses are 27 | irrevocable. Licensors should read and understand the terms 28 | and conditions of the license they choose before applying it. 29 | Licensors should also secure all rights necessary before 30 | applying our licenses so that the public can reuse the 31 | material as expected. Licensors should clearly mark any 32 | material not subject to the license. This includes other CC- 33 | licensed material, or material used under an exception or 34 | limitation to copyright. More considerations for licensors: 35 | wiki.creativecommons.org/Considerations_for_licensors 36 | 37 | Considerations for the public: By using one of our public 38 | licenses, a licensor grants the public permission to use the 39 | licensed material under specified terms and conditions. If 40 | the licensor's permission is not necessary for any reason--for 41 | example, because of any applicable exception or limitation to 42 | copyright--then that use is not regulated by the license. Our 43 | licenses grant only permissions under copyright and certain 44 | other rights that a licensor has authority to grant. Use of 45 | the licensed material may still be restricted for other 46 | reasons, including because others have copyright or other 47 | rights in the material. A licensor may make special requests, 48 | such as asking that all changes be marked or described. 49 | Although not required by our licenses, you are encouraged to 50 | respect those requests where reasonable. More considerations 51 | for the public: 52 | wiki.creativecommons.org/Considerations_for_licensees 53 | 54 | --- 55 | 56 | Creative Commons Attribution-ShareAlike 4.0 International Public 57 | License 58 | === 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | -------------------------------------------------------------------------------- /_posts/2025-08-27-notes-convergent-access-control-crdt.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Notes on building a convergent, offline-first Access Control CRDT" 4 | subtitle: "General learnings on CRDT design and how they can be integrated into applications" 5 | author: adz 6 | --- 7 | 8 | p2panda recently published the first release of [`p2panda-auth`](https://crates.io/crates/p2panda-auth), a convergent, offline-first CRDT ([Conflict-free Replicated Data-Type](https://mattweidner.com/2023/09/26/crdt-survey-1.html)) which helps managing members, admins or moderators in a group and giving them permission to sync, read or change application data. If you want to learn about the concrete implementation and design choices of `p2panda-auth` we have an [in-depth blog post](https://p2panda.org/2025/07/28/access-control.html) for you here. 9 | 10 | This post is about our general learnings on building an **Access Control CRDT** followed by our exploration of patterns for how application data can be “authorised”. This includes **Application Integrations** and **Combination with Key Agreement Protocols**. These patterns can be very different based on the individual application’s requirements (and we surely didn’t cover a lot of other approaches). There is no straight-forward answer or single solution - so here is an attempted “summary”! 11 | 12 | ## Do you need eventual consistency? 13 | 14 | If we can trust a single server to authorise edits from different users we can simply rely on the “linear history” of access control- and data changes. The changes are “serialised” in the order the server received them. 15 | 16 | ![Centralised server serializing all permission- and app-data changes](/assets/images/250827-centralised-server.png) 17 | 18 | > Centralised server serializing all permission- and app-data changes 19 | 20 | We can apply a similar rule in peer-to-peer systems as well: As soon as the peer learned that a permission was revoked (the user is not allowed to change the data) we reject any future data from now on. Every peer can manage that “authorisation state” themselves and act accordingly from their perspective. 21 | 22 | ![Authorisation in peer-to-peer without eventual consistency](/assets/images/250827-without-eventual-consistency.png) 23 | 24 | > Authorisation in peer-to-peer without eventual consistency 25 | 26 | This example shows how both peers “synced” the removal of Owl’s permission at one point, but allowed different edits of Owl before doing so. Peer A will end up with a different app state than Peer B. In systems like that we can’t guarantee the “eventual consistency” of the application data. At least not without further work. 27 | 28 | Imagine an often changing key/value database, if peers constantly overwrite the values with new data (in a “last-write-wins” fashion), it maybe doesn’t matter if they are briefly “out of sync” until they converge to the same state again. 29 | 30 | In p2panda we believe that this is a very valid option for building many peer-to-peer applications. The trick will be to figure out patterns (once again) to identify when and how which guarantee is required for which application. 31 | 32 | For a computer game that might not be so nice though as maybe Owl ended up making an extra move gaining more points for Peer A, while Peer B has a different game state. 33 | 34 | The answer is really: it depends! 35 | 36 | How would we introduce eventual consistency here? We would need to detect concurrent and conflicting changes, for example a user changing data while they concurrently have been removed and be able to retroactively handle the “unauthorised” edits. 37 | 38 | An access control system doesn’t need to be complicated when applications don’t require eventual consistency guarantees from it or if they can model them outside of that scope. Simple [Capability-based Tokens](https://en.wikipedia.org/wiki/Capability-based_security) with an expiry date can be enough to account for such applications. 39 | 40 | In p2panda we want an access control solution which can account for all sorts of application needs, including the guarantee to “converge” to the same application state based on the given access control, with all the bells and whistles we need around concurrency and conflict resolution. The answer is to build a “convergent”, offline-first Access Control CRDT and we will show later how we can integrate it into application data, with different patterns around eventual consistency, moderation and encryption. 41 | 42 | First let’s build that CRDT. 43 | 44 | ## Building a convergent, offline-first Access Control CRDT 45 | 46 | There are different strategies to “authorise” someone to do something in an application. In our setting we would like users to create groups. These groups provide a context to manage higher-level key agreement CRDTs for group encryption and are “composed” on top of the Access Control CRDT. A group creator is able to add and remove other users and give them “access rights”, for example write or admin access. 47 | 48 | This is similar to an [Access Control List](https://en.wikipedia.org/wiki/Access-control_list) (or ACL) which is a bit like a guest list of a party: If you’re on the list you are allowed to enter, if not - you can’t. Every peer manages such a list and checks it to ensure that received user actions are permitted. 49 | 50 | This is different from building a convergent capability-based CRDT, like in [Keyhive](https://www.inkandswitch.com/keyhive/notebook/), where users form “delegation” chains to permit access. However, the CRDT parts are fairly similar. 51 | 52 | ### Dependencies & out of order handling 53 | 54 | Independent of the application data we know one thing for sure: We want access control to *always* be consistent! Every peer should be able learn about the latest and same status of the access control system at one point. 55 | 56 | This doesn’t seem so hard to achieve at first sight: Every peer eventually receives the “user got removed” operation and converged to that state, knowing they need to reject that user’s data from this point on. 57 | 58 | Things can still get a little bit tricky though: What if we receive the operations out-of-order? We receive the user’s removal before they were even added? We need a way to describe “dependencies” to understand if we’re missing operations. 59 | 60 | For this we give every operation a unique identifier, for example a hash. Now operations can “declare dependencies” by mentioning a list of operation IDs which they consider important to be “processed before”. Peers who receive an operation can now understand if they’re still missing other operations before they can go on. 61 | 62 | This structure of dependencies forms a [Directed Acyclic Graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) (DAG). Our favourite graph for many peer-to-peer problems! <3 63 | 64 | ![Graph describing dependencies](/assets/images/250827-dependencies.png) 65 | 66 | > The “edges” of the graph describe the “dependencies” of every “node”. 67 | 68 | Peers can now easily reason about what they are missing and process things only if they know that they have seen every dependency of that operation. 69 | 70 | If we look closer we can see that the “processed” operations are effectively a linearised, [topologically ordered](https://en.wikipedia.org/wiki/Topological_sorting) sequence after their dependencies have been checked. 71 | 72 | Now we can at least be sure that we haven’t “missed” anything. If we choose our hashing algorithm wisely we can make sure that it is impossible to “guess” an operation id because they are simply too long and “too random”. Like this, peers can only refer to an operation if they really learned about it before. This gives us the guarantee of **Integrity**. 73 | 74 | ### Ordering 75 | 76 | Another property we have from DAGs is that we can start to reason about the “order” of operations. 77 | 78 | In our decentralised systems we can’t really reason about the “exact” order of concurrent operations as we don’t have one single source of truth which orders events for us (like a centralised server or a consensus-based, [single-ledger blockchain](https://en.wikipedia.org/wiki/Blockchain#Decentralization)). This is why DAGs are perfect to describe this “partial ordering”. Partial order is a set of operations where we can’t always compare two entries with each other and know which one was created first. When we can’t compare them directly we only know they occurred *at the same* “logical” time. 79 | 80 | ![Partial Order](/assets/images/250827-partial-order.png) 81 | 82 | > We don’t know exactly in which order C and D took place here, we only know that these have been “concurrent” operations as both of them didn’t know about each other while they got created. 83 | 84 | ### Authorisation 85 | 86 | How can we know that a member was really allowed to add someone to the group? 87 | 88 | We need to make sure that new operations can only be applied to the graph if previous operations authorised them. Like this we can form a sort of “trust chain” or proof and trace back the “logical” operations until the beginning and check on every step if they got authorised to do that action. 89 | 90 | ![Authorisation Chain](/assets/images/250827-authorization-chain.png) 91 | 92 | > Owl is allowed to add Pig to the group because we can see that Owl was made an Admin by Sheep before and that Sheep is an admin because they created the group before 93 | 94 | If we sign every operation on top with a [cryptographically-secure signature](https://en.wikipedia.org/wiki/Digital_Signature_Algorithm) we can make sure that the author of this change can prove their identity. This gives us the guarantee of **Provenance**. 95 | 96 | ### Concurrency 97 | 98 | By looking at a DAG we can reason about the partial order and concurrent operations easily. How do we identify concurrent operations programmatically? 99 | 100 | In a DAG we can use a traversal technique to identify all concurrent operations from the perspective of a single “target operation” by moving from that target to all other reachable nodes in the graph, in depth-first order. We mark all visited nodes as “successors” of that target. 101 | 102 | Next we *reverse* all edges and do the same traversal again. All visited nodes can now be marked as the “predecessor” of the target. Like this we get a set of all operations which are neither predecessors nor successors of the target operation which means that they have been concurrent to it. 103 | 104 | We need to repeat this process for every node in the graph. Each time we detect a set of “concurrent nodes” we recurse down each of them, repeating the same process again but merging the outcome as one “concurrent bubble”. Bubbles which only contain one node are ignored. 105 | 106 | ![Calculating concurrent bubbles I](/assets/images/250827-concurrent-bubbles-1.png) 107 | 108 | Traversing the graph with this technique gives us a list of all concurrent bubbles in it. At this stage it is not necessary to account for “nested” bubbles. It is sufficient to consider them as one single entity. 109 | 110 | ![Calculating concurrent bubbles II](/assets/images/250827-concurrent-bubbles-2.png) 111 | 112 | This is arguably a very involved way to compute concurrent bubbles in a graph and using [Lamport Timestamps](https://en.wikipedia.org/wiki/Lamport_timestamp) can be more efficient. However, they do not give us the integrity guarantees of the hashes, so implementations will probably end up having both systems next to each other. 113 | 114 | ### Conflicts 115 | 116 | Where things get the most tricky is on concurrent group changes which might be in conflict with each other. For example: What happens if someone wants to add a member to the group while they are concurrently being removed? 117 | 118 | Since we can now detect concurrent operations or “bubbles” we can apply rules for merging conflicting changes. 119 | 120 | There are very different approaches on how to handle these conflicts, all coming with different advantages and disadvantages: 121 | 122 | * **Seniority ranking:** “older” members win over concurrent removals. Prone to [Sibyl attack](https://en.wikipedia.org/wiki/Sybil_attack) so further mitigation is required 123 | * **Remove both:** Solution for byzantine scenarios where a group was “infiltrated” but it might end up without admins so the group needs to recover by starting anew 124 | * **Decide “by higher hash”:** Removal operation with “higher hash” value wins deterministically but “randomly”. Operations can be adversarially chosen to “win” 125 | 126 | Which strategy to pick should be decided by a treat model and how well it can be communicated to the users. Which conflict resolution strategies are working well for Authorisation CRDTs and their users is still to be explored. 127 | 128 | ### Consensus & Finality 129 | 130 | Whenever a peer publishes an operation in the DAG we can use their “dependency” pointers as proof that they have “acknowledged” the state up until that point in the graph. 131 | 132 | Since the number of members is known in the group at this point we have a fixed bound of how many acknowledgements we need to reach “finality” or consensus on that current state. 133 | 134 | This is useful for a whole range of improvements to our Auth CRDT: 135 | 136 | 1. We can explore pruning or compaction techniques to remove or “compress” parts of the history of the DAG 137 | 2. We can reason about what a peer has “missed” when they have acknowledged something. With that knowledge we might know something they didn’t, so we can “forward” that missing information to them. This gets especially interesting to account for concurrent changes in key agreement protocols, like `p2panda-encryption` 138 | 3. We can “lock in” the group state from this moment on and agree on finality: Every change which will be applied into that “past” will be considered **byzantine behaviour** and illegal. This “fork the past” we also call **equivocation** and consensus protocols like that can help us to detect and mitigate them 139 | 140 | ![Finality through consensus](/assets/images/250827-finality.png) 141 | 142 | ## Integration with p2panda 143 | 144 | To build CRDTs based on DAGs we already have all the tools we need inside of our `p2panda-core` crate. Our `Operation` core data type gives us: 145 | 146 | * Hashing functions to derive an identifier from the operation and guarantee Integrity 147 | * Digital Signature Scheme for each operation, to guarantee Provenance 148 | * Extensions to declare dependencies and partial order 149 | * Append-only log structure per author to detect forks within a log; this can be optionally used to build byzantine fault tolerance on that layer 150 | 151 | Additionally we provide ready implementations in `p2panda-stream` to efficiently order incoming operations based on their declared dependencies and keep them around (unloaded into a database) until “ready”. 152 | 153 | With operations and the orderer we can now work with partially-ordered, dependency-checked (handling out-of-order arrival), authenticated and linearised streams of operations. 154 | 155 | This is a basic building block to build powerful, authorised CRDTs or other new data types on top of. With `p2panda-auth` we’re introducing our first **eventually-consistent / convergent, authorisation CRDT** which can be easily combined with p2panda operations and our orderer. 156 | 157 | ## Integration with applications 158 | 159 | Different applications will have different requirements around integrating an Authorisation CRDT. 160 | 161 | In any case we need some sort of “Events API” for the Access Control layer to inform applications about any group changes. In p2panda we also give the application information about *when in logical time* this event took place. This is the ID of the operation in the graph. For removal events we also mention the set of operations which potentially have been created by the removed member concurrent to their removal. 162 | 163 | ![Application Events API](/assets/images/250827-app-event-api.png) 164 | 165 | With all of this information, group events and application messages, an application has everything to deal with authorisation changes. Developers will need to reason about the eventual consistency guarantees in their applications, depending on the kind of data, how content is “moderated” and how it is represented to the users. 166 | 167 | The aim is to come up with “patterns” describing different use-cases and concrete examples in the future which hopefully will make these decisions easier. 168 | 169 | We’ve already talked heaps about eventual consistency in the beginning of the blog post, so this might feel familiar: 170 | 171 | **“Weak” / No eventual consistency:** As soon as the application is informed about a member’s removal it simply starts to exclude future messages authored by the member from that point on. 172 | 173 | This approach is very simple and doesn’t guarantee eventual consistency. Peers might pick different points from where they’ve started to exclude messages from the removed member. 174 | 175 | Depending on the application data that might not be a problem though, either because consistency is dealt with on a higher level or because the application doesn’t care. 176 | 177 | Consistency could be reached again, for example, by using a key/value store with last-write-wins logic which converges to the same state as soon as another member overwrites the entry with a new value. 178 | 179 | There’s no “automatic moderation” taking place, as in members would need to edit the data or remove messages manually etc. 180 | 181 | **Guaranteed eventual consistency:** As soon as the application gets informed about a member’s removal it automatically removes the data from the point where the group changed, including concurrent messages. 182 | 183 | For simple data types, like chat messages or “social media” posts etc. it will be easier to remove every data type associated with that concurrent operation, while for Text CRDTs we might need to “re-play” the operation graph from the removal point on and filter out all concurrent operations during materialisation. 184 | 185 | With this “re-basing” or “re-playing” approach we can guarantee eventual consistency for complex data. 186 | 187 | For moderation we can be sure that no messages will be displayed concurrent to the removal; anything which took place before needs to be manually removed or edited. 188 | 189 | ![Application Example with complex data-type](/assets/images/250827-app-example-1.png) 190 | 191 | > First example shows a more “complex” text CRDT with an “invalid” change which needs to be retroactively removed. To remove the concurrent change of Panda we need to go back in time and “re-play” the changes to the text, without the concurrently removed operations. The state will be “re-materialised”. 192 | 193 | ![Application Example with simple data-type](/assets/images/250827-app-example-2.png) 194 | 195 | > Second example is simpler, as we don’t need to re-do anything. We can simply remove the concurrently created post based on the Operation IDs we learned about from the removal event. 196 | 197 | ## Integration with key agreement protocols 198 | 199 | When integrating an Authorisation CRDT with key agreement protocols for group encryption (for example `p2panda-encryption`) we have to be aware of some concurrency edge-cases which might lead to (accidentally) leaked secret keys. 200 | 201 | This is a little excursion into [Key Agreement protocols in decentralised, offline-first systems](https://p2panda.org/2025/02/24/group-encryption.html) but it also shows how the composition of different convergent data-types can lead to interesting problems we need to think about. 202 | 203 | We define our key agreement protocol in a way where on every “Add” Operation the newly introduced member learns about the secret keys for that group so they can decrypt other member's messages or encrypt new messages towards the group. 204 | 205 | In a scenario where Panda creates a group with Panda and Bear inside, Bear concurrently adds Owl to the group while Panda removed Bear. We can resolve this “conflict” easily with our Authorisation CRDT but unfortunately the secret keys might have been leaked to Owl without Panda being aware of it! 206 | 207 | ![Leaking Keys Scenario](/assets/images/250827-leaking-keys.png) 208 | 209 | In a system with forward secrecy, such as p2panda’s “Message Encryption” Scheme, we would not run into problems as Owl will not be able to decrypt any previously created messages. However, if we use encryption systems with only Post-Compromise security, like p2panda’s “Data Encryption” Scheme, Owl will now be able to decrypt all previously created data, even if they’ve only been very “briefly” in the group from Bear’s perspective! 210 | 211 | To avoid this scenario in PCS-only encryption systems we need to ask for consensus from the group *before* we hand over secret keys to new members. We can achieve this with a form of acknowledgement protocol (similar to what we’ve described previously) and only allow sharing secrets *after* every removal has been acknowledged by everyone or at least a majority of the group. 212 | 213 | ## Patterns everywhere! 214 | 215 | This was it for now for our little excursion into convergent data-types! We hope that it inspired you to see a range of "tricks" one can do with them to make them work in peer-to-peer environments. 216 | 217 | It is still too early, but it makes us hopeful for a future where we will slowly converge to re-usable "patterns" which can be applied to all sorts of problems, apart from access control solutions. Across the p2panda stack we can slowly see those "repetitions". It surely helped to have developed all data types independently from each other, outside of a monolithic all-in-one solution as it forced us to be very clear about the common interfaces they share. 218 | 219 | Maybe one day it will be very easy to "compose" them with other solutions outside of the p2panda universe because these patterns, terminology and requirements around convergent data types become well-understood and known? Imagine combining the access control data type with someone else's work to do efficient pruning or another one's code to detect and mitigate Byzantine behaviour? This is an quite exciting future for sharing code, progress and research across peer-to-peer projects! 220 | -------------------------------------------------------------------------------- /_posts/2025-07-28-access-control.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Access Control in Decentralised Systems" 4 | subtitle: "Design and implementation overview of p2panda-auth" 5 | author: glyph & sam 6 | --- 7 | 8 | _This post was edited on 02.09.25 to include corrections and an Informal Correctness 9 | Argument by [Dr. Erick Lavoie](https://dmi.unibas.ch/en/persons/lavoie-erick/)._ 10 | 11 | Having just released the first version of our [p2panda-auth](https://crates.io/crates/p2panda-auth) crate, it seems like the right time to write about access control in decentralised systems. In the process, we'll share an overview of the system we've designed - as well as a discussion of some of the technical challenges involved in implementing peer-to-peer group management and access control. We're grateful to [NLnet](https://nlnet.nl/project/P2Panda-groups/) for supporting us in this work. 12 | 13 | ## System Requirements 14 | 15 | Before diving into the details of the system we've implemented, let's first discuss why we might want an access control system? Broadly speaking, access control gives us a way to define **who** can interact with some specific data and **how** they can interact with the data. The "who" can be thought of as an actor; this might be a cryptographic keypair mapping to a single person or it may be a keypair which represents one of several devices controlled by a single person. An actor may even be a group which is itself composed of several other actors. 16 | 17 | ### Reading & Writing 18 | 19 | The two most basic forms of interacting with data in an access control system are reading and writing. In the process of composing this blog post, for example, I'd like to give my teammates the ability to read _and_ modify the text while only allowing external advisors to read it. As another example, I may wish to have a music folder on my laptop which is shared between all of my other devices. Access control allows us to clearly define abilities and gives us a means of realising 20 | these scenarios. 21 | 22 | ![Encrypted Data Read and Write](/assets/images/250716-read-write.png) 23 | 24 | > Sloth has written a list of their favourite activities: sleeping, cuddling and climbing. Owl has read-access to the document; they can read the list that Sloth has authored but not make any changes. Cat, on the other hand, has write-access and decides to replace "climbing" with "meowing". 25 | 26 | ### Replication 27 | 28 | In decentralised or peer-to-peer systems we also need to keep in mind that data travels between devices in a network. Since a direct connection to a particular peer is not always possible, we may wish for some intermediate peers to be able to assist with passing data through the network. Since those peers may be untrusted, or may simply not be the intended recipient of some data, it's useful to have a means of allowing the right to replicate without the write to read. 29 | 30 | This is where encryption comes into the picture; it allows us to prevent unauthorised reading of data. The ability to read is thus mapped to the ability to decrypt. In systems with connection-based replication protocols we can rely on a seperate access control level to define who is allowed to receive data (even though that data is still encrypted). When connecting to a peer, before we begin sending any requested data, we first check whether that peer has pull-access. If not, we refrain from fulfilling the request. 31 | 32 | ![Encrypted Data Pull and Read](/assets/images/250716-pull-read.png) 33 | 34 | > Sloth has write-access to a list of activities: sleeping, cuddling and climbing. Beetle only has pull-access to the list, meaning that they can receive and pass-on that data but cannot decrypt it for reading. Beetle replicates the data from Sloth and forwards it to Shark. Shark has read-access; they're able to decrypt the data and read the list that Sloth authored. 35 | 36 | ### Intuitive & Customisable 37 | 38 | In the context of p2panda, we want access control to be intuitive for application developers to integrate into their software. In addition, we wish to allow for custom access conditions which can further constrain an actor's access level over some data, and we aim to be conservative in terms of meeting our security requirements. These aspects of our work will become clearer throughout the rest of this post. 39 | 40 | ## Implementation Approaches 41 | 42 | There are several design approaches to meeting the requirements we outlined above. Here we briefly describe two such systems for maintaining and enforcing access to resources. 43 | 44 | ### Capability-Based Access Control 45 | 46 | Capability-based access control systems rely on secure authorisation tokens and use delegation chains to verify which actors have access to any particular set of data. For example, I may issue a token granting read access to my photo-sharing folder with a relative. That token is then handed over as proof of access when my relative tries to read the folder. Such systems allow delegation of received access; an actor can pass on any received capability to other actors. [Meadowcap](https://willowprotocol.org/specs/meadowcap/index.html) from the Willow team is a good example of a pure capability-based access control system. 47 | 48 | Delegations will most often include an expiry date, the expectation being that if access should be maintained a new token will be issued before the previous one has expired. Some systems include the ability to retroactively revoke a previously-delegated access. In such cases, all dependent delegations will also be revoked. 49 | 50 | ### Distributed Access Control Lists 51 | 52 | An alternative approach to capability-based systems is the Distributed Access Control List (ACL). ACLs are commonly used to restrict filesystem access and access to resources on centralised servers. In such systems, a list is kept which maps an actor to an access level. In decentralised contexts, we also require the ability to collaboratively maintain and modify the list. To do so, some actors are given special rights which allow them to edit the ACL. Given the possibily of conflicts resulting from concurrent edits, Distributed ACLs are likely to rely on a Conflict-Free Replicated Data Type (CRDT) to encode changes to the list. 53 | 54 | ## Design & Implementation 55 | 56 | We've ended up with a generic decentralised group management system with fine-grained, per-member permissions. Once a group has been created, members can be added, removed, promoted and demoted. A group member can either be an individual (usually represented by a single public key) or another group. Assigned access levels can be restricted with application specific conditions. 57 | 58 | ### Access Levels 59 | 60 | Each member has an associated access level which can be used to determine their permissions. The access levels we've defined are `Pull`, `Read`, `Write` and `Manage`. The precise access granted by each level is left open to interpretation but in the upcoming integration of our p2panda auth and encryption systems they will be as follows: `Pull` gives the ability to replicate encrypted data, `Read` gives the ability to decrypt data, `Write` gives the ability to mutate data and `Manage` gives the ability to mutate the group state. 61 | 62 | Each access level is cumulative, meaning that it includes the rights granted by lower levels (ie. `Read` also includes `Pull` rights). Each access level can be assigned an associated set of conditions; this allows fine-grained partitioning of each access level. For example, `Read` conditions could be assigned with a path to restrict access to specific areas of a dataset. Finally, only members with `Manage` access are allowed to modify the group state by adding, removing, promoting or demoting other members. 63 | 64 | ### Group Control Operations 65 | 66 | The aforementioned group actions are published as group control operations; each operation is cryptographically-signed, contains a group identifier and action and refers to `previous` operations and `dependencies`. Together, these operations form a causal Directed Acyclic Graph (DAG) which is used to track modifications to the group state over time. The `previous` field allows us to establish a causal "happened before" relationship between group actions, while the `dependencies` field offers a way to point to operations outside of the group which may need to be processed before the action is applied (such as application-specific dependencies or dependencies on other groups). 67 | 68 | ### Concurrency Resolver 69 | 70 | Membership state for a group is maintained locally using a Causal-Length CRDT based on grow-only sets which allow for efficiently merging states across graph branches. However, it's simplicity does not allow us to fully handle conflicting group states emerging from some concurrent scenarios. In such cases, all operations in the DAG are walked in a depth-first search so that any "bubbles" of concurrent operations may be identified. Resolution rules are then applied to the operations in these bubbles in order to populate a filter of operations to be invalidated. Once the offending operations have been invalidated, any dependent operations are then invalidated in turn. 71 | 72 | We have defined the `Resolver` as a Rust trait to allow for multiple implementations with contrasting rulesets for identifying which concurrent operations are to be considered invalid. This approach arises from the understanding that applications have different requirements around trust and security; some may operate in high-stakes contexts where the most cautious implementation is always preferred, while others may operate in low-stakes contexts without the need for strict conflict resolution. The initial offering of our `p2panda-auth` crate offers a single resolver implementation which we refer to as a "strong removal" resolver. The ruleset is as follows: 73 | 74 | 1) Removal or demotion of a manager causes any concurrent actions by that member to be invalidated 75 | 2) Mutual removals, where two managers remove or demote one another concurrently, are not invalidated; both removals are applied to the group state but any other concurrent actions by those members are invalidated 76 | 3) Re-adds are allowed; if Alice removes Charlie then re-adds them, they are still a member of the group but all of their concurrent actions are invalidated 77 | 4) Invalidation of transitive operations; invalidation of an operation due to the application of the aforementioned rules results in all dependent operations being invalidated 78 | 79 | We fully realise, as mentioned before, that this ruleset is not optimal or desirable for all cases. For example, an alternative implementation of the `Resolver` might utilise the seniority of a member to act as a tie-breaker in the case of a mutual removal. In that scenario, the member who was added to the group first would remain in the group and the more recently added member would be removed. 80 | 81 | Another scenario: what happens when the only manager of a group is removed or demoted? Is the group state forever frozen? Or are all group members automatically promoted to managers? The flexibility of our approach allows for both options to be catered for. We look forward to further discussions around these different requirement scenarios and we would be available to assist anyone who wishes to implement their own custom resolver for `p2panda-auth`. 82 | 83 | ### Debugging Graphs 84 | 85 | When trying to reason about various group membership and access control scenarios, it can become extremely challenging to hold complex operation graphs in one's head. We spent many hours dicussing such scenarios during the design of our system and often resorted to sketching diagrams to try and gain an understanding of what was happening. To make things easier, we've implemented a means of printing an auth group graph (using graphviz) to allow for visualising the group control message DAG. This is especially helpful when debugging group state and understanding the impact of concurrency. 86 | 87 | ![Transitive Invalidation Graph](/assets/images/250716-transitive-invalidation-graph.jpg) 88 | 89 | > Here we have a visualisation of a DAG of group membership operations. The graph is read from bottom to top. A group is created with two initial members: individuals A and B. Both of these individuals are assigned manage-access. We then have two concurrent branches of operations. In the right-hand branch: individual A removes B. In the left-hand branch: individual B adds C to the group and assigns them manage-access (this operation is shown with a red background); C then adds individual D to the group with read-access (this operation is shown with an orange background). Since individual B is removed in the right-hand branch, any concurrent actions of theirs are invalidated (shown in red) and any dependent (aka. transitive) actions are also invalidated (shown in orange). A "Group Members" table with a green background shows individual A as the only member; this is a representation of the resolved state of the DAG. 90 | 91 | ### Nested Groups 92 | 93 | Each member in a p2panda auth group can either be an individual or a group. Individuals are understood to be "stateless" due to the fact that they represent a single immutable identity. Groups, on the other hand, are understood to be "stateful" as they contain a mutable set of members. Defining group members in this way allows us to create nested group relationships, where a single group may contain several sub-groups as members. 94 | 95 | ![Nested Group Graph](/assets/images/250716-nested-group-graph.jpg) 96 | 97 | > Here we have another visualisation of a DAG of group membership operations, this time illustrating a nested group scenario. A group 'T' is created with a single initial member: individual A with manage-access. Separately, a group D is created with two initial members: individuals L (manage-access) and M (write-access). Individual A adds individual B to group 'T' with manage-access. Individual B then adds individual C to group 'T' with read-access. The last operation in the graph points the creation of the 'D' group as a dependency and adds that group to group 'T' with manage-access. A "Group Members" table with a green background shows five members: A: manage, B: manage, C: read, L: manage and M: write. 98 | 99 | ## Challenges 100 | 101 | ### Centralised vs. Decentralised 102 | 103 | Group management and access control is relatively straightforward in a centralised context where a server is the single source of truth and all group updates are received in total order. The server "knows" exactly which actions occurred before the others and is able to validate each one before 104 | updating the group state or allowing access of a specific resource to a member. This means that there can never be conflicting group states (unless in the case of a bug or exploited vulnerability). We don't have such luxuries when building peer-to-peer systems. 105 | 106 | In our world, a peer in the network may receive group updates in any order. To make matters worse, there may be long delays between when a group action is taken and when it's learned about by other peers in a network (this could even take years!). So, unlike centralised systems, we have to rely on partial order of actions (we know that one action happened after another but we don't know exactly when each one happened) to detect concurrent modifications of the group state and then apply specific rules to ensure that all members will eventually converge on the same state. 107 | 108 | We also need to take into account malicious actors who may try to manipulate the group state for their own gains; for example, to harrass other members or retain access to their data. It is this paired need to ensure eventual consistency in the face of concurrency and negate byzantine actors that drives many of our design decisions. 109 | 110 | ### Complex Edge Cases 111 | 112 | As we've already mentioned, concurrency brings about some complex and challenging scenarios for group management and eventual consistency. Here we outline a few such scenarios and describe how our current implementation handles them. 113 | 114 | **Mutual Removal Involving Byzantine Actor** 115 | 116 | Penguin is a group manager and promotes Parrot to manager access level. Right afterward, Penguin changes her mind about Parrot and immediately demotes him. Parrot quite enjoys his promotion to manager status and chooses to ignore Penguin's demotion action. As a result, all of Parrot's future actions are technically considered concurrent with Penguin's demotion action. Parrot then goes on to demote all other group members, making him then the single authority figure in the group. 117 | 118 | ![Concurrent Mutual Removal](/assets/images/250716-concurrent-mutual-removal.png) 119 | 120 | > Here we have a Concurrency Diagram depicting a case of "mutual removal" involving a byzantine actor, as described in the paragraph above. Operations on replicas are shown in boxes. Synchronization between replicas is shown with a "Merge" arrow. An operation happens before a later operation if there exists a path between the first and the second, potentially going through merge arrows. If no path exists, then the operations are concurrent. 121 | 122 | In this scenario, a third group member (such as Duck) who has received Penguin's demotion of Parrot and Parrot's demotion of Penguin will determine that a concurrent, mutual removal has occurred. As such, they will remove both Penguin and Parrot from the group and roll-back or ignore any subsequent actions by those two members. This ensures that Parrot's nefarious plan is ultimately undone. 123 | 124 | It's worth noting that in a group with only two managers, a mutual removal effectively freezes the group membership state; no manager remains to add, remove, promote or demote other members. An alternative resolver implementation might choose to promote all remaining members to manager level in that case. Alternatively, one could rely on a seniority principle - where a remaining member with the longest history of group membership would be declared a manager. We have chosen what we believe to be a more conservative approach, where the remaining group members would need to create an entirely new group to re-establish manager roles. 125 | 126 | **Concurrent Removal** 127 | 128 | Duck creates a group and promotes Penguin to manager access level. Penguin receives the promotion control message after syncing with Duck and then decides to promote Parrot to manager. Parrot goes on to promote some of his friends to manager access level. At this point, without yet knowing about Penguin's promotion of Parrot and Parrot's subsequent actions, Duck choses to demote Penguin so that she is no longer a manager. 129 | 130 | ![Concurrent Removal](/assets/images/250716-concurrent-removal.png) 131 | 132 | > Here we have another Concurrency Diagram, this one depicting a case of "concurrent removal", as described in the paragraph above. 133 | 134 | In this scenario, since Penguin's promotion of Parrot and Duck's demotion of Penguin happened concurrently, Penguin is no longer a manager (since the demotion takes precedence) and any downstream actions taken by Penguin are ignored. This means that Parrot and his friends are no longer managers of the group and any actions they took as managers are invalidated. 135 | 136 | ### Broadcast-Only Contexts 137 | 138 | Another open question which emerged during our work is how to achieve access control in broadcast-based systems; this includes systems which rely exclusively on gossip-based replication strategies. In such cases, we can't control who will receive the data - just like a community radio station can't control who listens to their broadcast. As long as we have strong encryption in place, we can at least control who is able to make sense of the received data. We consider this an open problem and look forward to discuss possible solutions with other researchers. 139 | 140 | ## Informal Correctness Argument 141 | 142 | Ultimately, our access-control design is based on replicating a grow-only set of authenticated immutable operations, since every participant replicates and maintain the full history of operations associated with every group, which is already known to be state-based CRDT. Only correct operations are replicated, by verifying that the causal history of an operation, i.e. the set of all operations that happened-before, indeeds proves that the author had the permission to perform that operation at the time. In the presence of concurrent operations that are replicated later, and given a Resolver strategy such as the one presented before, some correct operations may be later invalidated. Since invalidated operation will never be considered valid again, no matter what additional information is further obtained by replicating new operations, then the set of operations used to compute permissions is a state-based CRDT and is therefore convergent, i.e. given the same set of replicated operations two participants will compute the same permissions. Our design is therefore eventually consistent (see [Byzantine Eventual-Consistency](https://arxiv.org/abs/2012.00472)). 143 | 144 | ## Related Work 145 | 146 | How does our approach compare with other decentralised access control systems? 147 | 148 | ### localfirst/auth 149 | 150 | The TypeScript library [localfirst/auth](https://github.com/local-first-web/auth) uses groups ("teams") to define access-control and encryption boundaries. In their own words: 151 | 152 | > This library provides a Team class, which wraps the signature chain and encapsulates the team's members, devices, and roles. With this object, you can invite new members and manage their permissions. 153 | 154 | Group membership is managed using the operation-based CRDT library [CRDX](https://github.com/local-first-web/auth/tree/main/packages/crdx). Roles can be dynamically added to a group; a member's access-level is inferred from the roles they are assigned. Members assigned the special "admin" role can perform actions which change the group membership (ie. add/remove members, create/assign/remove roles). New members are invited to the group using [Seitan Tokens](https://book.keybase.io/docs/teams/seitan). 155 | 156 | Our approach is similar in how group state is managed using an operation-based CRDT. We were quite influenced by the way in which localfirst/auth allows custom approaches to conflict resolution. We differ in our use of nested-groups and access-levels with associated conditions (rather than roles) to describe a group member's capabilities. Another difference is that we do not use invitation tokens. 157 | 158 | ### Keyhive 159 | 160 | The Ink & Switch project [Keyhive](https://www.inkandswitch.com/keyhive/notebook/) also uses a groups abstraction for their integration of access-control and encryption systems into the [automerge](https://automerge.org/) CRDT library. They describe their approach as using "convergent capabilities", which are intended to be similar to object-capabilities while being partition-tolerant and therefore suitable for use with local-first/offline-first applications and CRDTs in general. Group membership is derived from delegation chains; any member can delegate the capability they hold to another actor. A previously-delegated capability can be revoked by the original delegator or any member with special "manage" authority. 161 | 162 | Our approach uses similar access-levels with attached conditions and also makes uses of nested-groups. We differ in the fact that we only allow "manager" members to add new members to a group (rather than any member being able to delegate their own capability). That said, it's still possible for users to follow a similar delegation approach using nested groups with the [Principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) (POLA). 163 | 164 | Both localfirst/auth and Keyhive use something similar to a [Cryptree](https://ieeexplore.ieee.org/document/4032481) for data encryption. This is different from our approach which can be read about in detail [here](https://p2panda.org/2025/02/24/group-encryption.html). 165 | 166 | ## What's next? 167 | 168 | So far most of the work has gone into how access levels are defined and associated with individuals or groups of actors. The next steps are integrating this system with `p2panda-encryption` and to take on the task of how we associate groups with a set of application data. This final piece of the puzzle is tentatively named `p2panda-spaces`. 169 | -------------------------------------------------------------------------------- /assets/images/deepsea-panda.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | --------------------------------------------------------------------------------