npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@transmute/did-transmute

v0.0.4

Published

An opinionated typescript library for decentralized identifiers and verifiable credentials.

Downloads

337

Readme

did:transmute

CI Branches Functions Lines Statements Jest coverage

Questions? Contact Transmute

This repository contains experimental implementations of various DID Methods.

A primary use case for this is "DID Method Projection", in which an existing identifier space such as all JWK or JWT, is projected into a Decentralizied Identifier space, such as did:jwk: or did:jwt.

This is accomplished by defining resolution and dereferencing for the DID URLs under the "projection method".

Composition

%%{
  init: {
    'flowchart': { 'curve': 'monotoneX' },
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a2d4c',
      'primaryTextColor': '#565a7c',
      'nodeBorder': '#565a7c',
      'edgeLabelBackground': '#2a2d4c',
      'clusterBkg': '#2a2d4c',
      'clusterBorder': '#2a2d4c',
      'lineColor': '#565a7c',
      'fontFamily': 'monospace',
      'darkmode': true
    }
  }
}%%
%% Support https://transmute.industries
graph LR
	subgraph &nbsp
		direction LR
        root("did:web:  ")
        0("did:jwk: base64url ( json-web-key ) ")
        1("did:jwt: compact-json-web-token ")
        2("compact-json-web-signature ")
        3("compact-json-web-encryption ")
        root -- derive -->  0
        0 -- sign --> 1
        0 -- encrypt --> 1
        1 -- as --> 2
        1 -- as --> 3
        root -- derive -->  1
	end

style root color: #fff, fill: #594aa8
style 0 color: #fcb373, stroke: #fcb373
style 1 color: #fcb373, stroke: #fcb373
style 2 color: #8286a3, stroke: #8286a3
linkStyle 0,5 color:#2cb3d9, stroke-width: 2.0px
linkStyle 1,2 color:#ff605d, stroke:#8286a3, stroke-width: 2.0px
linkStyle 3,4 color:#48caca, stroke-width: 2.0px
%% export const transmute = {
%%   primary: {
%%     purple: { dark: "#27225b", light: "#594aa8" },
%%     red: "#ff605d",
%%     orange: "#fcb373",
%%     grey: "#f5f7fd",
%%     white: "#fff",
%%   },
%%   secondary: {
%%     teal: "#48caca",
%%     aqua: "#2cb3d9",
%%     dark: "#2a2d4c",
%%     medium: "#565a7c",
%%     light: "#8286a3",
%%   },
%% };

Usage

npm install '@transmute/did-transmute'
import transmute from '@transmute/did-transmute';
const transmute = require('@transmute/did-transmute');

See also transmute-industries/verifiable-credentials.

This api is exposed on the default export, for example:

const actor = await transmute.did.jwk.exportable({
  alg: "ES384",
});
const issuer = await transmute.w3c.vc.issuer({ 
  signer: await transmute.w3c.controller.key.attached.signer({ 
    privateKey: actor.key.privateKey 
  }) 
});
// issue a vc+ld+jwt
const vc = await issuer.issue({
  protectedHeader: {
    kid: actor.did + '#0',
    alg: actor.key.publicKey.alg,
  },
  claimset: {
    "@context": [
      "https://www.w3.org/ns/credentials/v2",
      "https://www.w3.org/ns/credentials/examples/v2"
    ],
    "id": "https://contoso.example/credentials/35327255",
    "type": ["VerifiableCredential", "KYCExample"],
    "issuer": "did:web:contoso.example",
    "validFrom": "2019-05-25T03:10:16.992Z",
    "validUntil": "2027-05-25T03:10:16.992Z",
    "credentialStatus": {
      "id": "https://contoso.example/credentials/status/4#3",
      "type": "StatusList2021Entry",
      "statusPurpose": "suspension",
      "statusListIndex": "3",
      "statusListCredential": "https://contoso.example/credentials/status/4"
    },
    "credentialSchema": {
      "id": "https://contoso.example/bafybeigdyr...lqabf3oclgtqy55fbzdi",
      "type": "JsonSchema"
    },
    "credentialSubject": {
      "id": "did:example:1231588",
      "type": "Person"
    }
  },
});

did:jwk

Generate

const actor1 = await transmute.did.jwk.exportable({
  alg: 'ES256',
});
// Use software isolation: 
// See https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/exportKey
const actor2 = await transmute.did.jwk.isolated({
  alg: 'ES256',
});

Resolve & Dereference

const { 
  did 
} = await transmute.did.jwk.exportable({
  alg: 'ES256',
});
const didDocument = await transmute.did.jwk.resolve({ 
  id: did,
  documentLoader: transmute.did.jwk.documentLoader
});
// See https://www.w3.org/TR/did-core/#verification-relationships
const { publicKeyJwk } = await transmute.did.jwk.dereference({
  id: `${did}#0`,
  documentLoader: transmute.did.jwk.documentLoader
});

Sign & Verify

const { 
  key: { privateKey, publicKey } 
} = await transmute.did.jwk.exportable({
  alg: transmute.jose.alg.ES256,
});
const jws = await transmute.sign({
  privateKey: privateKey,
  protectedHeader: {
    alg: privateKey.alg,
  },
  payload: new TextEncoder().encode("It’s a dangerous business, Frodo, going out your door. 🧠💎"),
});
const v = await transmute.verify({
  jws,
  publicKey: publicKey,
});

Encrypt & Decrypt

const { 
  key: { privateKey, publicKey } 
} = await transmute.did.jwk.exportable({
  alg: transmute.jose.alg.ECDH_ES_A256KW,
});
const jwe = await transmute.encrypt({
  publicKey: publicKey,
  plaintext: new TextEncoder().encode("It’s a dangerous business, Frodo, going out your door. 🧠💎"),
  protectedHeader: {
    alg: publicKey.alg,
    enc: transmute.jose.enc.A256GCM,
  },
});
const v = await transmute.decrypt({
  jwe,
  privateKey: privateKey,
});

did:jwt

This method is very 🚧 experimental 🏗️.

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a2d4c',
      'primaryTextColor': '#565a7c',
      'nodeBorder': '#565a7c',
      'edgeLabelBackground': '#2a2d4c',
      'clusterBkg': '#2a2d4c',
      'clusterBorder': '#2a2d4c',
      'lineColor': '#565a7c',
      'fontFamily': 'monospace',
      'darkmode': true
    }
  }
}%%
%% Support https://transmute.industries
graph LR
	subgraph &nbsp
		direction LR
    Did("did:jwt: compact-json-web-token ")
    KeyDereference{"Key Resolver"}
    DidResolution{"Did Resolution"}
    DidDocument("Did Document")
    DidDocumentMetadata("Did Document Metadata")
    ProtectedHeader("ProtectedHeader")
    ClaimSet("Claim Set")
    Trusted{{"Is Issuer Trusted?"}}
    Untrusted("No")

    Did -- Protected Header  --> KeyDereference
    KeyDereference --> Trusted
    
    Trusted -- Payload --> DidResolution
    DidResolution --> DidDocument
    DidDocument --> ClaimSet
    DidResolution -.-> DidDocumentMetadata
    DidDocumentMetadata -.-> ProtectedHeader

    Trusted -.-> Untrusted

	end

%% orange
style Did color: #fcb373, stroke: #fcb373
style DidDocument color: #fcb373, stroke: #fcb373

%% teal
style Trusted color: #27225b, fill: #2cb3d9

%% purple
style ProtectedHeader color: #fff, fill: #594aa8
style ClaimSet color: #fff, fill: #594aa8

%% light grey
style DidDocumentMetadata color: #8286a3, stroke: #8286a3

%% red
style KeyDereference color: #ff605d, stroke: #ff605d
style DidResolution color: #ff605d, stroke: #ff605d


%% red lines
linkStyle 0,2 color:#ff605d, stroke-width: 2.0px

%% linkStyle 1,2,4 color:#ff605d, stroke:#8286a3, stroke-width: 2.0px
%% export const transmute = {
%%   primary: {
%%     purple: { dark: "#27225b", light: "#594aa8" },
%%     red: "#ff605d",
%%     orange: "#fcb373",
%%     grey: "#f5f7fd",
%%     white: "#fff",
%%   },
%%   secondary: {
%%     teal: "#48caca",
%%     aqua: "#2cb3d9",
%%     dark: "#2a2d4c",
%%     medium: "#565a7c",
%%     light: "#8286a3",
%%   },
%% };

There are several different ways to "trust" a JSON Web Token issuer, based exclusively or the header and verify or decrypt operations.

Embedding keys

Using jwk and x5c.

Embedding the key within the token is a straightforward way to enable key distribution. To ensure the security of this mechanism, the consumer of the JWT needs to restrict which keys it accepts. Failure to do so allows an attacker to generate tokens signed with a malicious private key. An overly permitting consumer would merely use the embedded public key to verify the signature, which will be valid. To avoid such issues, the consumer needs to match the key used against a set of explicitly whitelisted keys. In case the key comes in the form of an X509 certificate, the consumer can use the certificate information to verify the authenticity.

When jwk is present in the Protected Header of a JWT, a custom did:jwk resoler will be used as the the allow-list.

A null resolution is treated as a deny operation.

See also panva/jose.

Distributing keys

Using jku and x5u.

TODO

See RFC7515

Proof of Possession

TODO

See RFC7800

Using OpenID Connect Discovery

🚧 Experimental 🏗️.

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a2d4c',
      'primaryTextColor': '#565a7c',
      'nodeBorder': '#565a7c',
      'edgeLabelBackground': '#2a2d4c',
      'clusterBkg': '#2a2d4c',
      'clusterBorder': '#2a2d4c',
      'lineColor': '#565a7c',
      'fontFamily': 'monospace',
      'darkmode': true
    }
  }
}%%
%% Support https://transmute.industries

flowchart LR
subgraph 0 [Open ID Connect 'DID Method']
direction LR
ProtectedHeader("Protected Header")
ProtectedClaimSet("Protected Claim Set")
DecodedVerificationMethodComponents("{ iss, kid }")
WellKnownConfig(".well-known/openid-configuration")
WellKnownJwks(".well-known/jwks.json")
DidDocument("DID Document")

ProtectedHeader -.-> DecodedVerificationMethodComponents
ProtectedClaimSet -.-> DecodedVerificationMethodComponents
DecodedVerificationMethodComponents -.-> WellKnownConfig
WellKnownConfig -.-> WellKnownJwks
WellKnownJwks -.-> DidDocument

VerificationMethod("{{iss}}#{{kid}}")
DecodedVerificationMethodComponents -.-> VerificationMethod

DidDocument -.-> publicKey
publicKey("{ publicKeyJwk }")
VerificationMethod -.-> publicKey

class ProtectedHeader,ProtectedClaimSet,publicKey PurpleNode
class DecodedVerificationMethodComponents,WellKnownConfig,WellKnownJwks RedNode
class DidDocument,VerificationMethod TealNode

classDef PurpleNode color:#fff, fill:#594aa8, stroke:#27225b, stroke-width:1px;
classDef RedNode color:#ff605d, stroke:#ff605d, stroke-width:1px;
classDef OrangeNode color:#fcb373, stroke:#fcb373, stroke-width:1px;
classDef GreyNode fill:#f5f7fd, stroke:#f5f7fd, stroke-width:1px;
classDef WhiteNode color:#fff, stroke:#fff, stroke-width:1px;
classDef DarkPurpleNode color:#f5f7fd, fill:#27225b, stroke:#f5f7fd, stroke-width:1px;
classDef TealNode color:#48caca, stroke:#48caca, stroke-width:1px;
classDef AquaNode color:#2cb3d9, stroke:#2cb3d9, stroke-width:1px;
end

Example DID Document

{
  "id": "{{iss}}",
  "verificationMethod":[{
    "id": "#{{kid}}",
    "type": "JsonWebKey",
    "controller": "{{iss}}",
    "publicKey":{
      "kid": "urn:ietf:params:oauth:jwk-thumbprint:sha-256:AXRYM9BnKWZj6c84ykLX6D-fE9FRV2_f3pRDwcJGSU0",
      "kty": "OKP",
      "crv": "Ed25519",
      "alg": "EdDSA",
      "x": "dh2c41edqveCxEzw3OVjtAmdcJPwe4lAg2fJ10rsZk0",
    }
  }],
  "assertionMethod": ["#{{kid}}"]
}

See openid-connect-discovery

Verifiable Credential's JSON Web Token Profile

This approach relies on the resolver to act as an allow list for absolute did urls, constructed from kid or a combination of kid and iss.

For example, a protectedHeader might look like:

{
  "alg": "ES256",
  "iss": "did:example:123",
  "kid": "#key-4"
}

or

{
  "alg": "ES256",
  "kid": "did:example:123#key-4"
}

This header will be used to dereference a verificationMethod which is expected to contain a publicKey.

For example:

{
  "id": "#key-4",
  "type": "JsonWebKey",
  "controller": "did:example:123",
  "publicKeyJwk": {
    "kid": "urn:ietf:params:oauth:jwk-thumbprint:sha-256:AXRYM9BnKWZj6c84ykLX6D-fE9FRV2_f3pRDwcJGSU0",
    "kty": "OKP",
    "crv": "Ed25519",
    "alg": "EdDSA",
    "x": "dh2c41edqveCxEzw3OVjtAmdcJPwe4lAg2fJ10rsZk0",
  }
}

or

{
  "id": "did:example:123#urn:ietf:params:oauth:jwk-thumbprint:sha-256:AXRYM9BnKWZj6c84ykLX6D-fE9FRV2_f3pRDwcJGSU0",
  "type": "JsonWebKey",
  "controller": "did:example:123",
  "publicKeyJwk": {
    "kid": "urn:ietf:params:oauth:jwk-thumbprint:sha-256:AXRYM9BnKWZj6c84ykLX6D-fE9FRV2_f3pRDwcJGSU0",
    "kty": "OKP",
    "crv": "Ed25519",
    "alg": "EdDSA",
    "x": "dh2c41edqveCxEzw3OVjtAmdcJPwe4lAg2fJ10rsZk0",
  }
}

See jwt-vc-presentation-profile

Sign

const issuer = await transmute.did.jwk.exportable({
    alg: alg.ES256,
  });
const subject = await transmute.did.jwt.sign({
  issuer: "did:example:123",
  audience: "did:example:456",
  protectedHeader: {
    alg: issuer.key.publicKey.alg,
  },
  claimSet: {
    "urn:example:claim": true,
  },
  privateKey: issuer.key.privateKey,
});

Encrypt

const issuer = await transmute.did.jwk.exportable({
  alg: alg.ECDH_ES_A256KW,
});
const subject = await transmute.did.jwt.encrypt({
  issuer: "did:example:123",
  protectedHeader: {
    alg: issuer.key.publicKey.alg,
    iss: "did:example:123",
    kid: "#0",
    enc: transmute.jose.enc.A256GCM,
  },
  claimSet: {
    service: [
      {
        id: "#dwn",
        type: "DecentralizedWebNode",
        serviceEndpoint: {
          nodes: ["https://dwn.example.com", "https://example.org/dwn"],
        },
      },
    ],
  },
  publicKey: issuer.key.publicKey,
});

Resolve

const didDocument = await transmute.did.jwt.resolve({
  id: subject.did,
  privateKeyLoader: async (id: string) => {
    if (id.startsWith("did:example:123")) {
      return issuer.key.privateKey;
    }
    throw new Error("privateKeyLoader does not support identifier: " + id);
  },
  profiles: ["encrypted-jwt"],
});

Dereference

type DwnService = {
  id: "#dwn";
  type: "DecentralizedWebNode";
  serviceEndpoint: {
    nodes: ["https://dwn.example.com", "https://example.org/dwn"];
  };
};
const service = await transmute.did.jwt.dereference<DwnService>({
  id: `${subject.did}#dwn`,
  privateKeyLoader: async (id: string) => {
    if (id.startsWith("did:example:123")) {
      return issuer.key.privateKey;
    }
    throw new Error("privateKeyLoader does not support identifier: " + id);
  },
  profiles: ["encrypted-jwt"],
});

did:web

This method is very 🚧 experimental 🏗️.

Generate

 const { did, didDocument, key } = await transmute.did.web.exportable({
  url: "https://id.gs1.transmute.example/01/9506000134352",
  alg: transmute.jose.alg.ES256,
  documentLoader: transmute.did.jwk.documentLoader,
});

From Private Key

const { 
  key: {privateKey} 
} = await transmute.did.jwk.exportable({
  alg: 'ES256',
});
const issuer = await transmute.did.web.fromPrivateKey({
  url: "https://id.gs1.transmute.example/01/9506000134352",
  privateKey: privateKey,
});

From Dids

const { 
  did
} = await transmute.did.jwk.exportable({
  alg: 'ES256',
});
const issuer = await transmute.did.web.fromDids({
  url: "https://id.gs1.transmute.example/01/9506000134352",
  dids: [did],
  documentLoader: transmute.did.jwk.documentLoader,
});

Resolve

const { 
  key: { privateKey } 
} = await transmute.did.jwk.exportable({
  alg: 'ES256',
});
const issuer = await transmute.did.web.fromPrivateKey({
  url: "https://id.gs1.transmute.example/01/9506000134352",
  privateKey: privateKey,
});
const didDocument = await transmute.did.web.resolve({
  id: issuer.did,
  documentLoader: async (iri: string) => {
    // for test purposes.
    if (iri === "https://id.gs1.transmute.example/01/9506000134352/did.json") {
      return { document: issuer.didDocument };
    }
    throw new Error("Unsupported IRI " + iri);
  },
});
// didDocument.id = "did:web:id.gs1.transmute.example:01:9506000134352"

Dereference

const issuer = await transmute.did.web.fromPrivateKey({
  url: "https://id.gs1.transmute.example/01/9506000134352",
  privateKey: {
    kid: "urn:ietf:params:oauth:jwk-thumbprint:sha-256:a9EEmV5OPmFQlAVU2EDuKB3cp5JpirRwnD12UdHc91Q",
    kty: "EC",
    crv: "P-256",
    alg: "ES256",
    x: "D1ygYPasDI88CrYAF_Ga_4aXEhp5fWetEXzyitdt1K8",
    y: "dkxXWzis0tQQIctZRzSvf6tdeITCLXim8HgTUhMOTrg",
    d: "RWgQ966yzek12KSlDJ-hmlqckRUhZzKDqJeM_QdbT-E",
  },
});
const verificationMethod = await transmute.did.web.dereference({
  id: `${issuer.did}#a9EEmV5OPmFQlAVU2EDuKB3cp5JpirRwnD12UdHc91Q`,
  documentLoader: async (iri: string) => {
    // for test purposes.
    if (
      iri === "https://id.gs1.transmute.example/01/9506000134352/did.json"
    ) {
      return { document: issuer.didDocument };
    }
    throw new Error("Unsupported IRI " + iri);
  },
});
const v = await transmute.verify({
  jws,
  publicKey: verificationMethod.publicKeyJwk,
});

Develop

npm i
npm t
npm run lint
npm run build