Skip to content

Commit

Permalink
Got to an equilibrium point
Browse files Browse the repository at this point in the history
  • Loading branch information
iherman committed Aug 26, 2024
1 parent 54954e2 commit 67fbcfe
Show file tree
Hide file tree
Showing 54 changed files with 163 additions and 163 deletions.
50 changes: 25 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,91 +8,91 @@ Conversion of cryptographic keys in [Multikey format](https://www.w3.org/TR/cont
from [WebCrypto](https://www.w3.org/TR/WebCryptoAPI/) and [JWK](https://datatracker.ietf.org/doc/html/rfc7517). The conversions are available for the three EC curves that are defined for Verifiable Credentials:
[ECDSA with P-256 and P-384](https://www.w3.org/TR/vc-di-ecdsa/#multikey) and [EDDSA](https://www.w3.org/TR/vc-di-eddsa/#multikey).

This is really a proof-of-concept implementation. It shows that such conversion _can indeed be done_,
which is an important in proving the practical usability of multikeys.
(This is really a proof-of-concept implementation. It shows that such conversion _can indeed be done_,
which is an important in proving the practical usability of multikeys. It would need extra tests using external Multikeys.)

The package has been written in TypeScript+Node.js. (There is also a [Typescript+Deno version](https://github.com/iherman/multikey-webcrypto-d).)

For a more detailed documentation, see the [code documentation](https://iherman.github.io/multikey-webcrypto/), generated by typedoc. A short set of examples may help.

## Necessary extra types used by the API

The interface makes use of the `JsonWebKey`, `CryptoKeyPair`, and `CryptoKey` types, which are global types in Node.js (or Deno), defined by WebCrypto.
The interface makes use of the `JsonWebKey`, `CryptoKeyPair`, and `CryptoKey` types, which are global types in Node.js (or Deno), defined by WebCrypto. The following types are also exported by the package:

```typescript
export interface JWKKeyPair {
public: JsonWebKey;
secret?: JsonWebKey;
publicKey: JsonWebKey;
privateKey?: JsonWebKey;
}

export type Multibase = string;

// The field names in `Multikey` reflect the Multikey specification.
export interface Multikey {
publicKeyMultibase: Multibase;
secretKeyMultibase?: Multibase;
}
```

The field names in `Multikey` reflect the [Multikey specification](https://www.w3.org/TR/controller-document/#multikey).

## Usage of the API functions

### Multikey and JWK

```typescript
import * as mkc from "multikey-webcrypto";

// Convert a JWK Pair to a Multikey Pair:
const jwk_pair: JWKKeyPair = {
public: your_jwk_public_key,
secret: your_jwk_secret_key,
// Get a JWK pair
const jwk_pair: mkc.JWKKeyPair = {
publicKey: your_jwk_public_key,
privateKey: your_jwk_private_key,
};
const mk_pair: MultikeyPair = mkc.JWKToMultikey(jwk_pair);
// mk_pair.publicKeyMultibase and mk_pair.secretKeyMultibase provide the right values
const mk_pair: mkc.Multikey = mkc.JWKToMultikey(jwk_pair);
// mk_pair.publicKeyMultibase and mk_pair.secretKeyMultibase provide the converted values

// Convert the multikey back to jwk
const generated_jwk_pair: JWKKeyPair = mkc.multikeyToJWK(mk_pair);
const generated_jwk_pair: mkc.JWKKeyPair = mkc.multikeyToJWK(mk_pair);

```

In all cases the secret key may be missing or set to `undefined`, so that only the public key is converted. The same can be achieved if the functions are uses with an overloaded signature:
In all cases the secret key may be missing or set to `undefined`, so that only the public key is converted. The same can be achieved if the functions are used with an overloaded signature:

```typescript
import * as mkc from "multikey-webcrypto";

const mk: Multikey = mkc.JWKToMultikey(your_jwk_public_key);
const mk: mkc.Multibase = mkc.JWKToMultikey(your_jwk_public_key);
// mk the encoded value

// Convert the multikey back to jwk
const generated_jwk_public_key: JWKKeyPair = mkc.multikeyToJWK(mk);
const generated_jwk_public_key: mkc.JWKKeyPair = mkc.multikeyToJWK(mk);
```

### Multikey and WebCrypto keys

The interface is similar to the JWK case. The only major difference is that functions are asynchronous (the reason is that WebCrypto implementations are using asynchronous functions).
The simplest is to use the `await` constructs in the code:
The interface is similar to the JWK case. The only major difference is that functions are asynchronous (the reason is that WebCrypto implementations are asynchronous).
The simplest approach is to use the `await` constructs in the code:

```typescript
import * as mkc from "multikey-webcrypto";

// Convert a JWK Pair to a Multikey Pair:
// Convert a JWK Pair to a Multikey.
// Note: the `CryptoKeyPair` interface is defined by the WebCrypto implementations, not by this package
const crypto_pair: CryptoKeyPair = {
public: your_web_crypto_public_key,
secret: your_web_crypto_secret_key,
publicKey: your_web_crypto_public_key,
privateKey: your_web_crypto_secret_key,
};
const mk_pair: MultikeyPair = await mkc.cryptoToMultikey(crypto_pair);
const mk_pair: Multikey = await mkc.cryptoToMultikey(crypto_pair);
// mk_pair.publicKeyMultibase and mk_pair.secretKeyMultibase provide the right values

// Convert the multikey back to jwk
const generated_crypto_pair: JWKKeyPair = await mkc.multikeyToCrypto(mk_pair);
const generated_crypto_pair: mkc.JWKKeyPair = await mkc.multikeyToCrypto(mk_pair);
```

Similarly to the JWK case, handling public keys only can be done with the aliased versions of the same functions:

```typescript
import * as mkc from "multikey-webcrypto";

const mk: Multikey = mkc.cryptoToMultikey(your_web_crypto_public_key);
const mk: Multibase = mkc.cryptoToMultikey(your_web_crypto_public_key);
// mk the encoded value

// Convert the multikey back to jwk
Expand Down
20 changes: 10 additions & 10 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function multikeyToJWK(keys) {
return jwk_keys;
}
else {
return jwk_keys.public;
return jwk_keys.publicKey;
}
}
// Implementation of the overloaded functions
Expand All @@ -32,10 +32,10 @@ async function multikeyToCrypto(keys) {
const jwkPair = multikeyToJWK(input);
const algorithm = { name: "" };
// We have to establish what the algorithm type is from the public jwk
switch (jwkPair.public.kty) {
switch (jwkPair.publicKey.kty) {
case 'EC':
algorithm.name = "ECDSA";
algorithm.namedCurve = jwkPair.public.crv;
algorithm.namedCurve = jwkPair.publicKey.crv;
break;
case 'OKP':
algorithm.name = "Ed25519";
Expand All @@ -47,11 +47,11 @@ async function multikeyToCrypto(keys) {
throw new Error("Unknown kty value for the JWK key");
}
const output = {
publicKey: await crypto.subtle.importKey("jwk", jwkPair.public, algorithm, true, ["verify"]),
publicKey: await crypto.subtle.importKey("jwk", jwkPair.publicKey, algorithm, true, ["verify"]),
privateKey: undefined,
};
if (jwkPair.secret != undefined) {
output.privateKey = await crypto.subtle.importKey("jwk", jwkPair.secret, algorithm, true, ["sign"]);
if (jwkPair.privateKey != undefined) {
output.privateKey = await crypto.subtle.importKey("jwk", jwkPair.privateKey, algorithm, true, ["sign"]);
}
// Got the return, the type depends on the overloaded input type
if (isMultikeyPair(keys)) {
Expand All @@ -64,9 +64,9 @@ async function multikeyToCrypto(keys) {
// Implementation of the overloaded functions
function JWKToMultikey(keys) {
function isJWKKeyPair(obj) {
return obj.public !== undefined;
return obj.publicKey !== undefined;
}
const input = isJWKKeyPair(keys) ? keys : { public: keys };
const input = isJWKKeyPair(keys) ? keys : { publicKey: keys };
const m_keys = convert.JWKToMultikey(input);
if (isJWKKeyPair(keys)) {
return m_keys;
Expand All @@ -84,10 +84,10 @@ async function cryptoToMultikey(keys) {
const input = isPair ? keys : { publicKey: keys, privateKey: undefined };
// Generate the JWK version of the cryptokeys:
const jwkKeyPair = {
public: await crypto.subtle.exportKey("jwk", input.publicKey),
publicKey: await crypto.subtle.exportKey("jwk", input.publicKey),
};
if (isPair && input.privateKey !== undefined) {
jwkKeyPair.secret = await crypto.subtle.exportKey("jwk", input.privateKey);
jwkKeyPair.privateKey = await crypto.subtle.exportKey("jwk", input.privateKey);
}
// Ready for conversion
const output = JWKToMultikey(jwkKeyPair);
Expand Down
8 changes: 4 additions & 4 deletions dist/lib/common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
* Public/secret pair of JWK instances
*/
export interface JWKKeyPair {
public: JsonWebKey;
secret?: JsonWebKey;
publicKey: JsonWebKey;
privateKey?: JsonWebKey;
}
/**
* Type for a Multibase
Expand All @@ -33,7 +33,7 @@ export interface MultikeyBinary {
/************************************************************************* */
/************************************************************************* */
/**
* Names for the various crypto curve
* Names for the various crypto curves
*/
export declare enum CryptoCurves {
ECDSA_384 = "secp384r1",
Expand Down Expand Up @@ -108,7 +108,7 @@ export declare const classToEncoder: ClassToEncoder;
*/
export declare const ECDSACurves: CryptoCurves[];
/**
* This is an internal type, used for the implementation: return the crypto curve and type from a preamble.
* This is an internal type, used for the implementation: return the crypto curve and type from a multikey preamble.
*
* So far, I have not yet found a way to encode that in a simple table, hence the separate function.
*/
Expand Down
2 changes: 1 addition & 1 deletion dist/lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const ecdsa = require("./ecdsa");
/* Values to handle the various preamble bytes for the different key types */
/************************************************************************* */
/**
* Names for the various crypto curve
* Names for the various crypto curves
*/
var CryptoCurves;
(function (CryptoCurves) {
Expand Down
18 changes: 9 additions & 9 deletions dist/lib/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,27 +121,27 @@ function JWKToMultikey(keys) {
throw new Error(`No kty value for the key (${JSON.stringify(key)})`);
}
};
const publicKeyCurve = keyCurve(keys.public);
const publicKeyCurve = keyCurve(keys.publicKey);
// The secret key class is calculated, but this is just for checking; the two must be identical...
if (keys.secret !== undefined) {
const secretKeyCurve = keyCurve(keys.secret);
if (keys.privateKey !== undefined) {
const secretKeyCurve = keyCurve(keys.privateKey);
if (publicKeyCurve !== secretKeyCurve) {
throw new Error(`Public and private keys refer to different EC curves (${JSON.stringify(keys)})`);
}
}
// The cryptokey values are x, y (for ecdsa), and d (for the secret key).
// Each of these are base 64 encoded strings; what we need is the
// binary versions thereof.
const x = decodeJWKField(keys.public.x);
const x = decodeJWKField(keys.publicKey.x);
if (x === undefined) {
throw new Error(`x value is missing from public key (${JSON.stringify(keys.public)})`);
throw new Error(`x value is missing from public key (${JSON.stringify(keys.publicKey)})`);
}
const y = decodeJWKField(keys.public.y);
const y = decodeJWKField(keys.publicKey.y);
if (common_1.ECDSACurves.includes(publicKeyCurve) && y === undefined) {
throw new Error(`y value is missing from the ECDSA public key (${JSON.stringify(keys.public)})`);
throw new Error(`y value is missing from the ECDSA public key (${JSON.stringify(keys.publicKey)})`);
}
const d = (keys.secret) ? decodeJWKField(keys.secret.d) : undefined;
if (keys.secret && d === undefined) {
const d = (keys.privateKey) ? decodeJWKField(keys.privateKey.d) : undefined;
if (keys.privateKey && d === undefined) {
throw new Error(`d value is missing from private key (${JSON.stringify(keys)})`);
}
const converter = common_1.classToEncoder[publicKeyCurve];
Expand Down
4 changes: 2 additions & 2 deletions dist/lib/ecdsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function multikeyBinaryToJWK(curve, xb, db) {
const x = base64.encode(uncompressed.x);
const y = base64.encode(uncompressed.y);
const output = {
public: {
publicKey: {
kty: "EC",
crv: (curve === common_1.CryptoCurves.ECDSA_256) ? "P-256" : "P-384",
x,
Expand All @@ -70,7 +70,7 @@ function multikeyBinaryToJWK(curve, xb, db) {
}
};
if (db !== undefined) {
output.secret = {
output.privateKey = {
kty: "EC",
crv: (curve === common_1.CryptoCurves.ECDSA_256) ? "P-256" : "P-384",
x,
Expand Down
4 changes: 2 additions & 2 deletions dist/lib/eddsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function JWKToMultikeyBinary(_curve, x, d, _y) {
function multikeyBinaryToJWK(_curve, xb, db) {
const x = base64.encode(xb);
const output = {
public: {
publicKey: {
kty: "OKP",
crv: "Ed25519",
x,
Expand All @@ -49,7 +49,7 @@ function multikeyBinaryToJWK(_curve, xb, db) {
}
};
if (db !== undefined) {
output.secret = {
output.privateKey = {
kty: "OKP",
crv: "Ed25519",
x,
Expand Down
8 changes: 4 additions & 4 deletions docs/assets/highlight.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
--dark-hl-3: #4EC9B0;
--light-hl-4: #001080;
--dark-hl-4: #9CDCFE;
--light-hl-5: #A31515;
--dark-hl-5: #CE9178;
--light-hl-6: #008000;
--dark-hl-6: #6A9955;
--light-hl-5: #008000;
--dark-hl-5: #6A9955;
--light-hl-6: #A31515;
--dark-hl-6: #CE9178;
--light-hl-7: #0070C1;
--dark-hl-7: #4FC1FF;
--light-hl-8: #795E26;
Expand Down
2 changes: 1 addition & 1 deletion docs/assets/search.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 67fbcfe

Please sign in to comment.