{"id":18699397,"url":"https://github.com/decentralized-identity/ion-tools","last_synced_at":"2025-04-06T01:09:33.736Z","repository":{"id":40791377,"uuid":"350480452","full_name":"decentralized-identity/ion-tools","owner":"decentralized-identity","description":"Tools and utilities to make working with the ION network and using ION DIDs easy peasy lemon squeezy","archived":false,"fork":false,"pushed_at":"2024-03-19T15:54:10.000Z","size":682,"stargazers_count":139,"open_issues_count":25,"forks_count":36,"subscribers_count":16,"default_branch":"main","last_synced_at":"2024-04-14T04:46:08.137Z","etag":null,"topics":["wg-sidetree"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/decentralized-identity.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}},"created_at":"2021-03-22T20:30:11.000Z","updated_at":"2024-06-18T15:21:49.798Z","dependencies_parsed_at":"2024-06-18T15:21:44.310Z","dependency_job_id":"51e339e9-0051-4cdc-9813-2aca7de682f4","html_url":"https://github.com/decentralized-identity/ion-tools","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentralized-identity%2Fion-tools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentralized-identity%2Fion-tools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentralized-identity%2Fion-tools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decentralized-identity%2Fion-tools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/decentralized-identity","download_url":"https://codeload.github.com/decentralized-identity/ion-tools/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247419860,"owners_count":20936012,"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":["wg-sidetree"],"created_at":"2024-11-07T11:32:55.788Z","updated_at":"2025-04-06T01:09:33.709Z","avatar_url":"https://github.com/decentralized-identity.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ION Tools\n\nThis repo includes tools and utilities to make working with the ION network and using ION DIDs easy for developers integrating DIDs into their apps and services. The packages within are geared toward making interactions with ION maximally accessible for developers, with a primary focus on making their functionality dually available in both client Web and server environments.\n\n## Installation\n```bash\nnpm install @decentralized-identity/ion-tools\n```\n\n\u003e💡 Note: Browser bundles are included in package under `dist/browser`.\n\n\n## Additional Setup\nThis package depends on the [`@noble/ed25519`](https://github.com/paulmillr/noble-ed25519#usage) and [`@noble/secp256k1`](https://github.com/paulmillr/noble-secp256k1#usage) v2, thus additional steps are needed for some environments:\n\n```ts\n// node.js 18 and earlier, needs globalThis.crypto polyfill\nimport { webcrypto } from 'node:crypto';\n// @ts-ignore\nif (!globalThis.crypto) globalThis.crypto = webcrypto;\n\n// React Native needs crypto.getRandomValues polyfill and sha256 for `@noble/secp256k1`\nimport 'react-native-get-random-values';\nimport { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nsecp.etc.hmacSha256Sync = (k, ...m) =\u003e hmac(sha256, k, secp.etc.concatBytes(...m));\nsecp.etc.hmacSha256Async = (k, ...m) =\u003e Promise.resolve(secp.etc.hmacSha256Sync(k, ...m));\n\n// React Native needs crypto.getRandomValues polyfill and sha512 for `@noble/ed25519`\nimport 'react-native-get-random-values';\nimport { sha512 } from '@noble/hashes/sha512';\ned.etc.sha512Sync = (...m) =\u003e sha512(ed.etc.concatBytes(...m));\ned.etc.sha512Async = (...m) =\u003e Promise.resolve(ed.etc.sha512Sync(...m));\n```\n\n\n## Usage\n\n### Create ION DID\n\n```javascript\nimport { anchor, DID, generateKeyPair } from '@decentralized-identity/ion-tools';\nimport { writeFile } from 'fs/promises';\n\n// Generate keys and ION DID\nlet authnKeys = await generateKeyPair();\nlet did = new DID({\n  content: {\n    publicKeys: [\n      {\n        id: 'key-1',\n        type: 'EcdsaSecp256k1VerificationKey2019',\n        publicKeyJwk: authnKeys.publicJwk,\n        purposes: [ 'authentication' ]\n      }\n    ],\n    services: [\n      {\n        id: 'domain-1',\n        type: 'LinkedDomains',\n        serviceEndpoint: 'https://foo.example.com'\n      }\n    ]\n  }\n});\n\n// Generate and publish create request to an ION node\nlet createRequest = await did.generateRequest(0);\nlet anchorResponse = await anchor(createRequest);\n\n// Store the key material and source data of all operations that have been created for the DID\nlet ionOps = await did.getAllOperations();\nawait writeFile('./ion-did-ops-v1.json', JSON.stringify({ ops: ionOps }));\n```\n\n### Update ION DID\n\n```javascript\nimport { anchor, DID, generateKeyPair } from '@decentralized-identity/ion-tools';\nimport { readFile, writeFile } from 'fs/promises';\n\n// Generate new keys\nlet authnKeys2 = await generateKeyPair();\n\n// Instantiate DID using previously saved state\nlet ionOps;\nawait readFile('./ion-did-ops-v1.json', { encoding: 'utf8' })\n  .then((data) =\u003e {\n    ionOps = JSON.parse(data);\n  })\n  .catch((error) =\u003e {\n    console.log(error);\n    process.exit(1);\n  });\nlet did = new DID(ionOps);\n\n// Generate update operation to remove key-1, add key-2, remove some-service-1, and add some-service-2.\nlet updateOperation = await did.generateOperation('update', {\n  removePublicKeys: ['key-1'],\n  addPublicKeys: [\n    {\n      id: 'key-2',\n      type: 'EcdsaSecp256k1VerificationKey2019',\n      publicKeyJwk: authnKeys2.publicJwk,\n      purposes: [ 'authentication' ]\n    }\n  ],\n  removeServices: ['some-service-1'],\n  addServices: [\n    {\n      'id': 'some-service-2',\n      'type': 'SomeServiceType',\n      'serviceEndpoint': 'http://www.example.com'\n    }\n  ]\n});\n\n// Generate and publish update request to an ION node\nlet updateRequest = await did.generateRequest(updateOperation);\nlet anchorRespons = await anchor(updateRequest);\n\n// Store the revised key material and source data for the DID\nionOps = await did.getAllOperations();\nawait writeFile('./ion-did-ops-v2.json', JSON.stringify({ ops: ionOps }), { encoding: 'utf8' });\n```\n\n## Contributing\n```bash\ngit clone https://github.com/decentralized-identity/ion-tools\ncd ion-tools\nnpm install\n```\n\n### Available npm scripts\nrun `npm run \u003cscript_name\u003e` to use any of the scripts listed in the table below:\n\n| script     | description                                                     |\n| ---------- | --------------------------------------------------------------- |\n| `bundle`   | generates  and saves browser bundle to `dist/browser-bundle.js` |\n| `lint`     | runs linter without auto-fixing                                 |\n| `lint:fix` | runs linter and automatically fixes issues                      |\n\n## API Reference\n\nION is a high-level library that wraps the lower-level ION SDK to make interfacing with ION components as simple as possible.\n\n### `new DID()`\n\nThe `ION.DID` class enables you to generate a fully usable ION DID in a single line of code. The class is invoked as follows:\n\n```js\nimport { DID, generateKeyPair } from '@decentralized-identity/ion-tools';\n\nlet authnKeys = await generateKeyPair();\nlet did = new DID({\n  content: {\n    publicKeys: [\n      {\n        id: 'key-1',\n        type: 'EcdsaSecp256k1VerificationKey2019',\n        publicKeyJwk: authnKeys.publicJwk,\n        purposes: [ 'authentication' ]\n      }\n    ],\n    services: [\n      {\n        id: 'domain-1',\n        type: 'LinkedDomains',\n        serviceEndpoint: 'https://foo.example.com'\n      }\n    ]\n  }\n});\n```\n\nThe `DID` class provides the following methods:\n\n#### `getURI()` *async*\n\nThe `getURI` method of the `DID` class is an async function that returns the URI string for the DID the class instance represents. There are two forms of ION DID URI, the Long-Form URI, which can be used instantly without anchoring an ION DID, and the Short-Form URI, which is only resolvable after a DID has been published to the ION network.\n\n```js\nlet did = new DID();\nlet longFormURI = await did.getURI();\nlet shortFormURI = await did.getURI('short');\n```\n\n#### `getSuffix()` *async*\n\nThe `getSuffix` method of the `DID` class is an async function that returns the suffix portion of the DID string for the DID URI the class instance represents.\n\n```js\nlet did = new DID();\n// Example DID URI --\u003e \"did:ion:EiCZws6U61LV3YmvxmOIlt4Ap5RSJdIkb_lJXhuUPqQYBg\"\nlet suffix = await did.getSuffix();\n// Example suffix value --\u003e \"EiCZws6U61LV3YmvxmOIlt4Ap5RSJdIkb_lJXhuUPqQYBg\"\n```\n\n#### `generateOperation(TYPE, CONTENTS, COMMIT)` *async*\n\nThe `generateOperation` method of the `DID` class is an async function that generates `update`, `recover`, and `deactivate` operations based on the current lineage of the DID instance. The method returns an operation entry that is appended to the DID operation lineage managed within the class. The function takes the following arguments:\n\n- `type` - String, *required*: the type of operation you want to generate. Supported operations are `update`, `recover`, `deactivate`.\n- `contents` - Object, *optional*: the content of a given operation type that should be reflected in the new DID state (examples below).\n- `commit` - Boolean, *default: true*: generated operations are automatically appended to the operation chain of the DID instance. If you do not want the operation added to the DID's chain of retained operations, pass an explicit `false` to leave the operation uncommitted.\n\n```js\nlet did = new DID();\nlet authnKeys2 = await generateKeyPair();\n\n// UPDATE EXAMPLE\n\nlet updateOperation = await did.generateOperation('update', {\n  removePublicKeys: [\"key-1\"],\n  addPublicKeys: [\n    {\n      id: 'key-2',\n      type: 'EcdsaSecp256k1VerificationKey2019',\n      publicKeyJwk: authnKeys2.publicJwk,\n      purposes: [ 'authentication' ]\n    }\n  ],\n  removeServices: ['some-service-1'],\n  addServices: [\n    {\n      'id': 'some-service-2',\n      'type': 'SomeServiceType',\n      'serviceEndpoint': 'http://www.example.com'\n    }\n  ]\n});\n\n// RECOVERY EXAMPLE\n\nlet authnKeys3 = await generateKeyPair();\nlet recoverOperation = await did.generateOperation('recover', {\n  removePublicKeys: ['key-2'],\n  addPublicKeys: [\n    {\n      id: 'key-3',\n      type: 'EcdsaSecp256k1VerificationKey2019',\n      publicKeyJwk: authnKeys3.publicJwk,\n      purposes: [ 'authentication' ]\n    }\n  ],\n  removeServices: ['some-service-2'],\n  addServices: [\n    {\n      'id': 'some-service-3',\n      'type': 'SomeServiceType',\n      'serviceEndpoint': 'http://www.example.com'\n    }\n  ]\n});\n\n// DEACTIVATE EXAMPLE\n\nlet deactivateOperation = await did.generateOperation('deactivate');\n```\n\n\n#### `generateRequest()` *async*\n\nThe `generateRequest` method of the `DID` class is an async function that takes either a `number`, in reference to an operation index, or a direct operation payload and returns an operation request object that can be published via an ION node.\n\n```js\nlet did = new DID({ ... });\nlet request = await did.generateRequest(0); // 0 = Create, same as did.#ops[0]\n\nRETURN VALUE:\n{\n  \"type\": \"create\",\n  \"suffixData\": {\n    \"deltaHash\": \"EiDuQtYw8kc30k5nnIHcv870qkTCmCC6a4ghcXRjsgZQpw\",\n    \"recoveryCommitment\": \"EiD9AtwEsDH7p963JPMk2CAzQyu-bT-V49j3_pyw-amSCg\"\n  },\n  \"delta\": {\n    \"updateCommitment\": \"EiDEiLHSm7lcsVmk47wmVNUZASRcv49mSgr4KmW6tG37-w\",\n    \"patches\": [\n      {\n        \"action\": \"replace\",\n        \"document\": {\n          \"publicKeys\": [\n            {\n              \"id\": \"key-1\",\n              \"type\": \"EcdsaSecp256k1VerificationKey2019\",\n              \"publicKeyJwk\": {\n                \"kty\": \"EC\",\n                \"crv\": \"secp256k1\",\n                \"x\": \"bk7ApbCTcBAcRtfLK8bVFMQyhwLb6Rw47KoYnDeOq90\",\n                \"y\": \"T8ElVBOT81E_E5jSg0U1iVqCj--brrjROedXFkehDv8\"\n              },\n              \"purposes\": [\n                \"authentication\"\n              ]\n            }\n          ],\n          \"services\": [\n            {\n              \"id\": \"domain-1\",\n              \"type\": \"LinkedDomains\",\n              \"serviceEndpoint\": \"https://foo.example.com\"\n            }\n          ]\n        }\n      }\n    ]\n  }\n}\n```\n\n#### `getState()` *async*\n\nThe `getState` method of the `DID` class is an async function that returns the exported state of the DID instance, a JSON object composed of the following values:\n\n- `shortForm` - String: Short hash-based version of the DID URI string (only resolvable when anchored).\n- `longForm` - String: Fully self-resolving payload-embedded version of the DID URI string.\n- `ops` - Array: Exported array of all operations that have been included in the state chain of the DID (NOTE: reflection of operations in the network subject to inclusion via broadcast and anchoring).\n\n#### `getAllOperations()` *async*\n\nThe `getAllOperations` method of the `DID` class is an async function that returns all operations that have been created for the DID the class instance represents. This is useful in storing the key material and source data of operation. (e.g. for wallets that need to output a static data representation of a DID's state)\n\n```js\nlet did = new DID({ ... });\nlet operations = await did.getAllOperations();\n```\n\n### `generateKeyPair()` *async*\n\nThe `generateKeyPair` method is an async function that makes generation of keys effortless. The currently supported key types are `secp256k1` and `Ed25519` (more are in the process of being added).\n\nExample:\n\n```js\nlet secp256k1KeyPair = await generateKeyPair('secp256k1');\n\nRETURN VALUE:\n{\n  \"publicJwk\": {\n    \"crv\": \"secp256k1\",\n    \"kty\": \"EC\",\n    \"x\": \"L60Mcg_4uhbAO4RaL1eAJ5CKVqBD8cm6PrBuua4gyGA\",\n    \"y\": \"wwVm2dFCamLZkpGTlRMhdASmPtWuPW9Eg1wLfziwEAs\"\n  },\n  \"privateJwk\": {\n    \"crv\": \"secp256k1\",\n    \"d\": \"kbnyOrsZGaslyeofzDYqMCibWzsRLJb7ZnnQ2rbdJLA\",\n    \"kty\": \"EC\",\n    \"x\": \"L60Mcg_4uhbAO4RaL1eAJ5CKVqBD8cm6PrBuua4gyGA\",\n    \"y\": \"wwVm2dFCamLZkpGTlRMhdASmPtWuPW9Eg1wLfziwEAs\"\n  }\n}\n\nlet Ed25519KeyPair = await generateKeyPair('Ed25519');\n\nRETURN VALUE:\n{\n  \"publicJwk\": {\n    \"crv\": \"Ed25519\",\n    \"x\": \"WfrBXcm2vliqsQtHBr6xIBXZHEtangbUnmxs2KT0VD0\",\n    \"kty\": \"OKP\"\n  },\n  \"privateJwk\": {\n    \"crv\": \"Ed25519\",\n    \"d\": \"CsUUtvDcTM7EmoNuhLyeGQqSBmrpml1bUdjUAVJvj1I\",\n    \"x\": \"WfrBXcm2vliqsQtHBr6xIBXZHEtangbUnmxs2KT0VD0\",\n    \"kty\": \"OKP\"\n  }\n}\n```\n\n### `sign(PARAMS)`\n\nThe `sign` method generates a signed JWS output of a provided payload, and accepts the following parameters:\n\n1. `PARAMS` - Object, *required*: An object for passing the following properties used in the resolution request:\n    - `payload` - *required*: The payload to be signed\n    - `privateJwk` - Object, *required*: The JWK object for the private key that will be used to sign\n    - `header` - Object, *optional*: Additional JWK header values.\n\n```javascript\nimport { generateKeyPair, sign } from '@decentralized-identity/ion-tools';\n\nconst { privateJwk } = await generateKeyPair();\n\nconst jws = await sign({ payload: 'hello world', privateJwk });\n\n// RESULT\n// eyJhbGciOiJFUzI1NksifQ.ImhlbGxvIHdvcmxkIg.NKRJVCjK2...\n```\n\n### `verify(PARAMS)`\n\nThe `verify` method verifies a signed JWS output, and accepts the following parameters:\n\n1. `PARAMS` - Object, *required*: An object for passing the following properties used in the resolution request:\n    - `jws` - String, *required*: The JWS to be verified.\n    - `publicJwk` - Object, *required*: The JWK object for the public key that will be used to verify the JWS.\n\n```javascript\nimport { generateKeyPair, sign, verify } from '@decentralized-identity/ion-tools';\n\nconst { privateJwk, publicJwk } = await generateKeyPair();\n\nconst jws = await sign({ payload: 'hello world', privateJwk });\nconst isLegit = await verify({ jws, publicJwk }); // true/false\n```\n\n#### `resolve(DID_URI, OPTIONS)` *async*\n\nThe `resolve` library method resolves a DID URI string and returns the associated DID resolution response object. The method arguments are as follows:\n\n1. `DID_URI` - URI String, *required*\n2. `OPTIONS` - Object, *optional*: An object for passing the following options used in the resolution request:\n    - `nodeEndpoint` - String, *optional*: URI of the node you desire to contact for resolution. If you are running your own node, use this to pass in your node's resolution endpoint.\n\n```js\nimport { DID, resolve } from '@decentralized-identity/ion-tools';\n\nconst did = new DID();\nconst longFormDID = await did.getURI();\n\nconst didDoc = await resolve(longFormDID);\nconsole.log(didDoc);\n\n/*\nRETURN VALUE: \n{\n  '@context': 'https://w3id.org/did-resolution/v1',\n  didDocument: {\n    id: 'did:ion:EiDERULqAU2ndqI2ha1RRrdJUf6Un0HpVqKhbNMJNUm0Rw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnt9fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUMtUHptOGpPcFh6TGJtbDNWdmNBcUVzOUFXRTQxSEF0WWdLXzd1Qm95R013In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCSzFITU1rVmhoSkxpQ3k3OWFUd0tnam9mRDBOOHViQ19McmU2c2ZxZDRHdyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQkhPU0hxZmFFaERjRDNtcFFLd0Y2aV9WTl9XclNwRmhlQlFyZ3ZCQ3FSVVEifX0',\n    '@context': [ 'https://www.w3.org/ns/did/v1', [Object] ]\n  },\n  didDocumentMetadata: {\n    method: {\n      published: false,\n      recoveryCommitment: 'EiBHOSHqfaEhDcD3mpQKwF6i_VN_WrSpFheBQrgvBCqRUQ',\n      updateCommitment: 'EiC-Pzm8jOpXzLbml3VvcAqEs9AWE41HAtYgK_7uBoyGMw'\n    },\n    equivalentId: [ 'did:ion:EiDERULqAU2ndqI2ha1RRrdJUf6Un0HpVqKhbNMJNUm0Rw' ]\n  }\n}\n*/\n```\n\n### `anchor(REQUEST_BODY, OPTIONS)`\n\nThe `anchor` function is used to submit an ION operation for anchoring with an ION node that implements a challenge and response gated ION node endpoint. The class instantiation arguments are as follows:\n\n1. `REQUEST_BODY` - Object, *required*\n2. `OPTIONS` - Object, *optional*: An object for passing the following options used in the resolution request:\n    - `challengeEndpoint` - URI String, *optional*: URI of the challenge endpoint for the ION node you are submitting to.\n    - `solutionEndpoint` - URI String, *optional*: URI of the solution endpoint for the ION node you are submitting your completed challenge to.\n\n\u003e NOTE: Endpoint URIs will default to `https://beta.ion.msidentity.com` if not supplied\n\n```javascript\nconst did = new DID();\nconst anchorRequest = await did.generateRequest();\nconst respone = await anchor(anchorRequest);\n```\n\n\u003e NOTE: The `requestBody` value above is the JSON representation of the ION operation produced by the DID class's `generateRequest()` function.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecentralized-identity%2Fion-tools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdecentralized-identity%2Fion-tools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecentralized-identity%2Fion-tools/lists"}