GithubHelp home page GithubHelp logo

did-method-key's Introduction

did:key method driver (@digitalbazaar/did-method-key)

Node.js CI Coverage status NPM Version

A DID (Decentralized Identifier) method driver for the did-io library and for standalone use

Table of Contents

Background

See also (related specs):

A did:key method driver for the did-io client library and for standalone use.

The did:key method is used to express public keys in a way that doesn't require a DID Registry of any kind. Its general format is:

did:key:<multibase encoded, multicodec identified, public key>

So, for example, the following DID would be derived from a base-58 encoded ed25519 public key:

did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH

That DID would correspond to the following DID Document:

Example DID Document

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/ed25519-2020/v1",
    "https://w3id.org/security/suites/x25519-2020/v1"
  ],
  "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
  "verificationMethod": [
    {
      "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
      "type": "Ed25519VerificationKey2020",
      "controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
      "publicKeyMultibase": "z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
    }
  ],
  "authentication": [
    "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
  ],
  "assertionMethod": [
    "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
  ],
  "capabilityDelegation": [
    "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
  ],
  "capabilityInvocation": [
    "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
  ],
  "keyAgreement": [
    {
      "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc",
      "type": "X25519KeyAgreementKey2020",
      "controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
      "publicKeyMultibase": "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc"
    }
  ]
}

Security

The keyAgreement key is a Curve25519 public key (suitable for Diffie-Hellman key exchange) that is deterministically derived from the source Ed25519 key, using ed2curve-js.

Note that this derived key is optional -- there's at least one proof that this is safe to do.

Install

Requires Node.js 16+

To install from npm:

npm install --save @digitalbazaar/did-method-key

To install locally (for development):

git clone https://github.com/digitalbazaar/did-method-key.git
cd did-method-key
npm install

Usage

use()

This method registers a multibase-multikey header and a multibase-multikey deserializer and configures a driver to use a multibase-multikey deserializer to handle data using that multibase-multikey header.

import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey';
import {driver} from '@digitalbazaar/did-method-key';

const didKeyDriverMultikey = driver();

didKeyDriverMultikey.use({
  multibaseMultikeyHeader: 'zDna',
  fromMultibase: EcdsaMultikey.from
});

createFromMultibase()

This utility function can be used to adapt legacy verification suites such as Ed25519VerificationSuite2018 to work properly with fromMultibase() calls in DidKeyDriver.

import {driver} from '@digitalbazaar/did-method-key';
import {Ed25519VerificationKey2018} from
  '@digitalbazaar/ed25519-verification-key-2018';

const didKeyDriver2018 = driver();

didKeyDriver2018.use({
  multibaseMultikeyHeader: header,
  fromMultibase: createFromMultibase(Ed25519VerificationKey2018)
});

fromKeyPair()

To generate a new key and get its corresponding did:key method DID Document from a verification keypair.

import {driver} from '@digitalbazaar/did-method-key';
import {Ed25519VerificationKey2020} from
  '@digitalbazaar/ed25519-verification-key-2020';

const didKeyDriver = driver();

didKeyDriver.use({
  multibaseMultikeyHeader: 'z6Mk',
  fromMultibase: Ed25519VerificationKey2020.from
});

const publicKeyMultibase = 'z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH';
const verificationKeyPair = await Ed25519VerificationKey2020.from({
  publicKeyMultibase
});
// or perhaps:
// const verificationKeyPair = await Ed25519VerificationKey2020.generate();

const {didDocument, keyPairs, methodFor} = await didKeyDriver.fromKeyPair({
  verificationKeyPair
});

// print the DID Document above
console.log(JSON.stringify(didDocument, null, 2));

// keyPairs will be set like so =>
Map(2) {
  'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T' => Ed25519VerificationKey2020 {
    id: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T',
    controller: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T',
    revoked: undefined,
    type: 'Ed25519VerificationKey2020',
    publicKeyMultibase: 'z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T',
    privateKeyMultibase: undefined
  },
  'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW' => X25519KeyAgreementKey2020 {
    id: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW',
    controller: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T',
    revoked: undefined,
    type: 'X25519KeyAgreementKey2020',
    publicKeyMultibase: 'z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW',
    privateKeyMultibase: undefined
  }
}

methodFor is a convenience function that returns a key pair instance that contains publicKeyMultibase for given purpose. For example, a verification key (containing a signer() and verifier() functions) are frequently useful for jsonld-signatures or vc-js operations. After generating a new did:key DID, you can do:

// For signing Verifiable Credentials
const assertionKeyPair = methodFor({purpose: 'assertionMethod'});
// For Authorization Capabilities (zCaps)
const invocationKeyPair = methodFor({purpose: 'capabilityInvocation'});
// For Encryption using `@digitalbazaar/minimal-cipher`
const keyAgreementPair = methodFor({purpose: 'keyAgreement'});

Note that methodFor returns a key pair that contains a publicKeyMultibase. This makes it useful for verifying and encrypting operations.

publicKeyToDidDoc()

If you already have an Ed25519VerificationKey2020 public key object (as an LDKeyPair instance, or a plain key description object), you can turn it into a DID Document:

const {didDocument} = await didKeyDriver.publicKeyToDidDoc({publicKeyDescription});

get()

Getting a full DID Document from a did:key DID

To get a DID Document for an existing did:key DID:

const did = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T';
const didDocument = await didKeyDriver.get({did});

(Results in the example DID Doc above).

Getting the DID Document from key id

You can also use a .get() to retrieve an individual key, if you know its id already (this is useful for constructing documentLoaders for JSON-LD Signature libs, and the resulting key does include the appropriate @context).

const verificationKeyId = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T';

const keyAgreementKeyId = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW';
const didDocument = await didKeyDriver.get({url: verificationKeyId});
// OR
const didDocument = await didKeyDriver.get({url: keyAgreementKeyId});

// DID Document ->
console.log(JSON.stringify(didDocument, null, 2));

publicMethodFor()

Often, you have just a did:key DID, and you need to get a key for a particular purpose from it, such as an assertionMethod key to verify a VC signature, or a keyAgreement key to encrypt a document for that DID's controller.

For that purpose, you can use a combination of get() and publicMethodFor:

// Start with the DID
const didDocument = await didKeyDriver.get({did});
// This lets you use `publicMethodFor()` to get a key for a specific purpose
const keyAgreementMethod = didKeyDriver.publicMethodFor({
  didDocument, purpose: 'keyAgreement'
});
const assertionMethod = didKeyDriver.publicMethodFor({
  didDocument, purpose: 'assertionMethod'
});

// If you have a known key type, for example, `Ed25519VerificationKey2020`,
// you can create key instances which allow you to get access to a
// `verify()` function.
const assertionMethodPublicKey = await Ed25519VerificationKey2020.from(
  assertionMethod);
const {verify} = assertionMethodPublicKey.verifier();

publicMethodFor will throw an error if no key is found for a given purpose.

Contribute

See the contribute file!

PRs accepted.

If editing the Readme, please conform to the standard-readme specification.

Commercial Support

Commercial support for this library is available upon request from Digital Bazaar: [email protected]

License

New BSD License (3-clause) © Digital Bazaar

did-method-key's People

Contributors

davidlehn avatar dlongley avatar dmitrizagidulin avatar jsassassin avatar mattcollier avatar merlinstardust avatar msporny avatar or13 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

did-method-key's Issues

`id` of publicKey is the same as the subject `id`

In the following example DID Document:

{
  "@context": "https://w3id.org/did/v1",
  "id": "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14",
  "assertionMethod": [
    "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14"
  ],
  "authentication": [
    "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14"
  ],
  "capabilityDelegation": [
    "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14"
  ],
  "capabilityInvocation": [
    "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14"
  ],
  "keyAgreement": [
    {
      "id": "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14#zC3hrJ5iJGYJ8FmaChkJf66ttRGkMh6gtpZuqM2hc9dEcK",
      "type": "X25519KeyAgreementKey2019",
      "controller": "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14",
      "publicKeyBase58": "4D8aiXV5AYqpfXEFLp67wjD82UYjXzHKg2YKBcokWzW7"
    }
  ],
  "publicKey": [
    {
      "id": "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14",
      "type": "Ed25519VerificationKey2018",
      "controller": "did:key:z6MknxumoAwcGrjM3M4N8bWSTDZLwRgMxChytWqqWe2E4d14",
      "publicKeyBase58": "9WejCvhAwKEsvrDfT2Ybc81M7rQWYKTdCVvugN4D9QDg"
    }
  ]
}

... the id of the public Ed25519VerificationKey2018 is the same as the id of the subject of the DID Document. Isn't the idea that id values should be unique and refer to one specific thing?

Driver instance should support multiple "verification suites" (or key types)

Ideally, verification suites would be renamed to key types -- and a driver instance could support N-many of these. Otherwise N-many driver instances must be created and mapped and managed externally.

This is particularly true for using get() -- where systems may want a driver that can resolve multiple different did:key types. The driver should support detecting the multikey header used and use the appropriate key type to import and generate the DID document.

methodFor helper function no longer accessing public / private key pair

Previously when I used this library the methodFor function would act as a

convenience function that returns the public/private key pair instance for a given purpose (authentication, assertionMethod, keyAgreement, etc). - See current code comment here

It no longer appears to do this.

Is there now a different way to use this library to get the requisite private keys (with id and controller) required for the vc.js library?

multibase multikey header explanation?

So I understand that the multibaseMultikeyHeader is the first 4 characters of a multibase public key and that the first character is the algorithm identifier as defined by this spec.

Example: This public key from the docs z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH has a header of z6Mk

Where do the other 3 characters come from? Are these 3 characters where I could use a mnemonic for hierarchical deterministic wallets?

Support Node 12

Not compiling in Node 12:

npm i

> [email protected] install ~/code/_DigitalBazaar/uni-resolver-did-v1-driver/node_modules/equihash
> node-gyp rebuild

  CXX(target) Release/obj.target/khovratovich/lib/khovratovich/addon.o
../lib/khovratovich/addon.cc:28:11: error: no member named 'Handle' in namespace 'v8'

A KeyAgreement key is required to generate a DID document, even though KeyAgreement is an optional field

I am attempting to use this library with https://github.com/digitalbazaar/ecdsa-secp256k1-verification-key-2019.

This requires a few tweaks like adding the fromFingerprint method. But after I get round that I run into an error when running:

const secpDidController = await didKeyDriverSecp.fromKeyPair({
    verificationKeyPair: secpKeyPair
});

The error is Error: Cannot derive key agreement key from verification key type "EcdsaSecp256k1VerificationKey2019".
After digging into the code it appears that the error comes from here

default: {

My question is why is this an error, can't you just generate the DID document without a key agreement key?

I added a specific case for EcdsaSecp256k1VerificationKey2019 with just a break, similar to the Multikey case and the DID doc generated fine.

TypeError: Cannot read property 'modes' of undefined

Now using node v12.16.0 (npm v6.14.4)

[email protected] /Users/orie/Desktop/Code/Transmute/did.actor
└── [email protected] 
const didKeyDriver = require("did-method-key").driver();
 FAIL  __tests__/make-degree.spec.js
  ● Test suite failed to run

    /Users/orie/Desktop/Code/Transmute/did.actor/node_modules/x25519-key-pair/node_modules/node-forge/lib/aes.js:1
    TypeError: Cannot read property 'modes' of undefined
    at Object.<anonymous> (node_modules/x25519-key-pair/node_modules/node-forge/lib/aes.js:254:43)

Should this library throw errors from the `did:key` spec?

The did:key spec now contains multiple normative statements about did:key. While we do throw many errors related to those statements, we don't throw them in a way that conforms to the spec.

I'm proposing that checks that can be made at the general level of any did:key be made here and then checks related to key material be made in their respective libraries.

That means the following checks need to be made in this library:

  • MUST raise invalidDid error if scheme is not did (NOTE: this check might be needed elsewhere)
  • MUST raise invalidDid error if method is not key (this check is did:key specific)
  • MUST raise invalidDid if version is not convertible to a positive integer value.
  • MUST raise invalidDid if the multibaseValue does not begin with the letter z.

The errors might be specific to the didResolver:

  • If "didDocument.id" is not a valid DID, an invalidDid error MUST be raised
  • If verificationMethod.id is not a valid DID URL, an invalidDidUrl error MUST be raised.
  • For Signature Verification Methods, if options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not Multikey, JsonWebKey2020, or Ed25519VerificationKey2020, an invalidPublicKeyType error MUST be raised.
  • For Encryption Verification Methods, if options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not Multikey, JsonWebKey2020, or X25519KeyAgreementKey2020, an invalidPublicKeyType error MUST be raised.
  • If verificationMethod.controller is not a valid DID, an invalidDid error MUST be raised.

We can throw an error like this:

export class InvalidDid extends Error {
  constructor(message) {
    super(message);
    this.name = 'InvalidDidError';
    this.code = 'invalidDid';
  }
}

export class InvalidDidUrl extends Error {
  constructor(message) {
    super(message);
    this.name = 'InvalidDidUrlError';
    this.code = 'invalidDidUrl';
  }
}

Related:
digitalbazaar/ed25519-verification-key-2020#18
w3c-ccg/did-method-key#60

Library needs to implement Options from `did:key` v0.7 spec

The did:key 0.7 spec introduces some options (with defaults) in the document creation algorithm.

The options are:

  • publicKeyFormat (should be multiKey or Ed25519VerificationKey2020)
  • enableExperimentalPublicKeyTypes (defaults to false)
  • defaultContext (defaults to [https://www.w3.org/ns/did/v1])
  • enableEncryptionKeyDerivation (should be true to enable keyAgreementKey )

These options should be passed in on didDocument creation regardless of if creating from from privateKey or publicKey seed.

These options also have some validation rules behind them:

  • If publicKeyFormat is not known to the implementation, an unsupportedPublicKeyType error MUST be raised.
  • If options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not Multikey, JsonWebKey2020, or Ed25519VerificationKey2020, an invalidPublicKeyType error MUST be raised.
  • If options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not Multikey, JsonWebKey2020, or X25519KeyAgreementKey2020, an invalidPublicKeyType error MUST be raised.

Rename repo (make room for did-method-key spec repo)

I propose we rename this repo to something like did-method-key-driver or did-method-key-js. And open the current did-method-key name for the repo that contains the DID Method Definition spec (so it can be submitted to the CCG).

Add support to handle `keyAgreement` for P-256 multikey

P-256 multikeys should have a keyAgreement section in DID docs so they can be used as key agreement keys if that's the desirable use for the key (as opposed to for signature verification via the other typical verification relationships).

HD Keys

Wondering if there is way to use did:key to encode a root public key and an hd path...

This would let you use the same entropy source for many different did:keys... At resolve time, each did:key would be decoded, and then the hd path would be applied... bonus points if you can ask for ethereum or bitcoin addresses via the hd path... this would make did key a weird cousin of did:ethr and did:btcr...

unable to sign a did key doc with a did key...


  console.log tests/__fixtures__/documentLoader.js:23
    did:key:z6MkfBth5EfE8HgwtA9YfGgBCqqerTaeSKPPjy5aFHumngqj

  console.log tests/__fixtures__/documentLoader.js:23
    https://w3id.org/did/v1

 FAIL  tests/did-method-spec-conformance.spec.js
  did core spec > method conformance
    ✕ did:key (313ms)

  ● did core spec > method conformance › did:key

    The property "assertionMethod" in the input was not defined in the context.

      at Object.<anonymous>.module.exports.info (node_modules/jsonld-signatures/lib/expansionMap.js:9:11)
      at _expandObject (node_modules/jsonld/lib/expand.js:439:26)
      at Object.<anonymous>.api.expand (node_modules/jsonld/lib/expand.js:246:9)

https://w3id.org/did/v1 does not define assertionMethod, despite did:key relying on it.

Add support for `JsonWebKey2020`

This library is nearly there with JsonWebKey2020 support it just needs:

  1. to remove the representationNotSupported check for publicKeyFormat JsonWebKey2020`
  2. Add logic to convert an Ed25519 2020 key to JsonWebKey2020
  3. Add tests to ensure resulting didDocument is ok

I'm facing issue with cannot import and use it.

error: Error: While trying to resolve module @digitalbazaar/did-method-key from file C:\Users\***\Work\***\app\lib\did.ts, the package C:\Users\***\Work\***\\node_modules\@digitalbazaar\did-method-key\package.json was s
uccessfully found. However, this package itself specifies a main module field that could not be resolved (C:\Users\***\\Work\***\\node_modules\@digitalbazaar\did-method-key\index. Indeed, none of these files exist:

  • C:\Users*\Work*\node_modules@digitalbazaar\did-method-key\index(.native|.android.jsx|.native.jsx|.jsx|.android.js|.native.js|.js|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx|.android.cjs|.native.cjs|.cjs|.
  • C:\Users*\Work*\node_modules@digitalbazaar\did-method-key\index\index(.native|.android.jsx|.native.jsx|.jsx|.android.js|.native.js|.js|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx|.android.cjs|.native.cjs|
    .cjs|.android.json|.native.json|.json)

Support for NIST Curves

https://digitalbazaar.github.io/did-method-key/#the-did-key-format

{
  "crv": "P-256",
  "ext": true,
  "key_ops": [
    "verify"
  ],
  "kty": "EC",
  "x": "FJ6hTzeQwP93G3gE1B-2M5nlOHQl-kWw7uhQ-oNWop8",
  "y": "96FI6LJJ5dOM9ew4whZqfFOTFiJ81Ejz4HqEYFymvJE"
}

Feels like this should be pretty easy to accomplish... x and y need to be converted to a single buffer: Buffer.concat([base64url.toBuffer(x),base64url.toBuffer(y)]) ?

and we need a new entry here: https://github.com/multiformats/multicodec/blob/545181e8038b2112e92636cbc5395ac3fabb855c/table.csv#L73

for the multicodec representation of p-256 public key.

This would allow did:key to be used with nonextractable browser keys, and would unlock a lot of other functionality, including in browser JOSE support...

Determine how to specify outputting a different public key format

We have API changes to properly multiple key types (as identified by multibase-multikey headers) in #55. We should figure out how we want to layer using different public key formats on top of it -- or decide that it's not appropriate to do that at all and either:

  1. A different driver instance should be created with different fromMultibase plugins, OR
  2. Presume that cryptosuites should allow all the different formats they accept and perform the necessary conversions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.