{"id":13438437,"url":"https://github.com/cryptpad/chainpad","last_synced_at":"2025-12-12T03:17:15.590Z","repository":{"id":12197494,"uuid":"14802646","full_name":"cryptpad/chainpad","owner":"cryptpad","description":"Realtime Collaborative Editor Algorithm","archived":false,"fork":false,"pushed_at":"2025-07-30T17:30:38.000Z","size":819,"stargazers_count":408,"open_issues_count":3,"forks_count":29,"subscribers_count":54,"default_branch":"master","last_synced_at":"2025-10-14T00:26:09.509Z","etag":null,"topics":["chainpad","cryptpad"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cryptpad.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2013-11-29T14:00:17.000Z","updated_at":"2025-06-25T15:10:21.000Z","dependencies_parsed_at":"2025-08-21T06:26:21.676Z","dependency_job_id":"91671524-f4f4-43c3-abbd-444606acf7f6","html_url":"https://github.com/cryptpad/chainpad","commit_stats":null,"previous_names":["xwiki-contrib/chainpad"],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/cryptpad/chainpad","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cryptpad%2Fchainpad","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cryptpad%2Fchainpad/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cryptpad%2Fchainpad/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cryptpad%2Fchainpad/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cryptpad","download_url":"https://codeload.github.com/cryptpad/chainpad/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cryptpad%2Fchainpad/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27675376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-12T02:00:06.775Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chainpad","cryptpad"],"created_at":"2024-07-31T03:01:05.536Z","updated_at":"2025-12-12T03:17:15.570Z","avatar_url":"https://github.com/cryptpad.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","others"],"sub_categories":[],"readme":"# ChainPad\n\n[![XWiki labs logo](https://raw.githubusercontent.com/xwiki-labs/xwiki-labs-logo/master/projects/xwikilabs/xwikilabsproject.png \"XWiki labs\")](https://labs.xwiki.com/xwiki/bin/view/Projects/XWikiLabsProject)\n\nChainPad Algorithm is a Realtime Collaborative Editor algorithm based on\n[Nakamoto Blockchains](https://en.bitcoin.it/wiki/Block_chain). This implementation is designed\nto run with a dumb broadcasting server but with minimal effort, the algorithm could be ported to\nfull peer-to-peer. Because the ChainPad server need not be aware of the content which is being\nedited, different types of editors can exist in harmony on the same system.\n\nThis library is currently licensed as LGPL-2.1. Previous versions of this library (v5.2.7 and below) were licensed as AGPL-3.0.\n\n## Getting Started\n\nTo embed ChainPad in your web application, it is recommended that you use the contained node.js\nwebsocket server. You may examine `test.html` to see how to bind the editor to a simple textarea.\n\n### Building\n\nTo compile the code into `chainpad.js` run the following:\n\n    npm install\n    node make\n\nThis will run the tests and concatenate the js files into the resulting `chainpad.js` output file.\n\n## The API\n\n```javascript\nvar chainpad = ChainPad.create(config);\n\n// The bindings are not included in the engine, see below.\nbindToDataTransport(chainpad);\nbindToUserInterface(chainpad);\n\nchainpad.start();\n```\n\n### Configuration Parameters\n\nConfig is an *optional* object parameter which may have one or more of the following contents.\n**NOTE:** it's critical that every ChainPad instance in the session has the same values for these\nparameters.\n\n* **initialState** (string) content to start off the pad with, default is empty-string.\n* **checkpointInterval** (number) the number of patches which should be allowed to go across the\nwire before sending a *checkpoint*. A small number will result in lots of sending of *checkpoints*\nwhich are necessarily large because they send the whole document in the message. A large number\nwill result in more patches to download for a new person joining the pad.\n* **avgSyncMilliseconds** (number) the number of milliseconds to wait before sending to the server\nif there is anything to be sent. Making this number smaller will cause lots of patches to be sent\n(however the number will be limited by the RTT to the server because ChainPad will only keep one\nunacknowledged message on the wire at a time).\n* **validateContent** (function) if specified, this function will be called during each patch and\nreceive the content of the document after the patch, if the document has semantic requirements\nthen this function can validate them if they are broken then the patch will be rejected.\n* **strictCheckpointValidation** (boolean) if true then we will fail any checkpoint which comes\nat an interval which is not in agreement with **checkpointInterval**. Default: *false*.\n* **patchTransformer** (function) if specified, this function will be used for Operational\nTransformation. You have 3 options which are packaged with ChainPad or you can implement your own.\n  * `ChainPad.TextTransformer` (this is default so you need not pass anything) if you're using\n  ChainPad on plain text, you probably want to use this.\n  * `ChainPad.SmartJSONTransformer` if you are using ChainPad to patch JSON data, you probably\n  want this.\n  * `ChainPad.NaiveJSONTransformer` this is effectively just TextTransformer with a\n  validation step to make sure the result is JSON, using this is not recommended.\n* **operationSimplify** (function) This is an optional function which will override the function \n`ChainPad.Operation.simplify` in case you want to use a different one. Simplify is used for \"fixing\"\noperations which remove content and then put back the same content. The default simplify will not\ncreate patches containing strings with single characters from\n[surrogate pairs](https://en.wikipedia.org/wiki/UTF-16#U.2B0000_to_U.2BD7FF_and_U.2BE000_to_U.2BFFFF).\n* **logLevel** (number) If this is zero, none of the normal logs will be printed.\n* **userName** (string) This is a string which will appear at the beginning of all logs in the\nconsole, if multiple ChainPad instances are running at the same time, this will help differentiate\nthem.\n* **noPrune** (boolean) If this is true, history will not be pruned when a checkpoint is encountered.\nCaution: this can end up occupying a lot of memory!\n* **diffFunction** (function) This is a function which takes 2 strings and outputs and array of\nOperations. If unspecified, ChainPad will use the `ChainPad.Diff` which is a smart diff algorithm\nbased on the one used by Fossel. The default diff function will not create patches containing strings\nwith single characters from\n[surrogate pairs](https://en.wikipedia.org/wiki/UTF-16#U.2B0000_to_U.2BD7FF_and_U.2BE000_to_U.2BFFFF).\n* **diffBlockSize** (number) This is an optional number which will inform the default diff function\n`ChainPad.Diff` how big the rolling window should be. Smaller numbers imply more resource usage but\ncommon areas within a pair of documents which are smaller than this number will not be seen.\nThe default is 8.\n* **transformFunction** (function) This parameter has been removed, if you attempt to pass this\nargument ChainPad will fail to start and throw an error.\n\n\n## Binding the ChainPad Session to the Data Transport\n\nTo bind the session to a data transport such as a websocket, you'll need to use the `message()`\nand `onMessage()` methods of the ChainPad session object as follows:\n\n* **message**: Function which takes a String and signals the ChainPad engine of an incoming\nmessage.\n* **onMessage**: Function which takes a function taking a String, called by the ChainPad engine\nwhen a message is to be sent.\n\n```javascript\nvar socket = new WebSocket(\"ws://your.server:port/\");\nsocket.onopen = function(evt) {\n    socket.onmessage = function (evt) { chainpad.message(evt.data); };\n    chainpad.onMessage(function (message, cb) {\n        socket.send(message);\n        // Really the callback should only be called after you are sure the server has the patch.\n        cb();\n    });\n});\n```\n\n### Binding the ChainPad Session to the User Interface\n\n* Register a function to handle *changes* to the document, a change comprises an offset, a number\nof characters to be removed and a number of characters to be inserted. This is the easiest way\nto interact with ChainPad.\n```javascript\nvar myContent = '';\nchainpad.onChange(function (offset, toRemove, toInsert) {\n    myContent = myContent.substring(0, offset) + toInsert + myContent.substring(offset + toRemove);\n});\n```\n\n* Signal to chainpad engine that the user has inserted and/or removed content with the *change()*\nfunction.\n```javascript\nvar chainpad = ChainPad.create();\nchainpad.change(0, 0, \"Hello world\");\nconsole.log(chainpad.getUserDoc()); // -\u003e \"Hello world\"\nchainpad.change(0, 5, \"Goodbye cruel\");\nconsole.log(chainpad.getUserDoc()); // -\u003e \"Goodbye cruel world\"\n```\n\n* Register a function to handle a patch to the document, a patch is a series of insertions and\ndeletions which may must be applied atomically. When applying, the operations in the patch must\nbe applied in *decending* order, from highest index to zero. For more information about Patch,\nsee `chainpad.Patch`.\n```javascript\nchainpad.onPatch(function(patch) {});\n```\n\n* Signal the chainpad engine that the user has inserted and/or removed content to/from the document.\nThe Patch object can be constructed using Patch.create and Operations can be added to the patch\nusing Operation.create and Patch.addOperation(). See **ChainPad Internals** for more information.\n```javascript\nchainpad.patch(patch);\n```\n\n## Block Object\n\nA block object is an internal representation of a message sent on the wire, each block contains a\n**Patch** which itself contains one or more **Operations**. You can access **Blocks** using\n`chainpad.getAuthBlock()` or `chainpad.getBlockForHash()`.\n\n### Fields/Functions\n\n* **hashOf**: Calculated SHA256 of the on-wire representation of this **Block** (as a **Message**).\n* **lastMsgHash**: SHA256 of previous/parent **Block** in the chain. If this is all zeros then this\n**Block** is the initial block.\n* **isCheckpoint**: True if this **Block** represents a *checkpoint*. A *checkpoint* always removes\nall of the content from the document and then adds it back, leaving the document as it was.\n* **getParent**`() -\u003e Block`: Get the parent block of this block, this is fast because the blocks\nare already in the chain in memory.\n* **getContent**`() -\u003e string`: Get the content of the *Authoritative Document* at the point in the\nhistory represented by this block. This takes time because it requires replaying part of the chain.\n* **getPatch**`() -\u003e Patch`: Get a clone of the **Patch** which is contained in this block.\n* **getInversePatch**`() -\u003e Patch`: Get a clone of the inverse **Patch** (the **Patch** which would\nundo the **Patch** provided by **getPatch**). This is calculated when the **Message** comes in to\nChainPad.\n* **equals**`(Block) -\u003e Boolean`: Find out if another **Block** is representing the same underlying\nstructure, since **Blocks** are created whenever one is requested, using triple-equals is not ok.\n\n## Control Functions\n\n### chainpad.start()\n\nStart the engine, this will cause the engine to setup a setInterval to sync back the changes\nreported. Before start() is called, you can still inform chainpad of changes from the network.\n\n### chainpad.abort()\n\nStop the engine, no more messages will be sent, even if there is *Uncommitted Work*.\n\n### chainpad.sync()\n\nFlush the *Uncommitted Work* back to the server, there is no guarantee that the work is actually\ncommitted, just that it has attempted to send it to the server.\n\n### chainpad.getAuthDoc()\n\nAccess the *Authoritative Document*, this is the content which everybody has agreed upon and has\nbeen entered into the chain.\n\n### chainpad.getAuthBlock()\n\nAccess the blockchain block which is at the head of the chain, this block contains the last patch\nwhich made the *Authoritative Document* what it is. This returns a *Block Object*.\n\n### chainpad.getBlockForHash()\n\nAccess the stored block which based on the SHA-256 hash.\n\n### chainpad.getUserDoc()\n\nAccess the document which the engine believes is in the user interface, this is equivilant to\nthe *Authoritative Document* with the *Uncommitted Work* patch applied. Useful for debugging.\nThis should be equivilant to the string representation of the content which is in the UI.\n\n### chainpad.getDepthOfState(state [,minDepth])\n\nDetermine how deep a particular state is in the chain _relative to the current state_. Depth means\nthe number of patches.\n\n```javascript\n// the authDoc is 0 patches deep, by definition\n0 === chainpad.getDepthOfState(chainpad.getAuthDoc());\n\n// if a state never existed in the chain, return value is -1\n-1 === chainpad.getDepthOfState(\"said no one ever\");\n// ^^ assuming the state of the document was never \"said no one ever\"\n```\n\nYou can specify a minimum depth to traverse, skip forward (down) this number of patches before\nstarting to try to match the specified content. This allows you to see multiple times in history\nwhen the content was equal to the specified content. This function will not detect depth of states\nolder than the second checkpoint because this is pruned.\n\n```javascript\n// determine the last time the userDoc was 'pewpew'\nvar firstEncounter = chainpad.getDepthOfState('pewpew');\n\n// check if it was ever previously in that state\nif (chainpad.getDepthOfState('pewpew', firstEncounter) !== -1) {\n    // use this pattern to check if the document state was 'pewpew'\n    // at more than one point in its history\n    console.log(\"the state 'pewpew' exists in the chain in at least two states\");\n}\n```\n\n### chainpad.onSettle()\n\nRegister a handler to be called *once* when there is no *Uncommitted Work* left. This does not\nprove that no patch will be reverted because of a chain fork, but it does verify that the message\nhas hit the server and been acknowledged. The handler will be called only once the next time the\nstate is settled but you can re-register inside of the handler.\n\n### chainpad.getLag()\n\nTells the amount of lag between the last onMessage events being fired by chainpad and the callback.\nSpecifically this returns an object with lag and pending properties. Pending is true if a message\nhas been sent which has not yet been acknowledged. Lag is the amount of time between the previous\nsent message and it's response or if the previously send message has not yet been acknowledged, it\nis the amount of time since it was sent.\n\n# Internals\n\n## Data Types\n\n* **Operation**: An atomic insertion and/or deletion of a string at an offset in the document.\nAn Operation can contain both insertion and deletion and in this case, the deletion will occur\nfirst.\n* **Patch**: A list of **Operations** to be applied to the document in order and a hash of the\ndocument content at the previous state (before the patch is applied).\n* **Message**: Either a request to register the user, an announcement of a user having joined the\ndocument or an encapsulation of a **Patch** to be sent over the wire.\n* **Block**: This is an API encapsulation of the **Message** when it is in the chain.\n\n## Functions\n\n* **apply**`(Patch, Document) -\u003e Document`: This function is fairly self-explanatory, a new document\nis returned which reflects the result of applying the **Patch** to the document. The hash of the\ndocument must be equal to `patch.parentHash`, otherwise an error will result.\n* **merge**`(Patch, Patch) -\u003e Patch`: Merging of two mergable **Patches** yields a **Patch** which\ndoes the equivilant of applying the first **Patch**, then the second. Any two **Operations** which\nact upon overlapping or abutting sections of a document can (and must) be merged. A **Patch**\ncontaining mergable operations in invalid.\n* **invert**`(Patch, Document) -\u003e Patch`: Given a **Patch** and the document to which it could be\napplied, calculate the *inverse* **Patch**, IE: the **Patch** which would un-do the operation of\napplying the original **Patch**.\n* **simplify**`(Patch, Document) -\u003e Patch`: After **merging** of **Patches**, it is possible to end\nup with a **Patch** which contains some redundant or partially redundant **Operations**, a redundant\n**Operation** is one which removes some content from the document and then adds back the very same\ncontent. Since the actual content to be removed is not stored in the **Operation** or **Patch**, the\n**simplify** function exists to find and remove any redundancy in the **Patch**. Any **Patch** which\nis sent over the wire which can still be **simplified** is invalid.\n* **transform**`(Patch, Patch, Document) -\u003e Patch`: This is the traditional Operational Transform\nfunction. This is the only function which can *lose information*, for example if Alice and Bob both\ndelete the same text at the same time, **transform** will merge those two deletions. It is critical\nto note that **transform** is only carried out upon the user's *Uncommitted Work*, never on any\nother user's work so **transform's** decision making cannot possibly lead to de-synchronization.\n\n## Mechanics\n\nInternally the client stores a document known as the *Authoritative Document* this is the last known\nstate of the document which is agreed upon by all of the clients and the *Authoritative Document*\ncan only be changed as a result of an incoming **Patch** from the server. The difference between\nwhat the user sees in their screen and the *Authoritative Document* is represented by a **Patch**\nknown as the *Uncommitted Work*.\n\nWhen the user types in the document, onInsert() and onRemove() are called, creating **Operations**\nwhich are **merged** into the *Uncommitted Work*. As the user adds and removes text, this **Patch**\ngrows. Periodically the engine transmits the *Uncommitted Work* to the server.\nWhen the *Uncommitted Work* is transmitted to the server which will broadcast it out to all clients.\n\nWhen a **Patch** is received from the server, it is first examined for validity and discarded if it\nis obviously invalid. If this **Patch** is rooted in the current *Authoritative Document*, the\n**Patch** is applied to the *Authoritative Document* and the user's *Uncommitted Work* is\n**transformed** by that patch. If the **Patch** happens to be created by the current user, the\ninverse of the **Patch** is merged with the user's *Uncommitted Work*, thus removing the committed\npart.\n\nIf a **Patch** is received which does not root in the *Authoritative Document*, it is stored\nby the client in case it is actually part of the chain but other patches have not yet been filled\nin. If a **Patch** is rooted in a previous state of the document which is not the\n*Authoritative Document*, the patch is stored in case it might be part of a fork of the patch-chain\nwhich proves longer than the chain which the engine currently is aware of.\n\nIn the event that a fork of the chain becomes longer than the currently accepted chain, a\n\"reorganization\" (Bitcoin term) will occur which will cause the *Authoritative Document* to be\nrolled back to a previous state and then rolled forward along the winning chain. In the event of a\n\"reorganization\", work which the user wrote which was committed may be reverted and as the engine\ndetects that it's own patch has been reverted, the content will be re-added to the user's\n*Uncommitted Work* to be pushed to the server next time it is synced.\n\nThe initial startup of the engine, the server is asked for all of the **Messages** to date. These\nare filtered through the engine as with any other incoming **Message** in a process which Bitcoin\ndevelopers will recognize as \"syncing the chain\".\n\nA special type of **Patch** is known as a **Checkpoint** and a checkpoint always removes and re-adds\nall content to the pad. The server may detect checkpoint patches because they are represented on\nthe wire as an array with a 4 as the first element. In order to improve performance of new users\njoining the pad and \"syncing\" the chain, the server may send only the second most recent checkpoint\nand all patches newer than that.\n\n\n## Relationship to Bitcoin\n\nThose with knowledge of Bitcoin will recognize this consensus protocol as inherently a\nNakamoto Chain. Whereas Bitcoin uses blocks, each of which point to the previous block, ChainPad\nuses **Patches** each of which point to the previous state of the document. In the case of ChainPad\nthere is of course no mining or difficulty as security is not intended by this protocol. Obviously\nit would be trivial to generate ever longer side-chains, causing all work to be reverted and\njamming the document.\n\nA more subtle difference is the use of \"lowest hash wins\" as a tie-breaker. Bitcoin very cleverly\ndoes *not* use \"lowest hash wins\" in order to prevent miners from withholding valid blocks with\nparticularly low hashes in order to gain advantages by mining against their own block before anyone\nelse gets a chance. Again since security is not a consideration in this design, \"lowest hash wins\"\nis used in order to expediate convergence in the event of a split.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcryptpad%2Fchainpad","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcryptpad%2Fchainpad","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcryptpad%2Fchainpad/lists"}