From 69b07cd94dfa1d424a3efb03aeac091ca5a01028 Mon Sep 17 00:00:00 2001 From: Ivan Herman Date: Wed, 14 Aug 2024 15:00:46 +0200 Subject: [PATCH] Generated a documentation and tested the generation of a distribution. --- README.md | 12 +- dist/index.d.ts | 37 + dist/index.js | 33 + dist/lib/common.d.ts | 137 ++ dist/lib/common.js | 155 ++ dist/lib/convert.d.ts | 34 + dist/lib/convert.js | 160 ++ dist/lib/ecdsa.d.ts | 41 + dist/lib/ecdsa.js | 143 ++ dist/lib/eddsa.d.ts | 35 + dist/lib/eddsa.js | 64 + dist/lib/encodings/base58/baseN.d.ts | 21 + dist/lib/encodings/base58/baseN.js | 144 ++ dist/lib/encodings/base58/index.d.ts | 2 + dist/lib/encodings/base58/index.js | 16 + dist/lib/encodings/base64.d.ts | 20 + dist/lib/encodings/base64.js | 49 + docs/.nojekyll | 1 + docs/assets/26e93147f10415a0ed4a.svg | 6 + docs/assets/75c9471662e97ee24f29.svg | 7 + docs/assets/db90e4df2373980c497d.svg | 9 + docs/assets/hierarchy.css | 1 + docs/assets/hierarchy.js | 1 + docs/assets/highlight.css | 22 + docs/assets/icons.js | 18 + docs/assets/icons.svg | 1 + docs/assets/main.js | 60 + docs/assets/navigation.js | 1 + docs/assets/search.js | 1 + docs/assets/style.css | 1448 +++++++++++++++++ docs/enums/lib_common.CryptoCurves.html | 5 + docs/enums/lib_common.CryptoKeyTypes.html | 4 + docs/functions/index.JWKToMultikey.html | 14 + docs/functions/index.multikeyToJWK.html | 12 + docs/functions/lib_common.isJWKKeyPair.html | 4 + docs/functions/lib_common.isMultikeyPair.html | 4 + .../lib_common.preambleToCryptoData.html | 4 + docs/functions/lib_convert.JWKToMultikey.html | 7 + docs/functions/lib_convert.multikeyToJWK.html | 9 + .../lib_ecdsa.JWKToMultikeyBinary.html | 11 + .../lib_ecdsa.multikeyBinaryToJWK.html | 9 + .../lib_eddsa.JWKToMultikeyBinary.html | 9 + .../lib_eddsa.multikeyBinaryToJWK.html | 9 + docs/index.html | 10 + docs/interfaces/lib_common.CryptoKeyData.html | 5 + docs/interfaces/lib_common.JWKKeyPair.html | 3 + docs/interfaces/lib_common.MultikeyPair.html | 5 + .../lib_common.MultikeyPairBinary.html | 4 + docs/modules/index.html | 6 + docs/modules/lib_common.html | 23 + docs/modules/lib_convert.html | 4 + docs/modules/lib_ecdsa.html | 7 + docs/modules/lib_eddsa.html | 5 + docs/types/lib_common.ClassToDecoder.html | 2 + docs/types/lib_common.ClassToEncoder.html | 2 + docs/types/lib_common.ClassToPreamble.html | 2 + docs/types/lib_common.Multikey.html | 3 + docs/types/lib_common.Preamble.html | 2 + docs/variables/lib_common.ECDSACurves.html | 3 + .../lib_common.Ecdsa256Preambles.html | 2 + .../lib_common.Ecdsa384Preambles.html | 2 + docs/variables/lib_common.EddsaPreambles.html | 2 + .../lib_common.classToDecoder-1.html | 2 + .../lib_common.classToEncoder-1.html | 2 + .../lib_common.classToPreamble-1.html | 2 + index.ts | 68 + lib/common.ts | 243 +++ lib/convert.ts | 183 +++ lib/ecdsa.ts | 155 ++ lib/eddsa.ts | 70 + lib/encodings/base58/baseN.js | 155 ++ lib/encodings/base58/index.d.ts | 19 + lib/encodings/base58/index.js | 35 + lib/encodings/base64.ts | 53 + tests/roundtrip.ts | 50 + tsconfig.json | 2 +- 76 files changed, 3906 insertions(+), 5 deletions(-) create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/lib/common.d.ts create mode 100644 dist/lib/common.js create mode 100644 dist/lib/convert.d.ts create mode 100644 dist/lib/convert.js create mode 100644 dist/lib/ecdsa.d.ts create mode 100644 dist/lib/ecdsa.js create mode 100644 dist/lib/eddsa.d.ts create mode 100644 dist/lib/eddsa.js create mode 100644 dist/lib/encodings/base58/baseN.d.ts create mode 100644 dist/lib/encodings/base58/baseN.js create mode 100644 dist/lib/encodings/base58/index.d.ts create mode 100644 dist/lib/encodings/base58/index.js create mode 100644 dist/lib/encodings/base64.d.ts create mode 100644 dist/lib/encodings/base64.js create mode 100644 docs/.nojekyll create mode 100644 docs/assets/26e93147f10415a0ed4a.svg create mode 100644 docs/assets/75c9471662e97ee24f29.svg create mode 100644 docs/assets/db90e4df2373980c497d.svg create mode 100644 docs/assets/hierarchy.css create mode 100644 docs/assets/hierarchy.js create mode 100644 docs/assets/highlight.css create mode 100644 docs/assets/icons.js create mode 100644 docs/assets/icons.svg create mode 100644 docs/assets/main.js create mode 100644 docs/assets/navigation.js create mode 100644 docs/assets/search.js create mode 100644 docs/assets/style.css create mode 100644 docs/enums/lib_common.CryptoCurves.html create mode 100644 docs/enums/lib_common.CryptoKeyTypes.html create mode 100644 docs/functions/index.JWKToMultikey.html create mode 100644 docs/functions/index.multikeyToJWK.html create mode 100644 docs/functions/lib_common.isJWKKeyPair.html create mode 100644 docs/functions/lib_common.isMultikeyPair.html create mode 100644 docs/functions/lib_common.preambleToCryptoData.html create mode 100644 docs/functions/lib_convert.JWKToMultikey.html create mode 100644 docs/functions/lib_convert.multikeyToJWK.html create mode 100644 docs/functions/lib_ecdsa.JWKToMultikeyBinary.html create mode 100644 docs/functions/lib_ecdsa.multikeyBinaryToJWK.html create mode 100644 docs/functions/lib_eddsa.JWKToMultikeyBinary.html create mode 100644 docs/functions/lib_eddsa.multikeyBinaryToJWK.html create mode 100644 docs/index.html create mode 100644 docs/interfaces/lib_common.CryptoKeyData.html create mode 100644 docs/interfaces/lib_common.JWKKeyPair.html create mode 100644 docs/interfaces/lib_common.MultikeyPair.html create mode 100644 docs/interfaces/lib_common.MultikeyPairBinary.html create mode 100644 docs/modules/index.html create mode 100644 docs/modules/lib_common.html create mode 100644 docs/modules/lib_convert.html create mode 100644 docs/modules/lib_ecdsa.html create mode 100644 docs/modules/lib_eddsa.html create mode 100644 docs/types/lib_common.ClassToDecoder.html create mode 100644 docs/types/lib_common.ClassToEncoder.html create mode 100644 docs/types/lib_common.ClassToPreamble.html create mode 100644 docs/types/lib_common.Multikey.html create mode 100644 docs/types/lib_common.Preamble.html create mode 100644 docs/variables/lib_common.ECDSACurves.html create mode 100644 docs/variables/lib_common.Ecdsa256Preambles.html create mode 100644 docs/variables/lib_common.Ecdsa384Preambles.html create mode 100644 docs/variables/lib_common.EddsaPreambles.html create mode 100644 docs/variables/lib_common.classToDecoder-1.html create mode 100644 docs/variables/lib_common.classToEncoder-1.html create mode 100644 docs/variables/lib_common.classToPreamble-1.html create mode 100644 index.ts create mode 100644 lib/common.ts create mode 100644 lib/convert.ts create mode 100644 lib/ecdsa.ts create mode 100644 lib/eddsa.ts create mode 100644 lib/encodings/base58/baseN.js create mode 100644 lib/encodings/base58/index.d.ts create mode 100644 lib/encodings/base58/index.js create mode 100644 lib/encodings/base64.ts create mode 100644 tests/roundtrip.ts diff --git a/README.md b/README.md index f41d1dd..c50f266 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,16 @@ # Multikey <-> WebCrypto and JWK conversions -Conversion to and from [Multikey format](https://www.w3.org/TR/controller-document/#multikey) from WebCrypto and JWK for the three EC curves that are defined for Verifiable Credentials: +Conversion of cryptographic keys in [Multikey format](https://www.w3.org/TR/controller-document/#multikey) to and +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, not sure it has a wide interest out there. But it shows that such conversion can indeed be done, which is an important feature in practice where multikey implementations are rare. +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. -The package has been written in TypeScript+Node.js +The package has been written in TypeScript+Node.js. (There is also a Deno version.) -The interface is pretty straightforward, see `index.ts` for now. More documentation may come. +For a more detailed documentation, see the [code documentation](https://iherman.github.io/multikey-webcrypto/), generated by typedoc. + +[Examples to come] diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..4e2a150 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,37 @@ +/** + * Conversion to and from [Multikey format](https://www.w3.org/TR/controller-document/#multikey) from + * JWK 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). + * + * @package + */ +import { JWKKeyPair, MultikeyPair, Multikey } from './lib/common'; +export type { JWKKeyPair, MultikeyPair, Multikey } from './lib/common'; +/** + * Generic function to convert a multikey pair to JWK. This function decodes the multikey data + * into a binary buffer, checks the preambles and invokes the crypto specific converter functions + * (depending on the preamble values) that do the final + * conversion from the binary data to JWK. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + * + * @param keys + * @throws - exceptions if something is incorrect in the incoming data + */ +export declare function multikeyToJWK(keys: Multikey): JsonWebKey; +export declare function multikeyToJWK(keys: MultikeyPair): JWKKeyPair; +/** + * Convert JWK Key pair to Multikeys. This function decodes the JWK keys, finds out which binary key it encodes + * and converts the key to the multikey versions depending on the exact curve. + * + * Note that the code does not check (yet?) all combination of the JWK pairs and fields for possible errors, only + * those that would lead to error in this package. E.g., it does not check whether the x (and possibly y) values + * are identical in the secret and private JWK keys. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + * + * @param keys + * @throws - exceptions if something is incorrect in the incoming data + */ +export declare function JWKToMultikey(keys: JsonWebKey): Multikey; +export declare function JWKToMultikey(keys: JWKKeyPair): MultikeyPair; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..4d07b17 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,33 @@ +"use strict"; +/** + * Conversion to and from [Multikey format](https://www.w3.org/TR/controller-document/#multikey) from + * JWK 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). + * + * @package + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.multikeyToJWK = multikeyToJWK; +exports.JWKToMultikey = JWKToMultikey; +const convert = require("./lib/convert"); +const common_1 = require("./lib/common"); +function multikeyToJWK(keys) { + const input = (0, common_1.isMultikeyPair)(keys) ? keys : { publicKeyMultibase: keys }; + const jwk_keys = convert.multikeyToJWK(input); + if ((0, common_1.isMultikeyPair)(keys)) { + return jwk_keys; + } + else { + return jwk_keys.public; + } +} +function JWKToMultikey(keys) { + const input = (0, common_1.isJWKKeyPair)(keys) ? keys : { public: keys }; + const m_keys = convert.JWKToMultikey(input); + if ((0, common_1.isJWKKeyPair)(keys)) { + return m_keys; + } + else { + return m_keys.publicKeyMultibase; + } +} diff --git a/dist/lib/common.d.ts b/dist/lib/common.d.ts new file mode 100644 index 0000000..b94803f --- /dev/null +++ b/dist/lib/common.d.ts @@ -0,0 +1,137 @@ +export interface JWKKeyPair { + public: JsonWebKey; + secret?: JsonWebKey; +} +/** + * Typeguard for JWK Key Pair. + * It is not really elaborate, it only tries to differentiate between a JWK Single Key and a Key Pair. + * + * @param obj + * @returns is it a JWKKeyPair? + */ +export declare function isJWKKeyPair(obj: any): obj is JWKKeyPair; +/** + * Type for a Multikey + * + * One day this could become a string with a fixed regexp... + */ +export type Multikey = string; +/** + * The specification is a bit fuzzy and talks about Multikey for a pair, and for individual constituents. + * We need to differentiate those two... + */ +export interface MultikeyPair { + publicKeyMultibase: Multikey; + secretKeyMultibase?: Multikey; +} +/** + * Typeguard for a Multikey Pair. + * It is not really elaborate, it only tries to differentiate between a single Multikey and a Key Pair. + * + * @param obj + * @returns is it a MultikeyPair? + */ +export declare function isMultikeyPair(obj: any): obj is MultikeyPair; +/** + * Same as the Multikey Pair, but decoded and without the preambles. Just the bare key values. + */ +export interface MultikeyPairBinary { + public: Uint8Array; + secret?: Uint8Array; +} +/************************************************************************* */ +/************************************************************************* */ +/** + * Names for the various crypto curves + */ +export declare enum CryptoCurves { + ECDSA_384 = "secp384r1", + ECDSA_256 = "secp256r1", + EDDSA = "ed25519" +} +/** + * Names for the key types + */ +export declare enum CryptoKeyTypes { + PUBLIC = "public", + SECRET = "secret" +} +/************************************* Preambles ***************************/ +/** + * Type used for preambles, which are, so far, a single pair of numbers. + */ +export type Preamble = [T, T]; +/** + * Each crypto class has two preamble, on for the public and one for the secret keys + */ +interface MultikeyPreambles { + public: Preamble; + secret: Preamble; +} +/** + * Preamble value for ECDSA, a.k.a. ed25519 curve + */ +export declare const EddsaPreambles: MultikeyPreambles; +/** + * Preamble for ECDSA P-256, a.k.a. secp256r1 curve + */ +export declare const Ecdsa256Preambles: MultikeyPreambles; +/** + * Preamble for ECDSA P-256, a.k.a. secp384r1 curve + */ +export declare const Ecdsa384Preambles: MultikeyPreambles; +/************************************ Converter tables **********************************/ +/** + * What preambles must be used for a Curve (mapping type? + */ +export type ClassToPreamble = { + [key in CryptoCurves]: MultikeyPreambles; +}; +/** + * What preambles must be used for a Curve (data)? + */ +export declare const classToPreamble: ClassToPreamble; +/** + * What coder function must be used to convert from Multikey to JWK (type)? + */ +export type ClassToDecoder = { + [key in CryptoCurves]: (keyCurve: CryptoCurves, x: Uint8Array, d?: Uint8Array) => JWKKeyPair; +}; +/** + * hat coder function must be used to convert from Multikey to JWK (data)? + */ +export declare const classToDecoder: ClassToDecoder; +/** + * What coder function must be used to convert from JWK to Multikey (type)? + */ +export type ClassToEncoder = { + [key in CryptoCurves]: (keyCurve: CryptoCurves, x: Uint8Array, d: Uint8Array | undefined, _y?: Uint8Array) => MultikeyPairBinary; +}; +/** + * What coder function must be used to convert from JWK to Multikey (data)? + */ +export declare const classToEncoder: ClassToEncoder; +/** + * List of possible ECDSA Curves. Having this here declaratively may make it easier if + * in the future, a new curve is added to the family (P-512)? + */ +export declare const ECDSACurves: CryptoCurves[]; +/** + * This is an internal type, used for the implementation: return the crypto curve and type from a preamble. + * + * So far, I have not yet found a way to encode that in a simple table, hence the separate function. + */ +export interface CryptoKeyData { + crCurve: CryptoCurves; + crType: CryptoKeyTypes; +} +/** + * Classify the crypto key based on the multikey preamble characters that are at the start of the code. + * These are two binary numbers, signalling the crypto class (ecdsa or eddsa) and, in the former case, + * the hash function. + * + * @param preamble + * @returns + */ +export declare function preambleToCryptoData(preamble: Preamble): CryptoKeyData; +export {}; diff --git a/dist/lib/common.js b/dist/lib/common.js new file mode 100644 index 0000000..5898d6f --- /dev/null +++ b/dist/lib/common.js @@ -0,0 +1,155 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ECDSACurves = exports.classToEncoder = exports.classToDecoder = exports.classToPreamble = exports.Ecdsa384Preambles = exports.Ecdsa256Preambles = exports.EddsaPreambles = exports.CryptoKeyTypes = exports.CryptoCurves = void 0; +exports.isJWKKeyPair = isJWKKeyPair; +exports.isMultikeyPair = isMultikeyPair; +exports.preambleToCryptoData = preambleToCryptoData; +const eddsa = require("./eddsa"); +const ecdsa = require("./ecdsa"); +/** + * Typeguard for JWK Key Pair. + * It is not really elaborate, it only tries to differentiate between a JWK Single Key and a Key Pair. + * + * @param obj + * @returns is it a JWKKeyPair? + */ +// deno-lint-ignore no-explicit-any +function isJWKKeyPair(obj) { + return obj.public !== undefined; +} +/** + * Typeguard for a Multikey Pair. + * It is not really elaborate, it only tries to differentiate between a single Multikey and a Key Pair. + * + * @param obj + * @returns is it a MultikeyPair? + */ +// deno-lint-ignore no-explicit-any +function isMultikeyPair(obj) { + return obj.publicKeyMultibase !== undefined; +} +/************************************************************************* */ +/* Values to handle the various preamble bytes for the different key types */ +/************************************************************************* */ +/** + * Names for the various crypto curves + */ +var CryptoCurves; +(function (CryptoCurves) { + CryptoCurves["ECDSA_384"] = "secp384r1"; + CryptoCurves["ECDSA_256"] = "secp256r1"; + CryptoCurves["EDDSA"] = "ed25519"; +})(CryptoCurves || (exports.CryptoCurves = CryptoCurves = {})); +/** + * Names for the key types + */ +var CryptoKeyTypes; +(function (CryptoKeyTypes) { + CryptoKeyTypes["PUBLIC"] = "public"; + CryptoKeyTypes["SECRET"] = "secret"; +})(CryptoKeyTypes || (exports.CryptoKeyTypes = CryptoKeyTypes = {})); +/** + * Preamble value for ECDSA, a.k.a. ed25519 curve + */ +exports.EddsaPreambles = { + public: [0xed, 0x01], + secret: [0x80, 0x26], +}; +/** + * Preamble for ECDSA P-256, a.k.a. secp256r1 curve + */ +exports.Ecdsa256Preambles = { + public: [0x80, 0x24], + secret: [0x86, 0x26], +}; +/** + * Preamble for ECDSA P-256, a.k.a. secp384r1 curve + */ +exports.Ecdsa384Preambles = { + public: [0x81, 0x24], + secret: [0x87, 0x26], +}; +/** + * What preambles must be used for a Curve (data)? + */ +exports.classToPreamble = { + [CryptoCurves.EDDSA]: exports.EddsaPreambles, + [CryptoCurves.ECDSA_256]: exports.Ecdsa256Preambles, + [CryptoCurves.ECDSA_384]: exports.Ecdsa384Preambles, +}; +/** + * hat coder function must be used to convert from Multikey to JWK (data)? + */ +exports.classToDecoder = { + [CryptoCurves.EDDSA]: eddsa.multikeyBinaryToJWK, + [CryptoCurves.ECDSA_256]: ecdsa.multikeyBinaryToJWK, + [CryptoCurves.ECDSA_384]: ecdsa.multikeyBinaryToJWK, +}; +/** + * What coder function must be used to convert from JWK to Multikey (data)? + */ +exports.classToEncoder = { + [CryptoCurves.EDDSA]: eddsa.JWKToMultikeyBinary, + [CryptoCurves.ECDSA_256]: ecdsa.JWKToMultikeyBinary, + [CryptoCurves.ECDSA_384]: ecdsa.JWKToMultikeyBinary, +}; +/** + * List of possible ECDSA Curves. Having this here declaratively may make it easier if + * in the future, a new curve is added to the family (P-512)? + */ +exports.ECDSACurves = [CryptoCurves.ECDSA_256, CryptoCurves.ECDSA_384]; +/** + * Classify the crypto key based on the multikey preamble characters that are at the start of the code. + * These are two binary numbers, signalling the crypto class (ecdsa or eddsa) and, in the former case, + * the hash function. + * + * @param preamble + * @returns + */ +function preambleToCryptoData(preamble) { + // Ugly but effective and simple trick to compare two arrays + const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b); + if (preamble.length !== 2) { + throw new Error(`${preamble} is not valid, it should have a size of exactly 2.`); + } + // The real classification... + if (eq(preamble, exports.Ecdsa256Preambles.secret)) { + return { + crCurve: CryptoCurves.ECDSA_256, + crType: CryptoKeyTypes.SECRET, + }; + } + else if (eq(preamble, exports.Ecdsa256Preambles.public)) { + return { + crCurve: CryptoCurves.ECDSA_256, + crType: CryptoKeyTypes.PUBLIC, + }; + } + else if (eq(preamble, exports.Ecdsa384Preambles.secret)) { + return { + crCurve: CryptoCurves.ECDSA_384, + crType: CryptoKeyTypes.SECRET, + }; + } + else if (eq(preamble, exports.Ecdsa384Preambles.public)) { + return { + crCurve: CryptoCurves.ECDSA_384, + crType: CryptoKeyTypes.PUBLIC, + }; + } + else if (eq(preamble, exports.EddsaPreambles.secret)) { + return { + crCurve: CryptoCurves.EDDSA, + crType: CryptoKeyTypes.SECRET, + }; + } + else if (eq(preamble, exports.EddsaPreambles.public)) { + return { + crCurve: CryptoCurves.EDDSA, + crType: CryptoKeyTypes.PUBLIC, + }; + } + else { + throw new Error(`${preamble} is unknown. Should refer to secret or private eddsa or ecdsa (the latter with P-256 or P-384)`); + } +} diff --git a/dist/lib/convert.d.ts b/dist/lib/convert.d.ts new file mode 100644 index 0000000..1447c3a --- /dev/null +++ b/dist/lib/convert.d.ts @@ -0,0 +1,34 @@ +/** + * The real work for the whole library are done in the main functions in this module. + * + * @module + */ +import { JWKKeyPair, MultikeyPair } from "./common"; +/****************************************************************************************/ +/****************************************************************************************/ +/** + * Generic function to convert a multikey pair to JWK. This function decodes the multikey data + * into a binary buffer, checks the preambles and invokes the crypto curve specific converter functions + * (depending on the preamble values) that do the final + * conversion from the binary data to JWK. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + * + * @param keys + * @returns + * @throws - exceptions if something is incorrect in the incoming data + */ +export declare function multikeyToJWK(keys: MultikeyPair): JWKKeyPair; +/** + * Convert JWK Key pair to Multikeys. This function decodes the JWK keys, finds out which binary key it encodes + * and converts the key to the multikey versions depending on the exact curve. + * + * Note that the code does not check (yet?) all combination of the JWK pairs where they would be erroneous, only + * those that would lead to error in this cose. E.g., it does not check whether the x (and possibly y) values + * are identical in the secret and private JWK keys. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + + * @param keys + */ +export declare function JWKToMultikey(keys: JWKKeyPair): MultikeyPair; diff --git a/dist/lib/convert.js b/dist/lib/convert.js new file mode 100644 index 0000000..8848e3e --- /dev/null +++ b/dist/lib/convert.js @@ -0,0 +1,160 @@ +"use strict"; +/** + * The real work for the whole library are done in the main functions in this module. + * + * @module + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.multikeyToJWK = multikeyToJWK; +exports.JWKToMultikey = JWKToMultikey; +const common_1 = require("./common"); +const base58 = require("./encodings/base58/index"); +const base64 = require("./encodings/base64"); +/****************************************************************************************/ +/* The real converter functions */ +/****************************************************************************************/ +/** + * Generic function to convert a multikey pair to JWK. This function decodes the multikey data + * into a binary buffer, checks the preambles and invokes the crypto curve specific converter functions + * (depending on the preamble values) that do the final + * conversion from the binary data to JWK. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + * + * @param keys + * @returns + * @throws - exceptions if something is incorrect in the incoming data + */ +function multikeyToJWK(keys) { + // Separate the preamble of a multikey from the key value. By doing so, + // the initial 'z' value is also removed. + const convertBinary = (key) => { + // Check whether the first character is a 'z' before removing it + if (key[0] === 'z') { + const unencoded_key = base58.decode(key.slice(1)); + return { + preamble: [unencoded_key[0], unencoded_key[1]], + keyBinary: unencoded_key.slice(2), + }; + } + else { + throw new Error(`"${key}" is not encoded as required (first character should be a 'z')`); + } + }; + // Get the the public values values... + const publicBinary = convertBinary(keys.publicKeyMultibase); + // ... and find out, based on the preamble, which curve is used and whether it is indeed public + const publicData = (0, common_1.preambleToCryptoData)(publicBinary.preamble); + if (publicData.crType !== common_1.CryptoKeyTypes.PUBLIC) { + throw new Error(`"${keys.publicKeyMultibase}" has the wrong preamble (should refer to a public key).`); + } + // Get hold of the curve specific converter function that will do the real work on the binary + // data + const converter = common_1.classToDecoder[publicData.crCurve]; + // We have to repeat the previous steps for a secret key, if applicable, before converting the result into a JWK pair, + // A check is made on the fly to see that the keys are compatible in terms of crypto methods + if (keys.secretKeyMultibase) { + const secretBinary = convertBinary(keys.secretKeyMultibase); + const secretData = (0, common_1.preambleToCryptoData)(secretBinary.preamble); + if (secretData.crCurve !== publicData.crCurve) { + throw new Error(`Private and secret keys have different crypto methods`); + } + else if (secretData.crType !== common_1.CryptoKeyTypes.SECRET) { + throw new Error(`"${keys.secretKeyMultibase}" has the wrong preamble (should refer to a secret key).`); + } + // Everything seems to be fine + return converter(publicData.crCurve, publicBinary.keyBinary, secretBinary.keyBinary); + } + else { + return converter(publicData.crCurve, publicBinary.keyBinary); + } +} +/** + * Convert JWK Key pair to Multikeys. This function decodes the JWK keys, finds out which binary key it encodes + * and converts the key to the multikey versions depending on the exact curve. + * + * Note that the code does not check (yet?) all combination of the JWK pairs where they would be erroneous, only + * those that would lead to error in this cose. E.g., it does not check whether the x (and possibly y) values + * are identical in the secret and private JWK keys. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + + * @param keys + */ +function JWKToMultikey(keys) { + // Internal function for the common last step of encoding a multikey + const encodeMultikey = (val, preamble) => { + const val_mk = new Uint8Array([...preamble, ...val]); + return 'z' + base58.encode(val_mk); + }; + const decodeJWKField = (val) => { + if (val === undefined) { + return undefined; + } + else { + return base64.decode(val); + } + }; + // Find out the key curve, will be used for branching later: is it ECDSA or EDDSA and, if the former, + // which one? + const keyCurve = (key) => { + if (key.kty) { + if (key.kty === "EC") { + switch (key.crv) { + case "P-256": return common_1.CryptoCurves.ECDSA_256; + case "P-384": return common_1.CryptoCurves.ECDSA_384; + default: throw new Error(`Unknown crv value for an ecdsa key (${key.crv})`); + } + } + else if (key.kty === "OKP") { + if (key.crv === "Ed25519") { + return common_1.CryptoCurves.EDDSA; + } + else { + throw new Error(`Unknown crv value for an OKP key (${key.crv})`); + } + } + else { + throw new Error(`Unknown kty value for a key (${key.kty})`); + } + } + else { + throw new Error(`No kty value for the key (${JSON.stringify(key)})`); + } + }; + const publicKeyCurve = keyCurve(keys.public); + // 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 (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); + if (x === undefined) { + throw new Error(`x value is missing from public key (${JSON.stringify(keys.public)})`); + } + const y = decodeJWKField(keys.public.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)})`); + } + const d = (keys.secret) ? decodeJWKField(keys.secret.d) : undefined; + if (keys.secret && d === undefined) { + throw new Error(`d value is missing from private key (${JSON.stringify(keys)})`); + } + const converter = common_1.classToEncoder[publicKeyCurve]; + const finalBinary = converter(publicKeyCurve, x, d, y); + // We have the binary version of the multikey values, this must be converted into real multikey. + // This means adding a preamble and convert to base58. + const preambles = common_1.classToPreamble[publicKeyCurve]; + const output = { + publicKeyMultibase: encodeMultikey(finalBinary.public, preambles.public) + }; + if (finalBinary.secret !== undefined) { + output.secretKeyMultibase = encodeMultikey(finalBinary.secret, preambles.secret); + } + return output; +} diff --git a/dist/lib/ecdsa.d.ts b/dist/lib/ecdsa.d.ts new file mode 100644 index 0000000..5e7791a --- /dev/null +++ b/dist/lib/ecdsa.d.ts @@ -0,0 +1,41 @@ +/** + * Base conversion functions for ECDSA. The Multikey definition requires the usage of a compressed public key + * which must be compressed when creating the Multikey representation, and decompressed for the JWK conversion. + * + * The two exported functions, used by the rest of the package, just branch out to the internal functions that do the + * key (de)compression itself. + * + * @module + */ +import { JWKKeyPair, MultikeyPairBinary, CryptoCurves } from "./common"; +/** + * Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. + * The final encoding, with preambles, are done in the general level. + * + * For ECDSA, the compressed form must be calculated, by adding an extra byte signaling which of the + * two possible 'y' values are used. + * + * (The y value is set as optional in the signature but that is only to make TypeScript happy. A missing + * value generates an error) + * + * @param cl - choice between P-256 and P-384 + * @param x - x value for the elliptical curve + * @param d - d (private) value for the elliptical curve + * @param y - y value for the elliptical curve + * @returns + */ +export declare function JWKToMultikeyBinary(cl: CryptoCurves, x: Uint8Array, d: Uint8Array | undefined, y?: Uint8Array): MultikeyPairBinary; +/** + * Convert the multikey values to their JWK equivalents. The final `x` and `d` values are encoded + * in base64 and then the relevant JWK structure are created + * + * For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a + * constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must + * do some extra processing. + * + * @param cl - choice between P-256 and P-384 + * @param xb - binary version of the x value for the elliptical curve + * @param db - binary version of the d value for the elliptical curve + * @returns + */ +export declare function multikeyBinaryToJWK(cl: CryptoCurves, xb: Uint8Array, db?: Uint8Array): JWKKeyPair; diff --git a/dist/lib/ecdsa.js b/dist/lib/ecdsa.js new file mode 100644 index 0000000..4a371fc --- /dev/null +++ b/dist/lib/ecdsa.js @@ -0,0 +1,143 @@ +"use strict"; +/** + * Base conversion functions for ECDSA. The Multikey definition requires the usage of a compressed public key + * which must be compressed when creating the Multikey representation, and decompressed for the JWK conversion. + * + * The two exported functions, used by the rest of the package, just branch out to the internal functions that do the + * key (de)compression itself. + * + * @module + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JWKToMultikeyBinary = JWKToMultikeyBinary; +exports.multikeyBinaryToJWK = multikeyBinaryToJWK; +const common_1 = require("./common"); +const base64 = require("./encodings/base64"); +/** + * Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. + * The final encoding, with preambles, are done in the general level. + * + * For ECDSA, the compressed form must be calculated, by adding an extra byte signaling which of the + * two possible 'y' values are used. + * + * (The y value is set as optional in the signature but that is only to make TypeScript happy. A missing + * value generates an error) + * + * @param cl - choice between P-256 and P-384 + * @param x - x value for the elliptical curve + * @param d - d (private) value for the elliptical curve + * @param y - y value for the elliptical curve + * @returns + */ +function JWKToMultikeyBinary(cl, x, d, y) { + if (y === undefined) { + throw new Error("ECDSA encoding requires a 'y' value."); + } + return { + public: compressPublicKey(cl, x, y), + secret: d + }; +} +/** + * Convert the multikey values to their JWK equivalents. The final `x` and `d` values are encoded + * in base64 and then the relevant JWK structure are created + * + * For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a + * constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must + * do some extra processing. + * + * @param cl - choice between P-256 and P-384 + * @param xb - binary version of the x value for the elliptical curve + * @param db - binary version of the d value for the elliptical curve + * @returns + */ +function multikeyBinaryToJWK(cl, xb, db) { + // The extra complication with ECDSA: the multikey is the compressed 'x' value, the 'y' value + // must be calculated. + const uncompressed = uncompressPublicKey(cl, xb); + const x = base64.encode(uncompressed.x); + const y = base64.encode(uncompressed.y); + const output = { + public: { + kty: "EC", + crv: (cl === common_1.CryptoCurves.ECDSA_256) ? "P-256" : "P-384", + x, + y, + key_ops: [ + "verify" + ], + ext: true, + } + }; + if (db !== undefined) { + output.secret = { + kty: "EC", + crv: (cl === common_1.CryptoCurves.ECDSA_256) ? "P-256" : "P-384", + x, + y, + d: base64.encode(db), + key_ops: [ + "sign" + ], + ext: true + }; + } + return output; +} +/************************************************************************ + * + * Internal utility functions for key (de)compression. Some parts of the code below comes from + * a Perplexity.ai prompt. (I wish there was a better documentation of the + * packages instead...) + * +*************************************************************************/ +const p384_1 = require("@noble/curves/p384"); +const p256_1 = require("@noble/curves/p256"); +// Utility function to convert Uint8Array to hex string +function uint8ArrayToHex(uint8Array) { + return Array.from(uint8Array) + .map((byte) => byte.toString(16).padStart(2, '0')) + .join(''); +} +// Utility function to convert hex string to Uint8Array +function hexToUint8Array(hex) { + const result = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + result[i / 2] = parseInt(hex.substr(i, 2), 16); + } + return result; +} +/** + * Compress the public key. Could be done "manually" (look at the parity of the `y` value, and add a byte at the start of the `x`), but + * I was lazy and relied on the curve libraries' methods + * + * @param curve + * @param x + * @param y + * @returns + */ +function compressPublicKey(curve, x, y) { + const xBigInt = BigInt(`0x${uint8ArrayToHex(x)}`); + const yBigInt = BigInt(`0x${uint8ArrayToHex(y)}`); + const point = (curve === common_1.CryptoCurves.ECDSA_256) ? new p256_1.p256.ProjectivePoint(xBigInt, yBigInt, 1n) : new p384_1.p384.ProjectivePoint(xBigInt, yBigInt, 1n); + return point.toRawBytes(true); +} +/** + * Uncompress the compressed public key. The compressed `x` value (minus its first byte) must be by plugged in the curve equation to get the possible `y` values. + * The curve equation makes it difficult to do it "manually", hence the reliance on the external package. + * + * @param curve + * @param compressedKey + * @returns + */ +function uncompressPublicKey(curve, compressedKey) { + const point = (curve === common_1.CryptoCurves.ECDSA_256) ? p256_1.p256.ProjectivePoint.fromHex(compressedKey) : p384_1.p384.ProjectivePoint.fromHex(compressedKey); + const uncompressedKey = point.toRawBytes(false); + // The 'uncompressed key is a concatenation of the x and y values, plus an extra value at the start. The latter must be disposed off, and + // the remaining array to be cut into two. + const keyLength = (curve === common_1.CryptoCurves.ECDSA_256) ? 32 : 48; + const joinedXY = uncompressedKey.slice(1); + const x = joinedXY.slice(0, keyLength); + const y = joinedXY.slice(keyLength); + return { x, y }; +} diff --git a/dist/lib/eddsa.d.ts b/dist/lib/eddsa.d.ts new file mode 100644 index 0000000..1311adc --- /dev/null +++ b/dist/lib/eddsa.d.ts @@ -0,0 +1,35 @@ +/** + * Base conversion functions for EDDSA. The functions are straightforward, but the interfaces are made so that + * they coincide with the companion functions in ECDSA. + * + * @module + */ +import { JWKKeyPair, MultikeyPairBinary, CryptoCurves } from "./common"; +/** + * Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. + * The final encoding, with preambles, are done in the general level. + * + * For EDDSA, this is essentially, an empty function, which simply returns the `x` and `d` values. The + * interface is there to be reused by the ECDSA equivalent, which must do some extra processing. + * + * @param _cl - unused in this function, just a placeholder + * @param x - x value for the elliptical curve, as extracted from JWK + * @param d - d (private) value for the elliptical curve, as extracted from JWK + * @param _y - unused in this function, just a placeholder + * @returns + */ +export declare function JWKToMultikeyBinary(_cl: CryptoCurves, x: Uint8Array, d: Uint8Array | undefined, _y?: Uint8Array): MultikeyPairBinary; +/** + * Convert the multikey values to their JWK equivalents. The final `x` and `d` values are encoded + * in base64 and then the relevant JWK structure are created + * + * For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a + * constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must + * do some extra processing. + * + * @param _cl - unused in this function, just a placeholder + * @param xb - binary version of the x value for the elliptical curve + * @param db - binary version of the d value for the elliptical curve + * @returns + */ +export declare function multikeyBinaryToJWK(_cl: CryptoCurves, xb: Uint8Array, db?: Uint8Array): JWKKeyPair; diff --git a/dist/lib/eddsa.js b/dist/lib/eddsa.js new file mode 100644 index 0000000..90fb41e --- /dev/null +++ b/dist/lib/eddsa.js @@ -0,0 +1,64 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JWKToMultikeyBinary = JWKToMultikeyBinary; +exports.multikeyBinaryToJWK = multikeyBinaryToJWK; +const base64 = require("./encodings/base64"); +/** + * Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. + * The final encoding, with preambles, are done in the general level. + * + * For EDDSA, this is essentially, an empty function, which simply returns the `x` and `d` values. The + * interface is there to be reused by the ECDSA equivalent, which must do some extra processing. + * + * @param _cl - unused in this function, just a placeholder + * @param x - x value for the elliptical curve, as extracted from JWK + * @param d - d (private) value for the elliptical curve, as extracted from JWK + * @param _y - unused in this function, just a placeholder + * @returns + */ +function JWKToMultikeyBinary(_cl, x, d, _y) { + return { + public: x, + secret: d, + }; +} +/** + * Convert the multikey values to their JWK equivalents. The final `x` and `d` values are encoded + * in base64 and then the relevant JWK structure are created + * + * For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a + * constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must + * do some extra processing. + * + * @param _cl - unused in this function, just a placeholder + * @param xb - binary version of the x value for the elliptical curve + * @param db - binary version of the d value for the elliptical curve + * @returns + */ +function multikeyBinaryToJWK(_cl, xb, db) { + const x = base64.encode(xb); + const output = { + public: { + kty: "OKP", + crv: "Ed25519", + x, + key_ops: [ + "verify" + ], + ext: true + } + }; + if (db !== undefined) { + output.secret = { + kty: "OKP", + crv: "Ed25519", + x, + d: base64.encode(db), + key_ops: [ + "sign" + ], + ext: true + }; + } + return output; +} diff --git a/dist/lib/encodings/base58/baseN.d.ts b/dist/lib/encodings/base58/baseN.d.ts new file mode 100644 index 0000000..202e953 --- /dev/null +++ b/dist/lib/encodings/base58/baseN.d.ts @@ -0,0 +1,21 @@ +/** + * BaseN-encodes a Uint8Array using the given alphabet. + * + * @param {Uint8Array} input - The bytes to encode in a Uint8Array. + * @param {string} alphabet - The alphabet to use for encoding. + * @param {number} maxline - The maximum number of encoded characters per line + * to use, defaults to none. + * + * @returns {string} The baseN-encoded output string. + */ +export function encode(input: Uint8Array, alphabet: string, maxline: number): string; +/** + * Decodes a baseN-encoded (using the given alphabet) string to a + * Uint8Array. + * + * @param {string} input - The baseN-encoded input string. + * @param {string} alphabet - The alphabet to use for decoding. + * + * @returns {Uint8Array} The decoded bytes in a Uint8Array. + */ +export function decode(input: string, alphabet: string): Uint8Array; diff --git a/dist/lib/encodings/base58/baseN.js b/dist/lib/encodings/base58/baseN.js new file mode 100644 index 0000000..45cf4d0 --- /dev/null +++ b/dist/lib/encodings/base58/baseN.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.encode = encode; +exports.decode = decode; +/** + * Base-N/Base-X encoding/decoding functions. + * + * Original implementation from base-x: + * https://github.com/cryptocoinjs/base-x + * + * Which is MIT licensed: + * + * The MIT License (MIT) + * + * Copyright base-x contributors (c) 2016 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +// baseN alphabet indexes +const _reverseAlphabets = {}; +/** + * BaseN-encodes a Uint8Array using the given alphabet. + * + * @param {Uint8Array} input - The bytes to encode in a Uint8Array. + * @param {string} alphabet - The alphabet to use for encoding. + * @param {number} maxline - The maximum number of encoded characters per line + * to use, defaults to none. + * + * @returns {string} The baseN-encoded output string. + */ +function encode(input, alphabet, maxline) { + if (!(input instanceof Uint8Array)) { + throw new TypeError('"input" must be a Uint8Array.'); + } + if (typeof alphabet !== 'string') { + throw new TypeError('"alphabet" must be a string.'); + } + if (maxline !== undefined && typeof maxline !== 'number') { + throw new TypeError('"maxline" must be a number.'); + } + if (input.length === 0) { + return ''; + } + let output = ''; + let i = 0; + const base = alphabet.length; + const first = alphabet.charAt(0); + const digits = [0]; + for (i = 0; i < input.length; ++i) { + let carry = input[i]; + for (let j = 0; j < digits.length; ++j) { + carry += digits[j] << 8; + digits[j] = carry % base; + carry = (carry / base) | 0; + } + while (carry > 0) { + digits.push(carry % base); + carry = (carry / base) | 0; + } + } + // deal with leading zeros + for (i = 0; input[i] === 0 && i < input.length - 1; ++i) { + output += first; + } + // convert digits to a string + for (i = digits.length - 1; i >= 0; --i) { + output += alphabet[digits[i]]; + } + if (maxline) { + const regex = new RegExp('.{1,' + maxline + '}', 'g'); + output = output.match(regex).join('\r\n'); + } + return output; +} +/** + * Decodes a baseN-encoded (using the given alphabet) string to a + * Uint8Array. + * + * @param {string} input - The baseN-encoded input string. + * @param {string} alphabet - The alphabet to use for decoding. + * + * @returns {Uint8Array} The decoded bytes in a Uint8Array. + */ +function decode(input, alphabet) { + if (typeof input !== 'string') { + throw new TypeError('"input" must be a string.'); + } + if (typeof alphabet !== 'string') { + throw new TypeError('"alphabet" must be a string.'); + } + if (input.length === 0) { + return new Uint8Array(); + } + let table = _reverseAlphabets[alphabet]; + if (!table) { + // compute reverse alphabet + table = _reverseAlphabets[alphabet] = []; + for (let i = 0; i < alphabet.length; ++i) { + table[alphabet.charCodeAt(i)] = i; + } + } + // remove whitespace characters + input = input.replace(/\s/g, ''); + const base = alphabet.length; + const first = alphabet.charAt(0); + const bytes = [0]; + for (let i = 0; i < input.length; i++) { + const value = table[input.charCodeAt(i)]; + if (value === undefined) { + return; + } + let carry = value; + for (let j = 0; j < bytes.length; ++j) { + carry += bytes[j] * base; + bytes[j] = carry & 0xff; + carry >>= 8; + } + while (carry > 0) { + bytes.push(carry & 0xff); + carry >>= 8; + } + } + // deal with leading zeros + for (let k = 0; input[k] === first && k < input.length - 1; ++k) { + bytes.push(0); + } + return new Uint8Array(bytes.reverse()); +} diff --git a/dist/lib/encodings/base58/index.d.ts b/dist/lib/encodings/base58/index.d.ts new file mode 100644 index 0000000..54d9ce1 --- /dev/null +++ b/dist/lib/encodings/base58/index.d.ts @@ -0,0 +1,2 @@ +export function encode(input: any, maxline: any): string; +export function decode(input: any): Uint8Array; diff --git a/dist/lib/encodings/base58/index.js b/dist/lib/encodings/base58/index.js new file mode 100644 index 0000000..cdefb2a --- /dev/null +++ b/dist/lib/encodings/base58/index.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.encode = encode; +exports.decode = decode; +/** + * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. + */ +const baseN_js_1 = require("./baseN.js"); +// base58 characters (Bitcoin alphabet) +const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; +function encode(input, maxline) { + return (0, baseN_js_1.encode)(input, alphabet, maxline); +} +function decode(input) { + return (0, baseN_js_1.decode)(input, alphabet); +} diff --git a/dist/lib/encodings/base64.d.ts b/dist/lib/encodings/base64.d.ts new file mode 100644 index 0000000..84b1a38 --- /dev/null +++ b/dist/lib/encodings/base64.d.ts @@ -0,0 +1,20 @@ +/*************************************************************************************************/ +/*************************************************************************************************/ +/** + * Convert an array buffer to base64url value. + * + * (Created with the help of chatgpt...) + * + * @param arrayBuffer + * @returns + */ +export declare function encode(bytes: Uint8Array): string; +/** + * Convert a base64url value to Uint8Array + * + * (Created with the help of chatgpt...) + * + * @param string + * @returns + */ +export declare function decode(url: string): Uint8Array; diff --git a/dist/lib/encodings/base64.js b/dist/lib/encodings/base64.js new file mode 100644 index 0000000..e87d51b --- /dev/null +++ b/dist/lib/encodings/base64.js @@ -0,0 +1,49 @@ +"use strict"; +/*************************************************************************************************/ +/* Base64 Encoding */ +/*************************************************************************************************/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.encode = encode; +exports.decode = decode; +/* + * These two came from perplexity, hopefully it is correct... + */ +const base64ToUrl = (base64String) => { + return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +}; +const urlToBase64 = (base64Url) => { + return base64Url.replace(/-/g, '+').replace(/_/g, '/'); +}; +/** + * Convert an array buffer to base64url value. + * + * (Created with the help of chatgpt...) + * + * @param arrayBuffer + * @returns + */ +function encode(bytes) { + let binary = ""; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + const base64String = btoa(binary); + return base64ToUrl(base64String); +} +/** + * Convert a base64url value to Uint8Array + * + * (Created with the help of chatgpt...) + * + * @param string + * @returns + */ +function decode(url) { + const base64string = urlToBase64(url); + const binary = atob(base64string); + const byteArray = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + byteArray[i] = binary.charCodeAt(i); + } + return byteArray; +} diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e2ac661 --- /dev/null +++ b/docs/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/assets/26e93147f10415a0ed4a.svg b/docs/assets/26e93147f10415a0ed4a.svg new file mode 100644 index 0000000..09d06a6 --- /dev/null +++ b/docs/assets/26e93147f10415a0ed4a.svg @@ -0,0 +1,6 @@ + + +TypeScript logo + + + diff --git a/docs/assets/75c9471662e97ee24f29.svg b/docs/assets/75c9471662e97ee24f29.svg new file mode 100644 index 0000000..a8c3280 --- /dev/null +++ b/docs/assets/75c9471662e97ee24f29.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/docs/assets/db90e4df2373980c497d.svg b/docs/assets/db90e4df2373980c497d.svg new file mode 100644 index 0000000..074613e --- /dev/null +++ b/docs/assets/db90e4df2373980c497d.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/docs/assets/hierarchy.css b/docs/assets/hierarchy.css new file mode 100644 index 0000000..d3e55a5 --- /dev/null +++ b/docs/assets/hierarchy.css @@ -0,0 +1 @@ +.tree{background:var(--color-panel);margin-top:20px}.tree-config{display:flex;gap:8px;justify-content:end;padding:8px}.tree-config__button{align-items:center;background-color:transparent;border:0;color:var(--color-toolbar-text);cursor:pointer;display:flex;height:20px;justify-content:center;opacity:.8;padding:0;width:20px}.tree-config__button:hover{opacity:.9}.tree-content{color:var(--color-text);font-size:.85rem;font-weight:400;line-height:1.5;padding:0 20px 20px;position:relative}.tree-content span{font-size:13px;letter-spacing:.4px}.tree-content ul{list-style:none;margin:0;padding-left:5px}.tree-content ul li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding-bottom:5px;padding-left:15px;padding-top:5px;position:relative}.tree-content ul li:before{height:1px;margin:auto;top:15px;width:10px}.tree-content ul li:after,.tree-content ul li:before{background-color:#666;content:"";left:0;position:absolute}.tree-content ul li:after{bottom:0;height:100%;top:0;width:1px}.tree-content ul li:last-child:after{height:15px}.tree-content ul a{cursor:pointer}.category:not([data-id=root]){display:none}.category:not([data-id=root])._open{display:block}.category__title{color:var(--color-text-aside);cursor:pointer}.category__link,.category__title{align-items:center;display:flex;flex-shrink:0;text-decoration:none}a.category__link:hover,a.category__title:hover{text-decoration:underline}.category__title._open .category__folder{background:url(db90e4df2373980c497d.svg)}.category__folder{background:url(75c9471662e97ee24f29.svg);display:inline-block;flex-shrink:0;height:15px;margin-right:6px;width:15px}.category__link._active{color:inherit}.category__link--ts:before{background-image:url(26e93147f10415a0ed4a.svg);content:"";display:inline-block;flex-shrink:0;height:15px;margin:0 7px 2px 0;vertical-align:middle;width:15px} \ No newline at end of file diff --git a/docs/assets/hierarchy.js b/docs/assets/hierarchy.js new file mode 100644 index 0000000..456c3ad --- /dev/null +++ b/docs/assets/hierarchy.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={820:(e,t,s)=>{s.r(t)},78:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.HierarchyManager=void 0;const a=s(100);t.HierarchyManager=class{constructor(){this.stateManager=new a.StateManager,this.titleSelector=".js-category-title",this.listSelector=".js-category-list"}init(){this.addListeners(),this.initSaved(),this.openCurrentPath()}openPathAndSave(e){this.openPath(e),this.stateManager.addOpenedPath(e)}openPath(e){const t=document.querySelector(`${this.listSelector}[data-id="${e}"]`);t&&(t.classList.add("_open"),t.parentNode?.querySelector(this.titleSelector)?.classList.add("_open"))}closePath(e){const t=document.querySelector(`${this.listSelector}[data-id="${e}"]`);t&&(t.classList.remove("_open"),t.parentNode?.querySelector(this.titleSelector)?.classList.remove("_open"),this.stateManager.removeOpenedPath(e))}closePathWithChildren(e){this.closePath(e);const t=document.querySelector(`${this.listSelector}[data-id="${e}"]`);if(!t)return;const s=t.querySelectorAll(this.listSelector);for(const e of s)this.closePath(e.dataset.id||"")}togglePath(e){const t=document.querySelector(`${this.listSelector}[data-id="${e}"]`);t&&(t.classList.contains("_open")?this.closePathWithChildren(e):this.openPathAndSave(e))}addListeners(){const e=document.querySelectorAll('.js-category-title:not([data-id="root"])');for(const t of e)t.addEventListener("click",(()=>{const e=t.dataset.id||"";this.togglePath(e)}));this.addExpandListener(),this.addCollapseListener(),this.addTargetListener()}addExpandListener(){document.querySelector(".js-tree-expand")?.addEventListener("click",(()=>{const e=document.querySelectorAll(this.listSelector);for(const t of e){const e=t.dataset.id||"";this.openPathAndSave(e)}}))}addCollapseListener(){document.querySelector(".js-tree-collapse")?.addEventListener("click",(()=>{const e=document.querySelectorAll(this.listSelector);for(const t of e){const e=t.dataset.id||"";this.closePath(e)}}))}addTargetListener(){document.querySelector(".js-tree-target")?.addEventListener("click",(()=>{this.openCurrentPath()?.scrollIntoView()}))}initSaved(){const e=this.stateManager.getOpenedPaths();for(const t of e)this.openPath(t)}openCurrentPath(){const e=window.location.pathname.split("/"),t=`/${e[e.length-2]||""}/${e[e.length-1]||""}`,s=document.querySelector(`.js-category-link[data-id="${t}"]`);if(!s)return null;s.classList.add("_active");let a=s.closest(this.listSelector);for(;a;){const e=a.dataset.id||"";this.openPath(e),a=a.parentNode.closest(this.listSelector)}return s}}},100:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.StateManager=void 0;t.StateManager=class{constructor(){this.openedPathLsKey="opened-path-state",this.openedPaths=[];const e=localStorage.getItem("opened-path-state");this.openedPaths=e?JSON.parse(e):[]}addOpenedPath(e){this.openedPaths.push(e),this.updateState()}removeOpenedPath(e){this.openedPaths=this.openedPaths.filter((t=>t!==e)),this.updateState()}getOpenedPaths(){return this.openedPaths}updateState(){localStorage.setItem(this.openedPathLsKey,JSON.stringify(this.openedPaths))}}}},t={};function s(a){var o=t[a];if(void 0!==o)return o.exports;var r=t[a]={exports:{}};return e[a](r,r.exports,s),r.exports}s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};(()=>{const e=s(78);s(820);(new e.HierarchyManager).init()})()})(); \ No newline at end of file diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css new file mode 100644 index 0000000..5674cf3 --- /dev/null +++ b/docs/assets/highlight.css @@ -0,0 +1,22 @@ +:root { + --light-code-background: #FFFFFF; + --dark-code-background: #1E1E1E; +} + +@media (prefers-color-scheme: light) { :root { + --code-background: var(--light-code-background); +} } + +@media (prefers-color-scheme: dark) { :root { + --code-background: var(--dark-code-background); +} } + +:root[data-theme='light'] { + --code-background: var(--light-code-background); +} + +:root[data-theme='dark'] { + --code-background: var(--dark-code-background); +} + +pre, code { background: var(--code-background); } diff --git a/docs/assets/icons.js b/docs/assets/icons.js new file mode 100644 index 0000000..e88e8ca --- /dev/null +++ b/docs/assets/icons.js @@ -0,0 +1,18 @@ +(function() { + addIcons(); + function addIcons() { + if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); + const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + svg.innerHTML = `""`; + svg.style.display = "none"; + if (location.protocol === "file:") updateUseElements(); + } + + function updateUseElements() { + document.querySelectorAll("use").forEach(el => { + if (el.getAttribute("href").includes("#icon-")) { + el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); + } + }); + } +})() \ No newline at end of file diff --git a/docs/assets/icons.svg b/docs/assets/icons.svg new file mode 100644 index 0000000..e371b8b --- /dev/null +++ b/docs/assets/icons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/main.js b/docs/assets/main.js new file mode 100644 index 0000000..3572881 --- /dev/null +++ b/docs/assets/main.js @@ -0,0 +1,60 @@ +"use strict"; +window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings."}; +"use strict";(()=>{var Pe=Object.create;var ie=Object.defineProperty;var Oe=Object.getOwnPropertyDescriptor;var _e=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,Me=Object.prototype.hasOwnProperty;var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _e(e))!Me.call(t,i)&&i!==n&&ie(t,i,{get:()=>e[i],enumerable:!(r=Oe(e,i))||r.enumerable});return t};var Ae=(t,e,n)=>(n=t!=null?Pe(Re(t)):{},De(e||!t||!t.__esModule?ie(n,"default",{value:t,enumerable:!0}):n,t));var ue=Fe((ae,le)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,u],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[u+1]*i[d+1],u+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}s.str.length==1&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),p;m in s.node.edges?p=s.node.edges[m]:(p=new t.TokenSet,s.node.edges[m]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof ae=="object"?le.exports=n():e.lunr=n()}(this,function(){return t})})()});var se=[];function G(t,e){se.push({selector:e,constructor:t})}var U=class{constructor(){this.alwaysVisibleMember=null;this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){se.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!Ve(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function Ve(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var oe=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var pe=Ae(ue());async function ce(t,e){if(!window.searchData)return;let n=await fetch(window.searchData),r=new Blob([await n.arrayBuffer()]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();t.data=i,t.index=pe.Index.load(i.index),e.classList.remove("loading"),e.classList.add("ready")}function fe(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:t.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{ce(e,t)}),ce(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");i.addEventListener("mouseup",()=>{te(t)}),r.addEventListener("focus",()=>t.classList.add("has-focus")),He(t,i,r,e)}function He(t,e,n,r){n.addEventListener("input",oe(()=>{Ne(t,e,n,r)},200)),n.addEventListener("keydown",i=>{i.key=="Enter"?Be(e,t):i.key=="ArrowUp"?(de(e,n,-1),i.preventDefault()):i.key==="ArrowDown"&&(de(e,n,1),i.preventDefault())}),document.body.addEventListener("keypress",i=>{i.altKey||i.ctrlKey||i.metaKey||!n.matches(":focus")&&i.key==="/"&&(i.preventDefault(),n.focus())}),document.body.addEventListener("keyup",i=>{t.classList.contains("has-focus")&&(i.key==="Escape"||!e.matches(":focus-within")&&!n.matches(":focus"))&&(n.blur(),te(t))})}function te(t){t.classList.remove("has-focus")}function Ne(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=he(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` + ${he(l.parent,i)}.${d}`);let m=document.createElement("li");m.classList.value=l.classes??"";let p=document.createElement("a");p.href=r.base+l.url,p.innerHTML=u+d,m.append(p),p.addEventListener("focus",()=>{e.querySelector(".current")?.classList.remove("current"),m.classList.add("current")}),e.appendChild(m)}}function de(t,e,n){let r=t.querySelector(".current");if(!r)r=t.querySelector(n==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let i=r;if(n===1)do i=i.nextElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);else do i=i.previousElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);i?(r.classList.remove("current"),i.classList.add("current")):n===-1&&(r.classList.remove("current"),e.focus())}}function Be(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),te(e)}}function he(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ee(t.substring(s,o)),`${ee(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ee(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function ee(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var I=class{constructor(e){this.el=e.el,this.app=e.app}};var F="mousedown",ye="mousemove",N="mouseup",J={x:0,y:0},me=!1,ne=!1,qe=!1,D=!1,ve=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(ve?"is-mobile":"not-mobile");ve&&"ontouchstart"in document.documentElement&&(qe=!0,F="touchstart",ye="touchmove",N="touchend");document.addEventListener(F,t=>{ne=!0,D=!1;let e=F=="touchstart"?t.targetTouches[0]:t;J.y=e.pageY||0,J.x=e.pageX||0});document.addEventListener(ye,t=>{if(ne&&!D){let e=F=="touchstart"?t.targetTouches[0]:t,n=J.x-(e.pageX||0),r=J.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(N,()=>{ne=!1});document.addEventListener("click",t=>{me&&(t.preventDefault(),t.stopImmediatePropagation(),me=!1)});var X=class extends I{constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(N,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(F,n=>this.onDocumentPointerDown(n)),document.addEventListener(N,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var re;try{re=localStorage}catch{re={getItem(){return null},setItem(){}}}var Q=re;var ge=document.head.appendChild(document.createElement("style"));ge.dataset.for="filters";var Y=class extends I{constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),ge.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } +`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=Q.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){Q.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),this.app.updateIndexVisibility()}};var Z=class extends I{constructor(e){super(e),this.summary=this.el.querySelector(".tsd-accordion-summary"),this.icon=this.summary.querySelector("svg"),this.key=`tsd-accordion-${this.summary.dataset.key??this.summary.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`;let n=Q.getItem(this.key);this.el.open=n?n==="true":this.el.open,this.el.addEventListener("toggle",()=>this.update());let r=this.summary.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)}),this.update()}update(){this.icon.style.transform=`rotate(${this.el.open?0:-90}deg)`,Q.setItem(this.key,this.el.open.toString())}};function Ee(t){let e=Q.getItem("tsd-theme")||"os";t.value=e,xe(e),t.addEventListener("change",()=>{Q.setItem("tsd-theme",t.value),xe(t.value)})}function xe(t){document.documentElement.dataset.theme=t}var K;function we(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Le),Le())}async function Le(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let n=await(await fetch(window.navigationData)).arrayBuffer(),r=new Blob([n]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();K=t.dataset.base,K.endsWith("/")||(K+="/"),t.innerHTML="";for(let s of i)Se(s,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Se(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',be(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let u of t.children)Se(u,l,i)}else be(t,r,t.class)}function be(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));r.href=K+t.path,n&&(r.className=n),location.pathname===r.pathname&&r.classList.add("current"),t.kind&&(r.innerHTML=``),r.appendChild(document.createElement("span")).textContent=t.text}else e.appendChild(document.createElement("span")).textContent=t.text}G(X,"a[data-toggle]");G(Z,".tsd-accordion");G(Y,".tsd-filter-item input[type=checkbox]");var Te=document.getElementById("tsd-theme");Te&&Ee(Te);var $e=new U;Object.defineProperty(window,"app",{value:$e});fe();we();})(); +/*! Bundled license information: + +lunr/lunr.js: + (** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + *) + (*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + *) + (*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + *) + (*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + *) +*/ diff --git a/docs/assets/navigation.js b/docs/assets/navigation.js new file mode 100644 index 0000000..1e3afda --- /dev/null +++ b/docs/assets/navigation.js @@ -0,0 +1 @@ +window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE7WW226CMACG36XX7CAe5rzb1BvJEpOReGHMUmiNjdCSUoxk8d0XBbSFUmribuXr59+fHlj/AoGPAkwAoQgfgQMSKHZgAmKGsginL5efn3cijoAD9oQiMHEdEO5IhDimYLK+CuIsEmSPc58tVt5NtM1oKAijlUrBVPFocHKuusXK89lXCbfrFKyh20jCiARAnzxkccxoc+4RCX6KZ1YFkHSx8jycLyHhusCSTkYNHZC0mpmVU4YN1oRjGAcR9tmU54lgMyhgh1s3xPzuGj0QKjDfwlDtta0GdziSfM1lIPJEFemXgPv6/tYbuhqVTbj2PvXxztwnoZDn95iLESZ/0fk04wec3syYZrEilTFVN27IPJz75wo7dRXYLlyWa8Pwciqk6+XMEUphBUvZDpATGNS2pAqr6r5iDVEK3eHIWlznu9z98eAut8wb3NMIpqnPLPqtkV01h21ibeQa/dTrjjzDIUOYdycuQcvADa0pbwnbxJ1Ty7glaBm3oTXFLWFj3Pl09v1RPwb0y+xGmqZf7XD1BtAfWQrcPK3kmzZk9IC5aLtQLw8f80khCx/2YSFL7T8v8Hln66d8eWQ1YeXv6leJmrCQagYYph4roLHVQq8ZYC4BtZeA/qME9L8lIPsSNqfNH7MA1tZNCwAA" \ No newline at end of file diff --git a/docs/assets/search.js b/docs/assets/search.js new file mode 100644 index 0000000..4873e82 --- /dev/null +++ b/docs/assets/search.js @@ -0,0 +1 @@ +window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE72aW2/iRhTHv8vk1c0yFxvM226Sh25aadVNuw9WtAI8q3oBGxmTFEX57tX4gs/xHMMYaJ8SwZz/ucxvLj7mjeXZ65ZNoze2TNKYTYXH0tlasylL0lj/wzy2y1dsytZZvFvp7Yfy09u/i/WKeWyxmm23esumjL17jUKgDhLr3apIlnr/lH3+9niQ+rFLF0WSpY0YGkVIe2wzy3VaHGIifX3+9viU/V5L9fpCo4b5aiuzSuYfFtl6naVWeVbJ/Hv1lWuNku3nb4+Pev9lluRE2EAQjjweOgiwx2dTAhevcOxlfje5nq3nK/2U3eX7TZHdz4rZce+UxRkxCD+AoHTLnaSFzn/MFngCL6o2HwmQ926+ShbD3N0cjE55BZZ9AWz1ItfFwAAORucFIEbhmPvtqrHWZrHf4ADc1uXJCSbZplO+EG1ikh/1vhSdz7Z6qPMbUuJk9VG+RwG4KDhS4tzg+ubrU5LO8v2A2CqD/2WB9rgdsFCJTC9ZsH0BuS/cowFNDtFUe+/dLn/R20NMOt2tUThw1DkT0hLxcHf/9eN3OVEDnN1Ao5OJo4yOxCD8YHgMldE1Yri///pxkP/a4Fzf3Sl/1Psns0ufiqEZd9m0f/nz02+/3g3ydXOwcUz5kBEZwdeHuz8enoZFcLA5P4LuUfmlvvX0H5XNiDMKLls/D3G8nTVS7SS/zPJkNu9cZ/HYC/0u4u1M+IGr6+7wK3iXEzXEOxx+zvWkM8F3xvQpOz3PnYGXJb7o8Uqm3Rn8C79e2vd6kcU6P5l1Pe4qSXd9Hsu5HnvNlB9St5TrcVdJuevzWMr12LNShkvLnH+dewK9qNqBF972D5sqepikL0xo7KV3xkVexj/Y5U1r6H5ilMn1BmLOk3PiqO3ODqPbEklfdN5eWnFPpPzuWo0jKDmgfQSjPKuJBP0OaCXRfnH1tDlmyNqV37hWDkXVeZrCeVSyxPjTuVSxHp25SurY/FX+ifHD/XcqGfdWMv5vKhmfX8n4GpWMz69kx7/ioZIjVIRus8zuBN+gUW7d1K4jsmlDuOqMu8yZi6NTTp69+r/pG3vR+TbJUjZl4lbehsxjPxK9ik1/vXLtMbOjGnuPxdliV/77XA/7Sy+KLDeDq9EfRsyLRp70b0Wgnp+9qDEuvyg/aDTaT0pDzryIe3J0y30fGXLLkCNDwbxIUIbCMhTIUDIvklSo0jKUyFAxL1KUobIMFTL0mRf5lKFvGfrIMGBeFFCGgWUYIMMx86IxVZyxZThGhhPmRRNPBLfjkCPDiWU4QYYh86KQMgwtwxADYHjgIxIBGx7eoafEh+aHAAgTxA0XXFD15TZEHFPEDRuc5IjbIHFMEjd8cJIlbsPEMU3c75sjbvPEMVA86JsmbiPFMVPckMJJjrmNFcdccUMLJ1nmNlocs8UNMXxMGtt4ccyXKPmaUMbC5ktgvkTJV0ga23yJzg4l+iZKEHsUxkvIvokSNl0C0yUML4LcjYVNl8B0CQOM4KSxjZfAeAlDjCBXlLD5EpgvYYgR5IoSNl8C8yUMMUKR54HNl8B8ibDf2OZLYL6kIUb4lLG0+ZKYL8n7jW2+JOZLlkdgQBrbgMnOKSj7jYmDEBMmS8LIBSltwiQmTJaEkQtS2oRJTJgsCSMXpLQJk5gwaZiR5MKQNmESEyYNM5JcGNImTGLCZNh3zZE2YBIDpkZ9Nx1l86UwX8oQI8kFqWy+FOZLGWKkJD3bfCnMlyqvWeSaUjZfqnPTMsRI8qhRxGUL86X8/rBtvhTmSwX9Ydt8KcyX6r12KRsvhfFSk96bjLLxUhgvFfbeoZTNV/1R+TRgnvN1/Gv1VBBF1fNDkcVNC/KNfa8fGYTfPJO8MTli07d3j0lu/r63jwrlp4enBfNdGUslqlNbNACiohaVQ0Q3hw4xUFWtqphU9iJ0U80XVcMLqIUgxsBRpSjbVa2IHAGRsaOI6WQt6g5lK8XBRPAhUku9j8vGI8htAsLyh2kV1ds2EBgolHAjQ9evSzbt+w0QnAB6boXX9QsQWk8CPbfClXrVS1KQ5xhMgBtWlU75whfoAPj5xF3HRkKAiKRyU6o6TyAagIIYuWvQpeZAzI2r+jd8rQZYMW7hJNufr8ul3m/KfksrBHYDt9ok26ZB1ZUCS88tKzoiMF3jylC58fjzdVlk60O7BxQc1LtWdCsaUpzXrUOwb4E1oxplt9ThTw+BICB1gEzdrgc6gDDptnhA6xrogMopt00LNG6BDkBDuWFGzSMH1PP6lFVuewyexCL7+bpEAcJlIGtlt121bzVwMAec15JucwElbeo4iJUPK6aVN4yxvmC41ZO8WYDpEW6BNTJFVp2c3SMYHAJus9H8hquVADt3fdvhfn3rEQMkl3q/bn8vByoIlgh3E2x+1tWKgLtBWMcY1DG67QOVZG+MYF/hDoLPHtskG71KUs2m0fP7+78WJnEkXi4AAA=="; \ No newline at end of file diff --git a/docs/assets/style.css b/docs/assets/style.css new file mode 100644 index 0000000..9d619a6 --- /dev/null +++ b/docs/assets/style.css @@ -0,0 +1,1448 @@ +:root { + /* Light */ + --light-color-background: #f2f4f8; + --light-color-background-secondary: #eff0f1; + --light-color-warning-text: #222; + --light-color-background-warning: #e6e600; + --light-color-icon-background: var(--light-color-background); + --light-color-accent: #c5c7c9; + --light-color-active-menu-item: var(--light-color-accent); + --light-color-text: #222; + --light-color-text-aside: #6e6e6e; + --light-color-link: #1f70c2; + --light-color-focus-outline: #3584e4; + + --light-color-ts-keyword: #056bd6; + --light-color-ts-project: #b111c9; + --light-color-ts-module: var(--light-color-ts-project); + --light-color-ts-namespace: var(--light-color-ts-project); + --light-color-ts-enum: #7e6f15; + --light-color-ts-enum-member: var(--light-color-ts-enum); + --light-color-ts-variable: #4760ec; + --light-color-ts-function: #572be7; + --light-color-ts-class: #1f70c2; + --light-color-ts-interface: #108024; + --light-color-ts-constructor: var(--light-color-ts-class); + --light-color-ts-property: var(--light-color-ts-variable); + --light-color-ts-method: var(--light-color-ts-function); + --light-color-ts-call-signature: var(--light-color-ts-method); + --light-color-ts-index-signature: var(--light-color-ts-property); + --light-color-ts-constructor-signature: var(--light-color-ts-constructor); + --light-color-ts-parameter: var(--light-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --light-color-ts-type-parameter: #a55c0e; + --light-color-ts-accessor: var(--light-color-ts-property); + --light-color-ts-get-signature: var(--light-color-ts-accessor); + --light-color-ts-set-signature: var(--light-color-ts-accessor); + --light-color-ts-type-alias: #d51270; + /* reference not included as links will be colored with the kind that it points to */ + --light-color-document: #000000; + + --light-external-icon: url("data:image/svg+xml;utf8,"); + --light-color-scheme: light; + + /* Dark */ + --dark-color-background: #2b2e33; + --dark-color-background-secondary: #1e2024; + --dark-color-background-warning: #bebe00; + --dark-color-warning-text: #222; + --dark-color-icon-background: var(--dark-color-background-secondary); + --dark-color-accent: #9096a2; + --dark-color-active-menu-item: #5d5d6a; + --dark-color-text: #f5f5f5; + --dark-color-text-aside: #dddddd; + --dark-color-link: #00aff4; + --dark-color-focus-outline: #4c97f2; + + --dark-color-ts-keyword: #3399ff; + --dark-color-ts-project: #e358ff; + --dark-color-ts-module: var(--dark-color-ts-project); + --dark-color-ts-namespace: var(--dark-color-ts-project); + --dark-color-ts-enum: #f4d93e; + --dark-color-ts-enum-member: var(--dark-color-ts-enum); + --dark-color-ts-variable: #798dff; + --dark-color-ts-function: #a280ff; + --dark-color-ts-class: #8ac4ff; + --dark-color-ts-interface: #6cff87; + --dark-color-ts-constructor: var(--dark-color-ts-class); + --dark-color-ts-property: var(--dark-color-ts-variable); + --dark-color-ts-method: var(--dark-color-ts-function); + --dark-color-ts-call-signature: var(--dark-color-ts-method); + --dark-color-ts-index-signature: var(--dark-color-ts-property); + --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); + --dark-color-ts-parameter: var(--dark-color-ts-variable); + /* type literal not included as links will never be generated to it */ + --dark-color-ts-type-parameter: #e07d13; + --dark-color-ts-accessor: var(--dark-color-ts-property); + --dark-color-ts-get-signature: var(--dark-color-ts-accessor); + --dark-color-ts-set-signature: var(--dark-color-ts-accessor); + --dark-color-ts-type-alias: #ff6492; + /* reference not included as links will be colored with the kind that it points to */ + --dark-color-document: #ffffff; + + --dark-external-icon: url("data:image/svg+xml;utf8,"); + --dark-color-scheme: dark; +} + +@media (prefers-color-scheme: light) { + :root { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } +} + +html { + color-scheme: var(--color-scheme); +} + +body { + margin: 0; +} + +:root[data-theme="light"] { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-background-warning: var(--light-color-background-warning); + --color-warning-text: var(--light-color-warning-text); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-active-menu-item: var(--light-color-active-menu-item); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); + + --color-ts-keyword: var(--light-color-ts-keyword); + --color-ts-module: var(--light-color-ts-module); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-enum-member: var(--light-color-ts-enum-member); + --color-ts-variable: var(--light-color-ts-variable); + --color-ts-function: var(--light-color-ts-function); + --color-ts-class: var(--light-color-ts-class); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-constructor: var(--light-color-ts-constructor); + --color-ts-property: var(--light-color-ts-property); + --color-ts-method: var(--light-color-ts-method); + --color-ts-call-signature: var(--light-color-ts-call-signature); + --color-ts-index-signature: var(--light-color-ts-index-signature); + --color-ts-constructor-signature: var( + --light-color-ts-constructor-signature + ); + --color-ts-parameter: var(--light-color-ts-parameter); + --color-ts-type-parameter: var(--light-color-ts-type-parameter); + --color-ts-accessor: var(--light-color-ts-accessor); + --color-ts-get-signature: var(--light-color-ts-get-signature); + --color-ts-set-signature: var(--light-color-ts-set-signature); + --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); + + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); +} + +:root[data-theme="dark"] { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-background-warning: var(--dark-color-background-warning); + --color-warning-text: var(--dark-color-warning-text); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-active-menu-item: var(--dark-color-active-menu-item); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); + + --color-ts-keyword: var(--dark-color-ts-keyword); + --color-ts-module: var(--dark-color-ts-module); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-enum-member: var(--dark-color-ts-enum-member); + --color-ts-variable: var(--dark-color-ts-variable); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-constructor: var(--dark-color-ts-constructor); + --color-ts-property: var(--dark-color-ts-property); + --color-ts-method: var(--dark-color-ts-method); + --color-ts-call-signature: var(--dark-color-ts-call-signature); + --color-ts-index-signature: var(--dark-color-ts-index-signature); + --color-ts-constructor-signature: var( + --dark-color-ts-constructor-signature + ); + --color-ts-parameter: var(--dark-color-ts-parameter); + --color-ts-type-parameter: var(--dark-color-ts-type-parameter); + --color-ts-accessor: var(--dark-color-ts-accessor); + --color-ts-get-signature: var(--dark-color-ts-get-signature); + --color-ts-set-signature: var(--dark-color-ts-set-signature); + --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); + + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); +} + +*:focus-visible, +.tsd-accordion-summary:focus-visible svg { + outline: 2px solid var(--color-focus-outline); +} + +.always-visible, +.always-visible .tsd-signatures { + display: inherit !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + line-height: 1.2; +} + +h1 { + font-size: 1.875rem; + margin: 0.67rem 0; +} + +h2 { + font-size: 1.5rem; + margin: 0.83rem 0; +} + +h3 { + font-size: 1.25rem; + margin: 1rem 0; +} + +h4 { + font-size: 1.05rem; + margin: 1.33rem 0; +} + +h5 { + font-size: 1rem; + margin: 1.5rem 0; +} + +h6 { + font-size: 0.875rem; + margin: 2.33rem 0; +} + +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +.container { + max-width: 1700px; + padding: 0 2rem; +} + +/* Footer */ +footer { + border-top: 1px solid var(--color-accent); + padding-top: 1rem; + padding-bottom: 1rem; + max-height: 3.5rem; +} +footer > p { + margin: 0 1em; +} + +.container-main { + margin: 0 auto; + /* toolbar, footer, margin */ + min-height: calc(100vh - 41px - 56px - 4rem); +} + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes fade-out { + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + } +} +@keyframes fade-in-delayed { + 0% { + opacity: 0; + } + 33% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +@keyframes fade-out-delayed { + 0% { + opacity: 1; + visibility: visible; + } + 66% { + opacity: 0; + } + 100% { + opacity: 0; + } +} +@keyframes pop-in-from-right { + from { + transform: translate(100%, 0); + } + to { + transform: translate(0, 0); + } +} +@keyframes pop-out-to-right { + from { + transform: translate(0, 0); + visibility: visible; + } + to { + transform: translate(100%, 0); + } +} +body { + background: var(--color-background); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + color: var(--color-text); +} + +a { + color: var(--color-link); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a.external[target="_blank"] { + background-image: var(--external-icon); + background-position: top 3px right; + background-repeat: no-repeat; + padding-right: 13px; +} +a.tsd-anchor-link { + color: var(--color-text); +} + +code, +pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + padding: 0.2em; + margin: 0; + font-size: 0.875rem; + border-radius: 0.8em; +} + +pre { + position: relative; + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; + padding: 10px; + border: 1px solid var(--color-accent); +} +pre code { + padding: 0; + font-size: 100%; +} +pre > button { + position: absolute; + top: 10px; + right: 10px; + opacity: 0; + transition: opacity 0.1s; + box-sizing: border-box; +} +pre:hover > button, +pre > button.visible { + opacity: 1; +} + +blockquote { + margin: 1em 0; + padding-left: 1em; + border-left: 4px solid gray; +} + +.tsd-typography { + line-height: 1.333em; +} +.tsd-typography ul { + list-style: square; + padding: 0 0 0 20px; + margin: 0; +} +.tsd-typography .tsd-index-panel h3, +.tsd-index-panel .tsd-typography h3, +.tsd-typography h4, +.tsd-typography h5, +.tsd-typography h6 { + font-size: 1em; +} +.tsd-typography h5, +.tsd-typography h6 { + font-weight: normal; +} +.tsd-typography p, +.tsd-typography ul, +.tsd-typography ol { + margin: 1em 0; +} +.tsd-typography table { + border-collapse: collapse; + border: none; +} +.tsd-typography td, +.tsd-typography th { + padding: 6px 13px; + border: 1px solid var(--color-accent); +} +.tsd-typography thead, +.tsd-typography tr:nth-child(even) { + background-color: var(--color-background-secondary); +} + +.tsd-breadcrumb { + margin: 0; + padding: 0; + color: var(--color-text-aside); +} +.tsd-breadcrumb a { + color: var(--color-text-aside); + text-decoration: none; +} +.tsd-breadcrumb a:hover { + text-decoration: underline; +} +.tsd-breadcrumb li { + display: inline; +} +.tsd-breadcrumb li:after { + content: " / "; +} + +.tsd-comment-tags { + display: flex; + flex-direction: column; +} +dl.tsd-comment-tag-group { + display: flex; + align-items: center; + overflow: hidden; + margin: 0.5em 0; +} +dl.tsd-comment-tag-group dt { + display: flex; + margin-right: 0.5em; + font-size: 0.875em; + font-weight: normal; +} +dl.tsd-comment-tag-group dd { + margin: 0; +} +code.tsd-tag { + padding: 0.25em 0.4em; + border: 0.1em solid var(--color-accent); + margin-right: 0.25em; + font-size: 70%; +} +h1 code.tsd-tag:first-of-type { + margin-left: 0.25em; +} + +dl.tsd-comment-tag-group dd:before, +dl.tsd-comment-tag-group dd:after { + content: " "; +} +dl.tsd-comment-tag-group dd pre, +dl.tsd-comment-tag-group dd:after { + clear: both; +} +dl.tsd-comment-tag-group p { + margin: 0; +} + +.tsd-panel.tsd-comment .lead { + font-size: 1.1em; + line-height: 1.333em; + margin-bottom: 2em; +} +.tsd-panel.tsd-comment .lead:last-child { + margin-bottom: 0; +} + +.tsd-filter-visibility h4 { + font-size: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + margin: 0; +} +.tsd-filter-item:not(:last-child) { + margin-bottom: 0.5rem; +} +.tsd-filter-input { + display: flex; + width: -moz-fit-content; + width: fit-content; + align-items: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; +} +.tsd-filter-input input[type="checkbox"] { + cursor: pointer; + position: absolute; + width: 1.5em; + height: 1.5em; + opacity: 0; +} +.tsd-filter-input input[type="checkbox"]:disabled { + pointer-events: none; +} +.tsd-filter-input svg { + cursor: pointer; + width: 1.5em; + height: 1.5em; + margin-right: 0.5em; + border-radius: 0.33em; + /* Leaving this at full opacity breaks event listeners on Firefox. + Don't remove unless you know what you're doing. */ + opacity: 0.99; +} +.tsd-filter-input input[type="checkbox"]:focus-visible + svg { + outline: 2px solid var(--color-focus-outline); +} +.tsd-checkbox-background { + fill: var(--color-accent); +} +input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { + stroke: var(--color-text); +} +.tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { + fill: var(--color-background); + stroke: var(--color-accent); + stroke-width: 0.25rem; +} +.tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { + stroke: var(--color-accent); +} + +.settings-label { + font-weight: bold; + text-transform: uppercase; + display: inline-block; +} + +.tsd-filter-visibility .settings-label { + margin: 0.75rem 0 0.5rem 0; +} + +.tsd-theme-toggle .settings-label { + margin: 0.75rem 0.75rem 0 0; +} + +.tsd-hierarchy { + list-style: square; + margin: 0; +} +.tsd-hierarchy .target { + font-weight: bold; +} + +.tsd-full-hierarchy:not(:last-child) { + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid var(--color-accent); +} +.tsd-full-hierarchy, +.tsd-full-hierarchy ul { + list-style: none; + margin: 0; + padding: 0; +} +.tsd-full-hierarchy ul { + padding-left: 1.5rem; +} +.tsd-full-hierarchy a { + padding: 0.25rem 0 !important; + font-size: 1rem; + display: inline-flex; + align-items: center; + color: var(--color-text); +} + +.tsd-panel-group.tsd-index-group { + margin-bottom: 0; +} +.tsd-index-panel .tsd-index-list { + list-style: none; + line-height: 1.333em; + margin: 0; + padding: 0.25rem 0 0 0; + overflow: hidden; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 1rem; + grid-template-rows: auto; +} +@media (max-width: 1024px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(2, 1fr); + } +} +@media (max-width: 768px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(1, 1fr); + } +} +.tsd-index-panel .tsd-index-list li { + -webkit-page-break-inside: avoid; + -moz-page-break-inside: avoid; + -ms-page-break-inside: avoid; + -o-page-break-inside: avoid; + page-break-inside: avoid; +} + +.tsd-flag { + display: inline-block; + padding: 0.25em 0.4em; + border-radius: 4px; + color: var(--color-comment-tag-text); + background-color: var(--color-comment-tag); + text-indent: 0; + font-size: 75%; + line-height: 1; + font-weight: normal; +} + +.tsd-anchor { + position: relative; + top: -100px; +} + +.tsd-member { + position: relative; +} +.tsd-member .tsd-anchor + h3 { + display: flex; + align-items: center; + margin-top: 0; + margin-bottom: 0; + border-bottom: none; +} + +.tsd-navigation.settings { + margin: 1rem 0; +} +.tsd-navigation > a, +.tsd-navigation .tsd-accordion-summary { + width: calc(100% - 0.25rem); + display: flex; + align-items: center; +} +.tsd-navigation a, +.tsd-navigation summary > span, +.tsd-page-navigation a { + display: flex; + width: calc(100% - 0.25rem); + align-items: center; + padding: 0.25rem; + color: var(--color-text); + text-decoration: none; + box-sizing: border-box; +} +.tsd-navigation a.current, +.tsd-page-navigation a.current { + background: var(--color-active-menu-item); +} +.tsd-navigation a:hover, +.tsd-page-navigation a:hover { + text-decoration: underline; +} +.tsd-navigation ul, +.tsd-page-navigation ul { + margin-top: 0; + margin-bottom: 0; + padding: 0; + list-style: none; +} +.tsd-navigation li, +.tsd-page-navigation li { + padding: 0; + max-width: 100%; +} +.tsd-navigation .tsd-nav-link { + display: none; +} +.tsd-nested-navigation { + margin-left: 3rem; +} +.tsd-nested-navigation > li > details { + margin-left: -1.5rem; +} +.tsd-small-nested-navigation { + margin-left: 1.5rem; +} +.tsd-small-nested-navigation > li > details { + margin-left: -1.5rem; +} + +.tsd-page-navigation-section { + margin-left: 10px; +} +.tsd-page-navigation-section > summary { + padding: 0.25rem; +} +.tsd-page-navigation-section > div { + margin-left: 20px; +} +.tsd-page-navigation ul { + padding-left: 1.75rem; +} + +#tsd-sidebar-links a { + margin-top: 0; + margin-bottom: 0.5rem; + line-height: 1.25rem; +} +#tsd-sidebar-links a:last-of-type { + margin-bottom: 0; +} + +a.tsd-index-link { + padding: 0.25rem 0 !important; + font-size: 1rem; + line-height: 1.25rem; + display: inline-flex; + align-items: center; + color: var(--color-text); +} +.tsd-accordion-summary { + list-style-type: none; /* hide marker on non-safari */ + outline: none; /* broken on safari, so just hide it */ +} +.tsd-accordion-summary::-webkit-details-marker { + display: none; /* hide marker on safari */ +} +.tsd-accordion-summary, +.tsd-accordion-summary a { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + + cursor: pointer; +} +.tsd-accordion-summary a { + width: calc(100% - 1.5rem); +} +.tsd-accordion-summary > * { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; +} +.tsd-accordion .tsd-accordion-summary > svg { + margin-left: 0.25rem; + vertical-align: text-top; +} +.tsd-index-content > :not(:first-child) { + margin-top: 0.75rem; +} +.tsd-index-heading { + margin-top: 1.5rem; + margin-bottom: 0.75rem; +} + +.tsd-kind-icon { + margin-right: 0.5rem; + width: 1.25rem; + height: 1.25rem; + min-width: 1.25rem; + min-height: 1.25rem; +} +.tsd-kind-icon path { + transform-origin: center; + transform: scale(1.1); +} +.tsd-signature > .tsd-kind-icon { + margin-right: 0.8rem; +} + +.tsd-panel { + margin-bottom: 2.5rem; +} +.tsd-panel.tsd-member { + margin-bottom: 4rem; +} +.tsd-panel:empty { + display: none; +} +.tsd-panel > h1, +.tsd-panel > h2, +.tsd-panel > h3 { + margin: 1.5rem -1.5rem 0.75rem -1.5rem; + padding: 0 1.5rem 0.75rem 1.5rem; +} +.tsd-panel > h1.tsd-before-signature, +.tsd-panel > h2.tsd-before-signature, +.tsd-panel > h3.tsd-before-signature { + margin-bottom: 0; + border-bottom: none; +} + +.tsd-panel-group { + margin: 2rem 0; +} +.tsd-panel-group.tsd-index-group { + margin: 2rem 0; +} +.tsd-panel-group.tsd-index-group details { + margin: 2rem 0; +} +.tsd-panel-group > .tsd-accordion-summary { + margin-bottom: 1rem; +} + +#tsd-search { + transition: background-color 0.2s; +} +#tsd-search .title { + position: relative; + z-index: 2; +} +#tsd-search .field { + position: absolute; + left: 0; + top: 0; + right: 2.5rem; + height: 100%; +} +#tsd-search .field input { + box-sizing: border-box; + position: relative; + top: -50px; + z-index: 1; + width: 100%; + padding: 0 10px; + opacity: 0; + outline: 0; + border: 0; + background: transparent; + color: var(--color-text); +} +#tsd-search .field label { + position: absolute; + overflow: hidden; + right: -40px; +} +#tsd-search .field input, +#tsd-search .title, +#tsd-toolbar-links a { + transition: opacity 0.2s; +} +#tsd-search .results { + position: absolute; + visibility: hidden; + top: 40px; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); +} +#tsd-search .results li { + background-color: var(--color-background); + line-height: initial; + padding: 4px; +} +#tsd-search .results li:nth-child(even) { + background-color: var(--color-background-secondary); +} +#tsd-search .results li.state { + display: none; +} +#tsd-search .results li.current:not(.no-results), +#tsd-search .results li:hover:not(.no-results) { + background-color: var(--color-accent); +} +#tsd-search .results a { + display: flex; + align-items: center; + padding: 0.25rem; + box-sizing: border-box; +} +#tsd-search .results a:before { + top: 10px; +} +#tsd-search .results span.parent { + color: var(--color-text-aside); + font-weight: normal; +} +#tsd-search.has-focus { + background-color: var(--color-accent); +} +#tsd-search.has-focus .field input { + top: 0; + opacity: 1; +} +#tsd-search.has-focus .title, +#tsd-search.has-focus #tsd-toolbar-links a { + z-index: 0; + opacity: 0; +} +#tsd-search.has-focus .results { + visibility: visible; +} +#tsd-search.loading .results li.state.loading { + display: block; +} +#tsd-search.failure .results li.state.failure { + display: block; +} + +#tsd-toolbar-links { + position: absolute; + top: 0; + right: 2rem; + height: 100%; + display: flex; + align-items: center; + justify-content: flex-end; +} +#tsd-toolbar-links a { + margin-left: 1.5rem; +} +#tsd-toolbar-links a:hover { + text-decoration: underline; +} + +.tsd-signature { + margin: 0 0 1rem 0; + padding: 1rem 0.5rem; + border: 1px solid var(--color-accent); + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 14px; + overflow-x: auto; +} + +.tsd-signature-keyword { + color: var(--color-ts-keyword); + font-weight: normal; +} + +.tsd-signature-symbol { + color: var(--color-text-aside); + font-weight: normal; +} + +.tsd-signature-type { + font-style: italic; + font-weight: normal; +} + +.tsd-signatures { + padding: 0; + margin: 0 0 1em 0; + list-style-type: none; +} +.tsd-signatures .tsd-signature { + margin: 0; + border-color: var(--color-accent); + border-width: 1px 0; + transition: background-color 0.1s; +} +.tsd-signatures .tsd-index-signature:not(:last-child) { + margin-bottom: 1em; +} +.tsd-signatures .tsd-index-signature .tsd-signature { + border-width: 1px; +} +.tsd-description .tsd-signatures .tsd-signature { + border-width: 1px; +} + +ul.tsd-parameter-list, +ul.tsd-type-parameter-list { + list-style: square; + margin: 0; + padding-left: 20px; +} +ul.tsd-parameter-list > li.tsd-parameter-signature, +ul.tsd-type-parameter-list > li.tsd-parameter-signature { + list-style: none; + margin-left: -20px; +} +ul.tsd-parameter-list h5, +ul.tsd-type-parameter-list h5 { + font-size: 16px; + margin: 1em 0 0.5em 0; +} +.tsd-sources { + margin-top: 1rem; + font-size: 0.875em; +} +.tsd-sources a { + color: var(--color-text-aside); + text-decoration: underline; +} +.tsd-sources ul { + list-style: none; + padding: 0; +} + +.tsd-page-toolbar { + position: sticky; + z-index: 1; + top: 0; + left: 0; + width: 100%; + color: var(--color-text); + background: var(--color-background-secondary); + border-bottom: 1px var(--color-accent) solid; + transition: transform 0.3s ease-in-out; +} +.tsd-page-toolbar a { + color: var(--color-text); + text-decoration: none; +} +.tsd-page-toolbar a.title { + font-weight: bold; +} +.tsd-page-toolbar a.title:hover { + text-decoration: underline; +} +.tsd-page-toolbar .tsd-toolbar-contents { + display: flex; + justify-content: space-between; + height: 2.5rem; + margin: 0 auto; +} +.tsd-page-toolbar .table-cell { + position: relative; + white-space: nowrap; + line-height: 40px; +} +.tsd-page-toolbar .table-cell:first-child { + width: 100%; +} +.tsd-page-toolbar .tsd-toolbar-icon { + box-sizing: border-box; + line-height: 0; + padding: 12px 0; +} + +.tsd-widget { + display: inline-block; + overflow: hidden; + opacity: 0.8; + height: 40px; + transition: + opacity 0.1s, + background-color 0.2s; + vertical-align: bottom; + cursor: pointer; +} +.tsd-widget:hover { + opacity: 0.9; +} +.tsd-widget.active { + opacity: 1; + background-color: var(--color-accent); +} +.tsd-widget.no-caption { + width: 40px; +} +.tsd-widget.no-caption:before { + margin: 0; +} + +.tsd-widget.options, +.tsd-widget.menu { + display: none; +} +input[type="checkbox"] + .tsd-widget:before { + background-position: -120px 0; +} +input[type="checkbox"]:checked + .tsd-widget:before { + background-position: -160px 0; +} + +img { + max-width: 100%; +} + +.tsd-anchor-icon { + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + vertical-align: middle; + color: var(--color-text); +} + +.tsd-anchor-icon svg { + width: 1em; + height: 1em; + visibility: hidden; +} + +.tsd-anchor-link:hover > .tsd-anchor-icon svg { + visibility: visible; +} + +.deprecated { + text-decoration: line-through !important; +} + +.warning { + padding: 1rem; + color: var(--color-warning-text); + background: var(--color-background-warning); +} + +.tsd-kind-project { + color: var(--color-ts-project); +} +.tsd-kind-module { + color: var(--color-ts-module); +} +.tsd-kind-namespace { + color: var(--color-ts-namespace); +} +.tsd-kind-enum { + color: var(--color-ts-enum); +} +.tsd-kind-enum-member { + color: var(--color-ts-enum-member); +} +.tsd-kind-variable { + color: var(--color-ts-variable); +} +.tsd-kind-function { + color: var(--color-ts-function); +} +.tsd-kind-class { + color: var(--color-ts-class); +} +.tsd-kind-interface { + color: var(--color-ts-interface); +} +.tsd-kind-constructor { + color: var(--color-ts-constructor); +} +.tsd-kind-property { + color: var(--color-ts-property); +} +.tsd-kind-method { + color: var(--color-ts-method); +} +.tsd-kind-call-signature { + color: var(--color-ts-call-signature); +} +.tsd-kind-index-signature { + color: var(--color-ts-index-signature); +} +.tsd-kind-constructor-signature { + color: var(--color-ts-constructor-signature); +} +.tsd-kind-parameter { + color: var(--color-ts-parameter); +} +.tsd-kind-type-literal { + color: var(--color-ts-type-literal); +} +.tsd-kind-type-parameter { + color: var(--color-ts-type-parameter); +} +.tsd-kind-accessor { + color: var(--color-ts-accessor); +} +.tsd-kind-get-signature { + color: var(--color-ts-get-signature); +} +.tsd-kind-set-signature { + color: var(--color-ts-set-signature); +} +.tsd-kind-type-alias { + color: var(--color-ts-type-alias); +} + +/* if we have a kind icon, don't color the text by kind */ +.tsd-kind-icon ~ span { + color: var(--color-text); +} + +* { + scrollbar-width: thin; + scrollbar-color: var(--color-accent) var(--color-icon-background); +} + +*::-webkit-scrollbar { + width: 0.75rem; +} + +*::-webkit-scrollbar-track { + background: var(--color-icon-background); +} + +*::-webkit-scrollbar-thumb { + background-color: var(--color-accent); + border-radius: 999rem; + border: 0.25rem solid var(--color-icon-background); +} + +/* mobile */ +@media (max-width: 769px) { + .tsd-widget.options, + .tsd-widget.menu { + display: inline-block; + } + + .container-main { + display: flex; + } + html .col-content { + float: none; + max-width: 100%; + width: 100%; + } + html .col-sidebar { + position: fixed !important; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + z-index: 1024; + top: 0 !important; + bottom: 0 !important; + left: auto !important; + right: 0 !important; + padding: 1.5rem 1.5rem 0 0; + width: 75vw; + visibility: hidden; + background-color: var(--color-background); + transform: translate(100%, 0); + } + html .col-sidebar > *:last-child { + padding-bottom: 20px; + } + html .overlay { + content: ""; + display: block; + position: fixed; + z-index: 1023; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + visibility: hidden; + } + + .to-has-menu .overlay { + animation: fade-in 0.4s; + } + + .to-has-menu .col-sidebar { + animation: pop-in-from-right 0.4s; + } + + .from-has-menu .overlay { + animation: fade-out 0.4s; + } + + .from-has-menu .col-sidebar { + animation: pop-out-to-right 0.4s; + } + + .has-menu body { + overflow: hidden; + } + .has-menu .overlay { + visibility: visible; + } + .has-menu .col-sidebar { + visibility: visible; + transform: translate(0, 0); + display: flex; + flex-direction: column; + gap: 1.5rem; + max-height: 100vh; + padding: 1rem 2rem; + } + .has-menu .tsd-navigation { + max-height: 100%; + } + #tsd-toolbar-links { + display: none; + } + .tsd-navigation .tsd-nav-link { + display: flex; + } +} + +/* one sidebar */ +@media (min-width: 770px) { + .container-main { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + grid-template-areas: "sidebar content"; + margin: 2rem auto; + } + + .col-sidebar { + grid-area: sidebar; + } + .col-content { + grid-area: content; + padding: 0 1rem; + } +} +@media (min-width: 770px) and (max-width: 1399px) { + .col-sidebar { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + padding-top: 1rem; + } + .site-menu { + margin-top: 1rem; + } +} + +/* two sidebars */ +@media (min-width: 1200px) { + .container-main { + grid-template-columns: minmax(0, 1fr) minmax(0, 2.5fr) minmax(0, 20rem); + grid-template-areas: "sidebar content toc"; + } + + .col-sidebar { + display: contents; + } + + .page-menu { + grid-area: toc; + padding-left: 1rem; + } + .site-menu { + grid-area: sidebar; + } + + .site-menu { + margin-top: 1rem 0; + } + + .page-menu, + .site-menu { + max-height: calc(100vh - 2rem - 42px); + overflow: auto; + position: sticky; + top: 42px; + } +} diff --git a/docs/enums/lib_common.CryptoCurves.html b/docs/enums/lib_common.CryptoCurves.html new file mode 100644 index 0000000..234c6c4 --- /dev/null +++ b/docs/enums/lib_common.CryptoCurves.html @@ -0,0 +1,5 @@ +CryptoCurves | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/enums/lib_common.CryptoKeyTypes.html b/docs/enums/lib_common.CryptoKeyTypes.html new file mode 100644 index 0000000..1980fe1 --- /dev/null +++ b/docs/enums/lib_common.CryptoKeyTypes.html @@ -0,0 +1,4 @@ +CryptoKeyTypes | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/functions/index.JWKToMultikey.html b/docs/functions/index.JWKToMultikey.html new file mode 100644 index 0000000..95dd38b --- /dev/null +++ b/docs/functions/index.JWKToMultikey.html @@ -0,0 +1,14 @@ +JWKToMultikey | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Convert a JWK Key pair to Multikeys. This function decodes the JWK keys, finds out which binary key it encodes +and, converts the key to the multikey versions depending on the exact curve.

    +

    Note that the code does not check (yet?) all combination of JWK pairs and fields for possible errors, only +those that would lead to error in this package. E.g., it does not check whether the x (and possibly y) values +are identical in the secret and private JWK keys.

    +

    Works for ecdsa (both P-384 and P-256), and eddsa`.

    +

    Parameters

    Returns MultikeyPair

      +
    • exceptions if something is incorrect in the incoming data
    • +
    +
  • Variant of the conversion function for a single (public) key in JWK, returning the generated Multikey.

    +

    Parameters

    • keys: JsonWebKey

    Returns Multikey

      +
    • exceptions if something is incorrect in the incoming data
    • +
    +
diff --git a/docs/functions/index.multikeyToJWK.html b/docs/functions/index.multikeyToJWK.html new file mode 100644 index 0000000..b376fa0 --- /dev/null +++ b/docs/functions/index.multikeyToJWK.html @@ -0,0 +1,12 @@ +multikeyToJWK | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Generic function to convert a multikey pair to JWK. This function decodes the multikey data +into a binary buffer, checks the preambles and invokes the crypto specific converter functions +(depending on the preamble values) that do the final conversion from the binary data to JWK.

    +

    Works for ecdsa (both P-384 and P-256), and eddsa.

    +

    Parameters

    Returns JWKKeyPair

      +
    • exceptions if something is incorrect in the incoming data
    • +
    +
  • Variant of the conversion function for a single (public) key in Multikey, returning the generated JWK.

    +

    Parameters

    • keys: string

    Returns JsonWebKey

      +
    • exceptions if something is incorrect in the incoming data
    • +
    +
diff --git a/docs/functions/lib_common.isJWKKeyPair.html b/docs/functions/lib_common.isJWKKeyPair.html new file mode 100644 index 0000000..3c91207 --- /dev/null +++ b/docs/functions/lib_common.isJWKKeyPair.html @@ -0,0 +1,4 @@ +isJWKKeyPair | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/functions/lib_common.isMultikeyPair.html b/docs/functions/lib_common.isMultikeyPair.html new file mode 100644 index 0000000..74333fd --- /dev/null +++ b/docs/functions/lib_common.isMultikeyPair.html @@ -0,0 +1,4 @@ +isMultikeyPair | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/functions/lib_common.preambleToCryptoData.html b/docs/functions/lib_common.preambleToCryptoData.html new file mode 100644 index 0000000..bd4f428 --- /dev/null +++ b/docs/functions/lib_common.preambleToCryptoData.html @@ -0,0 +1,4 @@ +preambleToCryptoData | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Classify the crypto key based on the multikey preamble characters that are at the start of the code. +These are two binary numbers, signalling the crypto class (ecdsa or eddsa) and, in the former case, +the hash function.

    +

    Parameters

    Returns CryptoKeyData

diff --git a/docs/functions/lib_convert.JWKToMultikey.html b/docs/functions/lib_convert.JWKToMultikey.html new file mode 100644 index 0000000..81f671e --- /dev/null +++ b/docs/functions/lib_convert.JWKToMultikey.html @@ -0,0 +1,7 @@ +JWKToMultikey | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Convert JWK Key pair to Multikeys. This function decodes the JWK keys, finds out which binary key it encodes +and converts the key to the multikey versions depending on the exact curve.

    +

    Note that the code does not check (yet?) all combination of the JWK pairs where they would be erroneous, only +those that would lead to error in this cose. E.g., it does not check whether the x (and possibly y) values +are identical in the secret and private JWK keys.

    +

    Works for ecdsa (both P-384 and P-256), and eddsa.

    +

    Parameters

    Returns MultikeyPair

diff --git a/docs/functions/lib_convert.multikeyToJWK.html b/docs/functions/lib_convert.multikeyToJWK.html new file mode 100644 index 0000000..0d55f78 --- /dev/null +++ b/docs/functions/lib_convert.multikeyToJWK.html @@ -0,0 +1,9 @@ +multikeyToJWK | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Generic function to convert a multikey pair to JWK. This function decodes the multikey data +into a binary buffer, checks the preambles and invokes the crypto curve specific converter functions +(depending on the preamble values) that do the final +conversion from the binary data to JWK.

    +

    Works for ecdsa (both P-384 and P-256), and eddsa.

    +

    Parameters

    Returns JWKKeyPair

      +
    • exceptions if something is incorrect in the incoming data
    • +
    +
diff --git a/docs/functions/lib_ecdsa.JWKToMultikeyBinary.html b/docs/functions/lib_ecdsa.JWKToMultikeyBinary.html new file mode 100644 index 0000000..1f6bdab --- /dev/null +++ b/docs/functions/lib_ecdsa.JWKToMultikeyBinary.html @@ -0,0 +1,11 @@ +JWKToMultikeyBinary | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. +The final encoding, with preambles, are done in the general level.

    +

    For ECDSA, the compressed form must be calculated, by adding an extra byte signaling which of the +two possible 'y' values are used.

    +

    (The y value is set as optional in the signature but that is only to make TypeScript happy. A missing +value generates an error)

    +

    Parameters

    • cl: CryptoCurves

      choice between P-256 and P-384

      +
    • x: Uint8Array

      x value for the elliptical curve

      +
    • d: Uint8Array

      d (private) value for the elliptical curve

      +
    • Optionaly: Uint8Array

      y value for the elliptical curve

      +

    Returns MultikeyPairBinary

diff --git a/docs/functions/lib_ecdsa.multikeyBinaryToJWK.html b/docs/functions/lib_ecdsa.multikeyBinaryToJWK.html new file mode 100644 index 0000000..1de6ffe --- /dev/null +++ b/docs/functions/lib_ecdsa.multikeyBinaryToJWK.html @@ -0,0 +1,9 @@ +multikeyBinaryToJWK | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Convert the multikey values to their JWK equivalents. The final x and d values are encoded +in base64 and then the relevant JWK structure are created

    +

    For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a +constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must +do some extra processing.

    +

    Parameters

    • cl: CryptoCurves

      choice between P-256 and P-384

      +
    • xb: Uint8Array

      binary version of the x value for the elliptical curve

      +
    • Optionaldb: Uint8Array

      binary version of the d value for the elliptical curve

      +

    Returns JWKKeyPair

diff --git a/docs/functions/lib_eddsa.JWKToMultikeyBinary.html b/docs/functions/lib_eddsa.JWKToMultikeyBinary.html new file mode 100644 index 0000000..69aa994 --- /dev/null +++ b/docs/functions/lib_eddsa.JWKToMultikeyBinary.html @@ -0,0 +1,9 @@ +JWKToMultikeyBinary | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. +The final encoding, with preambles, are done in the general level.

    +

    For EDDSA, this is essentially, an empty function, which simply returns the x and d values. The +interface is there to be reused by the ECDSA equivalent, which must do some extra processing.

    +

    Parameters

    • _cl: CryptoCurves

      unused in this function, just a placeholder

      +
    • x: Uint8Array

      x value for the elliptical curve, as extracted from JWK

      +
    • d: Uint8Array

      d (private) value for the elliptical curve, as extracted from JWK

      +
    • Optional_y: Uint8Array

      unused in this function, just a placeholder

      +

    Returns MultikeyPairBinary

diff --git a/docs/functions/lib_eddsa.multikeyBinaryToJWK.html b/docs/functions/lib_eddsa.multikeyBinaryToJWK.html new file mode 100644 index 0000000..092fece --- /dev/null +++ b/docs/functions/lib_eddsa.multikeyBinaryToJWK.html @@ -0,0 +1,9 @@ +multikeyBinaryToJWK | Conversions to and from multikeys and WebCrypto - v0.0.1
  • Convert the multikey values to their JWK equivalents. The final x and d values are encoded +in base64 and then the relevant JWK structure are created

    +

    For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a +constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must +do some extra processing.

    +

    Parameters

    • _cl: CryptoCurves

      unused in this function, just a placeholder

      +
    • xb: Uint8Array

      binary version of the x value for the elliptical curve

      +
    • Optionaldb: Uint8Array

      binary version of the d value for the elliptical curve

      +

    Returns JWKKeyPair

diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..5809845 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,10 @@ +Conversions to and from multikeys and WebCrypto - v0.0.1

Conversions to and from multikeys and WebCrypto - v0.0.1

--- NOT PRODUCTION READY ---

+

Multikey <-> WebCrypto and JWK conversions

Conversion of cryptographic keys in Multikey format to and +from WebCrypto and JWK. The conversions are available for the three EC curves that are defined for Verifiable Credentials: +ECDSA with P-256 and P-384 and EDDSA.

+

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.

+

The package has been written in TypeScript+Node.js. (There is also a Deno version.)

+

For a more detailed documentation, see the code documentation, generated by typedoc.

+

[Examples to come]

+
diff --git a/docs/interfaces/lib_common.CryptoKeyData.html b/docs/interfaces/lib_common.CryptoKeyData.html new file mode 100644 index 0000000..27bd07a --- /dev/null +++ b/docs/interfaces/lib_common.CryptoKeyData.html @@ -0,0 +1,5 @@ +CryptoKeyData | Conversions to and from multikeys and WebCrypto - v0.0.1

This is an internal type, used for the implementation: return the crypto curve and type from a preamble.

+

So far, I have not yet found a way to encode that in a simple table, hence the separate function.

+
interface CryptoKeyData {
    crCurve: CryptoCurves;
    crType: CryptoKeyTypes;
}

Properties

Properties

crCurve: CryptoCurves
diff --git a/docs/interfaces/lib_common.JWKKeyPair.html b/docs/interfaces/lib_common.JWKKeyPair.html new file mode 100644 index 0000000..238cb45 --- /dev/null +++ b/docs/interfaces/lib_common.JWKKeyPair.html @@ -0,0 +1,3 @@ +JWKKeyPair | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/interfaces/lib_common.MultikeyPair.html b/docs/interfaces/lib_common.MultikeyPair.html new file mode 100644 index 0000000..de2fcd7 --- /dev/null +++ b/docs/interfaces/lib_common.MultikeyPair.html @@ -0,0 +1,5 @@ +MultikeyPair | Conversions to and from multikeys and WebCrypto - v0.0.1

Pair of keys in Multikey encoding. Using the field names as defined in the +Multikey specification.

+
interface MultikeyPair {
    publicKeyMultibase: string;
    secretKeyMultibase?: string;
}

Properties

publicKeyMultibase: string
secretKeyMultibase?: string
diff --git a/docs/interfaces/lib_common.MultikeyPairBinary.html b/docs/interfaces/lib_common.MultikeyPairBinary.html new file mode 100644 index 0000000..ead8a91 --- /dev/null +++ b/docs/interfaces/lib_common.MultikeyPairBinary.html @@ -0,0 +1,4 @@ +MultikeyPairBinary | Conversions to and from multikeys and WebCrypto - v0.0.1

Same as the Multikey Pair, but decoded and without the preambles. Just the bare key values.

+
interface MultikeyPairBinary {
    public: Uint8Array;
    secret?: Uint8Array;
}

Properties

Properties

public: Uint8Array
secret?: Uint8Array
diff --git a/docs/modules/index.html b/docs/modules/index.html new file mode 100644 index 0000000..4e8ef20 --- /dev/null +++ b/docs/modules/index.html @@ -0,0 +1,6 @@ +index | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/modules/lib_common.html b/docs/modules/lib_common.html new file mode 100644 index 0000000..f03a3d7 --- /dev/null +++ b/docs/modules/lib_common.html @@ -0,0 +1,23 @@ +lib/common | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/modules/lib_convert.html b/docs/modules/lib_convert.html new file mode 100644 index 0000000..0f3959e --- /dev/null +++ b/docs/modules/lib_convert.html @@ -0,0 +1,4 @@ +lib/convert | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/modules/lib_ecdsa.html b/docs/modules/lib_ecdsa.html new file mode 100644 index 0000000..40eee0c --- /dev/null +++ b/docs/modules/lib_ecdsa.html @@ -0,0 +1,7 @@ +lib/ecdsa | Conversions to and from multikeys and WebCrypto - v0.0.1

Base conversion functions for ECDSA. The Multikey definition requires the usage of a compressed public key +which must be compressed when creating the Multikey representation, and decompressed for the JWK conversion.

+

The two exported functions, used by the rest of the package, just branch out to the internal functions that do the +key (de)compression itself.

+

Index

Functions

diff --git a/docs/modules/lib_eddsa.html b/docs/modules/lib_eddsa.html new file mode 100644 index 0000000..4ca1d57 --- /dev/null +++ b/docs/modules/lib_eddsa.html @@ -0,0 +1,5 @@ +lib/eddsa | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/types/lib_common.ClassToDecoder.html b/docs/types/lib_common.ClassToDecoder.html new file mode 100644 index 0000000..6873a55 --- /dev/null +++ b/docs/types/lib_common.ClassToDecoder.html @@ -0,0 +1,2 @@ +ClassToDecoder | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/types/lib_common.ClassToEncoder.html b/docs/types/lib_common.ClassToEncoder.html new file mode 100644 index 0000000..eae43bc --- /dev/null +++ b/docs/types/lib_common.ClassToEncoder.html @@ -0,0 +1,2 @@ +ClassToEncoder | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/types/lib_common.ClassToPreamble.html b/docs/types/lib_common.ClassToPreamble.html new file mode 100644 index 0000000..a79f29e --- /dev/null +++ b/docs/types/lib_common.ClassToPreamble.html @@ -0,0 +1,2 @@ +ClassToPreamble | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/types/lib_common.Multikey.html b/docs/types/lib_common.Multikey.html new file mode 100644 index 0000000..26d4d5c --- /dev/null +++ b/docs/types/lib_common.Multikey.html @@ -0,0 +1,3 @@ +Multikey | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/types/lib_common.Preamble.html b/docs/types/lib_common.Preamble.html new file mode 100644 index 0000000..6d886b2 --- /dev/null +++ b/docs/types/lib_common.Preamble.html @@ -0,0 +1,2 @@ +Preamble | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/variables/lib_common.ECDSACurves.html b/docs/variables/lib_common.ECDSACurves.html new file mode 100644 index 0000000..0d13bca --- /dev/null +++ b/docs/variables/lib_common.ECDSACurves.html @@ -0,0 +1,3 @@ +ECDSACurves | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/variables/lib_common.Ecdsa256Preambles.html b/docs/variables/lib_common.Ecdsa256Preambles.html new file mode 100644 index 0000000..2e45eb2 --- /dev/null +++ b/docs/variables/lib_common.Ecdsa256Preambles.html @@ -0,0 +1,2 @@ +Ecdsa256Preambles | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/variables/lib_common.Ecdsa384Preambles.html b/docs/variables/lib_common.Ecdsa384Preambles.html new file mode 100644 index 0000000..fd122b0 --- /dev/null +++ b/docs/variables/lib_common.Ecdsa384Preambles.html @@ -0,0 +1,2 @@ +Ecdsa384Preambles | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/variables/lib_common.EddsaPreambles.html b/docs/variables/lib_common.EddsaPreambles.html new file mode 100644 index 0000000..5b34d1c --- /dev/null +++ b/docs/variables/lib_common.EddsaPreambles.html @@ -0,0 +1,2 @@ +EddsaPreambles | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/variables/lib_common.classToDecoder-1.html b/docs/variables/lib_common.classToDecoder-1.html new file mode 100644 index 0000000..977726b --- /dev/null +++ b/docs/variables/lib_common.classToDecoder-1.html @@ -0,0 +1,2 @@ +classToDecoder | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/variables/lib_common.classToEncoder-1.html b/docs/variables/lib_common.classToEncoder-1.html new file mode 100644 index 0000000..6f78d08 --- /dev/null +++ b/docs/variables/lib_common.classToEncoder-1.html @@ -0,0 +1,2 @@ +classToEncoder | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/docs/variables/lib_common.classToPreamble-1.html b/docs/variables/lib_common.classToPreamble-1.html new file mode 100644 index 0000000..36c71b0 --- /dev/null +++ b/docs/variables/lib_common.classToPreamble-1.html @@ -0,0 +1,2 @@ +classToPreamble | Conversions to and from multikeys and WebCrypto - v0.0.1
diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..f1577ca --- /dev/null +++ b/index.ts @@ -0,0 +1,68 @@ +/** + * Conversion to and from [Multikey format](https://www.w3.org/TR/controller-document/#multikey) from + * JWK 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). + * + * @package + */ + +import * as convert from './lib/convert'; +import { JWKKeyPair, MultikeyPair, Multikey, isJWKKeyPair, isMultikeyPair } from './lib/common'; +export type { JWKKeyPair, MultikeyPair, Multikey } from './lib/common'; + +/** + * Generic function to convert a multikey pair to JWK. This function decodes the multikey data + * into a binary buffer, checks the preambles and invokes the crypto specific converter functions + * (depending on the preamble values) that do the final conversion from the binary data to JWK. + * + * Works for `ecdsa` (both `P-384` and `P-256`), and `eddsa`. + * + * @param keys + * @throws - exceptions if something is incorrect in the incoming data + */ +export function multikeyToJWK(keys: MultikeyPair): JWKKeyPair; +/** + * Variant of the conversion function for a single (public) key in Multikey, returning the generated JWK. + * @param keys + * @throws - exceptions if something is incorrect in the incoming data + */ +export function multikeyToJWK(keys: Multikey): JsonWebKey; +export function multikeyToJWK(keys: MultikeyPair | Multikey): JWKKeyPair | JsonWebKey { + const input: MultikeyPair = isMultikeyPair(keys) ? keys as MultikeyPair : { publicKeyMultibase: keys }; + const jwk_keys = convert.multikeyToJWK(input); + if (isMultikeyPair(keys)) { + return jwk_keys; + } else { + return jwk_keys.public; + } +} + +/** + * Convert a JWK Key pair to Multikeys. This function decodes the JWK keys, finds out which binary key it encodes + * and, converts the key to the multikey versions depending on the exact curve. + * + * Note that the code does not check (yet?) all combination of JWK pairs and fields for possible errors, only + * those that would lead to error in this package. E.g., it does not check whether the x (and possibly y) values + * are identical in the secret and private JWK keys. + * + * Works for `ecdsa` (both `P-384` and `P`-256`), and `eddsa`. + * + * @param keys + * @throws - exceptions if something is incorrect in the incoming data + */ +export function JWKToMultikey(keys: JWKKeyPair): MultikeyPair; +/** + * Variant of the conversion function for a single (public) key in JWK, returning the generated Multikey. + * @param keys + * @throws - exceptions if something is incorrect in the incoming data + */ +export function JWKToMultikey(keys: JsonWebKey): Multikey; +export function JWKToMultikey(keys: JWKKeyPair | JsonWebKey): MultikeyPair | Multikey { + const input: JWKKeyPair = isJWKKeyPair(keys) ? keys : {public: keys}; + const m_keys = convert.JWKToMultikey(input); + if (isJWKKeyPair(keys)) { + return m_keys; + } else { + return m_keys.publicKeyMultibase; + } +} diff --git a/lib/common.ts b/lib/common.ts new file mode 100644 index 0000000..0a38f2f --- /dev/null +++ b/lib/common.ts @@ -0,0 +1,243 @@ +/** + * Common types, conversion functions and Multikey conversion utilities for the rest of the code. + * @module + */ + +import * as eddsa from "./eddsa"; +import * as ecdsa from "./ecdsa"; + + +export interface JWKKeyPair { + public: JsonWebKey; + secret?: JsonWebKey; +} + +/** + * Typeguard for JWK Key Pair. + * It is not really elaborate, it only tries to differentiate between a JWK Single Key and a Key Pair. + * + * @param obj + * @returns is it a JWKKeyPair? + */ +// deno-lint-ignore no-explicit-any +export function isJWKKeyPair(obj: any): obj is JWKKeyPair { + return (obj as JWKKeyPair).public !== undefined; +} + +/** + * Type for a Multikey + * + * One day this could become a string with a fixed regexp... + */ +export type Multikey = string; + +/** + * Pair of keys in Multikey encoding. Using the field names as defined in the + * [Multikey specification](https://www.w3.org/TR/controller-document/#multikey). + */ +export interface MultikeyPair { + publicKeyMultibase: Multikey; + secretKeyMultibase?: Multikey; +} + +/** + * Typeguard for a Multikey Pair. + * It is not really elaborate, it only tries to differentiate between a single Multikey and a Key Pair. + * + * @param obj + * @returns is it a MultikeyPair? + */ +// deno-lint-ignore no-explicit-any +export function isMultikeyPair(obj: any): obj is MultikeyPair { + return (obj as MultikeyPair).publicKeyMultibase !== undefined; +} + +/** + * Same as the Multikey Pair, but decoded and without the preambles. Just the bare key values. + */ +export interface MultikeyPairBinary { + public: Uint8Array; + secret?: Uint8Array +} + +/************************************************************************* */ +/* Values to handle the various preamble bytes for the different key types */ +/************************************************************************* */ + +/** + * Names for the various crypto curves + */ +export enum CryptoCurves { + ECDSA_384 = "secp384r1", + ECDSA_256 = "secp256r1", + EDDSA = "ed25519" +} + +/** + * Names for the key types + */ +export enum CryptoKeyTypes { + PUBLIC = "public", + SECRET = "secret" +} + +/************************************* Preambles ***************************/ +/** + * Type used for preambles, which are, so far, a single pair of numbers. + */ +export type Preamble = [T,T]; + +/** + * Each crypto class has two preambles, one for the public and one for the secret keys + */ +interface MultikeyPreambles { + public: Preamble, + secret: Preamble, +} + +/** + * Preamble value for EDDSA, a.k.a. `ed25519` curve + */ +export const EddsaPreambles: MultikeyPreambles = { + public: [0xed, 0x01], + secret: [0x80, 0x26], +}; + +/** + * Preamble for ECDSA `P-256`, a.k.a. `secp256r1` curve + */ +export const Ecdsa256Preambles: MultikeyPreambles = { + public: [0x80, 0x24], + secret: [0x86, 0x26], +} + +/** + * Preamble for ECDSA `P-384`, a.k.a. `secp384r1` curve + */ +export const Ecdsa384Preambles: MultikeyPreambles = { + public: [0x81, 0x24], + secret: [0x87, 0x26], +}; + + + +/************************************ Converter tables **********************************/ +// At various place in the code there is a choice to be made from preambles to specific curves +// and back. Better to encode these in conversion tables rather than build them in the code, +// this makes things less error-prone + +/** + * Type definition for the table mapping preambles to a specific curve. + */ +export type ClassToPreamble = { + [key in CryptoCurves]: MultikeyPreambles; +}; + +/** + * What preambles must be used for a Curve? + */ +export const classToPreamble: ClassToPreamble = { + [CryptoCurves.EDDSA]: EddsaPreambles, + [CryptoCurves.ECDSA_256]: Ecdsa256Preambles, + [CryptoCurves.ECDSA_384]: Ecdsa384Preambles, +}; + +/** + * Type definition for the table mapping curves to their decoder functions (i.e., mapping the Multikey to JWK). + */ +export type ClassToDecoder = { + [key in CryptoCurves]: (keyCurve: CryptoCurves, x: Uint8Array, d?: Uint8Array) => JWKKeyPair; +} + +/** + * What coder function must be used to convert from Multikey to JWK (data)? + */ +export const classToDecoder: ClassToDecoder = { + [CryptoCurves.EDDSA]: eddsa.multikeyBinaryToJWK, + [CryptoCurves.ECDSA_256]: ecdsa.multikeyBinaryToJWK, + [CryptoCurves.ECDSA_384]: ecdsa.multikeyBinaryToJWK, +}; + +/** + * Type definition for the table mapping curves to their encoder functions (i.e., mapping the JWK to Multikey). + */ +export type ClassToEncoder = { + [key in CryptoCurves]: (keyCurve: CryptoCurves, x: Uint8Array, d: Uint8Array | undefined, _y?: Uint8Array) => MultikeyPairBinary +} + +/** + * What coder function must be used to convert from JWK to Multikey? + */ +export const classToEncoder: ClassToEncoder = { + [CryptoCurves.EDDSA]: eddsa.JWKToMultikeyBinary, + [CryptoCurves.ECDSA_256]: ecdsa.JWKToMultikeyBinary, + [CryptoCurves.ECDSA_384]: ecdsa.JWKToMultikeyBinary, +}; + +/** + * List of possible ECDSA Curves. Having this here declaratively may make it easier if + * in the future, a new curve is added to the family (P-512)? + */ +export const ECDSACurves: CryptoCurves[] = [CryptoCurves.ECDSA_256, CryptoCurves.ECDSA_384]; + +/** + * This is an internal type, used for the implementation: return the crypto curve and type from a preamble. + * + * So far, I have not yet found a way to encode that in a simple table, hence the separate function. + */ +export interface CryptoKeyData { + crCurve: CryptoCurves, + crType: CryptoKeyTypes, +} + +/** + * Classify the crypto key based on the multikey preamble characters that are at the start of the code. + * These are two binary numbers, signalling the crypto class (ecdsa or eddsa) and, in the former case, + * the hash function. + * + * @param preamble + * @returns + */ +export function preambleToCryptoData(preamble: Preamble): CryptoKeyData { + // Ugly but effective and simple trick to compare two arrays + const eq = (a: Preamble, b: Preamble): boolean => JSON.stringify(a) === JSON.stringify(b); + + if (preamble.length !== 2) { + throw new Error(`${preamble} is not valid, it should have a size of exactly 2.`); + } + + // The real classification... + if (eq(preamble, Ecdsa256Preambles.secret)) { + return { + crCurve: CryptoCurves.ECDSA_256, + crType: CryptoKeyTypes.SECRET, + }; + } else if (eq(preamble, Ecdsa256Preambles.public)) { + return { + crCurve: CryptoCurves.ECDSA_256, + crType: CryptoKeyTypes.PUBLIC, + }; + } else if (eq(preamble, Ecdsa384Preambles.secret)) { + return { + crCurve: CryptoCurves.ECDSA_384, + crType: CryptoKeyTypes.SECRET, + }; + } else if (eq(preamble, Ecdsa384Preambles.public)) { + return { + crCurve: CryptoCurves.ECDSA_384, + crType: CryptoKeyTypes.PUBLIC, + }; + } else if (eq(preamble, EddsaPreambles.secret)) { + return { + crCurve: CryptoCurves.EDDSA, + crType: CryptoKeyTypes.SECRET, + }; + } else if (eq(preamble, EddsaPreambles.public)) { + return { + crCurve: CryptoCurves.EDDSA, + crType: CryptoKeyTypes.PUBLIC, + }; + } else { + throw new Error(`${preamble} is unknown. Should refer to secret or private eddsa or ecdsa (the latter with P-256 or P-384)`); + } +} diff --git a/lib/convert.ts b/lib/convert.ts new file mode 100644 index 0000000..39b2b61 --- /dev/null +++ b/lib/convert.ts @@ -0,0 +1,183 @@ +/** + * The real work for the whole library are done in the main functions in this module. + * + * @module + */ + +import { + JWKKeyPair, MultikeyPair, Multikey, Preamble, + CryptoCurves, CryptoKeyTypes, CryptoKeyData, + ECDSACurves, + MultikeyPairBinary, + classToPreamble, classToDecoder, classToEncoder, + preambleToCryptoData +} from "./common"; + +import * as base58 from './encodings/base58/index'; +import * as base64 from "./encodings/base64"; + +/****************************************************************************************/ +/* The real converter functions */ +/****************************************************************************************/ +/** + * Generic function to convert a multikey pair to JWK. This function decodes the multikey data + * into a binary buffer, checks the preambles and invokes the crypto curve specific converter functions + * (depending on the preamble values) that do the final + * conversion from the binary data to JWK. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + * + * @param keys + * @returns + * @throws - exceptions if something is incorrect in the incoming data + */ +export function multikeyToJWK(keys: MultikeyPair): JWKKeyPair { + // Separate the preamble from the real binary key value. + interface MultikeyData { + preamble: Preamble, + keyBinary: Uint8Array; + } + // Separate the preamble of a multikey from the key value. By doing so, + // the initial 'z' value is also removed. + const convertBinary = (key: Multikey): MultikeyData => { + // Check whether the first character is a 'z' before removing it + if (key[0] === 'z') { + const unencoded_key: Uint8Array = base58.decode(key.slice(1)); + return { + preamble : [unencoded_key[0], unencoded_key[1]], + keyBinary : unencoded_key.slice(2), + }; + } else { + throw new Error(`"${key}" is not encoded as required (first character should be a 'z')`); + } + }; + + // Get the the public values values... + const publicBinary = convertBinary(keys.publicKeyMultibase); + // ... and find out, based on the preamble, which curve is used and whether it is indeed public + const publicData: CryptoKeyData = preambleToCryptoData(publicBinary.preamble); + if (publicData.crType !== CryptoKeyTypes.PUBLIC) { + throw new Error(`"${keys.publicKeyMultibase}" has the wrong preamble (should refer to a public key).`); + } + + // Get hold of the curve specific converter function that will do the real work on the binary + // data + const converter = classToDecoder[publicData.crCurve]; + + // We have to repeat the previous steps for a secret key, if applicable, before converting the result into a JWK pair, + // A check is made on the fly to see that the keys are compatible in terms of crypto methods + if (keys.secretKeyMultibase) { + const secretBinary = convertBinary(keys.secretKeyMultibase); + const secretData: CryptoKeyData = preambleToCryptoData(secretBinary.preamble); + + if (secretData.crCurve !== publicData.crCurve) { + throw new Error(`Private and secret keys have different crypto methods`); + } else if (secretData.crType !== CryptoKeyTypes.SECRET) { + throw new Error(`"${keys.secretKeyMultibase}" has the wrong preamble (should refer to a secret key).`); + } + + // Everything seems to be fine + return converter(publicData.crCurve, publicBinary.keyBinary, secretBinary.keyBinary); + } else { + return converter(publicData.crCurve, publicBinary.keyBinary); + } +} + + +/** + * Convert JWK Key pair to Multikeys. This function decodes the JWK keys, finds out which binary key it encodes + * and converts the key to the multikey versions depending on the exact curve. + * + * Note that the code does not check (yet?) all combination of the JWK pairs where they would be erroneous, only + * those that would lead to error in this cose. E.g., it does not check whether the x (and possibly y) values + * are identical in the secret and private JWK keys. + * + * Works for ecdsa (both P-384 and P-256), and eddsa. + + * @param keys + */ +export function JWKToMultikey(keys: JWKKeyPair): MultikeyPair { + // Internal function for the common last step of encoding a multikey + const encodeMultikey = (val: Uint8Array, preamble: Preamble): string => { + const val_mk = new Uint8Array([...preamble, ...val]); + return 'z' + base58.encode(val_mk); + } + + const decodeJWKField = (val: string | undefined): Uint8Array | undefined => { + if (val === undefined) { + return undefined + } else { + return base64.decode(val); + } + } + + // Find out the key curve, will be used for branching later: is it ECDSA or EDDSA and, if the former, + // which one? + const keyCurve = (key: JsonWebKey): CryptoCurves => { + if (key.kty) { + if (key.kty === "EC") { + switch (key.crv) { + case "P-256": return CryptoCurves.ECDSA_256; + case "P-384": return CryptoCurves.ECDSA_384; + default: throw new Error(`Unknown crv value for an ecdsa key (${key.crv})`); + } + } else if (key.kty === "OKP") { + if (key.crv === "Ed25519") { + return CryptoCurves.EDDSA + } else { + throw new Error(`Unknown crv value for an OKP key (${key.crv})`) + } + } else { + throw new Error(`Unknown kty value for a key (${key.kty})`) + } + } else { + throw new Error(`No kty value for the key (${JSON.stringify(key)})`) + } + }; + + const publicKeyCurve = keyCurve(keys.public); + + // 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 (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: Uint8Array | undefined = decodeJWKField(keys.public.x); + if (x === undefined) { + throw new Error(`x value is missing from public key (${JSON.stringify(keys.public)})`); + } + + const y: Uint8Array | undefined = decodeJWKField(keys.public.y); + if (ECDSACurves.includes(publicKeyCurve) && y === undefined) { + throw new Error(`y value is missing from the ECDSA public key (${JSON.stringify(keys.public)})`); + } + + const d: Uint8Array | undefined = (keys.secret) ? decodeJWKField(keys.secret.d) : undefined; + if (keys.secret && d === undefined) { + throw new Error(`d value is missing from private key (${JSON.stringify(keys)})`); + } + + const converter = classToEncoder[publicKeyCurve] + const finalBinary: MultikeyPairBinary = converter(publicKeyCurve, x, d, y); + + // We have the binary version of the multikey values, this must be converted into real multikey. + // This means adding a preamble and convert to base58. + const preambles = classToPreamble[publicKeyCurve]; + const output: MultikeyPair = { + publicKeyMultibase : encodeMultikey(finalBinary.public, preambles.public) + } + if (finalBinary.secret !== undefined) { + output.secretKeyMultibase = encodeMultikey(finalBinary.secret, preambles.secret); + } + return output; +} + + + + diff --git a/lib/ecdsa.ts b/lib/ecdsa.ts new file mode 100644 index 0000000..813ac78 --- /dev/null +++ b/lib/ecdsa.ts @@ -0,0 +1,155 @@ +/** + * Base conversion functions for ECDSA. The Multikey definition requires the usage of a compressed public key + * which must be compressed when creating the Multikey representation, and decompressed for the JWK conversion. + * + * The two exported functions, used by the rest of the package, just branch out to the internal functions that do the + * key (de)compression itself. + * + * @module + */ + +import { JWKKeyPair, MultikeyPairBinary, CryptoCurves } from "./common"; +import * as base64 from "./encodings/base64"; + +/** + * Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. + * The final encoding, with preambles, are done in the general level. + * + * For ECDSA, the compressed form must be calculated, by adding an extra byte signaling which of the + * two possible 'y' values are used. + * + * (The y value is set as optional in the signature but that is only to make TypeScript happy. A missing + * value generates an error) + * + * @param cl - choice between P-256 and P-384 + * @param x - x value for the elliptical curve + * @param d - d (private) value for the elliptical curve + * @param y - y value for the elliptical curve + * @returns + */ +export function JWKToMultikeyBinary(cl: CryptoCurves, x: Uint8Array, d: Uint8Array | undefined, y?: Uint8Array): MultikeyPairBinary { + if (y === undefined) { + throw new Error("ECDSA encoding requires a 'y' value."); + } + + return { + public: compressPublicKey(cl, x, y), + secret: d + }; +} + +/** + * Convert the multikey values to their JWK equivalents. The final `x` and `d` values are encoded + * in base64 and then the relevant JWK structure are created + * + * For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a + * constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must + * do some extra processing. + * + * @param cl - choice between P-256 and P-384 + * @param xb - binary version of the x value for the elliptical curve + * @param db - binary version of the d value for the elliptical curve + * @returns + */ +export function multikeyBinaryToJWK(cl: CryptoCurves, xb: Uint8Array, db?: Uint8Array): JWKKeyPair { + // The extra complication with ECDSA: the multikey is the compressed 'x' value, the 'y' value + // must be calculated. + const uncompressed = uncompressPublicKey(cl, xb); + const x = base64.encode(uncompressed.x); + const y = base64.encode(uncompressed.y); + const output: JWKKeyPair = { + public: { + kty: "EC", + crv: (cl === CryptoCurves.ECDSA_256) ? "P-256" : "P-384", + x, + y, + key_ops: [ + "verify" + ], + ext: true, + } + }; + if (db !== undefined) { + output.secret = { + kty: "EC", + crv: (cl === CryptoCurves.ECDSA_256) ? "P-256" : "P-384", + x, + y, + d : base64.encode(db), + key_ops: [ + "sign" + ], + ext: true + } + } + return output; +} + +/************************************************************************ + * + * Internal utility functions for key (de)compression. Some parts of the code below comes from + * a Perplexity.ai prompt. (I wish there was a better documentation of the + * packages instead...) + * +*************************************************************************/ + +import { p384 } from '@noble/curves/p384'; +import { p256 } from '@noble/curves/p256'; + +// Utility function to convert Uint8Array to hex string +function uint8ArrayToHex(uint8Array: Uint8Array): string { + return Array.from(uint8Array) + .map((byte) => byte.toString(16).padStart(2, '0')) + .join(''); +} + +// Utility function to convert hex string to Uint8Array +function hexToUint8Array(hex: string): Uint8Array { + const result = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + result[i / 2] = parseInt(hex.substr(i, 2), 16); + } + return result; +} + +/** + * Compress the public key. Could be done "manually" (look at the parity of the `y` value, and add a byte at the start of the `x`), but + * I was lazy and relied on the curve libraries' methods + * + * @param curve + * @param x + * @param y + * @returns + */ +function compressPublicKey(curve: CryptoCurves, x: Uint8Array, y: Uint8Array): Uint8Array { + const xBigInt = BigInt(`0x${uint8ArrayToHex(x)}`); + const yBigInt = BigInt(`0x${uint8ArrayToHex(y)}`); + + const point = (curve === CryptoCurves.ECDSA_256) ? new p256.ProjectivePoint(xBigInt, yBigInt, 1n) : new p384.ProjectivePoint(xBigInt, yBigInt, 1n); + + return point.toRawBytes(true); +} + + +/** + * Uncompress the compressed public key. The compressed `x` value (minus its first byte) must be by plugged in the curve equation to get the possible `y` values. + * The curve equation makes it difficult to do it "manually", hence the reliance on the external package. + * + * @param curve + * @param compressedKey + * @returns + */ +function uncompressPublicKey(curve: CryptoCurves, compressedKey: Uint8Array): { x: Uint8Array, y: Uint8Array, } { + const point = (curve === CryptoCurves.ECDSA_256) ? p256.ProjectivePoint.fromHex(compressedKey) : p384.ProjectivePoint.fromHex(compressedKey); + const uncompressedKey = point.toRawBytes(false); + + // The 'uncompressed key is a concatenation of the x and y values, plus an extra value at the start. The latter must be disposed off, and + // the remaining array to be cut into two. + const keyLength = (curve === CryptoCurves.ECDSA_256) ? 32 : 48; + + const joinedXY = uncompressedKey.slice(1); + const x = joinedXY.slice(0, keyLength); + const y = joinedXY.slice(keyLength); + + return { x, y }; +} diff --git a/lib/eddsa.ts b/lib/eddsa.ts new file mode 100644 index 0000000..183fc06 --- /dev/null +++ b/lib/eddsa.ts @@ -0,0 +1,70 @@ +/** + * Base conversion functions for EDDSA. The functions are straightforward, but the interfaces are made so that + * they coincide with the companion functions in ECDSA. + * + * @module + */ +import { JWKKeyPair, MultikeyPairBinary, CryptoCurves } from "./common"; +import * as base64 from "./encodings/base64"; + +/** + * Convert the Crypto values from JWK to the equivalent Multikey Pairs' binary data. + * The final encoding, with preambles, are done in the general level. + * + * For EDDSA, this is essentially, an empty function, which simply returns the `x` and `d` values. The + * interface is there to be reused by the ECDSA equivalent, which must do some extra processing. + * + * @param _cl - unused in this function, just a placeholder + * @param x - x value for the elliptical curve, as extracted from JWK + * @param d - d (private) value for the elliptical curve, as extracted from JWK + * @param _y - unused in this function, just a placeholder + * @returns + */ +export function JWKToMultikeyBinary(_cl: CryptoCurves, x: Uint8Array, d: Uint8Array | undefined, _y?: Uint8Array): MultikeyPairBinary { + return { + public: x, + secret: d, + } +} + +/** + * Convert the multikey values to their JWK equivalents. The final `x` and `d` values are encoded + * in base64 and then the relevant JWK structure are created + * + * For EDDSA, this is a very straightforward operation by just encoding the values and plugging them into a + * constant JWK structure. The interface is there to be reused by the ECDSA equivalent, which must + * do some extra processing. + * + * @param _cl - unused in this function, just a placeholder + * @param xb - binary version of the x value for the elliptical curve + * @param db - binary version of the d value for the elliptical curve + * @returns + */ +export function multikeyBinaryToJWK(_cl: CryptoCurves, xb: Uint8Array, db?: Uint8Array): JWKKeyPair { + const x = base64.encode(xb); + const output: JWKKeyPair = { + public: { + kty: "OKP", + crv: "Ed25519", + x, + key_ops: [ + "verify" + ], + ext: true + } + }; + + if (db !== undefined) { + output.secret = { + kty: "OKP", + crv: "Ed25519", + x, + d : base64.encode(db), + key_ops: [ + "sign" + ], + ext: true + }; + } + return output; +} diff --git a/lib/encodings/base58/baseN.js b/lib/encodings/base58/baseN.js new file mode 100644 index 0000000..b3b0c5d --- /dev/null +++ b/lib/encodings/base58/baseN.js @@ -0,0 +1,155 @@ +/** + * Base-N/Base-X encoding/decoding functions. + * + * Original implementation from base-x: + * https://github.com/cryptocoinjs/base-x + * + * Which is MIT licensed: + * + * The MIT License (MIT) + * + * Copyright base-x contributors (c) 2016 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +// baseN alphabet indexes +const _reverseAlphabets = {}; + +/** + * BaseN-encodes a Uint8Array using the given alphabet. + * + * @param {Uint8Array} input - The bytes to encode in a Uint8Array. + * @param {string} alphabet - The alphabet to use for encoding. + * @param {number} maxline - The maximum number of encoded characters per line + * to use, defaults to none. + * + * @returns {string} The baseN-encoded output string. + */ +export function encode(input, alphabet, maxline) { + if(!(input instanceof Uint8Array)) { + throw new TypeError('"input" must be a Uint8Array.'); + } + if(typeof alphabet !== 'string') { + throw new TypeError('"alphabet" must be a string.'); + } + if(maxline !== undefined && typeof maxline !== 'number') { + throw new TypeError('"maxline" must be a number.'); + } + if(input.length === 0) { + return ''; + } + + let output = ''; + + let i = 0; + const base = alphabet.length; + const first = alphabet.charAt(0); + const digits = [0]; + for(i = 0; i < input.length; ++i) { + let carry = input[i]; + for(let j = 0; j < digits.length; ++j) { + carry += digits[j] << 8; + digits[j] = carry % base; + carry = (carry / base) | 0; + } + + while(carry > 0) { + digits.push(carry % base); + carry = (carry / base) | 0; + } + } + + // deal with leading zeros + for(i = 0; input[i] === 0 && i < input.length - 1; ++i) { + output += first; + } + // convert digits to a string + for(i = digits.length - 1; i >= 0; --i) { + output += alphabet[digits[i]]; + } + + if(maxline) { + const regex = new RegExp('.{1,' + maxline + '}', 'g'); + output = output.match(regex).join('\r\n'); + } + + return output; +} + +/** + * Decodes a baseN-encoded (using the given alphabet) string to a + * Uint8Array. + * + * @param {string} input - The baseN-encoded input string. + * @param {string} alphabet - The alphabet to use for decoding. + * + * @returns {Uint8Array} The decoded bytes in a Uint8Array. + */ +export function decode(input, alphabet) { + if(typeof input !== 'string') { + throw new TypeError('"input" must be a string.'); + } + if(typeof alphabet !== 'string') { + throw new TypeError('"alphabet" must be a string.'); + } + if(input.length === 0) { + return new Uint8Array(); + } + + let table = _reverseAlphabets[alphabet]; + if(!table) { + // compute reverse alphabet + table = _reverseAlphabets[alphabet] = []; + for(let i = 0; i < alphabet.length; ++i) { + table[alphabet.charCodeAt(i)] = i; + } + } + + // remove whitespace characters + input = input.replace(/\s/g, ''); + + const base = alphabet.length; + const first = alphabet.charAt(0); + const bytes = [0]; + for(let i = 0; i < input.length; i++) { + const value = table[input.charCodeAt(i)]; + if(value === undefined) { + return; + } + + let carry = value; + for(let j = 0; j < bytes.length; ++j) { + carry += bytes[j] * base; + bytes[j] = carry & 0xff; + carry >>= 8; + } + + while(carry > 0) { + bytes.push(carry & 0xff); + carry >>= 8; + } + } + + // deal with leading zeros + for(let k = 0; input[k] === first && k < input.length - 1; ++k) { + bytes.push(0); + } + + return new Uint8Array(bytes.reverse()); +} diff --git a/lib/encodings/base58/index.d.ts b/lib/encodings/base58/index.d.ts new file mode 100644 index 0000000..63d34fd --- /dev/null +++ b/lib/encodings/base58/index.d.ts @@ -0,0 +1,19 @@ + +/// +/** + * Encoder function for base58url, needed for the Multikey encoding + * + * @param input + * @param maxline + * @returns + */ +export function encode(input: Uint8Array, maxline?: number): string; + +/** + * Decoder function for base58url, needed for the Multikey encoding + * + * @param input + * @param maxline + * @returns + */ +export function decode(input: string): Uint8Array; diff --git a/lib/encodings/base58/index.js b/lib/encodings/base58/index.js new file mode 100644 index 0000000..1e20d74 --- /dev/null +++ b/lib/encodings/base58/index.js @@ -0,0 +1,35 @@ +/** + * Base58url functions. This code + * + * + * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved. + */ +import { + encode as _encode, + decode as _decode +} from './baseN.js'; + +// base58 characters (Bitcoin alphabet) +const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + +/** + * Encoder function for base58url, needed for the Multikey encoding + * + * @param {*} input + * @param {*} maxline + * @returns + */ +export function encode(input, maxline) { + return _encode(input, alphabet, maxline); +} + +/** + * Encoder function for base58url, needed for the Multikey encoding + * + * @param {*} input + * @param {*} maxline + * @returns + */ +export function decode(input) { + return _decode(input, alphabet); +} diff --git a/lib/encodings/base64.ts b/lib/encodings/base64.ts new file mode 100644 index 0000000..fc1a4e3 --- /dev/null +++ b/lib/encodings/base64.ts @@ -0,0 +1,53 @@ +/** + * Base 64 encoding/decoding + * + * @module + */ + +/* + * These two came from perplexity.io, hopefully it is correct... + */ +const base64ToUrl = (base64String: string): string => { + return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +}; + +const urlToBase64 = (base64Url: string): string => { + return base64Url.replace(/-/g, '+').replace(/_/g, '/'); +}; + +/** + * Convert an array buffer to base64url value. + * + * (Created with the help of perplexity.io...) + * + * @param arrayBuffer + * @returns + */ +export function encode(bytes: Uint8Array): string { + let binary: string = ""; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + const base64String = btoa(binary); + return base64ToUrl(base64String); +} + +/** + * Convert a base64url value to Uint8Array + * + * (Created with the help of perplexity.io...) + * + * @param string + * @returns + */ +export function decode(url: string): Uint8Array { + const base64string = urlToBase64(url); + + const binary = atob(base64string); + + const byteArray = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + byteArray[i] = binary.charCodeAt(i); + } + return byteArray; +} diff --git a/tests/roundtrip.ts b/tests/roundtrip.ts new file mode 100644 index 0000000..02bf388 --- /dev/null +++ b/tests/roundtrip.ts @@ -0,0 +1,50 @@ +import { JWKKeyPair, MultikeyPair, JWKToMultikey, multikeyToJWK } from "../index"; + +/** ----------------------------- */ + +/************* Debugging help ***********/ +// deno-lint-ignore no-explicit-any +export function str(inp: any): void { + console.log(JSON.stringify(inp, null, 4)); +} + +/** + * Convert a CryptoKey Pair into a JWK Pair. Not really used by these tools, but handy to have it to help debugging. + * @param newPair + * @returns + */ + +async function toJWK(newPair: CryptoKeyPair): Promise { + const publicKey: JsonWebKey = await crypto.subtle.exportKey("jwk", newPair.publicKey); + const privateKey: JsonWebKey = await crypto.subtle.exportKey("jwk", newPair.privateKey); + return { public: publicKey, secret: privateKey }; +} + +async function main(): Promise { + const onePair = async (label: string, pair: CryptoKeyPair): Promise => { + const keyPair: JWKKeyPair = await toJWK(pair); + const mk: MultikeyPair = JWKToMultikey(keyPair); + const mkPair: JWKKeyPair = multikeyToJWK(mk); + console.log(`----\n${label}:`); + str(keyPair); + str(mkPair); + + if (label === "EDDSA") { + console.log(`Values are equal? ${keyPair.secret?.x === mkPair.secret?.x && keyPair?.secret?.d === keyPair?.secret?.d}`) + } else { + console.log(`Values are equal? ${keyPair.secret?.x === mkPair.secret?.x && keyPair.secret?.y === mkPair.secret?.y && keyPair?.secret?.d === keyPair?.secret?.d}`) + } + } + + const eddsaPair: CryptoKeyPair = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]) as CryptoKeyPair; + await onePair("EDDSA", eddsaPair); + + const p256: CryptoKeyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]) as CryptoKeyPair; + await onePair("ECDSA P-256", p256); + + const p384: CryptoKeyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-384" }, true, ["sign", "verify"]) as CryptoKeyPair; + await onePair("ECDSA P-384", p384); +} + +main() + diff --git a/tsconfig.json b/tsconfig.json index 460fd02..fda1797 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ "target": "esnext" }, "typesRoots": ["types", "node_modules/@types"], - "include": ["lib/**/*", "index.ts"], + "include": ["lib/**/*", "index.ts","lib/encodings/base64.ts", "lib/encodings/base58/**/*"], "exclude": ["extras/**/*", "example/**/*", "tests/**/*", "tester/**/*", ".Attic/**/*"], "typedocOptions": { "plugin": ["typedoc-theme-hierarchy"],