{"id":20181700,"url":"https://github.com/nichoth/ssc","last_synced_at":"2025-04-10T05:10:22.990Z","repository":{"id":44437477,"uuid":"313464747","full_name":"nichoth/ssc","owner":"nichoth","description":"Static functions for working with a merkle-dag","archived":false,"fork":false,"pushed_at":"2024-10-11T18:43:31.000Z","size":3325,"stargazers_count":4,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-24T06:22:15.242Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://github.com/nichoth/ssc#readme","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nichoth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2020-11-17T00:40:50.000Z","updated_at":"2022-11-15T11:37:18.000Z","dependencies_parsed_at":"2024-06-21T16:35:37.604Z","dependency_job_id":"0189df16-f0ec-47c0-8da0-68b7a221f769","html_url":"https://github.com/nichoth/ssc","commit_stats":{"total_commits":406,"total_committers":1,"mean_commits":406.0,"dds":0.0,"last_synced_commit":"1e841ee9f56cf244c6684d3f46f7068dc5d178ca"},"previous_names":[],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichoth%2Fssc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichoth%2Fssc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichoth%2Fssc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichoth%2Fssc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nichoth","download_url":"https://codeload.github.com/nichoth/ssc/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161274,"owners_count":21057555,"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","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":[],"created_at":"2024-11-14T02:36:28.896Z","updated_at":"2025-04-10T05:10:22.963Z","avatar_url":"https://github.com/nichoth.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# ssc\n\nStatic functions for working with a merkle-dag. This is the operations in `ssb`, but decoupled from any storage layer.\n\nThis is `ssb` but more boring. `ssc` because c comes after b in the alphabet\n\n\n-------------------------------------------------------\n\n\n## CLI\n\n### install\n\n```\nnpm i -g @nichoth/ssc\n```\n\n### keys\nCreate a new keypair, written to stdout\n\n```\n% ssc keys\n```\n\n```\n{\n  \"public\": \"BCgXk5VVmWd6odnczvUTMuhqxRJSHkA9roas7mtV3BF/Uj2u3/Pr0lINgToXvGjO/b0oZKNh1d1d2Q9CHU3UGB8=\",\n  \"private\": \"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgem10DNzZ3BBXKmfFIELfisCzByOFh6joTn4+O+jE8TqhRANCAAQoF5OVVZlneqHZ3M71EzLoasUSUh5APa6GrO5rVdwRf1I9rt/z69JSDYE6F7xozv29KGSjYdXdXdkPQh1N1Bgf\",\n  \"did\": \"did:key:z82T5VzE8st7yLSEWweKnFHdZieEvE5rD2AevQ7RgtfwjFHjkguyB69KcHKHRx784Ybqnbmg91qCiMML5Sc3Xh34YbhNW\",\n  \"id\": \"@BCgXk5VVmWd6odnczvUTMuhqxRJSHkA9roas7mtV3BF/Uj2u3/Pr0lINgToXvGjO/b0oZKNh1d1d2Q9CHU3UGB8=.ed25519\"\n}\n```\n\n### post\nInput includes JSON keys piped into stdin. It also requires a `--text` option\n\nThis creates a new \"post\" type message with some new keys that we create, with **`null` as the preceding message**:\n\n```bash\n% ssc keys | ssc post --text \"woooo more test\"\n```\n\n```\n{\n  \"previous\": null,\n  \"sequence\": 1,\n  \"author\": \"@BEJZ0YhJYDKDdkWEzBZF+Rf2HYZeBdtwaXmmshQsjGhEkOEbT0PR6eQiWA5tgBv46iYOlmZp2Z+bhox5UzmlgeU=.ed25519\",\n  \"timestamp\": 1650750178099,\n  \"hash\": \"sha256\",\n  \"content\": {\n    \"type\": \"post\",\n    \"text\": \"wooo more test\"\n  },\n  \"signature\": \"CFaOTRL6QHpmfE6QmN1qhzN9Nh5kxJweHZIbkEVh29Rj4CVlf+EdzBAc2TJyB6b7prUgMd2CC79MbRUjncjYeA==\"\n}\n```\n\nTo **set a previous message in this message**, pass in the previous message as the `--prev` option, as a JSON string. This can be used to create a merkle list.\n\n```bash\nssc keys | ssc post --text \"woooo testing again\" --prev=\"$(cat ./test/cli/message-json.json)\"\n```\n\n```js\n{\n  \"previous\": \"%BUfo/WZh51h7eh91PBkdxQLnGfjrkG3ErsDdFmacWIg=.sha256\",\n  \"sequence\": 2,\n  \"author\": \"@BH7K096VMXU1M1aagMP8Sf7s67MaLZlYmgJ+UmxXutBAmQjnf0+/osPshE0EGHjvSiZ74BLj33u4eRDQFsdnz7U=.ed25519\",\n  \"timestamp\": 1650750310248,\n  \"hash\": \"sha256\",\n  \"content\": {\n    \"type\": \"post\",\n    \"text\": \"wooo testing again\"\n  },\n  \"signature\": \"/mNXHtq6XhK4WxuHPyM5B4lal6nx8iCnphPebGiMaTgBoEJ5GH+p1HxBfa3JaPPon3UlPVnbfcY5EzSU9HaDyw==\"\n}\n```\n\nWe pass in the full message because the program takes the hash of the message's JSON for the `previous` field, and also looks at the `sequence` field in the passed in message to determine the `sequence` for this message.\n\n\n### id\nThis takes a message value piped into stdin as input, and returns a sha256 hash, written to stdout.\n\n```bash\ncat test/cli/message-json.json | ssc id\n```\n\n```\n%BUfo/WZh51h7eh91PBkdxQLnGfjrkG3ErsDdFmacWIg=.sha256\n```\n\n\n\n--------------------------------------------------------\n\n\n\n## node\n\n### install\n```\nnpm i -S @nichoth/ssc\n```\n\n\n### examples\nThese demonstrate usage in node js.\n\n#### create keys\n```js\nimport { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\nconst test = require('tape')\nimport ssc from '../index.js'\n\ntest('create keys', t =\u003e {\n    ssc.createKeys().then(alice =\u003e {\n        t.ok(alice.did, 'should return a DID')\n        t.ok(alice.id, 'should return an ID')\n        t.equal(alice.id[0], '@', 'should have the right format ID')\n        t.ok(alice.id.includes('.ed25519'), 'should have the right format ID')\n        t.ok(alice.keys.publicKey, 'should have public key')\n        t.ok(alice.keys.privateKey, 'should have private key')\n        t.ok(alice.keys.publicKey instanceof webcrypto.CryptoKey,\n            'public key should be a CryptoKey')\n        t.ok(alice.keys.privateKey instanceof webcrypto.CryptoKey,\n            'private key should be a CryptoKey')\n        t.end()\n    })\n})\n```\n\n#### sign a string\n```js\nimport { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\nconst test = require('tape')\nimport ssc from '../index.js'\n\nvar keys\nvar sig\ntest('sign a string', function (t) {\n    ssc.createKeys().then(alice =\u003e {\n        t.ok(alice.did, 'should return a DID')\n        keys = alice.keys\n\n        ssc.sign(keys, 'a test message')\n            .then(_sig =\u003e {\n                sig = _sig\n                t.ok(sig, 'should return a signature')\n                t.equal(typeof sig, 'string', 'should return a string')\n                t.end()\n            })\n    })\n})\n\ntest('validate a signature', t =\u003e {\n    ssc.verify(keys, sig, 'a test message')\n        .then(isValid =\u003e {\n            t.equal(isValid, true, 'should say a valid signature is valid')\n            t.end()\n        })\n})\n```\n\n\n#### create a message\n```js\nimport { createRequire } from 'module'\nconst require = createRequire(import.meta.url)\nconst test = require('tape')\nimport ssc from '../index.js'\n\nvar keys\nvar msg\nvar msgTwo\n\ntest('create a message', function (t) {\n    var content = { type: 'test', text: 'woooo' }\n\n    ssc.createKeys()\n        .then(_keys =\u003e {\n            keys = _keys\n            ssc.createMsg(keys.keys, null, content)\n                .then(_msg =\u003e {\n                    msg = _msg\n                    t.equal(msg.author[0], '@',\n                        'should have the correct aughor ID prefix')\n                    t.equal(msg.author.split('.')[1], 'ed25519', \n                        'should have the correct author ID suffix')\n                    t.equal(msg.content.text, 'woooo',\n                        'should have the message text')\n                    t.equal(msg.previous, null,\n                        'should have `null` as previous message')\n                    t.end()\n                })\n        })\n})\n\ntest('create a second message', t =\u003e {\n    var content = { type: 'test', text: 'message two' }\n    ssc.createMsg(keys.keys, msg, content)\n        .then(_msgTwo =\u003e {\n            msgTwo = _msgTwo\n            t.equal(msgTwo.sequence, 2, 'should have the right sequence number')\n            t.equal(msgTwo.previous, ssc.getId(msg),\n                'should have the correct previous message ID')\n            t.end()\n        })\n})\n\ntest('verify a message', t =\u003e {\n    t.ok(ssc.isValidMsg(msg, null, keys.keys), 'should validate the first msg')\n    t.ok(ssc.isValidMsg(msgTwo, msg, keys.keys),\n        'should validate the second msg')\n    t.end()\n})\n\ntest('verify an invalid message', t =\u003e {\n    var badPrevMsg = ssc.createMsg(keys.keys, null,\n        { type: 'test', text: 'ok' })\n    t.equal(ssc.isValidMsg(msgTwo, badPrevMsg, keys.keys), false,\n        'should return that an invalid message is not valid')\n    t.end()\n})\n```\n\n\n#### create a merkle list\n```js\nimport { createRequire } from 'module'\nconst require = createRequire(import.meta.url)\nconst test = require('tape')\nimport ssc from '../index.js'\n\nvar alice\ntest('init', t =\u003e {\n    ssc.createKeys()\n        .then(_alice =\u003e {\n            alice = _alice\n            t.pass('create keys')\n            t.end()\n        })\n})\n\ntest('create a merkle list', async function (t) {\n    t.plan(3)\n    var arr = ['one', 'two', 'three']\n    // this is bad because the async reduce is confusing\n    var list = await arr.reduce(async function (acc, val) {\n        return acc.then(async _acc =\u003e {\n            var prev = (_acc[_acc.length - 1] ?? null)\n            var msg = await ssc.createMsg(alice.keys, prev, {\n                type: 'test',\n                text: val\n            })\n            _acc.push(msg)\n            return _acc\n        })\n    }, Promise.resolve([]))\n\n    t.equal(list.length, 3, 'should create a merkle list')\n    t.equal(list[2].content.text, 'three', 'should have the right msg content')\n\n    var isValidList = list.reduce(function (isValid, msg, i) {\n        var prev = list[i - 1] ?? null\n        return isValid \u0026\u0026 ssc.isValidMsg(msg, prev, alice.keys)\n    }, true)\n\n    t.equal(isValidList, true, 'reduced validation should be ok')\n})\n```\n\n\n#### create an ssb style message\nA message with `key` and `value` properties\n\n```js\nimport { createRequire } from 'module'\nconst require = createRequire(import.meta.url)\nconst test = require('tape')\nimport ssc from '../index.js'\n\nvar alice\ntest('init', t =\u003e {\n    ssc.createKeys()\n        .then(_alice =\u003e {\n            alice = _alice\n            t.pass('create keys')\n            t.end()\n        })\n})\n\ntest('create ssb style posts', async function (t) {\n    t.plan(4)\n    const arr = ['one', 'two', 'three']\n\n    const posts = await arr.reduce(async function (acc, val) {\n        return acc.then(async _acc =\u003e {\n            var prev = (_acc[_acc.length - 1] ?? null)\n            prev = prev === null ? prev : prev.value\n\n            var msg = await ssc.createMsg(alice.keys, prev, {\n                type: 'test',\n                text: val\n            })\n\n            _acc.push({\n                key: ssc.getId(msg, null),\n                value: msg\n            })\n            return _acc\n        })\n    }, Promise.resolve([]))\n\n    t.ok(posts[0].key, 'should have `.key`')\n    t.ok(ssc.verifyObj(alice.keys, null, posts[0].value),\n        'msg should have valid .value')\n    t.equal(posts[0].value.content.text, 'one',\n        'should have the right content at the right key')\n    t.equal(posts[0].key[0], '%', 'should have the right format id')\n})\n```\n\n\n## browser\n\n### install\n```\nnpm i -S @nichoth/ssc\n```\n\n### examples\nUse in a web browser\n\n#### create keys\n```js\nimport Ssc from '@nichoth/ssc/web'\nimport test from 'tape'\n// we use this just for tests. is not necessary for normal use\nimport { ECCKeyStore } from 'keystore-idb/lib/ecc/keystore'\nimport keystore from \"keystore-idb\";\n\nconst ssc = Ssc(keystore)\n\ntest('create keys', t =\u003e {\n    ssc.createKeys(ssc.keyTypes.ECC).then(ks =\u003e {\n        t.ok(ks, 'should return a keystore')\n        t.ok(ks instanceof ECCKeyStore, 'should be an instance of ECC keystore')\n\n        ssc.createKeys().then(keystore =\u003e {\n            t.ok(keystore, 'the keyType parameter is optional')\n            t.end()\n        })\n    })\n})\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnichoth%2Fssc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnichoth%2Fssc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnichoth%2Fssc/lists"}