{"id":14483674,"url":"https://github.com/ceramicnetwork/js-dag-jose","last_synced_at":"2025-04-09T15:06:07.964Z","repository":{"id":41201457,"uuid":"260893761","full_name":"ceramicnetwork/js-dag-jose","owner":"ceramicnetwork","description":null,"archived":false,"fork":false,"pushed_at":"2024-07-22T19:51:09.000Z","size":1714,"stargazers_count":50,"open_issues_count":4,"forks_count":11,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-09T15:06:03.533Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ceramicnetwork.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","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-05-03T11:19:21.000Z","updated_at":"2025-02-01T03:18:17.000Z","dependencies_parsed_at":"2024-06-18T13:56:01.568Z","dependency_job_id":"aeae768b-5530-4c55-a0b7-b69d44f83683","html_url":"https://github.com/ceramicnetwork/js-dag-jose","commit_stats":{"total_commits":41,"total_committers":10,"mean_commits":4.1,"dds":0.7560975609756098,"last_synced_commit":"c60b4b17ec346f1046ebbb40d70dd8a0ba01f4f0"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fjs-dag-jose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fjs-dag-jose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fjs-dag-jose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fjs-dag-jose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ceramicnetwork","download_url":"https://codeload.github.com/ceramicnetwork/js-dag-jose/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055284,"owners_count":21040157,"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-09-03T00:01:58.756Z","updated_at":"2025-04-09T15:06:07.943Z","avatar_url":"https://github.com/ceramicnetwork.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# dag-jose\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fceramicnetwork%2Fjs-dag-jose.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fceramicnetwork%2Fjs-dag-jose?ref=badge_shield)\n\nThis library provides a TypeScript implementation of the DAG-JOSE codec for IPLD.\n\nIt supports the new [multiformats](https://github.com/multiformats/js-multiformats) library in order to be compatible with both the current and future js-ipfs implementations.\n\nTo create and work with DAG-JOSE compatible JOSE objects we recommend using the [dag-jose-utils](https://github.com/ceramicnetwork/js-dag-jose-utils) library.\n\n* [JWS Signing Usage](#jws-signing-usage)\n* [JWE Encryption Usage](#jwe-encryption-usage)\n  * [Symmetric encryption](#symmetric-encryption)\n  * [Asymmetric encryption](#asymmetric-encryption)\n* [Maintainer](#maintainer)\n* [License](#license)\n\n## JWS Signing Usage\n\n_The following example is available in complete form in [example-signing-ipld.mjs](./example-signing-ipld.mjs)._\n\nFor independent usage as an IPLD codec:\n\n```js\nimport * as Block from 'multiformats/block'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport * as dagCbor from '@ipld/dag-cbor' // for decoding the signed payload\nimport * as dagJose from 'dag-jose'\n```\n\nImport additional libraries for JWS handling:\n\n```js\n// JWT \u0026 utilities\nimport {\n  ES256KSigner,\n  createJWS,\n  verifyJWS\n} from 'did-jwt'\nimport {\n  encodePayload,\n  toJWSPayload,\n  toJWSStrings\n} from 'dag-jose-utils'\n```\n\nGiven a keypair:\n\n```js\nconst pubkey = '03fdd57adec3d438ea237fe46b33ee1e016eda6b585c3e27ea66686c2ea5358479'\nconst privkey = '278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f'\n```\n\nCreate a signed envelope block:\n\n```js\nconst signer = ES256KSigner(privkey)\n// arbitrary data to DAG-CBOR encode, we get a:\n// { cid:CID, linkedBlock: Uint8Array }\nconst payloadBlock = await encodePayload(payload)\n// sign the CID as a JWS using our signer\nconst jws = await createJWS(toJWSPayload(payloadBlock), signer)\n\n// createJWS gives us a compact string form JWS, DAG-JOSE will accept both the\n// compact and general (object) form but a round-trip decode will always\n// result in the general form. If we want need `jws` to be isometric regardless\n// of whether it has been round-tripped through DAG-JOSE or straight out of\n// `createJWS()` we can call `toGeneral()` to ensure it is always in the\n// general form.\n// jws = dagJose.toGeneral(jws)\n\n// encode as a DagJWS IPLD block\nconst jwsBlock = await Block.encode({ value: jws, codec: dagJose, hasher: sha256 })\n\n// we now have two blocks, a signed envelope and a payload\n// DagJWS envelope:\n//  - CID: jwsBlock.cid\n//  - Bytes: jwsBlock.bytes\n// Payload:\n//  - CID: payloadBlock.cid\n//  - Bytes: payloadBlock.linkedBlock\n```\n\nGiven a DagJWS envelope block CID, load its bytes, verify the signature and load the linked payload block:\n\n```js\n// validate cid matches bytes and decode dag-jose JWS\nconst jwsBlock = await Block.create({ bytes, cid, codec: dagJose, hasher: sha256 })\nconst jwsStrings = toJWSStrings(jwsBlock.value)\n// verify the signatures found in the block against our pubkey\nfor (const jws of jwsStrings) {\n  const verifiedKey = verifyJWS(jws, [{ publicKeyHex: pubkey }]) // will throw if it doesn't verify\n  console.log(`Verified JWS envelope \\u001b[32m${cid}\\u001b[39m with public key:\\n\\t${verifiedKey.publicKeyHex}`)\n}\n\nconst payloadCid = jwsBlock.value.link\n// `store.get()` represents a block store, where `get(cid:string):Uint8Array`,\n// in this example case it's simply a `Map` but it could be any method of\n// fetching bytes for a CID\nconst payloadBytes = store.get(payloadCid.toString())\n// validate payloadCid matches bytes and decode dag-cbor payload\nconst payloadBlock = await Block.create({ bytes: payloadBytes, cid: payloadCid, codec: dagCbor, hasher: sha256 })\n\n// The signed and verified payload is available in `payloadBlock.value`\n```\n\n## JWE Encryption Usage\n\nWhen using DAG-JOSE (for JWE or JWS) with js-IPFS, you will need to convert it from a raw multiformats style codec to a legacy IPLD codec using [blockcodec-to-ipld-format](https://github.com/ipld/js-blockcodec-to-ipld-format).\n\n_The following example is available in complete form in [example-ipfs.mjs](./example-ipfs.mjs)._\n\n_A plain IPLD (without IPFS, for cases where you are managing the block store) version is available in [example-ipld.mjs](./example-ipld.mjs)._\n\n```js\n// IPLD \u0026 IPFS\nimport { create as createIpfs } from 'ipfs'\nimport { convert as toLegacyIpld } from 'blockcodec-to-ipld-format'\n\nimport * as dagJose from 'dag-jose'\n```\n\n```js\n// JWT \u0026 utilities\nimport {\n  xc20pDirEncrypter,\n  xc20pDirDecrypter,\n  x25519Encrypter,\n  x25519Decrypter,\n  decryptJWE,\n  createJWE\n} from 'did-jwt'\nimport {\n  decodeCleartext,\n  prepareCleartext\n} from 'dag-jose-utils'\n```\n\n```js\n// Miscellaneous crypto libraries to support the examples\nimport { randomBytes } from '@stablelib/random'\nimport { generateKeyPairFromSeed } from '@stablelib/x25519'\n```\n\nSet up js-IPFS:\n\n```js\nconst dagJoseIpldFormat = toLegacyIpld(dagJose)\n\n// Async setup tasks\nasync function setup () {\n  console.log('Starting IPFS ...')\n  // Instantiate an IPFS node, that knows how to deal with DAG-JOSE blocks\n  ipfs = await createIpfs({ ipld: { formats: [dagJoseIpldFormat] } })\n}\n```\n\n### Symmetric encryption\n\nEncrypt and store a payload using a secret key:\n\n```js\nconst storeEncrypted = async (payload, key) =\u003e {\n  const dirEncrypter = xc20pDirEncrypter(key)\n  // prepares a cleartext object to be encrypted in a JWE\n  const cleartext = await prepareCleartext(payload)\n  // encrypt into JWE container layout using secret key\n  const jwe = await createJWE(cleartext, [dirEncrypter])\n  // let IPFS store the bytes using the DAG-JOSE codec and return a CID\n  const cid = await ipfs.dag.put(jwe, { format: dagJoseIpldFormat.codec, hashAlg: 'sha2-256' })\n  console.log(`Encrypted block CID: \\u001b[32m${cid}\\u001b[39m`)\n  return cid\n}\n```\n\nLoad an encrypted block from a CID and decrypt the payload using a secret key:\n\n```js\nconst loadEncrypted = async (cid, key) =\u003e {\n  const dirDecrypter = xc20pDirDecrypter(key)\n  const retrieved = await ipfs.dag.get(cid)\n  const decryptedData = await decryptJWE(retrieved.value, dirDecrypter)\n  return decodeCleartext(decryptedData)\n}\n```\n\nCreate a key, encrypt and store a block, then load and decrypt it:\n\n```js\nconst key = randomBytes(32)\nconst secretz = { my: 'secret message' }\nconsole.log('Encrypting and storing secret:\\u001b[1m', secretz, '\\u001b[22m')\nconst cid = await storeEncrypted(secretz, key)\nconst decoded = await loadEncrypted(cid, key)\nconsole.log('Loaded and decrypted block content:\\u001b[1m', decoded, '\\u001b[22m')\n```\n\n### Asymmetric encryption\n\nEncrypt and store a payload using a public key:\n\n```js\nconst storeEncrypted = async (payload, pubkey) =\u003e {\n  const asymEncrypter = x25519Encrypter(pubkey)\n  // prepares a cleartext object to be encrypted in a JWE\n  const cleartext = await prepareCleartext(payload)\n  // encrypt into JWE container layout using public key\n  const jwe = await createJWE(cleartext, [asymEncrypter])\n  // let IPFS store the bytes using the DAG-JOSE codec and return a CID\n  const cid = await ipfs.dag.put(jwe, { format: dagJoseIpldFormat.codec, hashAlg: 'sha2-256' })\n  console.log(`Encrypted block CID: \\u001b[32m${cid}\\u001b[39m`)\n  return cid\n}\n```\n\nLoad an encrypted block from a CID and decrypt the payload using a secret key:\n\n```js\nconst loadEncrypted = async (cid, privkey) =\u003e {\n  const asymDecrypter = x25519Decrypter(privkey)\n  // decode the DAG-JOSE envelope\n  const retrieved = await ipfs.dag.get(cid)\n  const decryptedData = await decryptJWE(retrieved.value, asymDecrypter)\n  return decodeCleartext(decryptedData)\n}\n```\n\nCreate a key pair, encrypt and store a block using the public key, then load and decrypt it using the private key:\n\n```js\nconst privkey = randomBytes(32)\n// generate a public key from the existing private key\nconst pubkey = generateKeyPairFromSeed(privkey).publicKey\nconst secretz = { my: 'secret message' }\nconsole.log('Encrypting and storing secret with public key:\\u001b[1m', secretz, '\\u001b[22m')\nconst cid = await storeEncrypted(secretz, pubkey)\nconst decoded = await loadEncrypted(cid, privkey)\nconsole.log('Loaded and decrypted block content with private key:\\u001b[1m', decoded, '\\u001b[22m')\n```\n\n#### Encrypt and decrypt using other jose library\nThe `did-jwt` library only supports `x25519` key exchange and `XChacha20Poly1305`. If you want to use the `dag-jose` codec with other less secure algorithms you can encrypt another library and put the resulting JWE into the dag. Below is an example using the [jose](https://github.com/panva/jose/) library.\n\n```js\nconst jwk = jose.JWK.generateSync('oct', 256)\nconst cleartext = prepareCleartext({ my: 'secret message' })\n\n// encrypt and put into ipfs\nconst jwe = jose.JWE.encrypt.flattened(cleartext, jwk, { alg: 'dir', enc: 'A128CBC-HS256' })\nconst cid = await ipfs.dag.put(jwe, { format: format.codec, hashAlg: 'sha2-256' })\n\n// retreive and decrypt object\nconst retrived = await ipfs.dag.get(cid)\nconst decryptedData = jose.JWE.decrypt(retrived, jwk)\nconsole.log(decodeCleartext(decryptedData))\n// output:\n// { my: 'secret message' }\n```\n\n## Maintainer\n\n[Joel Thorstensson](https://github.com/oed)\n\n## License\n\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fceramicnetwork%2Fjs-dag-jose.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fceramicnetwork%2Fjs-dag-jose?ref=badge_large)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceramicnetwork%2Fjs-dag-jose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fceramicnetwork%2Fjs-dag-jose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceramicnetwork%2Fjs-dag-jose/lists"}