- Add GETTING_STARTED.md with quick start guide and development modes - Add INSTALL.sh automated installation script - Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md - Add QUICK_REFERENCE.md for common commands - Add SETUP_GUIDE.md with detailed setup instructions - Update README.md with improved project overview - Add did-wallet app dependencies and node_modules
6937 lines
249 KiB
JavaScript
6937 lines
249 KiB
JavaScript
"use strict";
|
||
var __create = Object.create;
|
||
var __defProp = Object.defineProperty;
|
||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||
var __getProtoOf = Object.getPrototypeOf;
|
||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||
var __export = (target, all) => {
|
||
for (var name in all)
|
||
__defProp(target, name, { get: all[name], enumerable: true });
|
||
};
|
||
var __copyProps = (to, from, except, desc) => {
|
||
if (from && typeof from === "object" || typeof from === "function") {
|
||
for (let key of __getOwnPropNames(from))
|
||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||
}
|
||
return to;
|
||
};
|
||
var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__getProtoOf(mod2)) : {}, __copyProps(
|
||
// If the importer is in node compatibility mode or this is not an ESM
|
||
// file that has been converted to a CommonJS file using a Babel-
|
||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||
isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target,
|
||
mod2
|
||
));
|
||
var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
|
||
|
||
// src/index.ts
|
||
var src_exports = {};
|
||
__export(src_exports, {
|
||
AgentCryptoApi: () => AgentCryptoApi,
|
||
AgentDidApi: () => AgentDidApi,
|
||
AgentDwnApi: () => AgentDwnApi,
|
||
AgentIdentityApi: () => AgentIdentityApi,
|
||
AgentSyncApi: () => AgentSyncApi,
|
||
BearerIdentity: () => BearerIdentity,
|
||
DidInterface: () => DidInterface,
|
||
DidRpcMethod: () => DidRpcMethod,
|
||
DwnConstant: () => import_dwn_sdk_js2.DwnConstant,
|
||
DwnDataStore: () => DwnDataStore,
|
||
DwnDateSort: () => import_dwn_sdk_js2.DateSort,
|
||
DwnDidStore: () => DwnDidStore,
|
||
DwnEncryptionAlgorithm: () => import_dwn_sdk_js2.EncryptionAlgorithm,
|
||
DwnEventSubscriptionHandler: () => import_dwn_sdk_js2.EventSubscriptionHandler,
|
||
DwnIdentityStore: () => DwnIdentityStore,
|
||
DwnInterface: () => DwnInterface,
|
||
DwnKeyDerivationScheme: () => import_dwn_sdk_js2.KeyDerivationScheme,
|
||
DwnKeyStore: () => DwnKeyStore,
|
||
DwnMessageSubscription: () => import_dwn_sdk_js2.MessageSubscription,
|
||
DwnPaginationCursor: () => import_dwn_sdk_js2.PaginationCursor,
|
||
DwnPublicKeyJwk: () => import_dwn_sdk_js2.PublicJwk,
|
||
DwnRecordSubscriptionHandler: () => import_dwn_sdk_js2.RecordSubscriptionHandler,
|
||
DwnSigner: () => import_dwn_sdk_js2.Signer,
|
||
HdIdentityVault: () => HdIdentityVault,
|
||
HttpWeb5RpcClient: () => HttpWeb5RpcClient,
|
||
InMemoryDataStore: () => InMemoryDataStore,
|
||
InMemoryDidStore: () => InMemoryDidStore,
|
||
InMemoryIdentityStore: () => InMemoryIdentityStore,
|
||
InMemoryKeyStore: () => InMemoryKeyStore,
|
||
LocalKeyManager: () => LocalKeyManager2,
|
||
PlatformAgentTestHarness: () => PlatformAgentTestHarness,
|
||
SyncEngineLevel: () => SyncEngineLevel,
|
||
Web5RpcClient: () => Web5RpcClient,
|
||
WebSocketWeb5RpcClient: () => WebSocketWeb5RpcClient,
|
||
blobToIsomorphicNodeReadable: () => blobToIsomorphicNodeReadable,
|
||
dwnMessageConstructors: () => dwnMessageConstructors,
|
||
getDwnServiceEndpointUrls: () => getDwnServiceEndpointUrls,
|
||
getPaginationCursor: () => getPaginationCursor,
|
||
getRecordAuthor: () => getRecordAuthor,
|
||
getRecordMessageCid: () => getRecordMessageCid,
|
||
isDidRequest: () => isDidRequest,
|
||
isDwnMessage: () => isDwnMessage,
|
||
isDwnRequest: () => isDwnRequest,
|
||
isIdentityMetadata: () => isIdentityMetadata,
|
||
isPortableIdentity: () => isPortableIdentity,
|
||
isRecordsWrite: () => isRecordsWrite,
|
||
webReadableToIsomorphicNodeReadable: () => webReadableToIsomorphicNodeReadable
|
||
});
|
||
module.exports = __toCommonJS(src_exports);
|
||
|
||
// src/types/dwn.ts
|
||
var import_dwn_sdk_js = require("@tbd54566975/dwn-sdk-js");
|
||
var import_dwn_sdk_js2 = require("@tbd54566975/dwn-sdk-js");
|
||
var DwnInterface = ((DwnInterface2) => {
|
||
DwnInterface2[DwnInterface2["EventsGet"] = import_dwn_sdk_js.DwnInterfaceName.Events + import_dwn_sdk_js.DwnMethodName.Get] = "EventsGet";
|
||
DwnInterface2[DwnInterface2["EventsQuery"] = import_dwn_sdk_js.DwnInterfaceName.Events + import_dwn_sdk_js.DwnMethodName.Query] = "EventsQuery";
|
||
DwnInterface2[DwnInterface2["EventsSubscribe"] = import_dwn_sdk_js.DwnInterfaceName.Events + import_dwn_sdk_js.DwnMethodName.Subscribe] = "EventsSubscribe";
|
||
DwnInterface2[DwnInterface2["MessagesGet"] = import_dwn_sdk_js.DwnInterfaceName.Messages + import_dwn_sdk_js.DwnMethodName.Get] = "MessagesGet";
|
||
DwnInterface2[DwnInterface2["ProtocolsConfigure"] = import_dwn_sdk_js.DwnInterfaceName.Protocols + import_dwn_sdk_js.DwnMethodName.Configure] = "ProtocolsConfigure";
|
||
DwnInterface2[DwnInterface2["ProtocolsQuery"] = import_dwn_sdk_js.DwnInterfaceName.Protocols + import_dwn_sdk_js.DwnMethodName.Query] = "ProtocolsQuery";
|
||
DwnInterface2[DwnInterface2["RecordsDelete"] = import_dwn_sdk_js.DwnInterfaceName.Records + import_dwn_sdk_js.DwnMethodName.Delete] = "RecordsDelete";
|
||
DwnInterface2[DwnInterface2["RecordsQuery"] = import_dwn_sdk_js.DwnInterfaceName.Records + import_dwn_sdk_js.DwnMethodName.Query] = "RecordsQuery";
|
||
DwnInterface2[DwnInterface2["RecordsRead"] = import_dwn_sdk_js.DwnInterfaceName.Records + import_dwn_sdk_js.DwnMethodName.Read] = "RecordsRead";
|
||
DwnInterface2[DwnInterface2["RecordsSubscribe"] = import_dwn_sdk_js.DwnInterfaceName.Records + import_dwn_sdk_js.DwnMethodName.Subscribe] = "RecordsSubscribe";
|
||
DwnInterface2[DwnInterface2["RecordsWrite"] = import_dwn_sdk_js.DwnInterfaceName.Records + import_dwn_sdk_js.DwnMethodName.Write] = "RecordsWrite";
|
||
return DwnInterface2;
|
||
})(DwnInterface || {});
|
||
var dwnMessageConstructors = {
|
||
[DwnInterface.EventsGet]: import_dwn_sdk_js.EventsGet,
|
||
[DwnInterface.EventsQuery]: import_dwn_sdk_js.EventsQuery,
|
||
[DwnInterface.EventsSubscribe]: import_dwn_sdk_js.EventsSubscribe,
|
||
[DwnInterface.MessagesGet]: import_dwn_sdk_js.MessagesGet,
|
||
[DwnInterface.ProtocolsConfigure]: import_dwn_sdk_js.ProtocolsConfigure,
|
||
[DwnInterface.ProtocolsQuery]: import_dwn_sdk_js.ProtocolsQuery,
|
||
[DwnInterface.RecordsDelete]: import_dwn_sdk_js.RecordsDelete,
|
||
[DwnInterface.RecordsQuery]: import_dwn_sdk_js.RecordsQuery,
|
||
[DwnInterface.RecordsRead]: import_dwn_sdk_js.RecordsRead,
|
||
[DwnInterface.RecordsSubscribe]: import_dwn_sdk_js.RecordsSubscribe,
|
||
[DwnInterface.RecordsWrite]: import_dwn_sdk_js.RecordsWrite
|
||
};
|
||
|
||
// src/bearer-identity.ts
|
||
var BearerIdentity = class {
|
||
constructor({ did, metadata }) {
|
||
this.did = did;
|
||
this.metadata = metadata;
|
||
}
|
||
/**
|
||
* Converts a `BearerIdentity` object to a portable format containing the DID and metadata
|
||
* associated with the Identity.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* // Assuming `identity` is an instance of BearerIdentity.
|
||
* const portableIdentity = await identity.export();
|
||
* // portableIdentity now contains the and metadata.
|
||
* ```
|
||
*
|
||
* @returns A `PortableIdentity` containing the DID and metadata associated with the
|
||
* `BearerIdentity`.
|
||
*/
|
||
async export() {
|
||
return {
|
||
portableDid: await this.did.export(),
|
||
metadata: this.metadata
|
||
};
|
||
}
|
||
};
|
||
|
||
// src/crypto-api.ts
|
||
var import_crypto8 = require("@web5/crypto");
|
||
|
||
// src/prototyping/crypto/algorithms/hkdf.ts
|
||
var import_crypto = require("@web5/crypto");
|
||
|
||
// src/prototyping/crypto/primitives/hkdf.ts
|
||
var import_utils = require("@noble/ciphers/webcrypto/utils");
|
||
var import_common = require("@web5/common");
|
||
var Hkdf = class {
|
||
/**
|
||
* Derives a key using the HMAC-based Extract-and-Expand Key Derivation Function (HKDF).
|
||
*
|
||
* This method generates a derived key using a hash function from input keying material given as
|
||
* `baseKeyBytes`. The length of the derived key can be specified. Optionally, it can also use a salt
|
||
* and info for the derivation process.
|
||
*
|
||
* HKDF is useful in various cryptographic applications and protocols, especially when
|
||
* there's a need to derive multiple keys from a single source of key material.
|
||
*
|
||
* Note: The `baseKeyBytes` that will be the input key material for HKDF should be a high-entropy
|
||
* secret value, such as a cryptographic key. It should be kept confidential and not be derived
|
||
* from a low-entropy value, such as a password.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const info = new Uint8Array([...]);
|
||
* const derivedKeyBytes = await Hkdf.deriveKeyBytes({
|
||
* baseKeyBytes: new Uint8Array([...]), // Input keying material
|
||
* hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512')
|
||
* salt: new Uint8Array([...]), // The salt value
|
||
* info: new Uint8Array([...]), // Optional application-specific information
|
||
* length: 256 // The length of the derived key in bits
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for key derivation.
|
||
* @returns A Promise that resolves to the derived key as a byte array.
|
||
*/
|
||
static async deriveKeyBytes({ baseKeyBytes, length, hash: hash2, salt, info = new Uint8Array() }) {
|
||
const webCrypto = (0, import_utils.getWebcryptoSubtle)();
|
||
const webCryptoKey = await webCrypto.importKey("raw", baseKeyBytes, { name: "HKDF" }, false, ["deriveBits"]);
|
||
salt = typeof salt === "string" ? import_common.Convert.string(salt).toUint8Array() : salt;
|
||
info = typeof info === "string" ? import_common.Convert.string(info).toUint8Array() : info;
|
||
const derivedKeyBuffer = await crypto.subtle.deriveBits(
|
||
{ name: "HKDF", hash: hash2, salt, info },
|
||
webCryptoKey,
|
||
length
|
||
);
|
||
const derivedKeyBytes = new Uint8Array(derivedKeyBuffer);
|
||
return derivedKeyBytes;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/hkdf.ts
|
||
var HkdfAlgorithm = class extends import_crypto.CryptoAlgorithm {
|
||
async deriveKeyBytes({ algorithm, ...params }) {
|
||
const hash2 = {
|
||
"HKDF-256": "SHA-256",
|
||
"HKDF-384": "SHA-384",
|
||
"HKDF-512": "SHA-512"
|
||
}[algorithm];
|
||
const derivedKeyBytes = await Hkdf.deriveKeyBytes({ ...params, hash: hash2 });
|
||
return derivedKeyBytes;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/ecdsa.ts
|
||
var import_crypto2 = require("@web5/crypto");
|
||
|
||
// src/prototyping/crypto/crypto-error.ts
|
||
var CryptoError = class _CryptoError extends Error {
|
||
/**
|
||
* Constructs an instance of CryptoError, a custom error class for handling Crypto-related errors.
|
||
*
|
||
* @param code - A {@link CryptoErrorCode} representing the specific type of error encountered.
|
||
* @param message - A human-readable description of the error.
|
||
*/
|
||
constructor(code, message) {
|
||
super(message);
|
||
this.code = code;
|
||
this.name = "CryptoError";
|
||
Object.setPrototypeOf(this, new.target.prototype);
|
||
if (Error.captureStackTrace) {
|
||
Error.captureStackTrace(this, _CryptoError);
|
||
}
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/ecdsa.ts
|
||
var EcdsaAlgorithm = class extends import_crypto2.CryptoAlgorithm {
|
||
async bytesToPrivateKey({ algorithm, privateKeyBytes }) {
|
||
switch (algorithm) {
|
||
case "ES256K":
|
||
case "secp256k1": {
|
||
const privateKey = await import_crypto2.Secp256k1.bytesToPrivateKey({ privateKeyBytes });
|
||
privateKey.alg = "EdDSA";
|
||
return privateKey;
|
||
}
|
||
case "ES256":
|
||
case "secp256r1": {
|
||
const privateKey = await import_crypto2.Secp256r1.bytesToPrivateKey({ privateKeyBytes });
|
||
privateKey.alg = "EdDSA";
|
||
return privateKey;
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Algorithm not supported: ${algorithm}`);
|
||
}
|
||
}
|
||
}
|
||
async bytesToPublicKey({ algorithm, publicKeyBytes }) {
|
||
switch (algorithm) {
|
||
case "ES256K":
|
||
case "secp256k1": {
|
||
const publicKey = await import_crypto2.Secp256k1.bytesToPublicKey({ publicKeyBytes });
|
||
publicKey.alg = "EdDSA";
|
||
return publicKey;
|
||
}
|
||
case "ES256":
|
||
case "secp256r1": {
|
||
const publicKey = await import_crypto2.Secp256r1.bytesToPublicKey({ publicKeyBytes });
|
||
publicKey.alg = "EdDSA";
|
||
return publicKey;
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Algorithm not supported: ${algorithm}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Derives the public key in JWK format from a given private key.
|
||
*
|
||
* @remarks
|
||
* This method takes a private key in JWK format and derives its corresponding public key,
|
||
* also in JWK format. The process ensures that the derived public key correctly corresponds to
|
||
* the given private key.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const ecdsa = new EcdsaAlgorithm();
|
||
* const privateKey = { ... }; // A Jwk object representing a private key
|
||
* const publicKey = await ecdsa.computePublicKey({ key: privateKey });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the public key derivation.
|
||
* @param params.key - The private key in JWK format from which to derive the public key.
|
||
*
|
||
* @returns A Promise that resolves to the derived public key in JWK format.
|
||
*/
|
||
async computePublicKey({ key }) {
|
||
if (!(0, import_crypto2.isEcPrivateJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an elliptic curve (EC) private key.");
|
||
switch (key.crv) {
|
||
case "secp256k1": {
|
||
const publicKey = await import_crypto2.Secp256k1.computePublicKey({ key });
|
||
publicKey.alg = "ES256K";
|
||
return publicKey;
|
||
}
|
||
case "P-256": {
|
||
const publicKey = await import_crypto2.Secp256r1.computePublicKey({ key });
|
||
publicKey.alg = "ES256";
|
||
return publicKey;
|
||
}
|
||
default: {
|
||
throw new Error(`Unsupported curve: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Generates a new private key with the specified algorithm in JSON Web Key (JWK) format.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const ecdsa = new EcdsaAlgorithm();
|
||
* const privateKey = await ecdsa.generateKey({ algorithm: 'ES256K' });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for key generation.
|
||
* @param params.algorithm - The algorithm to use for key generation.
|
||
*
|
||
* @returns A Promise that resolves to the generated private key in JWK format.
|
||
*/
|
||
async generateKey({ algorithm }) {
|
||
switch (algorithm) {
|
||
case "ES256K":
|
||
case "secp256k1": {
|
||
const privateKey = await import_crypto2.Secp256k1.generateKey();
|
||
privateKey.alg = "ES256K";
|
||
return privateKey;
|
||
}
|
||
case "ES256":
|
||
case "secp256r1": {
|
||
const privateKey = await import_crypto2.Secp256r1.generateKey();
|
||
privateKey.alg = "ES256";
|
||
return privateKey;
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Retrieves the public key properties from a given private key in JWK format.
|
||
*
|
||
* @remarks
|
||
* This method extracts the public key portion from an ECDSA private key in JWK format. It does
|
||
* so by removing the private key property 'd' and making a shallow copy, effectively yielding the
|
||
* public key.
|
||
*
|
||
* Note: This method offers a significant performance advantage, being about 200 times faster
|
||
* than `computePublicKey()`. However, it does not mathematically validate the private key, nor
|
||
* does it derive the public key from the private key. It simply extracts existing public key
|
||
* properties from the private key object. This makes it suitable for scenarios where speed is
|
||
* critical and the private key's integrity is already assured.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const ecdsa = new EcdsaAlgorithm();
|
||
* const privateKey = { ... }; // A Jwk object representing a private key
|
||
* const publicKey = await ecdsa.getPublicKey({ key: privateKey });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for retrieving the public key properties.
|
||
* @param params.key - The private key in JWK format.
|
||
*
|
||
* @returns A Promise that resolves to the public key in JWK format.
|
||
*/
|
||
async getPublicKey({ key }) {
|
||
if (!(0, import_crypto2.isEcPrivateJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an elliptic curve (EC) private key.");
|
||
switch (key.crv) {
|
||
case "secp256k1": {
|
||
const publicKey = await import_crypto2.Secp256k1.getPublicKey({ key });
|
||
publicKey.alg = "ES256K";
|
||
return publicKey;
|
||
}
|
||
case "P-256": {
|
||
const publicKey = await import_crypto2.Secp256r1.getPublicKey({ key });
|
||
publicKey.alg = "ES256";
|
||
return publicKey;
|
||
}
|
||
default: {
|
||
throw new Error(`Unsupported curve: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
async privateKeyToBytes({ privateKey }) {
|
||
switch (privateKey.crv) {
|
||
case "secp256k1": {
|
||
return await import_crypto2.Secp256k1.privateKeyToBytes({ privateKey });
|
||
}
|
||
case "P-256": {
|
||
return await import_crypto2.Secp256r1.privateKeyToBytes({ privateKey });
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Curve not supported: ${privateKey.crv}`);
|
||
}
|
||
}
|
||
}
|
||
async publicKeyToBytes({ publicKey }) {
|
||
switch (publicKey.crv) {
|
||
case "secp256k1": {
|
||
return await import_crypto2.Secp256k1.publicKeyToBytes({ publicKey });
|
||
}
|
||
case "P-256": {
|
||
return await import_crypto2.Secp256r1.publicKeyToBytes({ publicKey });
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Curve not supported: ${publicKey.crv}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Generates an ECDSA signature of given data using a private key.
|
||
*
|
||
* @remarks
|
||
* This method uses the signature algorithm determined by the given `algorithm` to sign the
|
||
* provided data.
|
||
*
|
||
* The signature can later be verified by parties with access to the corresponding
|
||
* public key, ensuring that the data has not been tampered with and was indeed signed by the
|
||
* holder of the private key.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const ecdsa = new EcdsaAlgorithm();
|
||
* const data = new TextEncoder().encode('Message');
|
||
* const privateKey = { ... }; // A Jwk object representing a private key
|
||
* const signature = await ecdsa.sign({
|
||
* key: privateKey,
|
||
* data
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the signing operation.
|
||
* @param params.key - The private key to use for signing, represented in JWK format.
|
||
* @param params.data - The data to sign.
|
||
*
|
||
* @returns A Promise resolving to the digital signature as a `Uint8Array`.
|
||
*/
|
||
async sign({ key, data }) {
|
||
if (!(0, import_crypto2.isEcPrivateJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an elliptic curve (EC) private key.");
|
||
switch (key.crv) {
|
||
case "secp256k1": {
|
||
return await import_crypto2.Secp256k1.sign({ key, data });
|
||
}
|
||
case "P-256": {
|
||
return await import_crypto2.Secp256r1.sign({ key, data });
|
||
}
|
||
default: {
|
||
throw new Error(`Unsupported curve: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Verifies an ECDSA signature associated with the provided data using the provided key.
|
||
*
|
||
* @remarks
|
||
* This method uses the signature algorithm determined by the `crv` property of the provided key
|
||
* to check the validity of a digital signature against the original data. It confirms whether the
|
||
* signature was created by the holder of the corresponding private key and that the data has not
|
||
* been tampered with.
|
||
*s
|
||
* @example
|
||
* ```ts
|
||
* const ecdsa = new EcdsaAlgorithm();
|
||
* const publicKey = { ... }; // Public key in JWK format corresponding to the private key that signed the data
|
||
* const signature = new Uint8Array([...]); // Signature to verify
|
||
* const data = new TextEncoder().encode('Message');
|
||
* const isValid = await ecdsa.verify({
|
||
* key: publicKey,
|
||
* signature,
|
||
* data
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the verification operation.
|
||
* @param params.key - The key to use for verification.
|
||
* @param params.signature - The signature to verify.
|
||
* @param params.data - The data to verify.
|
||
*
|
||
* @returns A Promise resolving to a boolean indicating whether the signature is valid.
|
||
*/
|
||
async verify({ key, signature, data }) {
|
||
if (!(0, import_crypto2.isEcPublicJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an elliptic curve (EC) public key.");
|
||
switch (key.crv) {
|
||
case "secp256k1": {
|
||
return await import_crypto2.Secp256k1.verify({ key, signature, data });
|
||
}
|
||
case "P-256": {
|
||
return await import_crypto2.Secp256r1.verify({ key, signature, data });
|
||
}
|
||
default: {
|
||
throw new Error(`Unsupported curve: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/eddsa.ts
|
||
var import_crypto3 = require("@web5/crypto");
|
||
var EdDsaAlgorithm = class extends import_crypto3.CryptoAlgorithm {
|
||
async bytesToPrivateKey({ algorithm, privateKeyBytes }) {
|
||
switch (algorithm) {
|
||
case "Ed25519": {
|
||
const privateKey = await import_crypto3.Ed25519.bytesToPrivateKey({ privateKeyBytes });
|
||
privateKey.alg = "EdDSA";
|
||
return privateKey;
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Algorithm not supported: ${algorithm}`);
|
||
}
|
||
}
|
||
}
|
||
async bytesToPublicKey({ algorithm, publicKeyBytes }) {
|
||
switch (algorithm) {
|
||
case "Ed25519": {
|
||
const publicKey = await import_crypto3.Ed25519.bytesToPublicKey({ publicKeyBytes });
|
||
publicKey.alg = "EdDSA";
|
||
return publicKey;
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Algorithm not supported: ${algorithm}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Derives the public key in JWK format from a given private key.
|
||
*
|
||
* @remarks
|
||
* This method takes a private key in JWK format and derives its corresponding public key,
|
||
* also in JWK format. The process ensures that the derived public key correctly corresponds to
|
||
* the given private key.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const eddsa = new EdDsaAlgorithm();
|
||
* const privateKey = { ... }; // A Jwk object representing a private key
|
||
* const publicKey = await eddsa.computePublicKey({ key: privateKey });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the public key derivation.
|
||
* @param params.key - The private key in JWK format from which to derive the public key.
|
||
*
|
||
* @returns A Promise that resolves to the derived public key in JWK format.
|
||
*/
|
||
async computePublicKey({ key }) {
|
||
if (!(0, import_crypto3.isOkpPrivateJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an octet key pair (OKP) private key.");
|
||
switch (key.crv) {
|
||
case "Ed25519": {
|
||
const publicKey = await import_crypto3.Ed25519.computePublicKey({ key });
|
||
publicKey.alg = "EdDSA";
|
||
return publicKey;
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Curve not supported: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Generates a new private key with the specified algorithm in JSON Web Key (JWK) format.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const eddsa = new EdDsaAlgorithm();
|
||
* const privateKey = await eddsa.generateKey({ algorithm: 'Ed25519' });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for key generation.
|
||
* @param params.algorithm - The algorithm to use for key generation.
|
||
*
|
||
* @returns A Promise that resolves to the generated private key in JWK format.
|
||
*/
|
||
async generateKey({ algorithm }) {
|
||
switch (algorithm) {
|
||
case "Ed25519": {
|
||
const privateKey = await import_crypto3.Ed25519.generateKey();
|
||
privateKey.alg = "EdDSA";
|
||
return privateKey;
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Retrieves the public key properties from a given private key in JWK format.
|
||
*
|
||
* @remarks
|
||
* This method extracts the public key portion from an EdDSA private key in JWK format. It does
|
||
* so by removing the private key property 'd' and making a shallow copy, effectively yielding the
|
||
* public key.
|
||
*
|
||
* Note: This method offers a significant performance advantage, being about 100 times faster
|
||
* than `computePublicKey()`. However, it does not mathematically validate the private key, nor
|
||
* does it derive the public key from the private key. It simply extracts existing public key
|
||
* properties from the private key object. This makes it suitable for scenarios where speed is
|
||
* critical and the private key's integrity is already assured.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const eddsa = new EdDsaAlgorithm();
|
||
* const privateKey = { ... }; // A Jwk object representing a private key
|
||
* const publicKey = await eddsa.getPublicKey({ key: privateKey });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for retrieving the public key properties.
|
||
* @param params.key - The private key in JWK format.
|
||
*
|
||
* @returns A Promise that resolves to the public key in JWK format.
|
||
*/
|
||
async getPublicKey({ key }) {
|
||
if (!(0, import_crypto3.isOkpPrivateJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an octet key pair (OKP) private key.");
|
||
switch (key.crv) {
|
||
case "Ed25519": {
|
||
const publicKey = await import_crypto3.Ed25519.getPublicKey({ key });
|
||
publicKey.alg = "EdDSA";
|
||
return publicKey;
|
||
}
|
||
default: {
|
||
throw new Error(`Unsupported curve: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
async privateKeyToBytes({ privateKey }) {
|
||
switch (privateKey.crv) {
|
||
case "Ed25519": {
|
||
return await import_crypto3.Ed25519.privateKeyToBytes({ privateKey });
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Curve not supported: ${privateKey.crv}`);
|
||
}
|
||
}
|
||
}
|
||
async publicKeyToBytes({ publicKey }) {
|
||
switch (publicKey.crv) {
|
||
case "Ed25519": {
|
||
return await import_crypto3.Ed25519.publicKeyToBytes({ publicKey });
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Curve not supported: ${publicKey.crv}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Generates an EdDSA signature of given data using a private key.
|
||
*
|
||
* @remarks
|
||
* This method uses the signature algorithm determined by the given `algorithm` to sign the
|
||
* provided data.
|
||
*
|
||
* The signature can later be verified by parties with access to the corresponding
|
||
* public key, ensuring that the data has not been tampered with and was indeed signed by the
|
||
* holder of the private key.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const eddsa = new EdDsaAlgorithm();
|
||
* const data = new TextEncoder().encode('Message');
|
||
* const privateKey = { ... }; // A Jwk object representing a private key
|
||
* const signature = await eddsa.sign({
|
||
* key: privateKey,
|
||
* data
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the signing operation.
|
||
* @param params.key - The private key to use for signing, represented in JWK format.
|
||
* @param params.data - The data to sign.
|
||
*
|
||
* @returns A Promise resolving to the digital signature as a `Uint8Array`.
|
||
*/
|
||
async sign({ key, data }) {
|
||
if (!(0, import_crypto3.isOkpPrivateJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an octet key pair (OKP) private key.");
|
||
switch (key.crv) {
|
||
case "Ed25519": {
|
||
return await import_crypto3.Ed25519.sign({ key, data });
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Curve not supported: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Verifies an EdDSA signature associated with the provided data using the provided key.
|
||
*
|
||
* @remarks
|
||
* This method uses the signature algorithm determined by the `crv` property of the provided key
|
||
* to check the validity of a digital signature against the original data. It confirms whether the
|
||
* signature was created by the holder of the corresponding private key and that the data has not
|
||
* been tampered with.
|
||
*s
|
||
* @example
|
||
* ```ts
|
||
* const eddsa = new EdDsaAlgorithm();
|
||
* const publicKey = { ... }; // Public key in JWK format corresponding to the private key that signed the data
|
||
* const signature = new Uint8Array([...]); // Signature to verify
|
||
* const data = new TextEncoder().encode('Message');
|
||
* const isValid = await eddsa.verify({
|
||
* key: publicKey,
|
||
* signature,
|
||
* data
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the verification operation.
|
||
* @param params.key - The key to use for verification.
|
||
* @param params.signature - The signature to verify.
|
||
* @param params.data - The data to verify.
|
||
*
|
||
* @returns A Promise resolving to a boolean indicating whether the signature is valid.
|
||
*/
|
||
async verify({ key, signature, data }) {
|
||
if (!(0, import_crypto3.isOkpPublicJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be an octet key pair (OKP) public key.");
|
||
switch (key.crv) {
|
||
case "Ed25519": {
|
||
return await import_crypto3.Ed25519.verify({ key, signature, data });
|
||
}
|
||
default: {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Curve not supported: ${key.crv}`);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/aes-kw.ts
|
||
var import_crypto5 = require("@web5/crypto");
|
||
|
||
// src/prototyping/crypto/primitives/aes-kw.ts
|
||
var import_utils2 = require("@noble/ciphers/webcrypto/utils");
|
||
var import_common2 = require("@web5/common");
|
||
var import_crypto4 = require("@web5/crypto");
|
||
var AES_KEY_LENGTHS = [128, 192, 256];
|
||
var AesKw = class {
|
||
/**
|
||
* Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format.
|
||
*
|
||
* @remarks
|
||
* This method takes a symmetric key represented as a byte array (Uint8Array) and
|
||
* converts it into a JWK object for use with AES (Advanced Encryption Standard)
|
||
* for key wrapping. The conversion process involves encoding the key into
|
||
* base64url format and setting the appropriate JWK parameters.
|
||
*
|
||
* The resulting JWK object includes the following properties:
|
||
* - `kty`: Key Type, set to 'oct' for Octet Sequence (representing a symmetric key).
|
||
* - `k`: The symmetric key, base64url-encoded.
|
||
* - `kid`: Key ID, generated based on the JWK thumbprint.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const privateKeyBytes = new Uint8Array([...]); // Replace with actual symmetric key bytes
|
||
* const privateKey = await AesKw.bytesToPrivateKey({ privateKeyBytes });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the symmetric key conversion.
|
||
* @param params.privateKeyBytes - The raw symmetric key as a Uint8Array.
|
||
*
|
||
* @returns A Promise that resolves to the symmetric key in JWK format.
|
||
*/
|
||
static async bytesToPrivateKey({ privateKeyBytes }) {
|
||
const privateKey = {
|
||
k: import_common2.Convert.uint8Array(privateKeyBytes).toBase64Url(),
|
||
kty: "oct"
|
||
};
|
||
privateKey.kid = await (0, import_crypto4.computeJwkThumbprint)({ jwk: privateKey });
|
||
const lengthInBits = privateKeyBytes.length * 8;
|
||
privateKey.alg = { 128: "A128KW", 192: "A192KW", 256: "A256KW" }[lengthInBits];
|
||
return privateKey;
|
||
}
|
||
/**
|
||
* Generates a symmetric key for AES for key wrapping in JSON Web Key (JWK) format.
|
||
*
|
||
* @remarks
|
||
* This method creates a new symmetric key of a specified length suitable for use with
|
||
* AES key wrapping. It uses cryptographically secure random number generation to
|
||
* ensure the uniqueness and security of the key. The generated key adheres to the JWK
|
||
* format, making it compatible with common cryptographic standards and easy to use in
|
||
* various cryptographic processes.
|
||
*
|
||
* The generated key includes the following components:
|
||
* - `kty`: Key Type, set to 'oct' for Octet Sequence.
|
||
* - `k`: The symmetric key component, base64url-encoded.
|
||
* - `kid`: Key ID, generated based on the JWK thumbprint.
|
||
* - `alg`: Algorithm, set to 'A128KW', 'A192KW', or 'A256KW' for AES Key Wrap with the
|
||
* specified key length.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const length = 256; // Length of the key in bits (e.g., 128, 192, 256)
|
||
* const privateKey = await AesKw.generateKey({ length });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the key generation.
|
||
* @param params.length - The length of the key in bits. Common lengths are 128, 192, and 256 bits.
|
||
*
|
||
* @returns A Promise that resolves to the generated symmetric key in JWK format.
|
||
*/
|
||
static async generateKey({ length }) {
|
||
if (!AES_KEY_LENGTHS.includes(length)) {
|
||
throw new RangeError(`The key length is invalid: Must be ${AES_KEY_LENGTHS.join(", ")} bits`);
|
||
}
|
||
const webCrypto = (0, import_utils2.getWebcryptoSubtle)();
|
||
const webCryptoKey = await webCrypto.generateKey({ name: "AES-KW", length }, true, ["wrapKey", "unwrapKey"]);
|
||
const { ext, key_ops, ...privateKey } = await webCrypto.exportKey("jwk", webCryptoKey);
|
||
privateKey.kid = await (0, import_crypto4.computeJwkThumbprint)({ jwk: privateKey });
|
||
return privateKey;
|
||
}
|
||
/**
|
||
* Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array).
|
||
*
|
||
* @remarks
|
||
* This method takes a symmetric key in JWK format and extracts its raw byte representation.
|
||
* It decodes the 'k' parameter of the JWK value, which represents the symmetric key in base64url
|
||
* encoding, into a byte array.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const privateKey = { ... }; // A symmetric key in JWK format
|
||
* const privateKeyBytes = await AesKw.privateKeyToBytes({ privateKey });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the symmetric key conversion.
|
||
* @param params.privateKey - The symmetric key in JWK format.
|
||
*
|
||
* @returns A Promise that resolves to the symmetric key as a Uint8Array.
|
||
*/
|
||
static async privateKeyToBytes({ privateKey }) {
|
||
if (!(0, import_crypto4.isOctPrivateJwk)(privateKey)) {
|
||
throw new Error(`AesKw: The provided key is not a valid oct private key.`);
|
||
}
|
||
const privateKeyBytes = import_common2.Convert.base64Url(privateKey.k).toUint8Array();
|
||
return privateKeyBytes;
|
||
}
|
||
static async unwrapKey({ wrappedKeyBytes, wrappedKeyAlgorithm, decryptionKey }) {
|
||
if (!("alg" in decryptionKey && decryptionKey.alg)) {
|
||
throw new CryptoError("invalidJwk" /* InvalidJwk */, `The decryption key is missing the 'alg' property.`);
|
||
}
|
||
if (!["A128KW", "A192KW", "A256KW"].includes(decryptionKey.alg)) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `The 'decryptionKey' algorithm is not supported: ${decryptionKey.alg}`);
|
||
}
|
||
const webCrypto = (0, import_utils2.getWebcryptoSubtle)();
|
||
const decryptionCryptoKey = await webCrypto.importKey(
|
||
"jwk",
|
||
// key format
|
||
decryptionKey,
|
||
// key data
|
||
{ name: "AES-KW" },
|
||
// algorithm identifier
|
||
true,
|
||
// key is extractable
|
||
["unwrapKey"]
|
||
// key usages
|
||
);
|
||
const webCryptoAlgorithm = {
|
||
A128KW: "AES-KW",
|
||
A192KW: "AES-KW",
|
||
A256KW: "AES-KW",
|
||
A128GCM: "AES-GCM",
|
||
A192GCM: "AES-GCM",
|
||
A256GCM: "AES-GCM"
|
||
}[wrappedKeyAlgorithm];
|
||
if (!webCryptoAlgorithm) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `The 'wrappedKeyAlgorithm' is not supported: ${wrappedKeyAlgorithm}`);
|
||
}
|
||
const unwrappedCryptoKey = await webCrypto.unwrapKey(
|
||
"raw",
|
||
// output format
|
||
wrappedKeyBytes.buffer,
|
||
// key to unwrap
|
||
decryptionCryptoKey,
|
||
// unwrapping key
|
||
"AES-KW",
|
||
// algorithm identifier
|
||
{ name: webCryptoAlgorithm },
|
||
// unwrapped key algorithm identifier
|
||
true,
|
||
// key is extractable
|
||
["unwrapKey"]
|
||
// key usages
|
||
);
|
||
const { ext, key_ops, ...unwrappedJsonWebKey } = await webCrypto.exportKey("jwk", unwrappedCryptoKey);
|
||
const unwrappedKey = unwrappedJsonWebKey;
|
||
unwrappedKey.kid = await (0, import_crypto4.computeJwkThumbprint)({ jwk: unwrappedKey });
|
||
return unwrappedKey;
|
||
}
|
||
static async wrapKey({ unwrappedKey, encryptionKey }) {
|
||
if (!("alg" in encryptionKey && encryptionKey.alg)) {
|
||
throw new CryptoError("invalidJwk" /* InvalidJwk */, `The encryption key is missing the 'alg' property.`);
|
||
}
|
||
if (!["A128KW", "A192KW", "A256KW"].includes(encryptionKey.alg)) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `The 'encryptionKey' algorithm is not supported: ${encryptionKey.alg}`);
|
||
}
|
||
if (!("alg" in unwrappedKey && unwrappedKey.alg)) {
|
||
throw new CryptoError("invalidJwk" /* InvalidJwk */, `The private key to wrap is missing the 'alg' property.`);
|
||
}
|
||
const webCrypto = (0, import_utils2.getWebcryptoSubtle)();
|
||
const encryptionCryptoKey = await webCrypto.importKey(
|
||
"jwk",
|
||
// key format
|
||
encryptionKey,
|
||
// key data
|
||
{ name: "AES-KW" },
|
||
// algorithm identifier
|
||
true,
|
||
// key is extractable
|
||
["wrapKey"]
|
||
// key usages
|
||
);
|
||
const webCryptoAlgorithm = {
|
||
A128KW: "AES-KW",
|
||
A192KW: "AES-KW",
|
||
A256KW: "AES-KW",
|
||
A128GCM: "AES-GCM",
|
||
A192GCM: "AES-GCM",
|
||
A256GCM: "AES-GCM"
|
||
}[unwrappedKey.alg];
|
||
if (!webCryptoAlgorithm) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `The 'unwrappedKey' algorithm is not supported: ${unwrappedKey.alg}`);
|
||
}
|
||
const unwrappedCryptoKey = await webCrypto.importKey(
|
||
"jwk",
|
||
// key format
|
||
unwrappedKey,
|
||
// key data
|
||
{ name: webCryptoAlgorithm },
|
||
// algorithm identifier
|
||
true,
|
||
// key is extractable
|
||
["unwrapKey"]
|
||
// key usages
|
||
);
|
||
const wrappedKeyBuffer = await webCrypto.wrapKey(
|
||
"raw",
|
||
// output format
|
||
unwrappedCryptoKey,
|
||
// key to wrap
|
||
encryptionCryptoKey,
|
||
// wrapping key
|
||
"AES-KW"
|
||
// algorithm identifier
|
||
);
|
||
const wrappedKeyBytes = new Uint8Array(wrappedKeyBuffer);
|
||
return wrappedKeyBytes;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/aes-kw.ts
|
||
var AesKwAlgorithm = class extends import_crypto5.CryptoAlgorithm {
|
||
async bytesToPrivateKey({ privateKeyBytes }) {
|
||
const privateKey = await AesKw.bytesToPrivateKey({ privateKeyBytes });
|
||
privateKey.alg = { 16: "A128KW", 24: "A192KW", 32: "A256KW" }[privateKeyBytes.length];
|
||
return privateKey;
|
||
}
|
||
/**
|
||
* Generates a symmetric key for AES for key wrapping in JSON Web Key (JWK) format.
|
||
*
|
||
* @remarks
|
||
* This method generates a symmetric AES key for use in key wrapping mode, based on the specified
|
||
* `algorithm` parameter which determines the key length. It uses cryptographically secure random
|
||
* number generation to ensure the uniqueness and security of the key. The key is returned in JWK
|
||
* format.
|
||
*
|
||
* The generated key includes the following components:
|
||
* - `kty`: Key Type, set to 'oct' for Octet Sequence.
|
||
* - `k`: The symmetric key component, base64url-encoded.
|
||
* - `kid`: Key ID, generated based on the JWK thumbprint.
|
||
* - `alg`: Algorithm, set to 'A128KW', 'A192KW', or 'A256KW' for AES Key Wrap with the
|
||
* specified key length.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const aesKw = new AesKwAlgorithm();
|
||
* const privateKey = await aesKw.generateKey({ algorithm: 'A256KW' });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the key generation.
|
||
*
|
||
* @returns A Promise that resolves to the generated symmetric key in JWK format.
|
||
*/
|
||
async generateKey({ algorithm }) {
|
||
const length = { A128KW: 128, A192KW: 192, A256KW: 256 }[algorithm];
|
||
const privateKey = await AesKw.generateKey({ length });
|
||
privateKey.alg = algorithm;
|
||
return privateKey;
|
||
}
|
||
async privateKeyToBytes({ privateKey }) {
|
||
const privateKeyBytes = await AesKw.privateKeyToBytes({ privateKey });
|
||
return privateKeyBytes;
|
||
}
|
||
/**
|
||
* Decrypts a wrapped key using the AES Key Wrap algorithm.
|
||
*
|
||
* @remarks
|
||
* This method unwraps a previously wrapped cryptographic key using the AES Key Wrap algorithm.
|
||
* The wrapped key, provided as a byte array, is unwrapped using the decryption key specified in
|
||
* the parameters.
|
||
*
|
||
* This operation is useful for securely receiving keys transmitted over untrusted mediums. The
|
||
* method returns the unwrapped key as a JSON Web Key (JWK).
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const aesKw = new AesKwAlgorithm();
|
||
* const wrappedKeyBytes = new Uint8Array([...]); // Byte array of a wrapped AES-256 GCM key
|
||
* const decryptionKey = { ... }; // A Jwk object representing the AES unwrapping key
|
||
* const unwrappedKey = await aesKw.unwrapKey({
|
||
* wrappedKeyBytes,
|
||
* wrappedKeyAlgorithm: 'A256GCM',
|
||
* decryptionKey
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the key unwrapping operation.
|
||
*
|
||
* @returns A Promise that resolves to the unwrapped key in JWK format.
|
||
*/
|
||
async unwrapKey(params) {
|
||
const unwrappedKey = await AesKw.unwrapKey(params);
|
||
return unwrappedKey;
|
||
}
|
||
/**
|
||
* Encrypts a given key using the AES Key Wrap algorithm.
|
||
*
|
||
* @remarks
|
||
* This method wraps a given cryptographic key using the AES Key Wrap algorithm. The private key
|
||
* to be wrapped is provided in the form of a JSON Web Key (JWK).
|
||
*
|
||
* This operation is useful for securely transmitting keys over untrusted mediums. The method
|
||
* returns the wrapped key as a byte array.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const aesKw = new AesKwAlgorithm();
|
||
* const unwrappedKey = { ... }; // A Jwk object representing the key to be wrapped
|
||
* const encryptionKey = { ... }; // A Jwk object representing the AES wrapping key
|
||
* const wrappedKeyBytes = await aesKw.wrapKey({ unwrappedKey, encryptionKey });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the key wrapping operation.
|
||
*
|
||
* @returns A Promise that resolves to the wrapped key as a Uint8Array.
|
||
*/
|
||
async wrapKey(params) {
|
||
const wrappedKeyBytes = AesKw.wrapKey(params);
|
||
return wrappedKeyBytes;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/pbkdf2.ts
|
||
var import_crypto6 = require("@web5/crypto");
|
||
|
||
// src/prototyping/crypto/primitives/pbkdf2.ts
|
||
var import_utils3 = require("@noble/ciphers/webcrypto/utils");
|
||
var Pbkdf2 = class {
|
||
/**
|
||
* Derives a cryptographic key from a password using the PBKDF2 algorithm.
|
||
*
|
||
* @remarks
|
||
* This method applies the PBKDF2 algorithm to the provided password along with
|
||
* a salt value and iterates the process a specified number of times. It uses
|
||
* a cryptographic hash function to enhance security and produce a key of the
|
||
* desired length. The method is capable of utilizing either the Web Crypto API
|
||
* or the Node.js Crypto module, depending on the environment's support.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const derivedKeyBytes = await Pbkdf2.deriveKeyBytes({
|
||
* baseKeyBytes: new TextEncoder().encode('password'), // The password as a Uint8Array
|
||
* hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512')
|
||
* salt: new Uint8Array([...]), // The salt value
|
||
* iterations: 600_000, // The number of iterations
|
||
* length: 256 // The length of the derived key in bits
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for key derivation.
|
||
* @returns A Promise that resolves to the derived key as a byte array.
|
||
*/
|
||
static async deriveKeyBytes({ baseKeyBytes, hash: hash2, salt, iterations, length }) {
|
||
const webCrypto = (0, import_utils3.getWebcryptoSubtle)();
|
||
const webCryptoKey = await webCrypto.importKey(
|
||
"raw",
|
||
// key format is raw bytes
|
||
baseKeyBytes,
|
||
// key data to import
|
||
{ name: "PBKDF2" },
|
||
// algorithm identifier
|
||
false,
|
||
// key is not extractable
|
||
["deriveBits"]
|
||
// key usages
|
||
);
|
||
const derivedKeyBuffer = await webCrypto.deriveBits(
|
||
{ name: "PBKDF2", hash: hash2, salt, iterations },
|
||
webCryptoKey,
|
||
length
|
||
);
|
||
const derivedKeyBytes = new Uint8Array(derivedKeyBuffer);
|
||
return derivedKeyBytes;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/pbkdf2.ts
|
||
var Pbkdf2Algorithm = class extends import_crypto6.CryptoAlgorithm {
|
||
async deriveKeyBytes({ algorithm, ...params }) {
|
||
const [, hashFunction] = algorithm.split(/[-+]/);
|
||
const hash2 = {
|
||
"HS256": "SHA-256",
|
||
"HS384": "SHA-384",
|
||
"HS512": "SHA-512"
|
||
}[hashFunction];
|
||
const derivedKeyBytes = await Pbkdf2.deriveKeyBytes({ ...params, hash: hash2 });
|
||
return derivedKeyBytes;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/algorithms/aes-gcm.ts
|
||
var import_crypto7 = require("@web5/crypto");
|
||
var AesGcmAlgorithm = class extends import_crypto7.CryptoAlgorithm {
|
||
async bytesToPrivateKey({ privateKeyBytes }) {
|
||
const privateKey = await import_crypto7.AesGcm.bytesToPrivateKey({ privateKeyBytes });
|
||
privateKey.alg = { 16: "A128GCM", 24: "A192GCM", 32: "A256GCM" }[privateKeyBytes.length];
|
||
return privateKey;
|
||
}
|
||
/**
|
||
* Decrypts the provided data using AES-GCM.
|
||
*
|
||
* @remarks
|
||
* This method performs AES-GCM decryption on the given encrypted data using the specified key.
|
||
* It requires an initialization vector (IV), the encrypted data along with the decryption key,
|
||
* and optionally, additional authenticated data (AAD). The method returns the decrypted data as a
|
||
* Uint8Array. The optional `tagLength` parameter specifies the size in bits of the authentication
|
||
* tag used when encrypting the data. If not specified, the default tag length of 128 bits is
|
||
* used.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const aesGcm = new AesGcmAlgorithm();
|
||
* const encryptedData = new Uint8Array([...]); // Encrypted data
|
||
* const iv = new Uint8Array([...]); // Initialization vector used during encryption
|
||
* const additionalData = new Uint8Array([...]); // Optional additional authenticated data
|
||
* const key = { ... }; // A Jwk object representing the AES key
|
||
* const decryptedData = await aesGcm.decrypt({
|
||
* data: encryptedData,
|
||
* iv,
|
||
* additionalData,
|
||
* key,
|
||
* tagLength: 128 // Optional tag length in bits
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the decryption operation.
|
||
*
|
||
* @returns A Promise that resolves to the decrypted data as a Uint8Array.
|
||
*/
|
||
async decrypt(params) {
|
||
const plaintext = import_crypto7.AesGcm.decrypt(params);
|
||
return plaintext;
|
||
}
|
||
/**
|
||
* Encrypts the provided data using AES-GCM.
|
||
*
|
||
* @remarks
|
||
* This method performs AES-GCM encryption on the given data using the specified key.
|
||
* It requires an initialization vector (IV), the encrypted data along with the decryption key,
|
||
* and optionally, additional authenticated data (AAD). The method returns the encrypted data as a
|
||
* Uint8Array. The optional `tagLength` parameter specifies the size in bits of the authentication
|
||
* tag generated in the encryption operation and used for authentication in the corresponding
|
||
* decryption. If not specified, the default tag length of 128 bits is used.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const aesGcm = new AesGcmAlgorithm();
|
||
* const data = new TextEncoder().encode('Messsage');
|
||
* const iv = new Uint8Array([...]); // Initialization vector
|
||
* const additionalData = new Uint8Array([...]); // Optional additional authenticated data
|
||
* const key = { ... }; // A Jwk object representing an AES key
|
||
* const encryptedData = await aesGcm.encrypt({
|
||
* data,
|
||
* iv,
|
||
* additionalData,
|
||
* key,
|
||
* tagLength: 128 // Optional tag length in bits
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the encryption operation.
|
||
*
|
||
* @returns A Promise that resolves to the encrypted data as a Uint8Array.
|
||
*/
|
||
async encrypt(params) {
|
||
const ciphertext = import_crypto7.AesGcm.encrypt(params);
|
||
return ciphertext;
|
||
}
|
||
/**
|
||
* Generates a symmetric key for AES in Galois/Counter Mode (GCM) in JSON Web Key (JWK) format.
|
||
*
|
||
* @remarks
|
||
* This method generates a symmetric AES key for use in GCM mode, based on the specified
|
||
* `algorithm` parameter which determines the key length. It uses cryptographically secure random
|
||
* number generation to ensure the uniqueness and security of the key. The key is returned in JWK
|
||
* format.
|
||
*
|
||
* The generated key includes the following components:
|
||
* - `kty`: Key Type, set to 'oct' for Octet Sequence.
|
||
* - `k`: The symmetric key component, base64url-encoded.
|
||
* - `kid`: Key ID, generated based on the JWK thumbprint.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const aesGcm = new AesGcmAlgorithm();
|
||
* const privateKey = await aesGcm.generateKey({ algorithm: 'A256GCM' });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the key generation.
|
||
*
|
||
* @returns A Promise that resolves to the generated symmetric key in JWK format.
|
||
*/
|
||
async generateKey({ algorithm }) {
|
||
const length = { A128GCM: 128, A192GCM: 192, A256GCM: 256 }[algorithm];
|
||
const privateKey = await import_crypto7.AesGcm.generateKey({ length });
|
||
privateKey.alg = algorithm;
|
||
return privateKey;
|
||
}
|
||
async privateKeyToBytes({ privateKey }) {
|
||
const privateKeyBytes = await import_crypto7.AesGcm.privateKeyToBytes({ privateKey });
|
||
return privateKeyBytes;
|
||
}
|
||
};
|
||
|
||
// src/crypto-api.ts
|
||
var supportedAlgorithms = {
|
||
"AES-GCM": {
|
||
implementation: AesGcmAlgorithm,
|
||
names: ["A128GCM", "A192GCM", "A256GCM"],
|
||
operations: ["bytesToPrivateKey", "decrypt", "encrypt", "generateKey"]
|
||
},
|
||
"AES-KW": {
|
||
implementation: AesKwAlgorithm,
|
||
names: ["A128KW", "A192KW", "A256KW"],
|
||
operations: ["bytesToPrivateKey", "generateKey", "privateKeyToBytes", "wrapKey", "unwrapKey"]
|
||
},
|
||
"Ed25519": {
|
||
implementation: EdDsaAlgorithm,
|
||
names: ["Ed25519"],
|
||
operations: ["bytesToPrivateKey", "bytesToPublicKey", "generateKey", "sign", "verify"]
|
||
},
|
||
"HKDF": {
|
||
implementation: HkdfAlgorithm,
|
||
names: ["HKDF-256", "HKDF-384", "HKDF-512"],
|
||
operations: ["deriveKey", "deriveKeyBytes"]
|
||
},
|
||
"PBKDF2": {
|
||
implementation: Pbkdf2Algorithm,
|
||
names: ["PBES2-HS256+A128KW", "PBES2-HS384+A192KW", "PBES2-HS512+A256KW"],
|
||
operations: ["deriveKey", "deriveKeyBytes"]
|
||
},
|
||
"secp256k1": {
|
||
implementation: EcdsaAlgorithm,
|
||
names: ["ES256K", "secp256k1"],
|
||
operations: ["bytesToPrivateKey", "bytesToPublicKey", "generateKey", "sign", "verify"]
|
||
},
|
||
"secp256r1": {
|
||
implementation: EcdsaAlgorithm,
|
||
names: ["ES256", "secp256r1"],
|
||
operations: ["bytesToPrivateKey", "bytesToPublicKey", "generateKey", "sign", "verify"]
|
||
},
|
||
"SHA-256": {
|
||
implementation: import_crypto8.Sha2Algorithm,
|
||
names: ["SHA-256"],
|
||
operations: ["digest"]
|
||
}
|
||
};
|
||
var AgentCryptoApi = class {
|
||
constructor() {
|
||
/**
|
||
* A private map that stores instances of cryptographic algorithm implementations. Each key in
|
||
* this map is an `AlgorithmConstructor`, and its corresponding value is an instance of a class
|
||
* that implements a specific cryptographic algorithm. This map is used to cache and reuse
|
||
* instances for performance optimization, ensuring that each algorithm is instantiated only once.
|
||
*/
|
||
this._algorithmInstances = /* @__PURE__ */ new Map();
|
||
}
|
||
async bytesToPrivateKey({ algorithm: algorithmIdentifier, privateKeyBytes }) {
|
||
const algorithm = this.getAlgorithmName({ algorithm: algorithmIdentifier });
|
||
const keyConverter = this.getAlgorithm({ algorithm });
|
||
const privateKey = await keyConverter.bytesToPrivateKey({ algorithm: algorithmIdentifier, privateKeyBytes });
|
||
return privateKey;
|
||
}
|
||
async bytesToPublicKey({ algorithm: algorithmIdentifier, publicKeyBytes }) {
|
||
const algorithm = this.getAlgorithmName({ algorithm: algorithmIdentifier });
|
||
const keyConverter = this.getAlgorithm({ algorithm });
|
||
const publicKey = await keyConverter.bytesToPublicKey({ algorithm: algorithmIdentifier, publicKeyBytes });
|
||
return publicKey;
|
||
}
|
||
async decrypt(params) {
|
||
const algorithm = this.getAlgorithmName({ key: params.key });
|
||
const cipher = this.getAlgorithm({ algorithm });
|
||
return await cipher.decrypt(params);
|
||
}
|
||
async deriveKey(params) {
|
||
const algorithm = this.getAlgorithmName({ algorithm: params.algorithm });
|
||
const kdf = this.getAlgorithm({ algorithm });
|
||
let derivedKeyAlgorithm;
|
||
switch (params.algorithm) {
|
||
case "HKDF-256":
|
||
case "HKDF-384":
|
||
case "HKDF-512": {
|
||
derivedKeyAlgorithm = params.derivedKeyAlgorithm;
|
||
break;
|
||
}
|
||
case "PBES2-HS256+A128KW":
|
||
case "PBES2-HS384+A192KW":
|
||
case "PBES2-HS512+A256KW": {
|
||
derivedKeyAlgorithm = params.algorithm.split(/[-+]/)[2];
|
||
break;
|
||
}
|
||
default:
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `The specified "algorithm" is not supported: ${params.algorithm}`);
|
||
}
|
||
const length = +(derivedKeyAlgorithm.match(/\d+/)?.[0] ?? -1);
|
||
if (length === -1) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `The derived key algorithm" is not supported: ${derivedKeyAlgorithm}`);
|
||
}
|
||
const privateKeyBytes = await kdf.deriveKeyBytes({ ...params, length });
|
||
return await this.bytesToPrivateKey({ algorithm: derivedKeyAlgorithm, privateKeyBytes });
|
||
}
|
||
async deriveKeyBytes(params) {
|
||
const algorithm = this.getAlgorithmName({ algorithm: params.algorithm });
|
||
const kdf = this.getAlgorithm({ algorithm });
|
||
const derivedKeyBytes = await kdf.deriveKeyBytes(params);
|
||
return derivedKeyBytes;
|
||
}
|
||
/**
|
||
* Generates a hash digest of the provided data.
|
||
*
|
||
* @remarks
|
||
* A digest is the output of the hash function. It's a fixed-size string of bytes that uniquely
|
||
* represents the data input into the hash function. The digest is often used for data integrity
|
||
* checks, as any alteration in the input data results in a significantly different digest.
|
||
*
|
||
* It takes the algorithm identifier of the hash function and data to digest as input and returns
|
||
* the digest of the data.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const cryptoApi = new AgentCryptoApi();
|
||
* const data = new Uint8Array([...]);
|
||
* const digest = await cryptoApi.digest({ algorithm: 'SHA-256', data });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the digest operation.
|
||
* @param params.algorithm - The name of hash function to use.
|
||
* @param params.data - The data to digest.
|
||
*
|
||
* @returns A Promise which will be fulfilled with the hash digest.
|
||
*/
|
||
async digest({ algorithm, data }) {
|
||
const hasher = this.getAlgorithm({ algorithm });
|
||
const hash2 = await hasher.digest({ algorithm, data });
|
||
return hash2;
|
||
}
|
||
async encrypt(params) {
|
||
const algorithm = this.getAlgorithmName({ key: params.key });
|
||
const cipher = this.getAlgorithm({ algorithm });
|
||
return await cipher.encrypt(params);
|
||
}
|
||
async generateKey(params) {
|
||
const algorithm = this.getAlgorithmName({ algorithm: params.algorithm });
|
||
const keyGenerator = this.getAlgorithm({ algorithm });
|
||
const privateKey = await keyGenerator.generateKey({ algorithm: params.algorithm });
|
||
privateKey.kid ??= await (0, import_crypto8.computeJwkThumbprint)({ jwk: privateKey });
|
||
return privateKey;
|
||
}
|
||
// ! TODO: Remove this once the `Dsa` interface is updated in @web5/crypto to remove KMS-specific methods.
|
||
async getKeyUri(_params) {
|
||
throw new Error("Method not implemented.");
|
||
}
|
||
async getPublicKey({ key }) {
|
||
const algorithm = this.getAlgorithmName({ key });
|
||
const keyGenerator = this.getAlgorithm({ algorithm });
|
||
const publicKey = await keyGenerator.getPublicKey({ key });
|
||
return publicKey;
|
||
}
|
||
async privateKeyToBytes({ privateKey }) {
|
||
const algorithm = this.getAlgorithmName({ key: privateKey });
|
||
const keyConverter = this.getAlgorithm({ algorithm });
|
||
const privateKeyBytes = await keyConverter.privateKeyToBytes({ privateKey });
|
||
return privateKeyBytes;
|
||
}
|
||
async publicKeyToBytes({ publicKey }) {
|
||
const algorithm = this.getAlgorithmName({ key: publicKey });
|
||
const keyConverter = this.getAlgorithm({ algorithm });
|
||
const publicKeyBytes = await keyConverter.publicKeyToBytes({ publicKey });
|
||
return publicKeyBytes;
|
||
}
|
||
async sign({ key, data }) {
|
||
const algorithm = this.getAlgorithmName({ key });
|
||
const signer = this.getAlgorithm({ algorithm });
|
||
const signature = signer.sign({ data, key });
|
||
return signature;
|
||
}
|
||
async unwrapKey(params) {
|
||
const algorithm = this.getAlgorithmName({ key: params.decryptionKey });
|
||
const keyWrapper = this.getAlgorithm({ algorithm });
|
||
return await keyWrapper.unwrapKey(params);
|
||
}
|
||
async verify({ key, signature, data }) {
|
||
const algorithm = this.getAlgorithmName({ key });
|
||
const signer = this.getAlgorithm({ algorithm });
|
||
const isSignatureValid = signer.verify({ key, signature, data });
|
||
return isSignatureValid;
|
||
}
|
||
async wrapKey(params) {
|
||
const algorithm = this.getAlgorithmName({ key: params.encryptionKey });
|
||
const keyWrapper = this.getAlgorithm({ algorithm });
|
||
return await keyWrapper.wrapKey(params);
|
||
}
|
||
/**
|
||
* Retrieves an algorithm implementation instance based on the provided algorithm name.
|
||
*
|
||
* @remarks
|
||
* This method checks if the requested algorithm is supported and returns a cached instance
|
||
* if available. If an instance does not exist, it creates and caches a new one. This approach
|
||
* optimizes performance by reusing algorithm instances across cryptographic operations.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const signer = this.getAlgorithm({ algorithm: 'Ed25519' });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for retrieving the algorithm implementation.
|
||
* @param params.algorithm - The name of the algorithm to retrieve.
|
||
*
|
||
* @returns An instance of the requested algorithm implementation.
|
||
*
|
||
* @throws Error if the requested algorithm is not supported.
|
||
*/
|
||
getAlgorithm({ algorithm }) {
|
||
const AlgorithmImplementation = supportedAlgorithms[algorithm]?.["implementation"];
|
||
if (!AlgorithmImplementation) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Algorithm not supported: ${algorithm}`);
|
||
}
|
||
if (!this._algorithmInstances.has(AlgorithmImplementation)) {
|
||
this._algorithmInstances.set(AlgorithmImplementation, new AlgorithmImplementation());
|
||
}
|
||
return this._algorithmInstances.get(AlgorithmImplementation);
|
||
}
|
||
getAlgorithmName({ algorithm, key }) {
|
||
const algProperty = key?.alg ?? algorithm;
|
||
const crvProperty = key?.crv;
|
||
for (const algorithmIdentifier of Object.keys(supportedAlgorithms)) {
|
||
const algorithmNames = supportedAlgorithms[algorithmIdentifier].names;
|
||
if (algProperty && algorithmNames.includes(algProperty)) {
|
||
return algorithmIdentifier;
|
||
} else if (crvProperty && algorithmNames.includes(crvProperty)) {
|
||
return algorithmIdentifier;
|
||
}
|
||
}
|
||
throw new CryptoError(
|
||
"algorithmNotSupported" /* AlgorithmNotSupported */,
|
||
`Algorithm not supported based on provided input: alg=${algProperty}, crv=${crvProperty}. Please check the documentation for the list of supported algorithms.`
|
||
);
|
||
}
|
||
};
|
||
|
||
// src/did-api.ts
|
||
var import_dids = require("@web5/dids");
|
||
|
||
// src/store-did.ts
|
||
var import_common4 = require("@web5/common");
|
||
|
||
// src/utils-internal.ts
|
||
var import_crypto9 = require("@web5/crypto");
|
||
var TENANT_SEPARATOR = "^";
|
||
var DeterministicKeyGenerator = class extends import_crypto9.LocalKeyManager {
|
||
constructor() {
|
||
super();
|
||
this._predefinedKeys = /* @__PURE__ */ new Map();
|
||
this._keyGenerator = this._predefinedKeys.keys();
|
||
}
|
||
async addPredefinedKeys({ privateKeys }) {
|
||
const predefinedKeys = {};
|
||
for (const key of privateKeys) {
|
||
key.kid ??= await (0, import_crypto9.computeJwkThumbprint)({ jwk: key });
|
||
const keyUri = await this.getKeyUri({ key });
|
||
predefinedKeys[keyUri] = key;
|
||
}
|
||
this._predefinedKeys = new Map(Object.entries(predefinedKeys));
|
||
this._keyGenerator = this._predefinedKeys.keys();
|
||
}
|
||
async exportKey({ keyUri }) {
|
||
const privateKey = this._predefinedKeys.get(keyUri);
|
||
if (!privateKey) {
|
||
throw new Error(`DeterministicKeyGenerator.exportKey: Key not found: ${keyUri}`);
|
||
}
|
||
return privateKey;
|
||
}
|
||
async generateKey(_params) {
|
||
const { value: keyUri, done } = this._keyGenerator.next();
|
||
if (done) {
|
||
throw new Error("Ran out of predefined keys");
|
||
}
|
||
return keyUri;
|
||
}
|
||
async getPublicKey({ keyUri }) {
|
||
const privateKey = this._predefinedKeys.get(keyUri);
|
||
if (!privateKey) {
|
||
throw new Error(`DeterministicKeyGenerator.getPublicKey: Key not found: ${keyUri}`);
|
||
}
|
||
const { d, ...publicKey } = privateKey;
|
||
return publicKey;
|
||
}
|
||
async sign({ keyUri, data }) {
|
||
const privateKey = this._predefinedKeys.get(keyUri);
|
||
if (!privateKey) {
|
||
throw new Error(`DeterministicKeyGenerator.sign: Key not found: ${keyUri}`);
|
||
}
|
||
const signature = await import_crypto9.Ed25519.sign({ data, key: privateKey });
|
||
return signature;
|
||
}
|
||
};
|
||
async function getDataStoreTenant({ agent, tenant, didUri }) {
|
||
if (tenant)
|
||
return tenant;
|
||
if (agent.agentDid)
|
||
return agent.agentDid.uri;
|
||
if (!didUri) {
|
||
throw new Error(`Failed to determine tenant DID: 'agent.agentDid', 'tenant', and 'didUri' are undefined`);
|
||
}
|
||
return didUri;
|
||
}
|
||
|
||
// src/prototyping/dids/utils.ts
|
||
function isPortableDid(obj) {
|
||
return !(!obj || typeof obj !== "object" || obj === null) && "uri" in obj && "document" in obj && "metadata" in obj && (!("keyManager" in obj) || obj.keyManager === void 0);
|
||
}
|
||
|
||
// src/store-data.ts
|
||
var import_ms = __toESM(require("ms"), 1);
|
||
var import_common3 = require("@web5/common");
|
||
var DwnDataStore = class {
|
||
constructor() {
|
||
this.name = "DwnDataStore";
|
||
/**
|
||
* Cache of Store Objects referenced by DWN record ID to Store Objects.
|
||
*
|
||
* Up to 100 entries are retained for 15 minutes.
|
||
*/
|
||
this._cache = new import_common3.TtlCache({ ttl: (0, import_ms.default)("15 minutes"), max: 100 });
|
||
/**
|
||
* Index for mappings from Store Identifier to DWN record ID.
|
||
*
|
||
* Up to 1,000 entries are retained for 2 hours.
|
||
*/
|
||
this._index = new import_common3.TtlCache({ ttl: (0, import_ms.default)("2 hours"), max: 1e3 });
|
||
/**
|
||
* Properties to use when writing and querying records with the DWN store.
|
||
*/
|
||
this._recordProperties = {
|
||
dataFormat: "application/json",
|
||
schema: "https://identity.foundation/schemas/web5/private-jwk"
|
||
};
|
||
}
|
||
async delete({ id, agent, tenant }) {
|
||
const tenantDid = await getDataStoreTenant({ agent, tenant, didUri: id });
|
||
let matchingRecordId = await this.lookupRecordId({ id, tenantDid, agent });
|
||
if (!matchingRecordId)
|
||
return false;
|
||
const { reply: { status } } = await agent.dwn.processRequest({
|
||
author: tenantDid,
|
||
target: tenantDid,
|
||
messageType: DwnInterface.RecordsDelete,
|
||
messageParams: { recordId: matchingRecordId }
|
||
});
|
||
if (status.code === 202) {
|
||
this._index.delete(`${tenantDid}${TENANT_SEPARATOR}${id}`);
|
||
this._cache.delete(matchingRecordId);
|
||
return true;
|
||
}
|
||
throw new Error(`${this.name}: Failed to delete '${id}' from store: (${status.code}) ${status.detail}`);
|
||
}
|
||
async get({ id, agent, tenant, useCache = false }) {
|
||
const tenantDid = await getDataStoreTenant({ agent, tenant, didUri: id });
|
||
let matchingRecordId = await this.lookupRecordId({ id, tenantDid, agent });
|
||
if (!matchingRecordId)
|
||
return void 0;
|
||
return await this.getRecord({ recordId: matchingRecordId, tenantDid, agent, useCache });
|
||
}
|
||
async list({ agent, tenant }) {
|
||
const tenantDid = await getDataStoreTenant({ tenant, agent });
|
||
const storedRecords = await this.getAllRecords({ agent, tenantDid });
|
||
return storedRecords;
|
||
}
|
||
async set({ id, data, tenant, agent, preventDuplicates = true, useCache = false }) {
|
||
const tenantDid = await getDataStoreTenant({ agent, tenant, didUri: id });
|
||
if (preventDuplicates) {
|
||
const matchingRecordId = await this.lookupRecordId({ id, tenantDid, agent });
|
||
if (matchingRecordId) {
|
||
throw new Error(`${this.name}: Import failed due to duplicate entry for: ${id}`);
|
||
}
|
||
}
|
||
const dataBytes = import_common3.Convert.object(data).toUint8Array();
|
||
const { message, reply: { status } } = await agent.dwn.processRequest({
|
||
author: tenantDid,
|
||
target: tenantDid,
|
||
messageType: DwnInterface.RecordsWrite,
|
||
messageParams: { ...this._recordProperties },
|
||
dataStream: new Blob([dataBytes], { type: "application/json" })
|
||
});
|
||
if (!(message && status.code === 202)) {
|
||
throw new Error(`${this.name}: Failed to write data to store for: ${id}`);
|
||
}
|
||
this._index.set(`${tenantDid}${TENANT_SEPARATOR}${id}`, message.recordId);
|
||
if (useCache) {
|
||
this._cache.set(message.recordId, data);
|
||
}
|
||
}
|
||
async getAllRecords(_params) {
|
||
throw new Error("Not implemented: Classes extending DwnDataStore must implement getAllRecords()");
|
||
}
|
||
async getRecord({ recordId, tenantDid, agent, useCache }) {
|
||
if (useCache) {
|
||
const record = this._cache.get(recordId);
|
||
if (record)
|
||
return record;
|
||
}
|
||
const { reply: readReply } = await agent.dwn.processRequest({
|
||
author: tenantDid,
|
||
target: tenantDid,
|
||
messageType: DwnInterface.RecordsRead,
|
||
messageParams: { filter: { recordId } }
|
||
});
|
||
if (!readReply.record?.data) {
|
||
throw new Error(`${this.name}: Failed to read data from DWN for: ${recordId}`);
|
||
}
|
||
const storeObject = await import_common3.NodeStream.consumeToJson({ readable: readReply.record.data });
|
||
if (useCache) {
|
||
this._cache.set(recordId, storeObject);
|
||
}
|
||
return storeObject;
|
||
}
|
||
async lookupRecordId({ id, tenantDid, agent }) {
|
||
let recordId = this._index.get(`${tenantDid}${TENANT_SEPARATOR}${id}`, { updateAgeOnGet: true });
|
||
if (!recordId) {
|
||
await this.getAllRecords({ agent, tenantDid });
|
||
recordId = this._index.get(`${tenantDid}${TENANT_SEPARATOR}${id}`);
|
||
}
|
||
return recordId;
|
||
}
|
||
};
|
||
var InMemoryDataStore = class {
|
||
constructor() {
|
||
this.name = "InMemoryDataStore";
|
||
/**
|
||
* A private field that contains the Map used as the in-memory data store.
|
||
*/
|
||
this.store = /* @__PURE__ */ new Map();
|
||
}
|
||
async delete({ id, agent, tenant }) {
|
||
const tenantDid = await getDataStoreTenant({ agent, tenant, didUri: id });
|
||
if (this.store.has(`${tenantDid}${TENANT_SEPARATOR}${id}`)) {
|
||
this.store.delete(`${tenantDid}${TENANT_SEPARATOR}${id}`);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
async get({ id, agent, tenant }) {
|
||
const tenantDid = await getDataStoreTenant({ agent, tenant, didUri: id });
|
||
return this.store.get(`${tenantDid}${TENANT_SEPARATOR}${id}`);
|
||
}
|
||
async list({ agent, tenant }) {
|
||
const tenantDid = await getDataStoreTenant({ tenant, agent });
|
||
const result = [];
|
||
for (const [key, storedRecord] of this.store.entries()) {
|
||
if (key.startsWith(`${tenantDid}${TENANT_SEPARATOR}`)) {
|
||
result.push(storedRecord);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
async set({ id, data, tenant, agent, preventDuplicates }) {
|
||
const tenantDid = await getDataStoreTenant({ agent, tenant, didUri: id });
|
||
if (preventDuplicates) {
|
||
const duplicateFound = this.store.has(`${tenantDid}${TENANT_SEPARATOR}${id}`);
|
||
if (duplicateFound) {
|
||
throw new Error(`${this.name}: Import failed due to duplicate entry for: ${id}`);
|
||
}
|
||
}
|
||
const clonedData = structuredClone(data);
|
||
this.store.set(`${tenantDid}${TENANT_SEPARATOR}${id}`, clonedData);
|
||
}
|
||
};
|
||
|
||
// src/store-did.ts
|
||
var DwnDidStore = class extends DwnDataStore {
|
||
constructor() {
|
||
super(...arguments);
|
||
this.name = "DwnDidStore";
|
||
/**
|
||
* Properties to use when writing and querying DID records with the DWN store.
|
||
*/
|
||
this._recordProperties = {
|
||
dataFormat: "application/json",
|
||
schema: "https://identity.foundation/schemas/web5/portable-did"
|
||
};
|
||
}
|
||
async delete(params) {
|
||
return await super.delete(params);
|
||
}
|
||
async get(params) {
|
||
return await super.get(params);
|
||
}
|
||
async list(params) {
|
||
return await super.list(params);
|
||
}
|
||
async set(params) {
|
||
return await super.set(params);
|
||
}
|
||
async getAllRecords({ agent, tenantDid }) {
|
||
this._index.clear();
|
||
const { reply: queryReply } = await agent.dwn.processRequest({
|
||
author: tenantDid,
|
||
target: tenantDid,
|
||
messageType: DwnInterface.RecordsQuery,
|
||
messageParams: { filter: { ...this._recordProperties } }
|
||
});
|
||
let storedDids = [];
|
||
for (const record of queryReply.entries ?? []) {
|
||
if (!record.encodedData) {
|
||
throw new Error(`${this.name}: Expected 'encodedData' to be present in the DWN query result entry`);
|
||
}
|
||
const storedDid = import_common4.Convert.base64Url(record.encodedData).toObject();
|
||
if (isPortableDid(storedDid)) {
|
||
const indexKey = `${tenantDid}${TENANT_SEPARATOR}${storedDid.uri}`;
|
||
this._index.set(indexKey, record.recordId);
|
||
this._cache.set(record.recordId, storedDid);
|
||
storedDids.push(storedDid);
|
||
}
|
||
}
|
||
return storedDids;
|
||
}
|
||
};
|
||
var InMemoryDidStore = class extends InMemoryDataStore {
|
||
constructor() {
|
||
super(...arguments);
|
||
this.name = "InMemoryDidStore";
|
||
}
|
||
async delete(params) {
|
||
return await super.delete(params);
|
||
}
|
||
async get(params) {
|
||
return await super.get(params);
|
||
}
|
||
async list(params) {
|
||
return await super.list(params);
|
||
}
|
||
async set(params) {
|
||
return await super.set(params);
|
||
}
|
||
};
|
||
|
||
// src/prototyping/dids/resolver-cache-memory.ts
|
||
var import_ms2 = __toESM(require("ms"), 1);
|
||
var import_common5 = require("@web5/common");
|
||
var DidResolverCacheMemory = class {
|
||
constructor({ ttl = "15m" } = {}) {
|
||
this.cache = new import_common5.TtlCache({ ttl: (0, import_ms2.default)(ttl) });
|
||
}
|
||
/**
|
||
* Retrieves a DID resolution result from the cache.
|
||
*
|
||
* If the cached item has exceeded its TTL, it's scheduled for deletion and undefined is returned.
|
||
*
|
||
* @param didUri - The DID string used as the key for retrieving the cached result.
|
||
* @returns The cached DID resolution result or undefined if not found or expired.
|
||
*/
|
||
async get(didUri) {
|
||
if (!didUri) {
|
||
throw new Error("Key cannot be null or undefined");
|
||
}
|
||
return this.cache.get(didUri);
|
||
}
|
||
/**
|
||
* Stores a DID resolution result in the cache with a TTL.
|
||
*
|
||
* @param didUri - The DID string used as the key for storing the result.
|
||
* @param resolutionResult - The DID resolution result to be cached.
|
||
* @returns A promise that resolves when the operation is complete.
|
||
*/
|
||
async set(didUri, resolutionResult) {
|
||
this.cache.set(didUri, resolutionResult);
|
||
}
|
||
/**
|
||
* Deletes a DID resolution result from the cache.
|
||
*
|
||
* @param didUri - The DID string used as the key for deletion.
|
||
* @returns A promise that resolves when the operation is complete.
|
||
*/
|
||
async delete(didUri) {
|
||
this.cache.delete(didUri);
|
||
}
|
||
/**
|
||
* Clears all entries from the cache.
|
||
*
|
||
* @returns A promise that resolves when the operation is complete.
|
||
*/
|
||
async clear() {
|
||
this.cache.clear();
|
||
}
|
||
/**
|
||
* This method is a no-op but exists to be consistent with other DID Resolver Cache
|
||
* implementations.
|
||
*
|
||
* @returns A promise that resolves immediately.
|
||
*/
|
||
async close() {
|
||
}
|
||
};
|
||
|
||
// src/did-api.ts
|
||
var DidInterface = /* @__PURE__ */ ((DidInterface2) => {
|
||
DidInterface2["Create"] = "Create";
|
||
DidInterface2["Resolve"] = "Resolve";
|
||
return DidInterface2;
|
||
})(DidInterface || {});
|
||
function isDidRequest(didRequest, messageType) {
|
||
return didRequest.messageType === messageType;
|
||
}
|
||
var AgentDidApi = class extends import_dids.UniversalResolver {
|
||
constructor({ agent, didMethods, resolverCache, store }) {
|
||
if (!didMethods) {
|
||
throw new TypeError(`AgentDidApi: Required parameter missing: 'didMethods'`);
|
||
}
|
||
super({
|
||
didResolvers: didMethods,
|
||
cache: resolverCache ?? new DidResolverCacheMemory()
|
||
});
|
||
this._didMethods = /* @__PURE__ */ new Map();
|
||
this._agent = agent;
|
||
this._store = store ?? new InMemoryDidStore();
|
||
for (const didMethod of didMethods) {
|
||
this._didMethods.set(didMethod.methodName, didMethod);
|
||
}
|
||
}
|
||
/**
|
||
* Retrieves the `Web5PlatformAgent` execution context.
|
||
*
|
||
* @returns The `Web5PlatformAgent` instance that represents the current execution context.
|
||
* @throws Will throw an error if the `agent` instance property is undefined.
|
||
*/
|
||
get agent() {
|
||
if (this._agent === void 0) {
|
||
throw new Error("AgentDidApi: Unable to determine agent execution context.");
|
||
}
|
||
return this._agent;
|
||
}
|
||
set agent(agent) {
|
||
this._agent = agent;
|
||
}
|
||
async create({
|
||
method,
|
||
tenant,
|
||
options,
|
||
store
|
||
}) {
|
||
const didMethod = this.getMethod(method);
|
||
const bearerDid = await didMethod.create({ keyManager: this.agent.keyManager, options });
|
||
if (store ?? true) {
|
||
const { uri, document, metadata } = bearerDid;
|
||
const portableDid = { uri, document, metadata };
|
||
await this._store.set({
|
||
id: portableDid.uri,
|
||
data: portableDid,
|
||
agent: this.agent,
|
||
tenant: tenant ?? portableDid.uri,
|
||
preventDuplicates: false,
|
||
useCache: true
|
||
});
|
||
}
|
||
return bearerDid;
|
||
}
|
||
async export({ didUri, tenant }) {
|
||
const bearerDid = await this.get({ didUri, tenant });
|
||
if (!bearerDid) {
|
||
throw new Error(`AgentDidApi: Failed to export due to DID not found: ${didUri}`);
|
||
}
|
||
const portableDid = await bearerDid.export();
|
||
return portableDid;
|
||
}
|
||
async get({ didUri, tenant }) {
|
||
const portableDid = await this._store.get({ id: didUri, agent: this.agent, tenant, useCache: true });
|
||
if (!portableDid)
|
||
return void 0;
|
||
const bearerDid = await import_dids.BearerDid.import({ portableDid, keyManager: this.agent.keyManager });
|
||
return bearerDid;
|
||
}
|
||
async getSigningMethod({ didUri, methodId }) {
|
||
const parsedDid = import_dids.Did.parse(didUri);
|
||
if (!parsedDid) {
|
||
throw new Error(`Invalid DID URI: ${didUri}`);
|
||
}
|
||
const didMethod = this.getMethod(parsedDid.method);
|
||
const { didDocument, didResolutionMetadata } = await this.resolve(didUri);
|
||
if (!didDocument) {
|
||
throw new Error(`DID resolution failed for '${didUri}': ${JSON.stringify(didResolutionMetadata)}`);
|
||
}
|
||
const verificationMethod = await didMethod.getSigningMethod({ didDocument, methodId });
|
||
return verificationMethod;
|
||
}
|
||
async import({ portableDid, tenant }) {
|
||
const bearerDid = await import_dids.BearerDid.import({ keyManager: this.agent.keyManager, portableDid });
|
||
const { uri, document, metadata } = bearerDid;
|
||
const portableDidWithoutKeys = { uri, document, metadata };
|
||
await this._store.set({
|
||
id: portableDidWithoutKeys.uri,
|
||
data: portableDidWithoutKeys,
|
||
agent: this.agent,
|
||
tenant: tenant ?? portableDidWithoutKeys.uri,
|
||
preventDuplicates: true,
|
||
useCache: true
|
||
});
|
||
return bearerDid;
|
||
}
|
||
async processRequest(request) {
|
||
if (isDidRequest(request, "Create" /* Create */)) {
|
||
try {
|
||
const bearerDid = await this.create({ ...request.messageParams });
|
||
const response = {
|
||
result: {
|
||
uri: bearerDid.uri,
|
||
document: bearerDid.document,
|
||
metadata: bearerDid.metadata
|
||
},
|
||
ok: true,
|
||
status: { code: 201, message: "Created" }
|
||
};
|
||
return response;
|
||
} catch (error) {
|
||
return {
|
||
ok: false,
|
||
status: { code: 500, message: error.message ?? "Unknown error occurred" }
|
||
};
|
||
}
|
||
}
|
||
if (isDidRequest(request, "Resolve" /* Resolve */)) {
|
||
const { didUri, options } = request.messageParams;
|
||
const resolutionResult = await this.resolve(didUri, options);
|
||
const response = {
|
||
result: resolutionResult,
|
||
ok: true,
|
||
status: { code: 200, message: "OK" }
|
||
};
|
||
return response;
|
||
}
|
||
throw new Error(`AgentDidApi: Unsupported request type: ${request.messageType}`);
|
||
}
|
||
getMethod(methodName) {
|
||
const didMethodApi = this._didMethods.get(methodName);
|
||
if (didMethodApi === void 0) {
|
||
throw new Error(`DID Method not supported: ${methodName}`);
|
||
}
|
||
return didMethodApi;
|
||
}
|
||
};
|
||
|
||
// src/dwn-api.ts
|
||
var import_common6 = require("@web5/common");
|
||
var import_crypto10 = require("@web5/crypto");
|
||
var import_dids3 = require("@web5/dids");
|
||
var import_dwn_sdk_js4 = require("@tbd54566975/dwn-sdk-js");
|
||
|
||
// src/utils.ts
|
||
var import_dids2 = require("@web5/dids");
|
||
var import_readable_web_to_node_stream = require("readable-web-to-node-stream");
|
||
var import_dwn_sdk_js3 = require("@tbd54566975/dwn-sdk-js");
|
||
function blobToIsomorphicNodeReadable(blob) {
|
||
return webReadableToIsomorphicNodeReadable(blob.stream());
|
||
}
|
||
async function getDwnServiceEndpointUrls(didUri, dereferencer) {
|
||
const dereferencingResult = await dereferencer.dereference(`${didUri}#dwn`);
|
||
if (dereferencingResult.dereferencingMetadata.error) {
|
||
throw new Error(`Failed to dereference '${didUri}#dwn': ${dereferencingResult.dereferencingMetadata.error}`);
|
||
}
|
||
if (import_dids2.utils.isDwnDidService(dereferencingResult.contentStream)) {
|
||
const { serviceEndpoint } = dereferencingResult.contentStream;
|
||
const serviceEndpointUrls = typeof serviceEndpoint === "string" ? [serviceEndpoint] : Array.isArray(serviceEndpoint) && serviceEndpoint.every((endpoint) => typeof endpoint === "string") ? serviceEndpoint : [];
|
||
if (serviceEndpointUrls.length > 0) {
|
||
return serviceEndpointUrls;
|
||
}
|
||
}
|
||
return [];
|
||
}
|
||
function getRecordAuthor(record) {
|
||
return import_dwn_sdk_js3.Records.getAuthor(record);
|
||
}
|
||
function isRecordsWrite(obj) {
|
||
if (!obj || typeof obj !== "object" || obj === null)
|
||
return false;
|
||
return "message" in obj && typeof obj.message === "object" && obj.message !== null && "descriptor" in obj.message && typeof obj.message.descriptor === "object" && obj.message.descriptor !== null && "interface" in obj.message.descriptor && obj.message.descriptor.interface === import_dwn_sdk_js3.DwnInterfaceName.Records && "method" in obj.message.descriptor && obj.message.descriptor.method === import_dwn_sdk_js3.DwnMethodName.Write;
|
||
}
|
||
function getRecordMessageCid(message) {
|
||
return import_dwn_sdk_js3.Message.getCid(message);
|
||
}
|
||
async function getPaginationCursor(message, dateSort) {
|
||
const value = dateSort === import_dwn_sdk_js3.DateSort.CreatedAscending || dateSort === import_dwn_sdk_js3.DateSort.CreatedDescending ? message.descriptor.dateCreated : message.descriptor.datePublished;
|
||
if (value === void 0) {
|
||
throw new Error("The dateCreated or datePublished property is missing from the record descriptor.");
|
||
}
|
||
return {
|
||
messageCid: await getRecordMessageCid(message),
|
||
value
|
||
};
|
||
}
|
||
function webReadableToIsomorphicNodeReadable(webReadable) {
|
||
return new import_readable_web_to_node_stream.ReadableWebToNodeStream(webReadable);
|
||
}
|
||
|
||
// src/dwn-api.ts
|
||
function isDwnRequest(dwnRequest, messageType) {
|
||
return dwnRequest.messageType === messageType;
|
||
}
|
||
function isDwnMessage(messageType, message) {
|
||
const incomingMessageInterfaceName = message.descriptor.interface + message.descriptor.method;
|
||
return incomingMessageInterfaceName === messageType;
|
||
}
|
||
var AgentDwnApi = class {
|
||
constructor({ agent, dwn }) {
|
||
this._agent = agent;
|
||
this._dwn = dwn;
|
||
}
|
||
/**
|
||
* Retrieves the `Web5PlatformAgent` execution context.
|
||
*
|
||
* @returns The `Web5PlatformAgent` instance that represents the current execution context.
|
||
* @throws Will throw an error if the `agent` instance property is undefined.
|
||
*/
|
||
get agent() {
|
||
if (this._agent === void 0) {
|
||
throw new Error("AgentDwnApi: Unable to determine agent execution context.");
|
||
}
|
||
return this._agent;
|
||
}
|
||
set agent(agent) {
|
||
this._agent = agent;
|
||
}
|
||
/**
|
||
* Public getter for the DWN instance used by this API.
|
||
*
|
||
* Notes:
|
||
* - This getter is public to allow advanced developers to access the DWN instance directly.
|
||
* However, it is recommended to use the `processRequest` method to interact with the DWN
|
||
* instance to ensure that the DWN message is constructed correctly.
|
||
* - The getter is named `node` to avoid confusion with the `dwn` property of the
|
||
* `Web5PlatformAgent`. In other words, so that a developer can call `agent.dwn.node` to access
|
||
* the DWN instance and not `agent.dwn.dwn`.
|
||
*/
|
||
get node() {
|
||
return this._dwn;
|
||
}
|
||
static async createDwn({
|
||
dataPath,
|
||
dataStore,
|
||
didResolver,
|
||
eventLog,
|
||
eventStream,
|
||
messageStore,
|
||
tenantGate
|
||
}) {
|
||
dataStore ??= new import_dwn_sdk_js4.DataStoreLevel({ blockstoreLocation: `${dataPath}/DWN_DATASTORE` });
|
||
didResolver ??= new import_dids3.UniversalResolver({
|
||
didResolvers: [import_dids3.DidDht, import_dids3.DidJwk],
|
||
cache: new import_dids3.DidResolverCacheLevel({ location: `${dataPath}/DID_RESOLVERCACHE` })
|
||
});
|
||
eventLog ??= new import_dwn_sdk_js4.EventLogLevel({ location: `${dataPath}/DWN_EVENTLOG` });
|
||
messageStore ??= new import_dwn_sdk_js4.MessageStoreLevel({
|
||
blockstoreLocation: `${dataPath}/DWN_MESSAGESTORE`,
|
||
indexLocation: `${dataPath}/DWN_MESSAGEINDEX`
|
||
});
|
||
return await import_dwn_sdk_js4.Dwn.create({ dataStore, didResolver, eventLog, eventStream, messageStore, tenantGate });
|
||
}
|
||
async processRequest(request) {
|
||
const { message, dataStream } = await this.constructDwnMessage({ request });
|
||
const { subscriptionHandler } = request;
|
||
const reply = request.store !== false ? await this._dwn.processMessage(request.target, message, { dataStream, subscriptionHandler }) : { status: { code: 202, detail: "Accepted" } };
|
||
return {
|
||
reply,
|
||
message,
|
||
messageCid: await import_dwn_sdk_js4.Message.getCid(message)
|
||
};
|
||
}
|
||
async sendRequest(request) {
|
||
const dwnEndpointUrls = await getDwnServiceEndpointUrls(request.target, this.agent.did);
|
||
if (dwnEndpointUrls.length === 0) {
|
||
throw new Error(`AgentDwnApi: DID Service is missing or malformed: ${request.target}#dwn`);
|
||
}
|
||
let messageCid;
|
||
let message;
|
||
let data;
|
||
let subscriptionHandler;
|
||
if ("messageCid" in request) {
|
||
({ message, data } = await this.getDwnMessage({
|
||
author: request.author,
|
||
messageCid: request.messageCid,
|
||
messageType: request.messageType
|
||
}));
|
||
messageCid = request.messageCid;
|
||
} else {
|
||
({ message } = await this.constructDwnMessage({ request }));
|
||
if (request.dataStream && !(request.dataStream instanceof Blob)) {
|
||
throw new Error("AgentDwnApi: DataStream must be provided as a Blob");
|
||
}
|
||
data = request.dataStream;
|
||
subscriptionHandler = request.subscriptionHandler;
|
||
}
|
||
const reply = await this.sendDwnRpcRequest({
|
||
targetDid: request.target,
|
||
dwnEndpointUrls,
|
||
message,
|
||
data,
|
||
subscriptionHandler
|
||
});
|
||
messageCid ??= await import_dwn_sdk_js4.Message.getCid(message);
|
||
return { reply, message, messageCid };
|
||
}
|
||
async sendDwnRpcRequest({
|
||
targetDid,
|
||
dwnEndpointUrls,
|
||
message,
|
||
data,
|
||
subscriptionHandler
|
||
}) {
|
||
const errorMessages = [];
|
||
if (message.descriptor.method === import_dwn_sdk_js4.DwnMethodName.Subscribe && subscriptionHandler === void 0) {
|
||
throw new Error("AgentDwnApi: Subscription handler is required for subscription requests.");
|
||
}
|
||
for (let dwnUrl of dwnEndpointUrls) {
|
||
try {
|
||
if (subscriptionHandler !== void 0) {
|
||
const serverInfo = await this.agent.rpc.getServerInfo(dwnUrl);
|
||
if (!serverInfo.webSocketSupport) {
|
||
errorMessages.push({
|
||
url: dwnUrl,
|
||
message: "WebSocket support is not enabled on the server."
|
||
});
|
||
continue;
|
||
}
|
||
const parsedUrl = new URL(dwnUrl);
|
||
parsedUrl.protocol = parsedUrl.protocol === "http:" ? "ws:" : "wss:";
|
||
dwnUrl = parsedUrl.toString();
|
||
}
|
||
const dwnReply = await this.agent.rpc.sendDwnRequest({
|
||
dwnUrl,
|
||
targetDid,
|
||
message,
|
||
data,
|
||
subscriptionHandler
|
||
});
|
||
return dwnReply;
|
||
} catch (error) {
|
||
errorMessages.push({
|
||
url: dwnUrl,
|
||
message: error instanceof Error ? error.message : "Unknown error"
|
||
});
|
||
}
|
||
}
|
||
throw new Error(`Failed to send DWN RPC request: ${JSON.stringify(errorMessages)}`);
|
||
}
|
||
async constructDwnMessage({ request }) {
|
||
const rawMessage = request.rawMessage;
|
||
let readableStream;
|
||
if (isDwnRequest(request, DwnInterface.RecordsWrite)) {
|
||
const messageParams = request.messageParams;
|
||
if (request.dataStream && !messageParams?.data) {
|
||
const { dataStream } = request;
|
||
let isomorphicNodeReadable;
|
||
if (dataStream instanceof Blob) {
|
||
isomorphicNodeReadable = blobToIsomorphicNodeReadable(dataStream);
|
||
readableStream = blobToIsomorphicNodeReadable(dataStream);
|
||
} else if (dataStream instanceof ReadableStream) {
|
||
const [forCid, forProcessMessage] = dataStream.tee();
|
||
isomorphicNodeReadable = webReadableToIsomorphicNodeReadable(forCid);
|
||
readableStream = webReadableToIsomorphicNodeReadable(forProcessMessage);
|
||
}
|
||
if (!rawMessage) {
|
||
messageParams.dataCid = await import_dwn_sdk_js4.Cid.computeDagPbCidFromStream(isomorphicNodeReadable);
|
||
messageParams.dataSize ??= isomorphicNodeReadable["bytesRead"];
|
||
}
|
||
}
|
||
}
|
||
const signer = await this.getSigner(request.author);
|
||
const dwnMessageConstructor = dwnMessageConstructors[request.messageType];
|
||
const dwnMessage = rawMessage ? await dwnMessageConstructor.parse(rawMessage) : await dwnMessageConstructor.create({
|
||
// TODO: Implement alternative to type assertion.
|
||
...request.messageParams,
|
||
signer
|
||
});
|
||
if (isRecordsWrite(dwnMessage) && request.signAsOwner) {
|
||
await dwnMessage.signAsOwner(signer);
|
||
}
|
||
return { message: dwnMessage.message, dataStream: readableStream };
|
||
}
|
||
async getSigner(author) {
|
||
if (author === this.agent.agentDid.uri) {
|
||
const signer = await this.agent.agentDid.getSigner();
|
||
return {
|
||
algorithm: signer.algorithm,
|
||
keyId: signer.keyId,
|
||
sign: async (data) => {
|
||
return await signer.sign({ data });
|
||
}
|
||
};
|
||
} else {
|
||
try {
|
||
const signingMethod = await this.agent.did.getSigningMethod({ didUri: author });
|
||
if (!signingMethod.publicKeyJwk) {
|
||
throw new Error(`Verification method '${signingMethod.id}' does not contain a public key in JWK format`);
|
||
}
|
||
const keyUri = await this.agent.keyManager.getKeyUri({ key: signingMethod.publicKeyJwk });
|
||
const publicKey = await this.agent.keyManager.getPublicKey({ keyUri });
|
||
const keyManager = this.agent.keyManager;
|
||
return {
|
||
algorithm: import_crypto10.utils.getJoseSignatureAlgorithmFromPublicKey(publicKey),
|
||
keyId: signingMethod.id,
|
||
sign: async (data) => {
|
||
return await keyManager.sign({ data, keyUri });
|
||
}
|
||
};
|
||
} catch (error) {
|
||
throw new Error(`AgentDwnApi: Unable to get signer for author '${author}': ${error.message}`);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* FURTHER REFACTORING NEEDED BELOW THIS LINE
|
||
*/
|
||
async getDwnMessage({ author, messageCid }) {
|
||
const signer = await this.getSigner(author);
|
||
const messagesGet = await dwnMessageConstructors[DwnInterface.MessagesGet].create({
|
||
messageCids: [messageCid],
|
||
signer
|
||
});
|
||
const result = await this._dwn.processMessage(author, messagesGet.message);
|
||
if (!(result.entries && result.entries.length === 1)) {
|
||
throw new Error("AgentDwnApi: Expected 1 message entry in the MessagesGet response but received none or more than one.");
|
||
}
|
||
const [messageEntry] = result.entries;
|
||
const message = messageEntry.message;
|
||
if (!message) {
|
||
throw new Error(`AgentDwnApi: Message not found with CID: ${messageCid}`);
|
||
}
|
||
let dwnMessageWithBlob = { message };
|
||
if (isRecordsWrite(messageEntry)) {
|
||
if (messageEntry.encodedData) {
|
||
const dataBytes = import_common6.Convert.base64Url(messageEntry.encodedData).toUint8Array();
|
||
dwnMessageWithBlob.data = new Blob([dataBytes]);
|
||
} else {
|
||
const recordsRead = await dwnMessageConstructors[DwnInterface.RecordsRead].create({
|
||
filter: {
|
||
recordId: messageEntry.message.recordId
|
||
},
|
||
signer
|
||
});
|
||
const reply = await this._dwn.processMessage(author, recordsRead.message);
|
||
if (reply.status.code >= 400) {
|
||
const { status: { code, detail } } = reply;
|
||
throw new Error(`AgentDwnApi: (${code}) Failed to read data associated with record ${messageEntry.message.recordId}. ${detail}}`);
|
||
} else if (reply.record) {
|
||
const dataBytes = await import_common6.NodeStream.consumeToBytes({ readable: reply.record.data });
|
||
dwnMessageWithBlob.data = new Blob([dataBytes]);
|
||
}
|
||
}
|
||
}
|
||
return dwnMessageWithBlob;
|
||
}
|
||
/**
|
||
* TODO: Refactor this to consolidate logic in AgentDwnApi and SyncEngineLevel.
|
||
* ADDED TO GET SYNC WORKING
|
||
* - createMessage()
|
||
* - processMessage()
|
||
*/
|
||
async createMessage({ author, messageParams, messageType }) {
|
||
const signer = await this.getSigner(author);
|
||
const dwnMessageConstructor = dwnMessageConstructors[messageType];
|
||
const dwnMessage = await dwnMessageConstructor.create({
|
||
// TODO: Explore whether 'messageParams' should be required in the ProcessDwnRequest type.
|
||
...messageParams,
|
||
signer
|
||
});
|
||
return dwnMessage;
|
||
}
|
||
async processMessage({ dataStream, message, targetDid }) {
|
||
return await this._dwn.processMessage(targetDid, message, { dataStream });
|
||
}
|
||
};
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/_assert.js
|
||
function number(n) {
|
||
if (!Number.isSafeInteger(n) || n < 0)
|
||
throw new Error(`Wrong positive integer: ${n}`);
|
||
}
|
||
function isBytes(a) {
|
||
return a instanceof Uint8Array || a != null && typeof a === "object" && a.constructor.name === "Uint8Array";
|
||
}
|
||
function bytes(b, ...lengths) {
|
||
if (!isBytes(b))
|
||
throw new Error("Expected Uint8Array");
|
||
if (lengths.length > 0 && !lengths.includes(b.length))
|
||
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
|
||
}
|
||
function hash(hash2) {
|
||
if (typeof hash2 !== "function" || typeof hash2.create !== "function")
|
||
throw new Error("Hash should be wrapped by utils.wrapConstructor");
|
||
number(hash2.outputLen);
|
||
number(hash2.blockLen);
|
||
}
|
||
function exists(instance, checkFinished = true) {
|
||
if (instance.destroyed)
|
||
throw new Error("Hash instance has been destroyed");
|
||
if (checkFinished && instance.finished)
|
||
throw new Error("Hash#digest() has already been called");
|
||
}
|
||
function output(out, instance) {
|
||
bytes(out);
|
||
const min = instance.outputLen;
|
||
if (out.length < min) {
|
||
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
|
||
}
|
||
}
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/cryptoNode.js
|
||
var nc = __toESM(require("node:crypto"), 1);
|
||
var crypto2 = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : void 0;
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/utils.js
|
||
function isBytes2(a) {
|
||
return a instanceof Uint8Array || a != null && typeof a === "object" && a.constructor.name === "Uint8Array";
|
||
}
|
||
var createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
||
var rotr = (word, shift) => word << 32 - shift | word >>> shift;
|
||
var isLE = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
|
||
if (!isLE)
|
||
throw new Error("Non little-endian hardware is not supported");
|
||
var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
||
function bytesToHex(bytes2) {
|
||
if (!isBytes2(bytes2))
|
||
throw new Error("Uint8Array expected");
|
||
let hex = "";
|
||
for (let i = 0; i < bytes2.length; i++) {
|
||
hex += hexes[bytes2[i]];
|
||
}
|
||
return hex;
|
||
}
|
||
var asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 };
|
||
function asciiToBase16(char) {
|
||
if (char >= asciis._0 && char <= asciis._9)
|
||
return char - asciis._0;
|
||
if (char >= asciis._A && char <= asciis._F)
|
||
return char - (asciis._A - 10);
|
||
if (char >= asciis._a && char <= asciis._f)
|
||
return char - (asciis._a - 10);
|
||
return;
|
||
}
|
||
function hexToBytes(hex) {
|
||
if (typeof hex !== "string")
|
||
throw new Error("hex string expected, got " + typeof hex);
|
||
const hl = hex.length;
|
||
const al = hl / 2;
|
||
if (hl % 2)
|
||
throw new Error("padded hex string expected, got unpadded hex of length " + hl);
|
||
const array = new Uint8Array(al);
|
||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
||
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
||
if (n1 === void 0 || n2 === void 0) {
|
||
const char = hex[hi] + hex[hi + 1];
|
||
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
||
}
|
||
array[ai] = n1 * 16 + n2;
|
||
}
|
||
return array;
|
||
}
|
||
function utf8ToBytes(str) {
|
||
if (typeof str !== "string")
|
||
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
||
return new Uint8Array(new TextEncoder().encode(str));
|
||
}
|
||
function toBytes(data) {
|
||
if (typeof data === "string")
|
||
data = utf8ToBytes(data);
|
||
if (!isBytes2(data))
|
||
throw new Error(`expected Uint8Array, got ${typeof data}`);
|
||
return data;
|
||
}
|
||
function concatBytes(...arrays) {
|
||
let sum = 0;
|
||
for (let i = 0; i < arrays.length; i++) {
|
||
const a = arrays[i];
|
||
if (!isBytes2(a))
|
||
throw new Error("Uint8Array expected");
|
||
sum += a.length;
|
||
}
|
||
const res = new Uint8Array(sum);
|
||
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
||
const a = arrays[i];
|
||
res.set(a, pad);
|
||
pad += a.length;
|
||
}
|
||
return res;
|
||
}
|
||
var Hash = class {
|
||
// Safe version that clones internal state
|
||
clone() {
|
||
return this._cloneInto();
|
||
}
|
||
};
|
||
var toStr = {}.toString;
|
||
function wrapConstructor(hashCons) {
|
||
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
||
const tmp = hashCons();
|
||
hashC.outputLen = tmp.outputLen;
|
||
hashC.blockLen = tmp.blockLen;
|
||
hashC.create = () => hashCons();
|
||
return hashC;
|
||
}
|
||
function randomBytes(bytesLength = 32) {
|
||
if (crypto2 && typeof crypto2.getRandomValues === "function") {
|
||
return crypto2.getRandomValues(new Uint8Array(bytesLength));
|
||
}
|
||
throw new Error("crypto.getRandomValues must be defined");
|
||
}
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/_sha2.js
|
||
function setBigUint64(view, byteOffset, value, isLE2) {
|
||
if (typeof view.setBigUint64 === "function")
|
||
return view.setBigUint64(byteOffset, value, isLE2);
|
||
const _32n2 = BigInt(32);
|
||
const _u32_max = BigInt(4294967295);
|
||
const wh = Number(value >> _32n2 & _u32_max);
|
||
const wl = Number(value & _u32_max);
|
||
const h = isLE2 ? 4 : 0;
|
||
const l = isLE2 ? 0 : 4;
|
||
view.setUint32(byteOffset + h, wh, isLE2);
|
||
view.setUint32(byteOffset + l, wl, isLE2);
|
||
}
|
||
var SHA2 = class extends Hash {
|
||
constructor(blockLen, outputLen, padOffset, isLE2) {
|
||
super();
|
||
this.blockLen = blockLen;
|
||
this.outputLen = outputLen;
|
||
this.padOffset = padOffset;
|
||
this.isLE = isLE2;
|
||
this.finished = false;
|
||
this.length = 0;
|
||
this.pos = 0;
|
||
this.destroyed = false;
|
||
this.buffer = new Uint8Array(blockLen);
|
||
this.view = createView(this.buffer);
|
||
}
|
||
update(data) {
|
||
exists(this);
|
||
const { view, buffer, blockLen } = this;
|
||
data = toBytes(data);
|
||
const len = data.length;
|
||
for (let pos = 0; pos < len; ) {
|
||
const take = Math.min(blockLen - this.pos, len - pos);
|
||
if (take === blockLen) {
|
||
const dataView = createView(data);
|
||
for (; blockLen <= len - pos; pos += blockLen)
|
||
this.process(dataView, pos);
|
||
continue;
|
||
}
|
||
buffer.set(data.subarray(pos, pos + take), this.pos);
|
||
this.pos += take;
|
||
pos += take;
|
||
if (this.pos === blockLen) {
|
||
this.process(view, 0);
|
||
this.pos = 0;
|
||
}
|
||
}
|
||
this.length += data.length;
|
||
this.roundClean();
|
||
return this;
|
||
}
|
||
digestInto(out) {
|
||
exists(this);
|
||
output(out, this);
|
||
this.finished = true;
|
||
const { buffer, view, blockLen, isLE: isLE2 } = this;
|
||
let { pos } = this;
|
||
buffer[pos++] = 128;
|
||
this.buffer.subarray(pos).fill(0);
|
||
if (this.padOffset > blockLen - pos) {
|
||
this.process(view, 0);
|
||
pos = 0;
|
||
}
|
||
for (let i = pos; i < blockLen; i++)
|
||
buffer[i] = 0;
|
||
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE2);
|
||
this.process(view, 0);
|
||
const oview = createView(out);
|
||
const len = this.outputLen;
|
||
if (len % 4)
|
||
throw new Error("_sha2: outputLen should be aligned to 32bit");
|
||
const outLen = len / 4;
|
||
const state = this.get();
|
||
if (outLen > state.length)
|
||
throw new Error("_sha2: outputLen bigger than state");
|
||
for (let i = 0; i < outLen; i++)
|
||
oview.setUint32(4 * i, state[i], isLE2);
|
||
}
|
||
digest() {
|
||
const { buffer, outputLen } = this;
|
||
this.digestInto(buffer);
|
||
const res = buffer.slice(0, outputLen);
|
||
this.destroy();
|
||
return res;
|
||
}
|
||
_cloneInto(to) {
|
||
to || (to = new this.constructor());
|
||
to.set(...this.get());
|
||
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
||
to.length = length;
|
||
to.pos = pos;
|
||
to.finished = finished;
|
||
to.destroyed = destroyed;
|
||
if (length % blockLen)
|
||
to.buffer.set(buffer);
|
||
return to;
|
||
}
|
||
};
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/_u64.js
|
||
var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
||
var _32n = /* @__PURE__ */ BigInt(32);
|
||
function fromBig(n, le = false) {
|
||
if (le)
|
||
return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
|
||
return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
||
}
|
||
function split(lst, le = false) {
|
||
let Ah = new Uint32Array(lst.length);
|
||
let Al = new Uint32Array(lst.length);
|
||
for (let i = 0; i < lst.length; i++) {
|
||
const { h, l } = fromBig(lst[i], le);
|
||
[Ah[i], Al[i]] = [h, l];
|
||
}
|
||
return [Ah, Al];
|
||
}
|
||
var toBig = (h, l) => BigInt(h >>> 0) << _32n | BigInt(l >>> 0);
|
||
var shrSH = (h, _l, s) => h >>> s;
|
||
var shrSL = (h, l, s) => h << 32 - s | l >>> s;
|
||
var rotrSH = (h, l, s) => h >>> s | l << 32 - s;
|
||
var rotrSL = (h, l, s) => h << 32 - s | l >>> s;
|
||
var rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
|
||
var rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
|
||
var rotr32H = (_h, l) => l;
|
||
var rotr32L = (h, _l) => h;
|
||
var rotlSH = (h, l, s) => h << s | l >>> 32 - s;
|
||
var rotlSL = (h, l, s) => l << s | h >>> 32 - s;
|
||
var rotlBH = (h, l, s) => l << s - 32 | h >>> 64 - s;
|
||
var rotlBL = (h, l, s) => h << s - 32 | l >>> 64 - s;
|
||
function add(Ah, Al, Bh, Bl) {
|
||
const l = (Al >>> 0) + (Bl >>> 0);
|
||
return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
|
||
}
|
||
var add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
|
||
var add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
|
||
var add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
|
||
var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
|
||
var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
|
||
var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
|
||
var u64 = {
|
||
fromBig,
|
||
split,
|
||
toBig,
|
||
shrSH,
|
||
shrSL,
|
||
rotrSH,
|
||
rotrSL,
|
||
rotrBH,
|
||
rotrBL,
|
||
rotr32H,
|
||
rotr32L,
|
||
rotlSH,
|
||
rotlSL,
|
||
rotlBH,
|
||
rotlBL,
|
||
add,
|
||
add3L,
|
||
add3H,
|
||
add4L,
|
||
add4H,
|
||
add5H,
|
||
add5L
|
||
};
|
||
var u64_default = u64;
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/sha512.js
|
||
var [SHA512_Kh, SHA512_Kl] = /* @__PURE__ */ (() => u64_default.split([
|
||
"0x428a2f98d728ae22",
|
||
"0x7137449123ef65cd",
|
||
"0xb5c0fbcfec4d3b2f",
|
||
"0xe9b5dba58189dbbc",
|
||
"0x3956c25bf348b538",
|
||
"0x59f111f1b605d019",
|
||
"0x923f82a4af194f9b",
|
||
"0xab1c5ed5da6d8118",
|
||
"0xd807aa98a3030242",
|
||
"0x12835b0145706fbe",
|
||
"0x243185be4ee4b28c",
|
||
"0x550c7dc3d5ffb4e2",
|
||
"0x72be5d74f27b896f",
|
||
"0x80deb1fe3b1696b1",
|
||
"0x9bdc06a725c71235",
|
||
"0xc19bf174cf692694",
|
||
"0xe49b69c19ef14ad2",
|
||
"0xefbe4786384f25e3",
|
||
"0x0fc19dc68b8cd5b5",
|
||
"0x240ca1cc77ac9c65",
|
||
"0x2de92c6f592b0275",
|
||
"0x4a7484aa6ea6e483",
|
||
"0x5cb0a9dcbd41fbd4",
|
||
"0x76f988da831153b5",
|
||
"0x983e5152ee66dfab",
|
||
"0xa831c66d2db43210",
|
||
"0xb00327c898fb213f",
|
||
"0xbf597fc7beef0ee4",
|
||
"0xc6e00bf33da88fc2",
|
||
"0xd5a79147930aa725",
|
||
"0x06ca6351e003826f",
|
||
"0x142929670a0e6e70",
|
||
"0x27b70a8546d22ffc",
|
||
"0x2e1b21385c26c926",
|
||
"0x4d2c6dfc5ac42aed",
|
||
"0x53380d139d95b3df",
|
||
"0x650a73548baf63de",
|
||
"0x766a0abb3c77b2a8",
|
||
"0x81c2c92e47edaee6",
|
||
"0x92722c851482353b",
|
||
"0xa2bfe8a14cf10364",
|
||
"0xa81a664bbc423001",
|
||
"0xc24b8b70d0f89791",
|
||
"0xc76c51a30654be30",
|
||
"0xd192e819d6ef5218",
|
||
"0xd69906245565a910",
|
||
"0xf40e35855771202a",
|
||
"0x106aa07032bbd1b8",
|
||
"0x19a4c116b8d2d0c8",
|
||
"0x1e376c085141ab53",
|
||
"0x2748774cdf8eeb99",
|
||
"0x34b0bcb5e19b48a8",
|
||
"0x391c0cb3c5c95a63",
|
||
"0x4ed8aa4ae3418acb",
|
||
"0x5b9cca4f7763e373",
|
||
"0x682e6ff3d6b2b8a3",
|
||
"0x748f82ee5defb2fc",
|
||
"0x78a5636f43172f60",
|
||
"0x84c87814a1f0ab72",
|
||
"0x8cc702081a6439ec",
|
||
"0x90befffa23631e28",
|
||
"0xa4506cebde82bde9",
|
||
"0xbef9a3f7b2c67915",
|
||
"0xc67178f2e372532b",
|
||
"0xca273eceea26619c",
|
||
"0xd186b8c721c0c207",
|
||
"0xeada7dd6cde0eb1e",
|
||
"0xf57d4f7fee6ed178",
|
||
"0x06f067aa72176fba",
|
||
"0x0a637dc5a2c898a6",
|
||
"0x113f9804bef90dae",
|
||
"0x1b710b35131c471b",
|
||
"0x28db77f523047d84",
|
||
"0x32caab7b40c72493",
|
||
"0x3c9ebe0a15c9bebc",
|
||
"0x431d67c49c100d4c",
|
||
"0x4cc5d4becb3e42b6",
|
||
"0x597f299cfc657e2a",
|
||
"0x5fcb6fab3ad6faec",
|
||
"0x6c44198c4a475817"
|
||
].map((n) => BigInt(n))))();
|
||
var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
|
||
var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
|
||
var SHA512 = class extends SHA2 {
|
||
constructor() {
|
||
super(128, 64, 16, false);
|
||
this.Ah = 1779033703 | 0;
|
||
this.Al = 4089235720 | 0;
|
||
this.Bh = 3144134277 | 0;
|
||
this.Bl = 2227873595 | 0;
|
||
this.Ch = 1013904242 | 0;
|
||
this.Cl = 4271175723 | 0;
|
||
this.Dh = 2773480762 | 0;
|
||
this.Dl = 1595750129 | 0;
|
||
this.Eh = 1359893119 | 0;
|
||
this.El = 2917565137 | 0;
|
||
this.Fh = 2600822924 | 0;
|
||
this.Fl = 725511199 | 0;
|
||
this.Gh = 528734635 | 0;
|
||
this.Gl = 4215389547 | 0;
|
||
this.Hh = 1541459225 | 0;
|
||
this.Hl = 327033209 | 0;
|
||
}
|
||
// prettier-ignore
|
||
get() {
|
||
const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
||
return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
|
||
}
|
||
// prettier-ignore
|
||
set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
|
||
this.Ah = Ah | 0;
|
||
this.Al = Al | 0;
|
||
this.Bh = Bh | 0;
|
||
this.Bl = Bl | 0;
|
||
this.Ch = Ch | 0;
|
||
this.Cl = Cl | 0;
|
||
this.Dh = Dh | 0;
|
||
this.Dl = Dl | 0;
|
||
this.Eh = Eh | 0;
|
||
this.El = El | 0;
|
||
this.Fh = Fh | 0;
|
||
this.Fl = Fl | 0;
|
||
this.Gh = Gh | 0;
|
||
this.Gl = Gl | 0;
|
||
this.Hh = Hh | 0;
|
||
this.Hl = Hl | 0;
|
||
}
|
||
process(view, offset) {
|
||
for (let i = 0; i < 16; i++, offset += 4) {
|
||
SHA512_W_H[i] = view.getUint32(offset);
|
||
SHA512_W_L[i] = view.getUint32(offset += 4);
|
||
}
|
||
for (let i = 16; i < 80; i++) {
|
||
const W15h = SHA512_W_H[i - 15] | 0;
|
||
const W15l = SHA512_W_L[i - 15] | 0;
|
||
const s0h = u64_default.rotrSH(W15h, W15l, 1) ^ u64_default.rotrSH(W15h, W15l, 8) ^ u64_default.shrSH(W15h, W15l, 7);
|
||
const s0l = u64_default.rotrSL(W15h, W15l, 1) ^ u64_default.rotrSL(W15h, W15l, 8) ^ u64_default.shrSL(W15h, W15l, 7);
|
||
const W2h = SHA512_W_H[i - 2] | 0;
|
||
const W2l = SHA512_W_L[i - 2] | 0;
|
||
const s1h = u64_default.rotrSH(W2h, W2l, 19) ^ u64_default.rotrBH(W2h, W2l, 61) ^ u64_default.shrSH(W2h, W2l, 6);
|
||
const s1l = u64_default.rotrSL(W2h, W2l, 19) ^ u64_default.rotrBL(W2h, W2l, 61) ^ u64_default.shrSL(W2h, W2l, 6);
|
||
const SUMl = u64_default.add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
|
||
const SUMh = u64_default.add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
|
||
SHA512_W_H[i] = SUMh | 0;
|
||
SHA512_W_L[i] = SUMl | 0;
|
||
}
|
||
let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
||
for (let i = 0; i < 80; i++) {
|
||
const sigma1h = u64_default.rotrSH(Eh, El, 14) ^ u64_default.rotrSH(Eh, El, 18) ^ u64_default.rotrBH(Eh, El, 41);
|
||
const sigma1l = u64_default.rotrSL(Eh, El, 14) ^ u64_default.rotrSL(Eh, El, 18) ^ u64_default.rotrBL(Eh, El, 41);
|
||
const CHIh = Eh & Fh ^ ~Eh & Gh;
|
||
const CHIl = El & Fl ^ ~El & Gl;
|
||
const T1ll = u64_default.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
|
||
const T1h = u64_default.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
|
||
const T1l = T1ll | 0;
|
||
const sigma0h = u64_default.rotrSH(Ah, Al, 28) ^ u64_default.rotrBH(Ah, Al, 34) ^ u64_default.rotrBH(Ah, Al, 39);
|
||
const sigma0l = u64_default.rotrSL(Ah, Al, 28) ^ u64_default.rotrBL(Ah, Al, 34) ^ u64_default.rotrBL(Ah, Al, 39);
|
||
const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
|
||
const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
|
||
Hh = Gh | 0;
|
||
Hl = Gl | 0;
|
||
Gh = Fh | 0;
|
||
Gl = Fl | 0;
|
||
Fh = Eh | 0;
|
||
Fl = El | 0;
|
||
({ h: Eh, l: El } = u64_default.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
|
||
Dh = Ch | 0;
|
||
Dl = Cl | 0;
|
||
Ch = Bh | 0;
|
||
Cl = Bl | 0;
|
||
Bh = Ah | 0;
|
||
Bl = Al | 0;
|
||
const All = u64_default.add3L(T1l, sigma0l, MAJl);
|
||
Ah = u64_default.add3H(All, T1h, sigma0h, MAJh);
|
||
Al = All | 0;
|
||
}
|
||
({ h: Ah, l: Al } = u64_default.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
|
||
({ h: Bh, l: Bl } = u64_default.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
|
||
({ h: Ch, l: Cl } = u64_default.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
|
||
({ h: Dh, l: Dl } = u64_default.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
|
||
({ h: Eh, l: El } = u64_default.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
|
||
({ h: Fh, l: Fl } = u64_default.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
|
||
({ h: Gh, l: Gl } = u64_default.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
|
||
({ h: Hh, l: Hl } = u64_default.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
|
||
this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
|
||
}
|
||
roundClean() {
|
||
SHA512_W_H.fill(0);
|
||
SHA512_W_L.fill(0);
|
||
}
|
||
destroy() {
|
||
this.buffer.fill(0);
|
||
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||
}
|
||
};
|
||
var sha512 = /* @__PURE__ */ wrapConstructor(() => new SHA512());
|
||
|
||
// ../../node_modules/.pnpm/@noble+curves@1.3.0/node_modules/@noble/curves/esm/abstract/utils.js
|
||
var _0n = BigInt(0);
|
||
var _1n = BigInt(1);
|
||
var _2n = BigInt(2);
|
||
function isBytes3(a) {
|
||
return a instanceof Uint8Array || a != null && typeof a === "object" && a.constructor.name === "Uint8Array";
|
||
}
|
||
var hexes2 = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
||
function bytesToHex2(bytes2) {
|
||
if (!isBytes3(bytes2))
|
||
throw new Error("Uint8Array expected");
|
||
let hex = "";
|
||
for (let i = 0; i < bytes2.length; i++) {
|
||
hex += hexes2[bytes2[i]];
|
||
}
|
||
return hex;
|
||
}
|
||
function hexToNumber(hex) {
|
||
if (typeof hex !== "string")
|
||
throw new Error("hex string expected, got " + typeof hex);
|
||
return BigInt(hex === "" ? "0" : `0x${hex}`);
|
||
}
|
||
var asciis2 = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 };
|
||
function asciiToBase162(char) {
|
||
if (char >= asciis2._0 && char <= asciis2._9)
|
||
return char - asciis2._0;
|
||
if (char >= asciis2._A && char <= asciis2._F)
|
||
return char - (asciis2._A - 10);
|
||
if (char >= asciis2._a && char <= asciis2._f)
|
||
return char - (asciis2._a - 10);
|
||
return;
|
||
}
|
||
function hexToBytes2(hex) {
|
||
if (typeof hex !== "string")
|
||
throw new Error("hex string expected, got " + typeof hex);
|
||
const hl = hex.length;
|
||
const al = hl / 2;
|
||
if (hl % 2)
|
||
throw new Error("padded hex string expected, got unpadded hex of length " + hl);
|
||
const array = new Uint8Array(al);
|
||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
||
const n1 = asciiToBase162(hex.charCodeAt(hi));
|
||
const n2 = asciiToBase162(hex.charCodeAt(hi + 1));
|
||
if (n1 === void 0 || n2 === void 0) {
|
||
const char = hex[hi] + hex[hi + 1];
|
||
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
||
}
|
||
array[ai] = n1 * 16 + n2;
|
||
}
|
||
return array;
|
||
}
|
||
function bytesToNumberBE(bytes2) {
|
||
return hexToNumber(bytesToHex2(bytes2));
|
||
}
|
||
function bytesToNumberLE(bytes2) {
|
||
if (!isBytes3(bytes2))
|
||
throw new Error("Uint8Array expected");
|
||
return hexToNumber(bytesToHex2(Uint8Array.from(bytes2).reverse()));
|
||
}
|
||
function numberToBytesBE(n, len) {
|
||
return hexToBytes2(n.toString(16).padStart(len * 2, "0"));
|
||
}
|
||
function numberToBytesLE(n, len) {
|
||
return numberToBytesBE(n, len).reverse();
|
||
}
|
||
function ensureBytes(title, hex, expectedLength) {
|
||
let res;
|
||
if (typeof hex === "string") {
|
||
try {
|
||
res = hexToBytes2(hex);
|
||
} catch (e) {
|
||
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
|
||
}
|
||
} else if (isBytes3(hex)) {
|
||
res = Uint8Array.from(hex);
|
||
} else {
|
||
throw new Error(`${title} must be hex string or Uint8Array`);
|
||
}
|
||
const len = res.length;
|
||
if (typeof expectedLength === "number" && len !== expectedLength)
|
||
throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
|
||
return res;
|
||
}
|
||
function concatBytes2(...arrays) {
|
||
let sum = 0;
|
||
for (let i = 0; i < arrays.length; i++) {
|
||
const a = arrays[i];
|
||
if (!isBytes3(a))
|
||
throw new Error("Uint8Array expected");
|
||
sum += a.length;
|
||
}
|
||
let res = new Uint8Array(sum);
|
||
let pad = 0;
|
||
for (let i = 0; i < arrays.length; i++) {
|
||
const a = arrays[i];
|
||
res.set(a, pad);
|
||
pad += a.length;
|
||
}
|
||
return res;
|
||
}
|
||
var bitMask = (n) => (_2n << BigInt(n - 1)) - _1n;
|
||
var validatorFns = {
|
||
bigint: (val) => typeof val === "bigint",
|
||
function: (val) => typeof val === "function",
|
||
boolean: (val) => typeof val === "boolean",
|
||
string: (val) => typeof val === "string",
|
||
stringOrUint8Array: (val) => typeof val === "string" || isBytes3(val),
|
||
isSafeInteger: (val) => Number.isSafeInteger(val),
|
||
array: (val) => Array.isArray(val),
|
||
field: (val, object) => object.Fp.isValid(val),
|
||
hash: (val) => typeof val === "function" && Number.isSafeInteger(val.outputLen)
|
||
};
|
||
function validateObject(object, validators, optValidators = {}) {
|
||
const checkField = (fieldName, type, isOptional) => {
|
||
const checkVal = validatorFns[type];
|
||
if (typeof checkVal !== "function")
|
||
throw new Error(`Invalid validator "${type}", expected function`);
|
||
const val = object[fieldName];
|
||
if (isOptional && val === void 0)
|
||
return;
|
||
if (!checkVal(val, object)) {
|
||
throw new Error(`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`);
|
||
}
|
||
};
|
||
for (const [fieldName, type] of Object.entries(validators))
|
||
checkField(fieldName, type, false);
|
||
for (const [fieldName, type] of Object.entries(optValidators))
|
||
checkField(fieldName, type, true);
|
||
return object;
|
||
}
|
||
|
||
// ../../node_modules/.pnpm/@noble+curves@1.3.0/node_modules/@noble/curves/esm/abstract/modular.js
|
||
var _0n2 = BigInt(0);
|
||
var _1n2 = BigInt(1);
|
||
var _2n2 = BigInt(2);
|
||
var _3n = BigInt(3);
|
||
var _4n = BigInt(4);
|
||
var _5n = BigInt(5);
|
||
var _8n = BigInt(8);
|
||
var _9n = BigInt(9);
|
||
var _16n = BigInt(16);
|
||
function mod(a, b) {
|
||
const result = a % b;
|
||
return result >= _0n2 ? result : b + result;
|
||
}
|
||
function pow(num, power, modulo) {
|
||
if (modulo <= _0n2 || power < _0n2)
|
||
throw new Error("Expected power/modulo > 0");
|
||
if (modulo === _1n2)
|
||
return _0n2;
|
||
let res = _1n2;
|
||
while (power > _0n2) {
|
||
if (power & _1n2)
|
||
res = res * num % modulo;
|
||
num = num * num % modulo;
|
||
power >>= _1n2;
|
||
}
|
||
return res;
|
||
}
|
||
function pow2(x, power, modulo) {
|
||
let res = x;
|
||
while (power-- > _0n2) {
|
||
res *= res;
|
||
res %= modulo;
|
||
}
|
||
return res;
|
||
}
|
||
function invert(number2, modulo) {
|
||
if (number2 === _0n2 || modulo <= _0n2) {
|
||
throw new Error(`invert: expected positive integers, got n=${number2} mod=${modulo}`);
|
||
}
|
||
let a = mod(number2, modulo);
|
||
let b = modulo;
|
||
let x = _0n2, y = _1n2, u = _1n2, v = _0n2;
|
||
while (a !== _0n2) {
|
||
const q = b / a;
|
||
const r = b % a;
|
||
const m = x - u * q;
|
||
const n = y - v * q;
|
||
b = a, a = r, x = u, y = v, u = m, v = n;
|
||
}
|
||
const gcd = b;
|
||
if (gcd !== _1n2)
|
||
throw new Error("invert: does not exist");
|
||
return mod(x, modulo);
|
||
}
|
||
function tonelliShanks(P) {
|
||
const legendreC = (P - _1n2) / _2n2;
|
||
let Q, S, Z;
|
||
for (Q = P - _1n2, S = 0; Q % _2n2 === _0n2; Q /= _2n2, S++)
|
||
;
|
||
for (Z = _2n2; Z < P && pow(Z, legendreC, P) !== P - _1n2; Z++)
|
||
;
|
||
if (S === 1) {
|
||
const p1div4 = (P + _1n2) / _4n;
|
||
return function tonelliFast(Fp2, n) {
|
||
const root = Fp2.pow(n, p1div4);
|
||
if (!Fp2.eql(Fp2.sqr(root), n))
|
||
throw new Error("Cannot find square root");
|
||
return root;
|
||
};
|
||
}
|
||
const Q1div2 = (Q + _1n2) / _2n2;
|
||
return function tonelliSlow(Fp2, n) {
|
||
if (Fp2.pow(n, legendreC) === Fp2.neg(Fp2.ONE))
|
||
throw new Error("Cannot find square root");
|
||
let r = S;
|
||
let g = Fp2.pow(Fp2.mul(Fp2.ONE, Z), Q);
|
||
let x = Fp2.pow(n, Q1div2);
|
||
let b = Fp2.pow(n, Q);
|
||
while (!Fp2.eql(b, Fp2.ONE)) {
|
||
if (Fp2.eql(b, Fp2.ZERO))
|
||
return Fp2.ZERO;
|
||
let m = 1;
|
||
for (let t2 = Fp2.sqr(b); m < r; m++) {
|
||
if (Fp2.eql(t2, Fp2.ONE))
|
||
break;
|
||
t2 = Fp2.sqr(t2);
|
||
}
|
||
const ge = Fp2.pow(g, _1n2 << BigInt(r - m - 1));
|
||
g = Fp2.sqr(ge);
|
||
x = Fp2.mul(x, ge);
|
||
b = Fp2.mul(b, g);
|
||
r = m;
|
||
}
|
||
return x;
|
||
};
|
||
}
|
||
function FpSqrt(P) {
|
||
if (P % _4n === _3n) {
|
||
const p1div4 = (P + _1n2) / _4n;
|
||
return function sqrt3mod4(Fp2, n) {
|
||
const root = Fp2.pow(n, p1div4);
|
||
if (!Fp2.eql(Fp2.sqr(root), n))
|
||
throw new Error("Cannot find square root");
|
||
return root;
|
||
};
|
||
}
|
||
if (P % _8n === _5n) {
|
||
const c1 = (P - _5n) / _8n;
|
||
return function sqrt5mod8(Fp2, n) {
|
||
const n2 = Fp2.mul(n, _2n2);
|
||
const v = Fp2.pow(n2, c1);
|
||
const nv = Fp2.mul(n, v);
|
||
const i = Fp2.mul(Fp2.mul(nv, _2n2), v);
|
||
const root = Fp2.mul(nv, Fp2.sub(i, Fp2.ONE));
|
||
if (!Fp2.eql(Fp2.sqr(root), n))
|
||
throw new Error("Cannot find square root");
|
||
return root;
|
||
};
|
||
}
|
||
if (P % _16n === _9n) {
|
||
}
|
||
return tonelliShanks(P);
|
||
}
|
||
var isNegativeLE = (num, modulo) => (mod(num, modulo) & _1n2) === _1n2;
|
||
var FIELD_FIELDS = [
|
||
"create",
|
||
"isValid",
|
||
"is0",
|
||
"neg",
|
||
"inv",
|
||
"sqrt",
|
||
"sqr",
|
||
"eql",
|
||
"add",
|
||
"sub",
|
||
"mul",
|
||
"pow",
|
||
"div",
|
||
"addN",
|
||
"subN",
|
||
"mulN",
|
||
"sqrN"
|
||
];
|
||
function validateField(field) {
|
||
const initial = {
|
||
ORDER: "bigint",
|
||
MASK: "bigint",
|
||
BYTES: "isSafeInteger",
|
||
BITS: "isSafeInteger"
|
||
};
|
||
const opts = FIELD_FIELDS.reduce((map, val) => {
|
||
map[val] = "function";
|
||
return map;
|
||
}, initial);
|
||
return validateObject(field, opts);
|
||
}
|
||
function FpPow(f2, num, power) {
|
||
if (power < _0n2)
|
||
throw new Error("Expected power > 0");
|
||
if (power === _0n2)
|
||
return f2.ONE;
|
||
if (power === _1n2)
|
||
return num;
|
||
let p = f2.ONE;
|
||
let d = num;
|
||
while (power > _0n2) {
|
||
if (power & _1n2)
|
||
p = f2.mul(p, d);
|
||
d = f2.sqr(d);
|
||
power >>= _1n2;
|
||
}
|
||
return p;
|
||
}
|
||
function FpInvertBatch(f2, nums) {
|
||
const tmp = new Array(nums.length);
|
||
const lastMultiplied = nums.reduce((acc, num, i) => {
|
||
if (f2.is0(num))
|
||
return acc;
|
||
tmp[i] = acc;
|
||
return f2.mul(acc, num);
|
||
}, f2.ONE);
|
||
const inverted = f2.inv(lastMultiplied);
|
||
nums.reduceRight((acc, num, i) => {
|
||
if (f2.is0(num))
|
||
return acc;
|
||
tmp[i] = f2.mul(acc, tmp[i]);
|
||
return f2.mul(acc, num);
|
||
}, inverted);
|
||
return tmp;
|
||
}
|
||
function nLength(n, nBitLength) {
|
||
const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length;
|
||
const nByteLength = Math.ceil(_nBitLength / 8);
|
||
return { nBitLength: _nBitLength, nByteLength };
|
||
}
|
||
function Field(ORDER, bitLen, isLE2 = false, redef = {}) {
|
||
if (ORDER <= _0n2)
|
||
throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
|
||
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
|
||
if (BYTES > 2048)
|
||
throw new Error("Field lengths over 2048 bytes are not supported");
|
||
const sqrtP = FpSqrt(ORDER);
|
||
const f2 = Object.freeze({
|
||
ORDER,
|
||
BITS,
|
||
BYTES,
|
||
MASK: bitMask(BITS),
|
||
ZERO: _0n2,
|
||
ONE: _1n2,
|
||
create: (num) => mod(num, ORDER),
|
||
isValid: (num) => {
|
||
if (typeof num !== "bigint")
|
||
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
|
||
return _0n2 <= num && num < ORDER;
|
||
},
|
||
is0: (num) => num === _0n2,
|
||
isOdd: (num) => (num & _1n2) === _1n2,
|
||
neg: (num) => mod(-num, ORDER),
|
||
eql: (lhs, rhs) => lhs === rhs,
|
||
sqr: (num) => mod(num * num, ORDER),
|
||
add: (lhs, rhs) => mod(lhs + rhs, ORDER),
|
||
sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
|
||
mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
|
||
pow: (num, power) => FpPow(f2, num, power),
|
||
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
|
||
// Same as above, but doesn't normalize
|
||
sqrN: (num) => num * num,
|
||
addN: (lhs, rhs) => lhs + rhs,
|
||
subN: (lhs, rhs) => lhs - rhs,
|
||
mulN: (lhs, rhs) => lhs * rhs,
|
||
inv: (num) => invert(num, ORDER),
|
||
sqrt: redef.sqrt || ((n) => sqrtP(f2, n)),
|
||
invertBatch: (lst) => FpInvertBatch(f2, lst),
|
||
// TODO: do we really need constant cmov?
|
||
// We don't have const-time bigints anyway, so probably will be not very useful
|
||
cmov: (a, b, c) => c ? b : a,
|
||
toBytes: (num) => isLE2 ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES),
|
||
fromBytes: (bytes2) => {
|
||
if (bytes2.length !== BYTES)
|
||
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes2.length}`);
|
||
return isLE2 ? bytesToNumberLE(bytes2) : bytesToNumberBE(bytes2);
|
||
}
|
||
});
|
||
return Object.freeze(f2);
|
||
}
|
||
function FpSqrtEven(Fp2, elm) {
|
||
if (!Fp2.isOdd)
|
||
throw new Error(`Field doesn't have isOdd`);
|
||
const root = Fp2.sqrt(elm);
|
||
return Fp2.isOdd(root) ? Fp2.neg(root) : root;
|
||
}
|
||
|
||
// ../../node_modules/.pnpm/@noble+curves@1.3.0/node_modules/@noble/curves/esm/abstract/curve.js
|
||
var _0n3 = BigInt(0);
|
||
var _1n3 = BigInt(1);
|
||
function wNAF(c, bits) {
|
||
const constTimeNegate = (condition, item) => {
|
||
const neg = item.negate();
|
||
return condition ? neg : item;
|
||
};
|
||
const opts = (W) => {
|
||
const windows = Math.ceil(bits / W) + 1;
|
||
const windowSize = 2 ** (W - 1);
|
||
return { windows, windowSize };
|
||
};
|
||
return {
|
||
constTimeNegate,
|
||
// non-const time multiplication ladder
|
||
unsafeLadder(elm, n) {
|
||
let p = c.ZERO;
|
||
let d = elm;
|
||
while (n > _0n3) {
|
||
if (n & _1n3)
|
||
p = p.add(d);
|
||
d = d.double();
|
||
n >>= _1n3;
|
||
}
|
||
return p;
|
||
},
|
||
/**
|
||
* Creates a wNAF precomputation window. Used for caching.
|
||
* Default window size is set by `utils.precompute()` and is equal to 8.
|
||
* Number of precomputed points depends on the curve size:
|
||
* 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
|
||
* - 𝑊 is the window size
|
||
* - 𝑛 is the bitlength of the curve order.
|
||
* For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
|
||
* @returns precomputed point tables flattened to a single array
|
||
*/
|
||
precomputeWindow(elm, W) {
|
||
const { windows, windowSize } = opts(W);
|
||
const points = [];
|
||
let p = elm;
|
||
let base = p;
|
||
for (let window = 0; window < windows; window++) {
|
||
base = p;
|
||
points.push(base);
|
||
for (let i = 1; i < windowSize; i++) {
|
||
base = base.add(p);
|
||
points.push(base);
|
||
}
|
||
p = base.double();
|
||
}
|
||
return points;
|
||
},
|
||
/**
|
||
* Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
|
||
* @param W window size
|
||
* @param precomputes precomputed tables
|
||
* @param n scalar (we don't check here, but should be less than curve order)
|
||
* @returns real and fake (for const-time) points
|
||
*/
|
||
wNAF(W, precomputes, n) {
|
||
const { windows, windowSize } = opts(W);
|
||
let p = c.ZERO;
|
||
let f2 = c.BASE;
|
||
const mask = BigInt(2 ** W - 1);
|
||
const maxNumber = 2 ** W;
|
||
const shiftBy = BigInt(W);
|
||
for (let window = 0; window < windows; window++) {
|
||
const offset = window * windowSize;
|
||
let wbits = Number(n & mask);
|
||
n >>= shiftBy;
|
||
if (wbits > windowSize) {
|
||
wbits -= maxNumber;
|
||
n += _1n3;
|
||
}
|
||
const offset1 = offset;
|
||
const offset2 = offset + Math.abs(wbits) - 1;
|
||
const cond1 = window % 2 !== 0;
|
||
const cond2 = wbits < 0;
|
||
if (wbits === 0) {
|
||
f2 = f2.add(constTimeNegate(cond1, precomputes[offset1]));
|
||
} else {
|
||
p = p.add(constTimeNegate(cond2, precomputes[offset2]));
|
||
}
|
||
}
|
||
return { p, f: f2 };
|
||
},
|
||
wNAFCached(P, precomputesMap, n, transform) {
|
||
const W = P._WINDOW_SIZE || 1;
|
||
let comp = precomputesMap.get(P);
|
||
if (!comp) {
|
||
comp = this.precomputeWindow(P, W);
|
||
if (W !== 1) {
|
||
precomputesMap.set(P, transform(comp));
|
||
}
|
||
}
|
||
return this.wNAF(W, comp, n);
|
||
}
|
||
};
|
||
}
|
||
function validateBasic(curve) {
|
||
validateField(curve.Fp);
|
||
validateObject(curve, {
|
||
n: "bigint",
|
||
h: "bigint",
|
||
Gx: "field",
|
||
Gy: "field"
|
||
}, {
|
||
nBitLength: "isSafeInteger",
|
||
nByteLength: "isSafeInteger"
|
||
});
|
||
return Object.freeze({
|
||
...nLength(curve.n, curve.nBitLength),
|
||
...curve,
|
||
...{ p: curve.Fp.ORDER }
|
||
});
|
||
}
|
||
|
||
// ../../node_modules/.pnpm/@noble+curves@1.3.0/node_modules/@noble/curves/esm/abstract/edwards.js
|
||
var _0n4 = BigInt(0);
|
||
var _1n4 = BigInt(1);
|
||
var _2n3 = BigInt(2);
|
||
var _8n2 = BigInt(8);
|
||
var VERIFY_DEFAULT = { zip215: true };
|
||
function validateOpts(curve) {
|
||
const opts = validateBasic(curve);
|
||
validateObject(curve, {
|
||
hash: "function",
|
||
a: "bigint",
|
||
d: "bigint",
|
||
randomBytes: "function"
|
||
}, {
|
||
adjustScalarBytes: "function",
|
||
domain: "function",
|
||
uvRatio: "function",
|
||
mapToCurve: "function"
|
||
});
|
||
return Object.freeze({ ...opts });
|
||
}
|
||
function twistedEdwards(curveDef) {
|
||
const CURVE = validateOpts(curveDef);
|
||
const { Fp: Fp2, n: CURVE_ORDER, prehash, hash: cHash, randomBytes: randomBytes2, nByteLength, h: cofactor } = CURVE;
|
||
const MASK = _2n3 << BigInt(nByteLength * 8) - _1n4;
|
||
const modP = Fp2.create;
|
||
const uvRatio2 = CURVE.uvRatio || ((u, v) => {
|
||
try {
|
||
return { isValid: true, value: Fp2.sqrt(u * Fp2.inv(v)) };
|
||
} catch (e) {
|
||
return { isValid: false, value: _0n4 };
|
||
}
|
||
});
|
||
const adjustScalarBytes2 = CURVE.adjustScalarBytes || ((bytes2) => bytes2);
|
||
const domain = CURVE.domain || ((data, ctx, phflag) => {
|
||
if (ctx.length || phflag)
|
||
throw new Error("Contexts/pre-hash are not supported");
|
||
return data;
|
||
});
|
||
const inBig = (n) => typeof n === "bigint" && _0n4 < n;
|
||
const inRange = (n, max) => inBig(n) && inBig(max) && n < max;
|
||
const in0MaskRange = (n) => n === _0n4 || inRange(n, MASK);
|
||
function assertInRange(n, max) {
|
||
if (inRange(n, max))
|
||
return n;
|
||
throw new Error(`Expected valid scalar < ${max}, got ${typeof n} ${n}`);
|
||
}
|
||
function assertGE0(n) {
|
||
return n === _0n4 ? n : assertInRange(n, CURVE_ORDER);
|
||
}
|
||
const pointPrecomputes = /* @__PURE__ */ new Map();
|
||
function isPoint(other) {
|
||
if (!(other instanceof Point))
|
||
throw new Error("ExtendedPoint expected");
|
||
}
|
||
class Point {
|
||
constructor(ex, ey, ez, et) {
|
||
this.ex = ex;
|
||
this.ey = ey;
|
||
this.ez = ez;
|
||
this.et = et;
|
||
if (!in0MaskRange(ex))
|
||
throw new Error("x required");
|
||
if (!in0MaskRange(ey))
|
||
throw new Error("y required");
|
||
if (!in0MaskRange(ez))
|
||
throw new Error("z required");
|
||
if (!in0MaskRange(et))
|
||
throw new Error("t required");
|
||
}
|
||
get x() {
|
||
return this.toAffine().x;
|
||
}
|
||
get y() {
|
||
return this.toAffine().y;
|
||
}
|
||
static fromAffine(p) {
|
||
if (p instanceof Point)
|
||
throw new Error("extended point not allowed");
|
||
const { x, y } = p || {};
|
||
if (!in0MaskRange(x) || !in0MaskRange(y))
|
||
throw new Error("invalid affine point");
|
||
return new Point(x, y, _1n4, modP(x * y));
|
||
}
|
||
static normalizeZ(points) {
|
||
const toInv = Fp2.invertBatch(points.map((p) => p.ez));
|
||
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
|
||
}
|
||
// "Private method", don't use it directly
|
||
_setWindowSize(windowSize) {
|
||
this._WINDOW_SIZE = windowSize;
|
||
pointPrecomputes.delete(this);
|
||
}
|
||
// Not required for fromHex(), which always creates valid points.
|
||
// Could be useful for fromAffine().
|
||
assertValidity() {
|
||
const { a, d } = CURVE;
|
||
if (this.is0())
|
||
throw new Error("bad point: ZERO");
|
||
const { ex: X, ey: Y, ez: Z, et: T } = this;
|
||
const X2 = modP(X * X);
|
||
const Y2 = modP(Y * Y);
|
||
const Z2 = modP(Z * Z);
|
||
const Z4 = modP(Z2 * Z2);
|
||
const aX2 = modP(X2 * a);
|
||
const left = modP(Z2 * modP(aX2 + Y2));
|
||
const right = modP(Z4 + modP(d * modP(X2 * Y2)));
|
||
if (left !== right)
|
||
throw new Error("bad point: equation left != right (1)");
|
||
const XY = modP(X * Y);
|
||
const ZT = modP(Z * T);
|
||
if (XY !== ZT)
|
||
throw new Error("bad point: equation left != right (2)");
|
||
}
|
||
// Compare one point to another.
|
||
equals(other) {
|
||
isPoint(other);
|
||
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
||
const { ex: X2, ey: Y2, ez: Z2 } = other;
|
||
const X1Z2 = modP(X1 * Z2);
|
||
const X2Z1 = modP(X2 * Z1);
|
||
const Y1Z2 = modP(Y1 * Z2);
|
||
const Y2Z1 = modP(Y2 * Z1);
|
||
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
||
}
|
||
is0() {
|
||
return this.equals(Point.ZERO);
|
||
}
|
||
negate() {
|
||
return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et));
|
||
}
|
||
// Fast algo for doubling Extended Point.
|
||
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
|
||
// Cost: 4M + 4S + 1*a + 6add + 1*2.
|
||
double() {
|
||
const { a } = CURVE;
|
||
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
||
const A = modP(X1 * X1);
|
||
const B = modP(Y1 * Y1);
|
||
const C = modP(_2n3 * modP(Z1 * Z1));
|
||
const D = modP(a * A);
|
||
const x1y1 = X1 + Y1;
|
||
const E = modP(modP(x1y1 * x1y1) - A - B);
|
||
const G2 = D + B;
|
||
const F = G2 - C;
|
||
const H = D - B;
|
||
const X3 = modP(E * F);
|
||
const Y3 = modP(G2 * H);
|
||
const T3 = modP(E * H);
|
||
const Z3 = modP(F * G2);
|
||
return new Point(X3, Y3, Z3, T3);
|
||
}
|
||
// Fast algo for adding 2 Extended Points.
|
||
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
|
||
// Cost: 9M + 1*a + 1*d + 7add.
|
||
add(other) {
|
||
isPoint(other);
|
||
const { a, d } = CURVE;
|
||
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
||
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other;
|
||
if (a === BigInt(-1)) {
|
||
const A2 = modP((Y1 - X1) * (Y2 + X2));
|
||
const B2 = modP((Y1 + X1) * (Y2 - X2));
|
||
const F2 = modP(B2 - A2);
|
||
if (F2 === _0n4)
|
||
return this.double();
|
||
const C2 = modP(Z1 * _2n3 * T2);
|
||
const D2 = modP(T1 * _2n3 * Z2);
|
||
const E2 = D2 + C2;
|
||
const G3 = B2 + A2;
|
||
const H2 = D2 - C2;
|
||
const X32 = modP(E2 * F2);
|
||
const Y32 = modP(G3 * H2);
|
||
const T32 = modP(E2 * H2);
|
||
const Z32 = modP(F2 * G3);
|
||
return new Point(X32, Y32, Z32, T32);
|
||
}
|
||
const A = modP(X1 * X2);
|
||
const B = modP(Y1 * Y2);
|
||
const C = modP(T1 * d * T2);
|
||
const D = modP(Z1 * Z2);
|
||
const E = modP((X1 + Y1) * (X2 + Y2) - A - B);
|
||
const F = D - C;
|
||
const G2 = D + C;
|
||
const H = modP(B - a * A);
|
||
const X3 = modP(E * F);
|
||
const Y3 = modP(G2 * H);
|
||
const T3 = modP(E * H);
|
||
const Z3 = modP(F * G2);
|
||
return new Point(X3, Y3, Z3, T3);
|
||
}
|
||
subtract(other) {
|
||
return this.add(other.negate());
|
||
}
|
||
wNAF(n) {
|
||
return wnaf.wNAFCached(this, pointPrecomputes, n, Point.normalizeZ);
|
||
}
|
||
// Constant-time multiplication.
|
||
multiply(scalar) {
|
||
const { p, f: f2 } = this.wNAF(assertInRange(scalar, CURVE_ORDER));
|
||
return Point.normalizeZ([p, f2])[0];
|
||
}
|
||
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
||
// It's faster, but should only be used when you don't care about
|
||
// an exposed private key e.g. sig verification.
|
||
// Does NOT allow scalars higher than CURVE.n.
|
||
multiplyUnsafe(scalar) {
|
||
let n = assertGE0(scalar);
|
||
if (n === _0n4)
|
||
return I;
|
||
if (this.equals(I) || n === _1n4)
|
||
return this;
|
||
if (this.equals(G))
|
||
return this.wNAF(n).p;
|
||
return wnaf.unsafeLadder(this, n);
|
||
}
|
||
// Checks if point is of small order.
|
||
// If you add something to small order point, you will have "dirty"
|
||
// point with torsion component.
|
||
// Multiplies point by cofactor and checks if the result is 0.
|
||
isSmallOrder() {
|
||
return this.multiplyUnsafe(cofactor).is0();
|
||
}
|
||
// Multiplies point by curve order and checks if the result is 0.
|
||
// Returns `false` is the point is dirty.
|
||
isTorsionFree() {
|
||
return wnaf.unsafeLadder(this, CURVE_ORDER).is0();
|
||
}
|
||
// Converts Extended point to default (x, y) coordinates.
|
||
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
||
toAffine(iz) {
|
||
const { ex: x, ey: y, ez: z } = this;
|
||
const is0 = this.is0();
|
||
if (iz == null)
|
||
iz = is0 ? _8n2 : Fp2.inv(z);
|
||
const ax = modP(x * iz);
|
||
const ay = modP(y * iz);
|
||
const zz = modP(z * iz);
|
||
if (is0)
|
||
return { x: _0n4, y: _1n4 };
|
||
if (zz !== _1n4)
|
||
throw new Error("invZ was invalid");
|
||
return { x: ax, y: ay };
|
||
}
|
||
clearCofactor() {
|
||
const { h: cofactor2 } = CURVE;
|
||
if (cofactor2 === _1n4)
|
||
return this;
|
||
return this.multiplyUnsafe(cofactor2);
|
||
}
|
||
// Converts hash string or Uint8Array to Point.
|
||
// Uses algo from RFC8032 5.1.3.
|
||
static fromHex(hex, zip215 = false) {
|
||
const { d, a } = CURVE;
|
||
const len = Fp2.BYTES;
|
||
hex = ensureBytes("pointHex", hex, len);
|
||
const normed = hex.slice();
|
||
const lastByte = hex[len - 1];
|
||
normed[len - 1] = lastByte & ~128;
|
||
const y = bytesToNumberLE(normed);
|
||
if (y === _0n4) {
|
||
} else {
|
||
if (zip215)
|
||
assertInRange(y, MASK);
|
||
else
|
||
assertInRange(y, Fp2.ORDER);
|
||
}
|
||
const y2 = modP(y * y);
|
||
const u = modP(y2 - _1n4);
|
||
const v = modP(d * y2 - a);
|
||
let { isValid, value: x } = uvRatio2(u, v);
|
||
if (!isValid)
|
||
throw new Error("Point.fromHex: invalid y coordinate");
|
||
const isXOdd = (x & _1n4) === _1n4;
|
||
const isLastByteOdd = (lastByte & 128) !== 0;
|
||
if (!zip215 && x === _0n4 && isLastByteOdd)
|
||
throw new Error("Point.fromHex: x=0 and x_0=1");
|
||
if (isLastByteOdd !== isXOdd)
|
||
x = modP(-x);
|
||
return Point.fromAffine({ x, y });
|
||
}
|
||
static fromPrivateKey(privKey) {
|
||
return getExtendedPublicKey(privKey).point;
|
||
}
|
||
toRawBytes() {
|
||
const { x, y } = this.toAffine();
|
||
const bytes2 = numberToBytesLE(y, Fp2.BYTES);
|
||
bytes2[bytes2.length - 1] |= x & _1n4 ? 128 : 0;
|
||
return bytes2;
|
||
}
|
||
toHex() {
|
||
return bytesToHex2(this.toRawBytes());
|
||
}
|
||
}
|
||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, _1n4, modP(CURVE.Gx * CURVE.Gy));
|
||
Point.ZERO = new Point(_0n4, _1n4, _1n4, _0n4);
|
||
const { BASE: G, ZERO: I } = Point;
|
||
const wnaf = wNAF(Point, nByteLength * 8);
|
||
function modN(a) {
|
||
return mod(a, CURVE_ORDER);
|
||
}
|
||
function modN_LE(hash2) {
|
||
return modN(bytesToNumberLE(hash2));
|
||
}
|
||
function getExtendedPublicKey(key) {
|
||
const len = nByteLength;
|
||
key = ensureBytes("private key", key, len);
|
||
const hashed = ensureBytes("hashed private key", cHash(key), 2 * len);
|
||
const head = adjustScalarBytes2(hashed.slice(0, len));
|
||
const prefix = hashed.slice(len, 2 * len);
|
||
const scalar = modN_LE(head);
|
||
const point = G.multiply(scalar);
|
||
const pointBytes = point.toRawBytes();
|
||
return { head, prefix, scalar, point, pointBytes };
|
||
}
|
||
function getPublicKey(privKey) {
|
||
return getExtendedPublicKey(privKey).pointBytes;
|
||
}
|
||
function hashDomainToScalar(context = new Uint8Array(), ...msgs) {
|
||
const msg = concatBytes2(...msgs);
|
||
return modN_LE(cHash(domain(msg, ensureBytes("context", context), !!prehash)));
|
||
}
|
||
function sign(msg, privKey, options = {}) {
|
||
msg = ensureBytes("message", msg);
|
||
if (prehash)
|
||
msg = prehash(msg);
|
||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
|
||
const r = hashDomainToScalar(options.context, prefix, msg);
|
||
const R = G.multiply(r).toRawBytes();
|
||
const k = hashDomainToScalar(options.context, R, pointBytes, msg);
|
||
const s = modN(r + k * scalar);
|
||
assertGE0(s);
|
||
const res = concatBytes2(R, numberToBytesLE(s, Fp2.BYTES));
|
||
return ensureBytes("result", res, nByteLength * 2);
|
||
}
|
||
const verifyOpts = VERIFY_DEFAULT;
|
||
function verify(sig, msg, publicKey, options = verifyOpts) {
|
||
const { context, zip215 } = options;
|
||
const len = Fp2.BYTES;
|
||
sig = ensureBytes("signature", sig, 2 * len);
|
||
msg = ensureBytes("message", msg);
|
||
if (prehash)
|
||
msg = prehash(msg);
|
||
const s = bytesToNumberLE(sig.slice(len, 2 * len));
|
||
let A, R, SB;
|
||
try {
|
||
A = Point.fromHex(publicKey, zip215);
|
||
R = Point.fromHex(sig.slice(0, len), zip215);
|
||
SB = G.multiplyUnsafe(s);
|
||
} catch (error) {
|
||
return false;
|
||
}
|
||
if (!zip215 && A.isSmallOrder())
|
||
return false;
|
||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
|
||
const RkA = R.add(A.multiplyUnsafe(k));
|
||
return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
|
||
}
|
||
G._setWindowSize(8);
|
||
const utils = {
|
||
getExtendedPublicKey,
|
||
// ed25519 private keys are uniform 32b. No need to check for modulo bias, like in secp256k1.
|
||
randomPrivateKey: () => randomBytes2(Fp2.BYTES),
|
||
/**
|
||
* We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT
|
||
* values. This slows down first getPublicKey() by milliseconds (see Speed section),
|
||
* but allows to speed-up subsequent getPublicKey() calls up to 20x.
|
||
* @param windowSize 2, 4, 8, 16
|
||
*/
|
||
precompute(windowSize = 8, point = Point.BASE) {
|
||
point._setWindowSize(windowSize);
|
||
point.multiply(BigInt(3));
|
||
return point;
|
||
}
|
||
};
|
||
return {
|
||
CURVE,
|
||
getPublicKey,
|
||
sign,
|
||
verify,
|
||
ExtendedPoint: Point,
|
||
utils
|
||
};
|
||
}
|
||
|
||
// ../../node_modules/.pnpm/@noble+curves@1.3.0/node_modules/@noble/curves/esm/ed25519.js
|
||
var ED25519_P = BigInt("57896044618658097711785492504343953926634992332820282019728792003956564819949");
|
||
var ED25519_SQRT_M1 = BigInt("19681161376707505956807079304988542015446066515923890162744021073123829784752");
|
||
var _0n5 = BigInt(0);
|
||
var _1n5 = BigInt(1);
|
||
var _2n4 = BigInt(2);
|
||
var _5n2 = BigInt(5);
|
||
var _10n = BigInt(10);
|
||
var _20n = BigInt(20);
|
||
var _40n = BigInt(40);
|
||
var _80n = BigInt(80);
|
||
function ed25519_pow_2_252_3(x) {
|
||
const P = ED25519_P;
|
||
const x2 = x * x % P;
|
||
const b2 = x2 * x % P;
|
||
const b4 = pow2(b2, _2n4, P) * b2 % P;
|
||
const b5 = pow2(b4, _1n5, P) * x % P;
|
||
const b10 = pow2(b5, _5n2, P) * b5 % P;
|
||
const b20 = pow2(b10, _10n, P) * b10 % P;
|
||
const b40 = pow2(b20, _20n, P) * b20 % P;
|
||
const b80 = pow2(b40, _40n, P) * b40 % P;
|
||
const b160 = pow2(b80, _80n, P) * b80 % P;
|
||
const b240 = pow2(b160, _80n, P) * b80 % P;
|
||
const b250 = pow2(b240, _10n, P) * b10 % P;
|
||
const pow_p_5_8 = pow2(b250, _2n4, P) * x % P;
|
||
return { pow_p_5_8, b2 };
|
||
}
|
||
function adjustScalarBytes(bytes2) {
|
||
bytes2[0] &= 248;
|
||
bytes2[31] &= 127;
|
||
bytes2[31] |= 64;
|
||
return bytes2;
|
||
}
|
||
function uvRatio(u, v) {
|
||
const P = ED25519_P;
|
||
const v3 = mod(v * v * v, P);
|
||
const v7 = mod(v3 * v3 * v, P);
|
||
const pow3 = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
|
||
let x = mod(u * v3 * pow3, P);
|
||
const vx2 = mod(v * x * x, P);
|
||
const root1 = x;
|
||
const root2 = mod(x * ED25519_SQRT_M1, P);
|
||
const useRoot1 = vx2 === u;
|
||
const useRoot2 = vx2 === mod(-u, P);
|
||
const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P);
|
||
if (useRoot1)
|
||
x = root1;
|
||
if (useRoot2 || noRoot)
|
||
x = root2;
|
||
if (isNegativeLE(x, P))
|
||
x = mod(-x, P);
|
||
return { isValid: useRoot1 || useRoot2, value: x };
|
||
}
|
||
var Fp = Field(ED25519_P, void 0, true);
|
||
var ed25519Defaults = {
|
||
// Param: a
|
||
a: BigInt(-1),
|
||
// Fp.create(-1) is proper; our way still works and is faster
|
||
// d is equal to -121665/121666 over finite field.
|
||
// Negative number is P - number, and division is invert(number, P)
|
||
d: BigInt("37095705934669439343138083508754565189542113879843219016388785533085940283555"),
|
||
// Finite field 𝔽p over which we'll do calculations; 2n**255n - 19n
|
||
Fp,
|
||
// Subgroup order: how many points curve has
|
||
// 2n**252n + 27742317777372353535851937790883648493n;
|
||
n: BigInt("7237005577332262213973186563042994240857116359379907606001950938285454250989"),
|
||
// Cofactor
|
||
h: BigInt(8),
|
||
// Base point (x, y) aka generator point
|
||
Gx: BigInt("15112221349535400772501151409588531511454012693041857206046113283949847762202"),
|
||
Gy: BigInt("46316835694926478169428394003475163141307993866256225615783033603165251855960"),
|
||
hash: sha512,
|
||
randomBytes,
|
||
adjustScalarBytes,
|
||
// dom2
|
||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||
// Constant-time, u/√v
|
||
uvRatio
|
||
};
|
||
var ed25519 = /* @__PURE__ */ twistedEdwards(ed25519Defaults);
|
||
function ed25519_domain(data, ctx, phflag) {
|
||
if (ctx.length > 255)
|
||
throw new Error("Context is too big");
|
||
return concatBytes(utf8ToBytes("SigEd25519 no Ed25519 collisions"), new Uint8Array([phflag ? 1 : 0, ctx.length]), ctx, data);
|
||
}
|
||
var ed25519ctx = /* @__PURE__ */ twistedEdwards({
|
||
...ed25519Defaults,
|
||
domain: ed25519_domain
|
||
});
|
||
var ed25519ph = /* @__PURE__ */ twistedEdwards({
|
||
...ed25519Defaults,
|
||
domain: ed25519_domain,
|
||
prehash: sha512
|
||
});
|
||
var ELL2_C1 = (Fp.ORDER + BigInt(3)) / BigInt(8);
|
||
var ELL2_C2 = Fp.pow(_2n4, ELL2_C1);
|
||
var ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE));
|
||
var ELL2_C4 = (Fp.ORDER - BigInt(5)) / BigInt(8);
|
||
var ELL2_J = BigInt(486662);
|
||
var ELL2_C1_EDWARDS = FpSqrtEven(Fp, Fp.neg(BigInt(486664)));
|
||
var SQRT_AD_MINUS_ONE = BigInt("25063068953384623474111414158702152701244531502492656460079210482610430750235");
|
||
var INVSQRT_A_MINUS_D = BigInt("54469307008909316920995813868745141605393597292927456921205312896311721017578");
|
||
var ONE_MINUS_D_SQ = BigInt("1159843021668779879193775521855586647937357759715417654439879720876111806838");
|
||
var D_MINUS_ONE_SQ = BigInt("40440834346308536858101042469323190826248399146238708352240133220865137265952");
|
||
var MAX_255B = BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/hmac.js
|
||
var HMAC = class extends Hash {
|
||
constructor(hash2, _key) {
|
||
super();
|
||
this.finished = false;
|
||
this.destroyed = false;
|
||
hash(hash2);
|
||
const key = toBytes(_key);
|
||
this.iHash = hash2.create();
|
||
if (typeof this.iHash.update !== "function")
|
||
throw new Error("Expected instance of class which extends utils.Hash");
|
||
this.blockLen = this.iHash.blockLen;
|
||
this.outputLen = this.iHash.outputLen;
|
||
const blockLen = this.blockLen;
|
||
const pad = new Uint8Array(blockLen);
|
||
pad.set(key.length > blockLen ? hash2.create().update(key).digest() : key);
|
||
for (let i = 0; i < pad.length; i++)
|
||
pad[i] ^= 54;
|
||
this.iHash.update(pad);
|
||
this.oHash = hash2.create();
|
||
for (let i = 0; i < pad.length; i++)
|
||
pad[i] ^= 54 ^ 92;
|
||
this.oHash.update(pad);
|
||
pad.fill(0);
|
||
}
|
||
update(buf) {
|
||
exists(this);
|
||
this.iHash.update(buf);
|
||
return this;
|
||
}
|
||
digestInto(out) {
|
||
exists(this);
|
||
bytes(out, this.outputLen);
|
||
this.finished = true;
|
||
this.iHash.digestInto(out);
|
||
this.oHash.update(out);
|
||
this.oHash.digestInto(out);
|
||
this.destroy();
|
||
}
|
||
digest() {
|
||
const out = new Uint8Array(this.oHash.outputLen);
|
||
this.digestInto(out);
|
||
return out;
|
||
}
|
||
_cloneInto(to) {
|
||
to || (to = Object.create(Object.getPrototypeOf(this), {}));
|
||
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
|
||
to = to;
|
||
to.finished = finished;
|
||
to.destroyed = destroyed;
|
||
to.blockLen = blockLen;
|
||
to.outputLen = outputLen;
|
||
to.oHash = oHash._cloneInto(to.oHash);
|
||
to.iHash = iHash._cloneInto(to.iHash);
|
||
return to;
|
||
}
|
||
destroy() {
|
||
this.destroyed = true;
|
||
this.oHash.destroy();
|
||
this.iHash.destroy();
|
||
}
|
||
};
|
||
var hmac = (hash2, key, message) => new HMAC(hash2, key).update(message).digest();
|
||
hmac.create = (hash2, key) => new HMAC(hash2, key);
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/ripemd160.js
|
||
var Rho = /* @__PURE__ */ new Uint8Array([7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8]);
|
||
var Id = /* @__PURE__ */ Uint8Array.from({ length: 16 }, (_, i) => i);
|
||
var Pi = /* @__PURE__ */ Id.map((i) => (9 * i + 5) % 16);
|
||
var idxL = [Id];
|
||
var idxR = [Pi];
|
||
for (let i = 0; i < 4; i++)
|
||
for (let j of [idxL, idxR])
|
||
j.push(j[i].map((k) => Rho[k]));
|
||
var shifts = /* @__PURE__ */ [
|
||
[11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8],
|
||
[12, 13, 11, 15, 6, 9, 9, 7, 12, 15, 11, 13, 7, 8, 7, 7],
|
||
[13, 15, 14, 11, 7, 7, 6, 8, 13, 14, 13, 12, 5, 5, 6, 9],
|
||
[14, 11, 12, 14, 8, 6, 5, 5, 15, 12, 15, 14, 9, 9, 8, 6],
|
||
[15, 12, 13, 13, 9, 5, 8, 6, 14, 11, 12, 11, 8, 6, 5, 5]
|
||
].map((i) => new Uint8Array(i));
|
||
var shiftsL = /* @__PURE__ */ idxL.map((idx, i) => idx.map((j) => shifts[i][j]));
|
||
var shiftsR = /* @__PURE__ */ idxR.map((idx, i) => idx.map((j) => shifts[i][j]));
|
||
var Kl = /* @__PURE__ */ new Uint32Array([
|
||
0,
|
||
1518500249,
|
||
1859775393,
|
||
2400959708,
|
||
2840853838
|
||
]);
|
||
var Kr = /* @__PURE__ */ new Uint32Array([
|
||
1352829926,
|
||
1548603684,
|
||
1836072691,
|
||
2053994217,
|
||
0
|
||
]);
|
||
var rotl = (word, shift) => word << shift | word >>> 32 - shift;
|
||
function f(group, x, y, z) {
|
||
if (group === 0)
|
||
return x ^ y ^ z;
|
||
else if (group === 1)
|
||
return x & y | ~x & z;
|
||
else if (group === 2)
|
||
return (x | ~y) ^ z;
|
||
else if (group === 3)
|
||
return x & z | y & ~z;
|
||
else
|
||
return x ^ (y | ~z);
|
||
}
|
||
var BUF = /* @__PURE__ */ new Uint32Array(16);
|
||
var RIPEMD160 = class extends SHA2 {
|
||
constructor() {
|
||
super(64, 20, 8, true);
|
||
this.h0 = 1732584193 | 0;
|
||
this.h1 = 4023233417 | 0;
|
||
this.h2 = 2562383102 | 0;
|
||
this.h3 = 271733878 | 0;
|
||
this.h4 = 3285377520 | 0;
|
||
}
|
||
get() {
|
||
const { h0, h1, h2, h3, h4 } = this;
|
||
return [h0, h1, h2, h3, h4];
|
||
}
|
||
set(h0, h1, h2, h3, h4) {
|
||
this.h0 = h0 | 0;
|
||
this.h1 = h1 | 0;
|
||
this.h2 = h2 | 0;
|
||
this.h3 = h3 | 0;
|
||
this.h4 = h4 | 0;
|
||
}
|
||
process(view, offset) {
|
||
for (let i = 0; i < 16; i++, offset += 4)
|
||
BUF[i] = view.getUint32(offset, true);
|
||
let al = this.h0 | 0, ar = al, bl = this.h1 | 0, br = bl, cl = this.h2 | 0, cr = cl, dl = this.h3 | 0, dr = dl, el = this.h4 | 0, er = el;
|
||
for (let group = 0; group < 5; group++) {
|
||
const rGroup = 4 - group;
|
||
const hbl = Kl[group], hbr = Kr[group];
|
||
const rl = idxL[group], rr = idxR[group];
|
||
const sl = shiftsL[group], sr = shiftsR[group];
|
||
for (let i = 0; i < 16; i++) {
|
||
const tl = rotl(al + f(group, bl, cl, dl) + BUF[rl[i]] + hbl, sl[i]) + el | 0;
|
||
al = el, el = dl, dl = rotl(cl, 10) | 0, cl = bl, bl = tl;
|
||
}
|
||
for (let i = 0; i < 16; i++) {
|
||
const tr = rotl(ar + f(rGroup, br, cr, dr) + BUF[rr[i]] + hbr, sr[i]) + er | 0;
|
||
ar = er, er = dr, dr = rotl(cr, 10) | 0, cr = br, br = tr;
|
||
}
|
||
}
|
||
this.set(this.h1 + cl + dr | 0, this.h2 + dl + er | 0, this.h3 + el + ar | 0, this.h4 + al + br | 0, this.h0 + bl + cr | 0);
|
||
}
|
||
roundClean() {
|
||
BUF.fill(0);
|
||
}
|
||
destroy() {
|
||
this.destroyed = true;
|
||
this.buffer.fill(0);
|
||
this.set(0, 0, 0, 0, 0);
|
||
}
|
||
};
|
||
var ripemd160 = /* @__PURE__ */ wrapConstructor(() => new RIPEMD160());
|
||
|
||
// ../../node_modules/.pnpm/@noble+hashes@1.3.3/node_modules/@noble/hashes/esm/sha256.js
|
||
var Chi = (a, b, c) => a & b ^ ~a & c;
|
||
var Maj = (a, b, c) => a & b ^ a & c ^ b & c;
|
||
var SHA256_K = /* @__PURE__ */ new Uint32Array([
|
||
1116352408,
|
||
1899447441,
|
||
3049323471,
|
||
3921009573,
|
||
961987163,
|
||
1508970993,
|
||
2453635748,
|
||
2870763221,
|
||
3624381080,
|
||
310598401,
|
||
607225278,
|
||
1426881987,
|
||
1925078388,
|
||
2162078206,
|
||
2614888103,
|
||
3248222580,
|
||
3835390401,
|
||
4022224774,
|
||
264347078,
|
||
604807628,
|
||
770255983,
|
||
1249150122,
|
||
1555081692,
|
||
1996064986,
|
||
2554220882,
|
||
2821834349,
|
||
2952996808,
|
||
3210313671,
|
||
3336571891,
|
||
3584528711,
|
||
113926993,
|
||
338241895,
|
||
666307205,
|
||
773529912,
|
||
1294757372,
|
||
1396182291,
|
||
1695183700,
|
||
1986661051,
|
||
2177026350,
|
||
2456956037,
|
||
2730485921,
|
||
2820302411,
|
||
3259730800,
|
||
3345764771,
|
||
3516065817,
|
||
3600352804,
|
||
4094571909,
|
||
275423344,
|
||
430227734,
|
||
506948616,
|
||
659060556,
|
||
883997877,
|
||
958139571,
|
||
1322822218,
|
||
1537002063,
|
||
1747873779,
|
||
1955562222,
|
||
2024104815,
|
||
2227730452,
|
||
2361852424,
|
||
2428436474,
|
||
2756734187,
|
||
3204031479,
|
||
3329325298
|
||
]);
|
||
var IV = /* @__PURE__ */ new Uint32Array([
|
||
1779033703,
|
||
3144134277,
|
||
1013904242,
|
||
2773480762,
|
||
1359893119,
|
||
2600822924,
|
||
528734635,
|
||
1541459225
|
||
]);
|
||
var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
||
var SHA256 = class extends SHA2 {
|
||
constructor() {
|
||
super(64, 32, 8, false);
|
||
this.A = IV[0] | 0;
|
||
this.B = IV[1] | 0;
|
||
this.C = IV[2] | 0;
|
||
this.D = IV[3] | 0;
|
||
this.E = IV[4] | 0;
|
||
this.F = IV[5] | 0;
|
||
this.G = IV[6] | 0;
|
||
this.H = IV[7] | 0;
|
||
}
|
||
get() {
|
||
const { A, B, C, D, E, F, G, H } = this;
|
||
return [A, B, C, D, E, F, G, H];
|
||
}
|
||
// prettier-ignore
|
||
set(A, B, C, D, E, F, G, H) {
|
||
this.A = A | 0;
|
||
this.B = B | 0;
|
||
this.C = C | 0;
|
||
this.D = D | 0;
|
||
this.E = E | 0;
|
||
this.F = F | 0;
|
||
this.G = G | 0;
|
||
this.H = H | 0;
|
||
}
|
||
process(view, offset) {
|
||
for (let i = 0; i < 16; i++, offset += 4)
|
||
SHA256_W[i] = view.getUint32(offset, false);
|
||
for (let i = 16; i < 64; i++) {
|
||
const W15 = SHA256_W[i - 15];
|
||
const W2 = SHA256_W[i - 2];
|
||
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
||
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
|
||
SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
||
}
|
||
let { A, B, C, D, E, F, G, H } = this;
|
||
for (let i = 0; i < 64; i++) {
|
||
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
||
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
||
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
|
||
const T2 = sigma0 + Maj(A, B, C) | 0;
|
||
H = G;
|
||
G = F;
|
||
F = E;
|
||
E = D + T1 | 0;
|
||
D = C;
|
||
C = B;
|
||
B = A;
|
||
A = T1 + T2 | 0;
|
||
}
|
||
A = A + this.A | 0;
|
||
B = B + this.B | 0;
|
||
C = C + this.C | 0;
|
||
D = D + this.D | 0;
|
||
E = E + this.E | 0;
|
||
F = F + this.F | 0;
|
||
G = G + this.G | 0;
|
||
H = H + this.H | 0;
|
||
this.set(A, B, C, D, E, F, G, H);
|
||
}
|
||
roundClean() {
|
||
SHA256_W.fill(0);
|
||
}
|
||
destroy() {
|
||
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
||
this.buffer.fill(0);
|
||
}
|
||
};
|
||
var sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
|
||
|
||
// ../../node_modules/.pnpm/ed25519-keygen@0.4.11/node_modules/ed25519-keygen/hdkey.js
|
||
var MASTER_SECRET = utf8ToBytes("ed25519 seed");
|
||
var HARDENED_OFFSET = 2147483648;
|
||
var ZERO = new Uint8Array([0]);
|
||
function ensureBytes2(b, ...lengths) {
|
||
if (typeof b === "string")
|
||
b = hexToBytes(b);
|
||
bytes(b, ...lengths);
|
||
return b;
|
||
}
|
||
var hash160 = (data) => ripemd160(sha256(data));
|
||
var fromU32 = (data) => createView(data).getUint32(0, false);
|
||
var toU32 = (n) => {
|
||
if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1) {
|
||
throw new Error(`Invalid number=${n}. Should be from 0 to 2 ** 32 - 1`);
|
||
}
|
||
const buf = new Uint8Array(4);
|
||
createView(buf).setUint32(0, n, false);
|
||
return buf;
|
||
};
|
||
var HDKey = class _HDKey {
|
||
get publicKeyRaw() {
|
||
return ed25519.getPublicKey(this.privateKey);
|
||
}
|
||
get publicKey() {
|
||
return concatBytes(ZERO, this.publicKeyRaw);
|
||
}
|
||
get pubHash() {
|
||
return hash160(this.publicKey);
|
||
}
|
||
get fingerprint() {
|
||
return fromU32(this.pubHash);
|
||
}
|
||
get fingerprintHex() {
|
||
return bytesToHex(toU32(this.fingerprint));
|
||
}
|
||
get parentFingerprintHex() {
|
||
return bytesToHex(toU32(this.parentFingerprint));
|
||
}
|
||
static fromMasterSeed(seed) {
|
||
seed = ensureBytes2(seed);
|
||
if (8 * seed.length < 128 || 8 * seed.length > 512) {
|
||
throw new Error(`HDKey: wrong seed length=${seed.length}. Should be between 128 and 512 bits; 256 bits is advised)`);
|
||
}
|
||
const I = hmac(sha512, MASTER_SECRET, seed);
|
||
return new _HDKey({
|
||
privateKey: I.slice(0, 32),
|
||
chainCode: I.slice(32)
|
||
});
|
||
}
|
||
constructor(opt) {
|
||
this.depth = 0;
|
||
this.index = 0;
|
||
this.parentFingerprint = 0;
|
||
if (!opt || typeof opt !== "object")
|
||
throw new Error("HDKey.constructor must not be called directly");
|
||
bytes(opt.privateKey, 32);
|
||
bytes(opt.chainCode, 32);
|
||
this.depth = opt.depth || 0;
|
||
this.index = opt.index || 0;
|
||
this.parentFingerprint = opt.parentFingerprint || 0;
|
||
if (!this.depth) {
|
||
if (this.parentFingerprint || this.index)
|
||
throw new Error("HDKey: zero depth with non-zero index/parent fingerprint");
|
||
}
|
||
this.chainCode = opt.chainCode;
|
||
this.privateKey = opt.privateKey;
|
||
}
|
||
derive(path, forceHardened = false) {
|
||
if (!/^[mM]'?/.test(path))
|
||
throw new Error('Path must start with "m" or "M"');
|
||
if (/^[mM]'?$/.test(path))
|
||
return this;
|
||
const parts = path.replace(/^[mM]'?\//, "").split("/");
|
||
let child = this;
|
||
for (const c of parts) {
|
||
const m = /^(\d+)('?)$/.exec(c);
|
||
if (!m || m.length !== 3)
|
||
throw new Error(`Invalid child index: ${c}`);
|
||
let idx = +m[1];
|
||
if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET)
|
||
throw new Error("Invalid index");
|
||
if (forceHardened || m[2] === "'")
|
||
idx += HARDENED_OFFSET;
|
||
child = child.deriveChild(idx);
|
||
}
|
||
return child;
|
||
}
|
||
deriveChild(index) {
|
||
if (index < HARDENED_OFFSET)
|
||
throw new Error(`Non-hardened child derivation not possible for Ed25519 (index=${index})`);
|
||
const data = concatBytes(ZERO, this.privateKey, toU32(index));
|
||
const I = hmac(sha512, this.chainCode, data);
|
||
return new _HDKey({
|
||
chainCode: I.slice(32),
|
||
depth: this.depth + 1,
|
||
parentFingerprint: this.fingerprint,
|
||
index,
|
||
privateKey: I.slice(0, 32)
|
||
});
|
||
}
|
||
sign(message) {
|
||
return ed25519.sign(message, this.privateKey);
|
||
}
|
||
verify(message, signature) {
|
||
signature = ensureBytes2(signature, 64);
|
||
return ed25519.verify(signature, message, this.publicKeyRaw);
|
||
}
|
||
};
|
||
|
||
// src/hd-identity-vault.ts
|
||
var import_dids4 = require("@web5/dids");
|
||
var import_common10 = require("@web5/common");
|
||
var import_english = require("@scure/bip39/wordlists/english");
|
||
var import_bip39 = require("@scure/bip39");
|
||
|
||
// src/local-key-manager.ts
|
||
var import_crypto13 = require("@web5/crypto");
|
||
|
||
// src/store-key.ts
|
||
var import_crypto12 = require("@web5/crypto");
|
||
var import_common7 = require("@web5/common");
|
||
var DwnKeyStore = class extends DwnDataStore {
|
||
constructor() {
|
||
super(...arguments);
|
||
this.name = "DwnKeyStore";
|
||
/**
|
||
* Properties to use when writing and querying Private Key records with the DWN store.
|
||
*/
|
||
this._recordProperties = {
|
||
dataFormat: "application/json",
|
||
schema: "https://identity.foundation/schemas/web5/private-jwk"
|
||
};
|
||
}
|
||
async delete(params) {
|
||
return await super.delete(params);
|
||
}
|
||
async get(params) {
|
||
return await super.get(params);
|
||
}
|
||
async set(params) {
|
||
await super.set(params);
|
||
}
|
||
async list(params) {
|
||
return await super.list(params);
|
||
}
|
||
async getAllRecords({ agent, tenantDid }) {
|
||
this._index.clear();
|
||
const { reply: queryReply } = await agent.dwn.processRequest({
|
||
author: tenantDid,
|
||
target: tenantDid,
|
||
messageType: DwnInterface.RecordsQuery,
|
||
messageParams: { filter: { ...this._recordProperties } }
|
||
});
|
||
let storedKeys = [];
|
||
for (const record of queryReply.entries ?? []) {
|
||
if (!record.encodedData) {
|
||
throw new Error(`${this.name}: Expected 'encodedData' to be present in the DWN query result entry`);
|
||
}
|
||
const storedKey = import_common7.Convert.base64Url(record.encodedData).toObject();
|
||
if ((0, import_crypto12.isPrivateJwk)(storedKey)) {
|
||
const indexKey = `${tenantDid}${TENANT_SEPARATOR}${import_crypto12.KEY_URI_PREFIX_JWK}${storedKey.kid}`;
|
||
this._index.set(indexKey, record.recordId);
|
||
this._cache.set(record.recordId, storedKey);
|
||
storedKeys.push(storedKey);
|
||
}
|
||
}
|
||
return storedKeys;
|
||
}
|
||
};
|
||
var InMemoryKeyStore = class extends InMemoryDataStore {
|
||
constructor() {
|
||
super(...arguments);
|
||
this.name = "InMemoryKeyStore";
|
||
}
|
||
async delete(params) {
|
||
return await super.delete(params);
|
||
}
|
||
async get(params) {
|
||
return await super.get(params);
|
||
}
|
||
async list(params) {
|
||
return await super.list(params);
|
||
}
|
||
async set(params) {
|
||
return await super.set(params);
|
||
}
|
||
};
|
||
|
||
// src/local-key-manager.ts
|
||
var supportedAlgorithms2 = {
|
||
"AES-GCM": {
|
||
implementation: import_crypto13.AesGcmAlgorithm,
|
||
names: ["A128GCM", "A192GCM", "A256GCM"]
|
||
},
|
||
"AES-KW": {
|
||
implementation: AesKwAlgorithm,
|
||
names: ["A128KW", "A192KW", "A256KW"]
|
||
},
|
||
"Ed25519": {
|
||
implementation: import_crypto13.EdDsaAlgorithm,
|
||
names: ["Ed25519"]
|
||
},
|
||
"secp256k1": {
|
||
implementation: import_crypto13.EcdsaAlgorithm,
|
||
names: ["ES256K", "secp256k1"]
|
||
},
|
||
"secp256r1": {
|
||
implementation: import_crypto13.EcdsaAlgorithm,
|
||
names: ["ES256", "secp256r1"]
|
||
},
|
||
"SHA-256": {
|
||
implementation: import_crypto13.Sha2Algorithm,
|
||
names: ["SHA-256"]
|
||
}
|
||
};
|
||
var LocalKeyManager2 = class {
|
||
constructor({ agent, keyStore } = {}) {
|
||
/**
|
||
* A private map that stores instances of cryptographic algorithm implementations. Each key in
|
||
* this map is an `AlgorithmConstructor`, and its corresponding value is an instance of a class
|
||
* that implements a specific cryptographic algorithm. This map is used to cache and reuse
|
||
* instances for performance optimization, ensuring that each algorithm is instantiated only once.
|
||
*/
|
||
this._algorithmInstances = /* @__PURE__ */ new Map();
|
||
this._agent = agent;
|
||
this._keyStore = keyStore ?? new InMemoryKeyStore();
|
||
}
|
||
/**
|
||
* Retrieves the `Web5PlatformAgent` execution context.
|
||
*
|
||
* @returns The `Web5PlatformAgent` instance that represents the current execution context.
|
||
* @throws Will throw an error if the `agent` instance property is undefined.
|
||
*/
|
||
get agent() {
|
||
if (this._agent === void 0) {
|
||
throw new Error("LocalKeyManager: Unable to determine agent execution context.");
|
||
}
|
||
return this._agent;
|
||
}
|
||
set agent(agent) {
|
||
this._agent = agent;
|
||
}
|
||
async decrypt({ keyUri, ...params }) {
|
||
const privateKey = await this.getPrivateKey({ keyUri });
|
||
const algorithm = this.getAlgorithmName({ key: privateKey });
|
||
const cipher = this.getAlgorithm({ algorithm });
|
||
const ciphertext = await cipher.decrypt({ key: privateKey, ...params });
|
||
return ciphertext;
|
||
}
|
||
digest(_params) {
|
||
throw new Error("Method not implemented.");
|
||
}
|
||
async encrypt({ keyUri, ...params }) {
|
||
const privateKey = await this.getPrivateKey({ keyUri });
|
||
const algorithm = this.getAlgorithmName({ key: privateKey });
|
||
const cipher = this.getAlgorithm({ algorithm });
|
||
const ciphertext = await cipher.encrypt({ key: privateKey, ...params });
|
||
return ciphertext;
|
||
}
|
||
/**
|
||
* Exports a private key identified by the provided key URI from the local KMS.
|
||
*
|
||
* @remarks
|
||
* This method retrieves the key from the key store and returns it. It is primarily used
|
||
* for extracting keys for backup or transfer purposes.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const keyManager = new LocalKeyManager();
|
||
* const keyUri = await keyManager.generateKey({ algorithm: 'Ed25519' });
|
||
* const privateKey = await keyManager.exportKey({ keyUri });
|
||
* ```
|
||
*
|
||
* @param params - Parameters for exporting the key.
|
||
* @param params.keyUri - The key URI identifying the key to export.
|
||
*
|
||
* @returns A Promise resolving to the JWK representation of the exported key.
|
||
*/
|
||
async exportKey({ keyUri }) {
|
||
const privateKey = await this.getPrivateKey({ keyUri });
|
||
return privateKey;
|
||
}
|
||
/**
|
||
* Generates a new cryptographic key in the local KMS with the specified algorithm and returns a
|
||
* unique key URI which can be used to reference the key in subsequent operations.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const keyManager = new LocalKeyManager();
|
||
* const keyUri = await keyManager.generateKey({ algorithm: 'Ed25519' });
|
||
* console.log(keyUri); // Outputs the key URI
|
||
* ```
|
||
*
|
||
* @param params - The parameters for key generation.
|
||
* @param params.algorithm - The algorithm to use for key generation, defined in `SupportedAlgorithm`.
|
||
*
|
||
* @returns A Promise that resolves to the key URI, a unique identifier for the generated key.
|
||
*/
|
||
async generateKey({ algorithm: algorithmIdentifier }) {
|
||
const algorithm = this.getAlgorithmName({ key: { alg: algorithmIdentifier } });
|
||
const keyGenerator = this.getAlgorithm({ algorithm });
|
||
const privateKey = await keyGenerator.generateKey({ algorithm: algorithmIdentifier });
|
||
privateKey.kid ??= await (0, import_crypto13.computeJwkThumbprint)({ jwk: privateKey });
|
||
const keyUri = await this.getKeyUri({ key: privateKey });
|
||
await this._keyStore.set({
|
||
id: keyUri,
|
||
data: privateKey,
|
||
agent: this.agent,
|
||
preventDuplicates: false,
|
||
useCache: true
|
||
});
|
||
return keyUri;
|
||
}
|
||
/**
|
||
* Computes the Key URI for a given public JWK (JSON Web Key).
|
||
*
|
||
* @remarks
|
||
* This method generates a {@link https://datatracker.ietf.org/doc/html/rfc3986 | URI}
|
||
* (Uniform Resource Identifier) for the given JWK, which uniquely identifies the key across all
|
||
* `CryptoApi` implementations. The key URI is constructed by appending the
|
||
* {@link https://datatracker.ietf.org/doc/html/rfc7638 | JWK thumbprint} to the prefix
|
||
* `urn:jwk:`. The JWK thumbprint is deterministically computed from the JWK and is consistent
|
||
* regardless of property order or optional property inclusion in the JWK. This ensures that the
|
||
* same key material represented as a JWK will always yield the same thumbprint, and therefore,
|
||
* the same key URI.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const keyManager = new LocalKeyManager();
|
||
* const keyUri = await keyManager.generateKey({ algorithm: 'Ed25519' });
|
||
* const publicKey = await keyManager.getPublicKey({ keyUri });
|
||
* const keyUriFromPublicKey = await keyManager.getKeyUri({ key: publicKey });
|
||
* console.log(keyUri === keyUriFromPublicKey); // Outputs `true`
|
||
* ```
|
||
*
|
||
* @param params - The parameters for getting the key URI.
|
||
* @param params.key - The JWK for which to compute the key URI.
|
||
*
|
||
* @returns A Promise that resolves to the key URI as a string.
|
||
*/
|
||
async getKeyUri({ key }) {
|
||
const jwkThumbprint = await (0, import_crypto13.computeJwkThumbprint)({ jwk: key });
|
||
const keyUri = `${import_crypto13.KEY_URI_PREFIX_JWK}${jwkThumbprint}`;
|
||
return keyUri;
|
||
}
|
||
/**
|
||
* Retrieves the public key associated with a previously generated private key, identified by
|
||
* the provided key URI.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const keyManager = new LocalKeyManager();
|
||
* const keyUri = await keyManager.generateKey({ algorithm: 'Ed25519' });
|
||
* const publicKey = await keyManager.getPublicKey({ keyUri });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for retrieving the public key.
|
||
* @param params.keyUri - The key URI of the private key to retrieve the public key for.
|
||
*
|
||
* @returns A Promise that resolves to the public key in JWK format.
|
||
*/
|
||
async getPublicKey({ keyUri }) {
|
||
const privateKey = await this.getPrivateKey({ keyUri });
|
||
const algorithm = this.getAlgorithmName({ key: privateKey });
|
||
const keyGenerator = this.getAlgorithm({ algorithm });
|
||
const publicKey = await keyGenerator.getPublicKey({ key: privateKey });
|
||
return publicKey;
|
||
}
|
||
/**
|
||
* Imports a private key into the local KMS.
|
||
*
|
||
* @remarks
|
||
* This method stores the provided JWK in the key store, making it available for subsequent
|
||
* cryptographic operations. It is particularly useful for initializing the KMS with pre-existing
|
||
* keys or for restoring keys from backups.
|
||
*
|
||
* Note that, if defined, the `kid` (key ID) property of the JWK is used as the key URI for the
|
||
* imported key. If the `kid` property is not provided, the key URI is computed from the JWK
|
||
* thumbprint of the key.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const keyManager = new LocalKeyManager();
|
||
* const privateKey = { ... } // A private key in JWK format
|
||
* const keyUri = await keyManager.importKey({ key: privateKey });
|
||
* ```
|
||
*
|
||
* @param params - Parameters for importing the key.
|
||
* @param params.key - The private key to import to in JWK format.
|
||
*
|
||
* @returns A Promise resolving to the key URI, uniquely identifying the imported key.
|
||
*/
|
||
async importKey({ key }) {
|
||
if (!(0, import_crypto13.isPrivateJwk)(key))
|
||
throw new TypeError("Invalid key provided. Must be a private key in JWK format.");
|
||
const privateKey = structuredClone(key);
|
||
privateKey.kid ??= await (0, import_crypto13.computeJwkThumbprint)({ jwk: privateKey });
|
||
const keyUri = await this.getKeyUri({ key: privateKey });
|
||
await this._keyStore.set({
|
||
id: keyUri,
|
||
data: privateKey,
|
||
agent: this.agent,
|
||
preventDuplicates: true,
|
||
useCache: true
|
||
});
|
||
return keyUri;
|
||
}
|
||
/**
|
||
* Signs the provided data using the private key identified by the provided key URI.
|
||
*
|
||
* @remarks
|
||
* This method uses the signature algorithm determined by the `alg` and/or `crv` properties of the
|
||
* private key identified by the provided key URI to sign the provided data. The signature can
|
||
* later be verified by parties with access to the corresponding public key, ensuring that the
|
||
* data has not been tampered with and was indeed signed by the holder of the private key.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const keyManager = new LocalKeyManager();
|
||
* const keyUri = await keyManager.generateKey({ algorithm: 'Ed25519' });
|
||
* const data = new TextEncoder().encode('Message to sign');
|
||
* const signature = await keyManager.sign({ keyUri, data });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the signing operation.
|
||
* @param params.keyUri - The key URI of the private key to use for signing.
|
||
* @param params.data - The data to sign.
|
||
*
|
||
* @returns A Promise resolving to the digital signature as a `Uint8Array`.
|
||
*/
|
||
async sign({ keyUri, data }) {
|
||
const privateKey = await this.getPrivateKey({ keyUri });
|
||
const algorithm = this.getAlgorithmName({ key: privateKey });
|
||
const signer = this.getAlgorithm({ algorithm });
|
||
const signature = signer.sign({ data, key: privateKey });
|
||
return signature;
|
||
}
|
||
async unwrapKey({ wrappedKeyBytes, wrappedKeyAlgorithm, decryptionKeyUri }) {
|
||
const decryptionKey = await this.getPrivateKey({ keyUri: decryptionKeyUri });
|
||
const algorithm = this.getAlgorithmName({ key: decryptionKey });
|
||
const keyWrapper = this.getAlgorithm({ algorithm });
|
||
const unwrappedKey = await keyWrapper.unwrapKey({ wrappedKeyBytes, wrappedKeyAlgorithm, decryptionKey });
|
||
return unwrappedKey;
|
||
}
|
||
/**
|
||
* Verifies a digital signature associated the provided data using the provided key.
|
||
*
|
||
* @remarks
|
||
* This method uses the signature algorithm determined by the `alg` and/or `crv` properties of the
|
||
* provided key to check the validity of a digital signature against the original data. It
|
||
* confirms whether the signature was created by the holder of the corresponding private key and
|
||
* that the data has not been tampered with.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const keyManager = new LocalKeyManager();
|
||
* const keyUri = await keyManager.generateKey({ algorithm: 'Ed25519' });
|
||
* const data = new TextEncoder().encode('Message to sign');
|
||
* const signature = await keyManager.sign({ keyUri, data });
|
||
* const isSignatureValid = await keyManager.verify({ keyUri, data, signature });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for the verification operation.
|
||
* @param params.key - The key to use for verification.
|
||
* @param params.signature - The signature to verify.
|
||
* @param params.data - The data to verify.
|
||
*
|
||
* @returns A Promise resolving to a boolean indicating whether the signature is valid.
|
||
*/
|
||
async verify({ key, signature, data }) {
|
||
const algorithm = this.getAlgorithmName({ key });
|
||
const signer = this.getAlgorithm({ algorithm });
|
||
const isSignatureValid = signer.verify({ key, signature, data });
|
||
return isSignatureValid;
|
||
}
|
||
async wrapKey({ unwrappedKey, encryptionKeyUri }) {
|
||
const encryptionKey = await this.getPrivateKey({ keyUri: encryptionKeyUri });
|
||
const algorithm = this.getAlgorithmName({ key: encryptionKey });
|
||
const keyWrapper = this.getAlgorithm({ algorithm });
|
||
const wrappedKeyBytes = await keyWrapper.wrapKey({ unwrappedKey, encryptionKey });
|
||
return wrappedKeyBytes;
|
||
}
|
||
/**
|
||
* Retrieves an algorithm implementation instance based on the provided algorithm name.
|
||
*
|
||
* @remarks
|
||
* This method checks if the requested algorithm is supported and returns a cached instance
|
||
* if available. If an instance does not exist, it creates and caches a new one. This approach
|
||
* optimizes performance by reusing algorithm instances across cryptographic operations.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const signer = this.getAlgorithm({ algorithm: 'Ed25519' });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for retrieving the algorithm implementation.
|
||
* @param params.algorithm - The name of the algorithm to retrieve.
|
||
*
|
||
* @returns An instance of the requested algorithm implementation.
|
||
*
|
||
* @throws Error if the requested algorithm is not supported.
|
||
*/
|
||
getAlgorithm({ algorithm }) {
|
||
const AlgorithmImplementation = supportedAlgorithms2[algorithm]?.["implementation"];
|
||
if (!AlgorithmImplementation) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Algorithm not supported: ${algorithm}`);
|
||
}
|
||
if (!this._algorithmInstances.has(AlgorithmImplementation)) {
|
||
this._algorithmInstances.set(AlgorithmImplementation, new AlgorithmImplementation());
|
||
}
|
||
return this._algorithmInstances.get(AlgorithmImplementation);
|
||
}
|
||
/**
|
||
* Determines the algorithm name based on the key's properties.
|
||
*
|
||
* @remarks
|
||
* This method facilitates the identification of the correct algorithm for cryptographic
|
||
* operations based on the `alg` or `crv` properties of a {@link Jwk | JWK}.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const publicKey = { ... }; // Public key in JWK format
|
||
* const algorithm = this.getAlgorithmName({ key: publicKey });
|
||
* ```
|
||
*
|
||
* @param params - The parameters for determining the algorithm name.
|
||
* @param params.key - A JWK containing the `alg` or `crv` properties.
|
||
*
|
||
* @returns The algorithm name associated with the key.
|
||
*
|
||
* @throws Error if the algorithm name cannot be determined from the provided input.
|
||
*/
|
||
getAlgorithmName({ key }) {
|
||
const algProperty = key.alg;
|
||
const crvProperty = key.crv;
|
||
for (const algorithmIdentifier of Object.keys(supportedAlgorithms2)) {
|
||
const algorithmNames = supportedAlgorithms2[algorithmIdentifier].names;
|
||
if (algProperty && algorithmNames.includes(algProperty)) {
|
||
return algorithmIdentifier;
|
||
} else if (crvProperty && algorithmNames.includes(crvProperty)) {
|
||
return algorithmIdentifier;
|
||
}
|
||
}
|
||
throw new CryptoError(
|
||
"algorithmNotSupported" /* AlgorithmNotSupported */,
|
||
`Algorithm not supported based on provided input: alg=${algProperty}, crv=${crvProperty}. Please check the documentation for the list of supported algorithms.`
|
||
);
|
||
}
|
||
/**
|
||
* Retrieves a private key from the key store based on the provided key URI.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const privateKey = this.getPrivateKey({ keyUri: 'urn:jwk:...' });
|
||
* ```
|
||
*
|
||
* @param params - Parameters for retrieving the private key.
|
||
* @param params.keyUri - The key URI identifying the private key to retrieve.
|
||
*
|
||
* @returns A Promise resolving to the JWK representation of the private key.
|
||
*
|
||
* @throws Error if the key is not found in the key store.
|
||
*/
|
||
async getPrivateKey({ keyUri }) {
|
||
const privateKey = await this._keyStore.get({ id: keyUri, agent: this.agent, useCache: true });
|
||
if (!privateKey) {
|
||
throw new Error(`Key not found: ${keyUri}`);
|
||
}
|
||
return privateKey;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/jose/jwe-compact.ts
|
||
var import_crypto15 = require("@web5/crypto");
|
||
|
||
// src/prototyping/crypto/jose/jwe.ts
|
||
var import_common8 = require("@web5/common");
|
||
function isValidJweHeader(obj) {
|
||
return typeof obj === "object" && obj !== null && "alg" in obj && obj.alg !== void 0 && "enc" in obj && obj.enc !== void 0;
|
||
}
|
||
var JweKeyManagement = class {
|
||
/**
|
||
* Decrypts the encrypted key (JWE Encrypted Key) using the specified key encryption algorithm
|
||
* defined in the JWE Header's "alg" parameter.
|
||
*
|
||
* This method supports multiple key management algorithms, including Direct Encryption (dir) and
|
||
* PBES2 schemes with key wrapping.
|
||
*
|
||
* The method takes a key, which can be a Key Identifier, JWK, or raw byte array, and the
|
||
* encrypted key along with the JWE header. It returns the decrypted Content Encryption Key (CEK)
|
||
* which can then be used to decrypt the JWE ciphertext.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* // Decrypting the CEK with the PBES2-HS512+A256KW algorithm
|
||
* const cek = await JweKeyManagement.decrypt({
|
||
* key: Convert.string(passphrase).toUint8Array(),
|
||
* encryptedKey: encryptedCek,
|
||
* joseHeader: {
|
||
* alg: 'PBES2-HS512+A256KW',
|
||
* enc: 'A256GCM',
|
||
* p2c: 210_000,
|
||
* p2s: Convert.uint8Array(saltInput).toBase64Url(),
|
||
* },
|
||
* crypto: new AgentCryptoApi()
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The decryption parameters.
|
||
* @throws Throws an error if the key management algorithm is not supported or if required
|
||
* parameters are missing or invalid.
|
||
*/
|
||
static async decrypt({
|
||
key,
|
||
encryptedKey,
|
||
joseHeader,
|
||
crypto: crypto3
|
||
}) {
|
||
switch (joseHeader.alg) {
|
||
case "dir": {
|
||
if (encryptedKey !== void 0) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'JWE "encrypted_key" is not allowed when using "dir" (Direct Encryption Mode).');
|
||
}
|
||
if (key instanceof Uint8Array) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'Key management "key" must be a Key URI or JWK when using "dir" (Direct Encryption Mode).');
|
||
}
|
||
return key;
|
||
}
|
||
case "PBES2-HS256+A128KW":
|
||
case "PBES2-HS384+A192KW":
|
||
case "PBES2-HS512+A256KW": {
|
||
if (typeof joseHeader.p2c !== "number") {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'JOSE Header "p2c" (PBES2 Count) is missing or not a number.');
|
||
}
|
||
if (typeof joseHeader.p2s !== "string") {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'JOSE Header "p2s" (PBES2 salt) is missing or not a string.');
|
||
}
|
||
if (!(key instanceof Uint8Array)) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'Key management "key" must be a Uint8Array when using "PBES2" (Key Encryption Mode).');
|
||
}
|
||
if (encryptedKey === void 0) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'JWE "encrypted_key" is required when using "PBES2" (Key Encryption Mode).');
|
||
}
|
||
let salt;
|
||
try {
|
||
salt = new Uint8Array([
|
||
...import_common8.Convert.string(joseHeader.alg).toUint8Array(),
|
||
0,
|
||
...import_common8.Convert.base64Url(joseHeader.p2s).toUint8Array()
|
||
]);
|
||
} catch {
|
||
throw new CryptoError("encodingError" /* EncodingError */, 'Failed to decode the JOSE Header "p2s" (PBES2 salt) value.');
|
||
}
|
||
const kek = await crypto3.deriveKey({
|
||
algorithm: joseHeader.alg,
|
||
baseKeyBytes: key,
|
||
iterations: joseHeader.p2c,
|
||
salt
|
||
});
|
||
if (!(kek.alg && ["A128KW", "A192KW", "A256KW"].includes(kek.alg))) {
|
||
throw new CryptoError("algorithmNotSupported" /* AlgorithmNotSupported */, `Unsupported Key Encryption Algorithm (alg) value: ${kek.alg}`);
|
||
}
|
||
return await crypto3.unwrapKey({
|
||
decryptionKey: kek,
|
||
wrappedKeyBytes: encryptedKey,
|
||
wrappedKeyAlgorithm: joseHeader.enc
|
||
});
|
||
}
|
||
default: {
|
||
throw new CryptoError(
|
||
"algorithmNotSupported" /* AlgorithmNotSupported */,
|
||
`Unsupported "alg" (Algorithm) Header Parameter value: ${joseHeader.alg}`
|
||
);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Encrypts a Content Encryption Key (CEK) using the key management algorithm specified in the
|
||
* JWE Header's "alg" parameter.
|
||
*
|
||
* This method supports various key management algorithms, including Direct Encryption (dir) and
|
||
* PBES2 with key wrapping.
|
||
*
|
||
* It generates a random CEK for the specified encryption algorithm in the JWE header, which
|
||
* can then be used to encrypt the actual payload. For algorithms that require an encrypted key,
|
||
* it returns the CEK along with the encrypted key.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* // Encrypting the CEK with the PBES2-HS512+A256KW algorithm
|
||
* const { cek, encryptedKey } = await JweKeyManagement.encrypt({
|
||
* key: Convert.string(passphrase).toUint8Array(),
|
||
* joseHeader: {
|
||
* alg: 'PBES2-HS512+A256KW',
|
||
* enc: 'A256GCM',
|
||
* p2c: 210_000,
|
||
* p2s: Convert.uint8Array(saltInput).toBase64Url(),
|
||
* },
|
||
* crypto: crypto: new AgentCryptoApi()
|
||
* });
|
||
* ```
|
||
*
|
||
* @param params - The encryption parameters.
|
||
* @returns The encrypted key result containing the CEK and optionally the encrypted CEK
|
||
* (JWE Encrypted Key).
|
||
* @throws Throws an error if the key management algorithm is not supported or if required
|
||
* parameters are missing or invalid.
|
||
*/
|
||
static async encrypt({
|
||
key,
|
||
joseHeader,
|
||
crypto: crypto3
|
||
}) {
|
||
let cek;
|
||
let encryptedKey;
|
||
switch (joseHeader.alg) {
|
||
case "dir": {
|
||
if (encryptedKey !== void 0) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'JWE "encrypted_key" is not allowed when using "dir" (Direct Encryption Mode).');
|
||
}
|
||
if (key instanceof Uint8Array) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'Key management "key" must be a Key URI or JWK when using "dir" (Direct Encryption Mode).');
|
||
}
|
||
cek = key;
|
||
break;
|
||
}
|
||
case "PBES2-HS256+A128KW":
|
||
case "PBES2-HS384+A192KW":
|
||
case "PBES2-HS512+A256KW": {
|
||
if (typeof joseHeader.p2c !== "number") {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'JOSE Header "p2c" (PBES2 Count) is missing or not a number.');
|
||
}
|
||
if (typeof joseHeader.p2s !== "string") {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'JOSE Header "p2s" (PBES2 salt) is missing or not a string.');
|
||
}
|
||
if (!(key instanceof Uint8Array)) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, 'Key management "key" must be a Uint8Array when using "PBES2" (Key Encryption Mode).');
|
||
}
|
||
cek = await crypto3.generateKey({ algorithm: joseHeader.enc });
|
||
let salt;
|
||
try {
|
||
salt = new Uint8Array([
|
||
...import_common8.Convert.string(joseHeader.alg).toUint8Array(),
|
||
0,
|
||
...import_common8.Convert.base64Url(joseHeader.p2s).toUint8Array()
|
||
]);
|
||
} catch {
|
||
throw new CryptoError("encodingError" /* EncodingError */, 'Failed to decode the JOSE Header "p2s" (PBES2 salt) value.');
|
||
}
|
||
const kek = await crypto3.deriveKey({
|
||
algorithm: joseHeader.alg,
|
||
baseKeyBytes: key,
|
||
iterations: joseHeader.p2c,
|
||
salt
|
||
});
|
||
encryptedKey = await crypto3.wrapKey({ encryptionKey: kek, unwrappedKey: cek });
|
||
break;
|
||
}
|
||
default: {
|
||
throw new CryptoError(
|
||
"algorithmNotSupported" /* AlgorithmNotSupported */,
|
||
`Unsupported "alg" (Algorithm) Header Parameter value: ${joseHeader.alg}`
|
||
);
|
||
}
|
||
}
|
||
return { cek, encryptedKey };
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/jose/jwe-flattened.ts
|
||
var import_common9 = require("@web5/common");
|
||
var import_crypto14 = require("@web5/crypto");
|
||
|
||
// src/prototyping/crypto/utils.ts
|
||
function isCipher(obj) {
|
||
return obj !== null && typeof obj === "object" && "encrypt" in obj && typeof obj.encrypt === "function" && "decrypt" in obj && typeof obj.decrypt === "function";
|
||
}
|
||
|
||
// src/prototyping/common/object.ts
|
||
function hasDuplicateProperties(...objects) {
|
||
const propertySet = /* @__PURE__ */ new Set();
|
||
const objectsWithoutUndefined = objects.filter(Boolean);
|
||
for (const obj of objectsWithoutUndefined) {
|
||
for (const key in obj) {
|
||
if (propertySet.has(key)) {
|
||
return true;
|
||
}
|
||
propertySet.add(key);
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// src/prototyping/crypto/jose/jwe-flattened.ts
|
||
function decodeHeaderParam(param, value) {
|
||
if (value === void 0)
|
||
return void 0;
|
||
try {
|
||
if (typeof value !== "string")
|
||
throw new Error();
|
||
return import_common9.Convert.base64Url(value).toUint8Array();
|
||
} catch {
|
||
throw new CryptoError(
|
||
"invalidJwe" /* InvalidJwe */,
|
||
`Failed to decode the JWE Header parameter '${param}' from Base64 URL format to Uint8Array. Ensure the value is properly encoded in Base64 URL format without padding.`
|
||
);
|
||
}
|
||
}
|
||
var FlattenedJwe = class _FlattenedJwe {
|
||
constructor(params) {
|
||
/** Base64URL encoded ciphertext. */
|
||
this.ciphertext = "";
|
||
Object.assign(this, params);
|
||
}
|
||
static async decrypt({
|
||
jwe,
|
||
key,
|
||
keyManager = new import_crypto14.LocalKeyManager(),
|
||
crypto: crypto3 = new AgentCryptoApi(),
|
||
options = {}
|
||
}) {
|
||
if (!isCipher(crypto3)) {
|
||
throw new CryptoError("operationNotSupported" /* OperationNotSupported */, 'Crypto API does not support the "encrypt" operation.');
|
||
}
|
||
if (!isCipher(keyManager)) {
|
||
throw new CryptoError("operationNotSupported" /* OperationNotSupported */, 'Key Manager does not support the "decrypt" operation.');
|
||
}
|
||
if (!jwe.protected && !jwe.header && !jwe.unprotected) {
|
||
throw new CryptoError(
|
||
"invalidJwe" /* InvalidJwe */,
|
||
'JWE is missing the required JOSE header parameters. Please provide at least one of the following: "protected", "header", or "unprotected"'
|
||
);
|
||
}
|
||
if (typeof jwe.ciphertext !== "string") {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, "JWE Ciphertext is missing or not a string.");
|
||
}
|
||
let parsedProtectedHeader;
|
||
if (jwe.protected) {
|
||
try {
|
||
parsedProtectedHeader = import_common9.Convert.base64Url(jwe.protected).toObject();
|
||
} catch {
|
||
throw new Error("JWE Protected Header is invalid");
|
||
}
|
||
}
|
||
if (hasDuplicateProperties(parsedProtectedHeader, jwe.header, jwe.unprotected)) {
|
||
throw new Error(
|
||
'Duplicate properties detected. Please ensure that each parameter is defined only once across the JWE "header", "protected", and "unprotected" objects.'
|
||
);
|
||
}
|
||
const joseHeader = { ...parsedProtectedHeader, ...jwe.header, ...jwe.unprotected };
|
||
if (!isValidJweHeader(joseHeader)) {
|
||
throw new Error('JWE Header is missing required "alg" (Algorithm) and/or "enc" (Encryption) Header Parameters');
|
||
}
|
||
if (Array.isArray(options.allowedAlgValues) && !options.allowedAlgValues.includes(joseHeader.alg)) {
|
||
throw new Error(`"alg" (Algorithm) Header Parameter value not allowed: ${joseHeader.alg}`);
|
||
}
|
||
if (Array.isArray(options.allowedEncValues) && !options.allowedEncValues.includes(joseHeader.enc)) {
|
||
throw new Error(`"enc" (Encryption Algorithm) Header Parameter value not allowed: ${joseHeader.enc}`);
|
||
}
|
||
let cek;
|
||
try {
|
||
const encryptedKey = jwe.encrypted_key ? import_common9.Convert.base64Url(jwe.encrypted_key).toUint8Array() : void 0;
|
||
cek = await JweKeyManagement.decrypt({ key, encryptedKey, joseHeader, keyManager, crypto: crypto3 });
|
||
} catch (error) {
|
||
if (error instanceof CryptoError && (error.code === "invalidJwe" /* InvalidJwe */ || error.code === "algorithmNotSupported" /* AlgorithmNotSupported */)) {
|
||
throw error;
|
||
}
|
||
cek = typeof key === "string" ? await keyManager.generateKey({ algorithm: joseHeader.enc }) : await crypto3.generateKey({ algorithm: joseHeader.enc });
|
||
}
|
||
const iv = decodeHeaderParam("iv", jwe.iv);
|
||
const tag = decodeHeaderParam("tag", jwe.tag);
|
||
const ciphertext = tag !== void 0 ? new Uint8Array([
|
||
...import_common9.Convert.base64Url(jwe.ciphertext).toUint8Array(),
|
||
...tag ?? []
|
||
]) : import_common9.Convert.base64Url(jwe.ciphertext).toUint8Array();
|
||
const additionalData = jwe.aad !== void 0 ? new Uint8Array([
|
||
...import_common9.Convert.string(jwe.protected ?? "").toUint8Array(),
|
||
...import_common9.Convert.string(".").toUint8Array(),
|
||
...import_common9.Convert.string(jwe.aad).toUint8Array()
|
||
]) : import_common9.Convert.string(jwe.protected ?? "").toUint8Array();
|
||
const plaintext = typeof cek === "string" ? await keyManager.decrypt({ keyUri: cek, data: ciphertext, iv, additionalData }) : await crypto3.decrypt({ key: cek, data: ciphertext, iv, additionalData });
|
||
return {
|
||
plaintext,
|
||
protectedHeader: parsedProtectedHeader,
|
||
additionalAuthenticatedData: decodeHeaderParam("aad", jwe.aad),
|
||
sharedUnprotectedHeader: jwe.unprotected,
|
||
unprotectedHeader: jwe.header
|
||
};
|
||
}
|
||
static async encrypt({
|
||
key,
|
||
plaintext,
|
||
additionalAuthenticatedData,
|
||
protectedHeader,
|
||
sharedUnprotectedHeader,
|
||
unprotectedHeader,
|
||
keyManager = new import_crypto14.LocalKeyManager(),
|
||
crypto: crypto3 = new AgentCryptoApi()
|
||
}) {
|
||
if (!isCipher(crypto3)) {
|
||
throw new CryptoError("operationNotSupported" /* OperationNotSupported */, 'Crypto API does not support the "encrypt" operation.');
|
||
}
|
||
if (!isCipher(keyManager)) {
|
||
throw new CryptoError("operationNotSupported" /* OperationNotSupported */, 'Key Manager does not support the "decrypt" operation.');
|
||
}
|
||
if (!protectedHeader && !sharedUnprotectedHeader && !unprotectedHeader) {
|
||
throw new CryptoError(
|
||
"invalidJwe" /* InvalidJwe */,
|
||
'JWE is missing the required JOSE header parameters. Please provide at least one of the following: "protectedHeader", "sharedUnprotectedHeader", or "unprotectedHeader"'
|
||
);
|
||
}
|
||
if (!(plaintext instanceof Uint8Array)) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, "Plaintext is missing or not a byte array.");
|
||
}
|
||
if (hasDuplicateProperties(protectedHeader, sharedUnprotectedHeader, unprotectedHeader)) {
|
||
throw new Error(
|
||
'Duplicate properties detected. Please ensure that each parameter is defined only once across the JWE "protectedHeader", "sharedUnprotectedHeader", and "unprotectedHeader" objects.'
|
||
);
|
||
}
|
||
const joseHeader = { ...protectedHeader, ...sharedUnprotectedHeader, ...unprotectedHeader };
|
||
if (!isValidJweHeader(joseHeader)) {
|
||
throw new Error('JWE Header is missing required "alg" (Algorithm) and/or "enc" (Encryption) Header Parameters');
|
||
}
|
||
const { cek, encryptedKey } = await JweKeyManagement.encrypt({ key, joseHeader, keyManager, crypto: crypto3 });
|
||
let iv;
|
||
switch (joseHeader.enc) {
|
||
case "A128GCM":
|
||
case "A192GCM":
|
||
case "A256GCM":
|
||
iv = import_crypto14.utils.randomBytes(12);
|
||
break;
|
||
default:
|
||
iv = new Uint8Array(0);
|
||
}
|
||
const encodedProtectedHeader = protectedHeader ? import_common9.Convert.object(protectedHeader).toBase64Url() : "";
|
||
let additionalData;
|
||
let encodedAad;
|
||
if (additionalAuthenticatedData) {
|
||
encodedAad = import_common9.Convert.uint8Array(additionalAuthenticatedData).toBase64Url();
|
||
additionalData = import_common9.Convert.string(encodedProtectedHeader + "." + encodedAad).toUint8Array();
|
||
} else {
|
||
additionalData = import_common9.Convert.string(encodedProtectedHeader).toUint8Array();
|
||
}
|
||
const ciphertextWithTag = typeof cek === "string" ? await keyManager.encrypt({ keyUri: cek, data: plaintext, iv, additionalData }) : await crypto3.encrypt({ key: cek, data: plaintext, iv, additionalData });
|
||
const ciphertext = ciphertextWithTag.slice(0, -16);
|
||
const authenticationTag = ciphertextWithTag.slice(-16);
|
||
const jwe = new _FlattenedJwe({
|
||
ciphertext: import_common9.Convert.uint8Array(ciphertext).toBase64Url()
|
||
});
|
||
if (encryptedKey)
|
||
jwe.encrypted_key = import_common9.Convert.uint8Array(encryptedKey).toBase64Url();
|
||
if (protectedHeader)
|
||
jwe.protected = encodedProtectedHeader;
|
||
if (sharedUnprotectedHeader)
|
||
jwe.unprotected = sharedUnprotectedHeader;
|
||
if (unprotectedHeader)
|
||
jwe.header = unprotectedHeader;
|
||
if (iv)
|
||
jwe.iv = import_common9.Convert.uint8Array(iv).toBase64Url();
|
||
if (encodedAad)
|
||
jwe.aad = encodedAad;
|
||
if (authenticationTag)
|
||
jwe.tag = import_common9.Convert.uint8Array(authenticationTag).toBase64Url();
|
||
return jwe;
|
||
}
|
||
};
|
||
|
||
// src/prototyping/crypto/jose/jwe-compact.ts
|
||
var CompactJwe = class {
|
||
/**
|
||
* Decrypts a JWE string in Compact Serialization format, extracting the plaintext and
|
||
* reconstructing the JWE Protected Header.
|
||
*
|
||
* This method parses the compact JWE, validates its structure, and applies the appropriate
|
||
* decryption algorithm as specified in the JWE Protected Header. It returns the decrypted
|
||
* plaintext along with the reconstructed protected header, ensuring the data's authenticity
|
||
* and integrity.
|
||
*
|
||
* @param params - The decryption parameters including the JWE string, cryptographic key, and
|
||
* optional instances of Key Manager and Crypto API.
|
||
* @returns A promise resolving to the decrypted content and the JWE Protected Header.
|
||
* @throws {@link CryptoError} if the JWE format is invalid or decryption fails.
|
||
*/
|
||
static async decrypt({
|
||
jwe,
|
||
key,
|
||
keyManager = new import_crypto15.LocalKeyManager(),
|
||
crypto: crypto3 = new AgentCryptoApi(),
|
||
options = {}
|
||
}) {
|
||
if (typeof jwe !== "string") {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, "Invalid JWE format. JWE must be a string.");
|
||
}
|
||
const {
|
||
0: protectedHeader,
|
||
1: encryptedKey,
|
||
2: initializationVector,
|
||
3: ciphertext,
|
||
4: authenticationTag,
|
||
length
|
||
} = jwe.split(".");
|
||
if (length !== 5) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, "Invalid JWE format. JWE must have 5 parts.");
|
||
}
|
||
const flattenedJwe = await FlattenedJwe.decrypt({
|
||
jwe: {
|
||
ciphertext,
|
||
encrypted_key: encryptedKey || void 0,
|
||
iv: initializationVector || void 0,
|
||
protected: protectedHeader,
|
||
tag: authenticationTag || void 0
|
||
},
|
||
key,
|
||
keyManager,
|
||
crypto: crypto3,
|
||
options
|
||
});
|
||
if (!isValidJweHeader(flattenedJwe.protectedHeader)) {
|
||
throw new CryptoError("invalidJwe" /* InvalidJwe */, "Decrypt operation failed due to missing or malformed JWE Protected Header");
|
||
}
|
||
return { plaintext: flattenedJwe.plaintext, protectedHeader: flattenedJwe.protectedHeader };
|
||
}
|
||
/**
|
||
* Encrypts plaintext to a JWE string in Compact Serialization format, encapsulating the content
|
||
* with the specified cryptographic protections.
|
||
*
|
||
* It constructs the JWE by encrypting the plaintext, then serializing the output to the
|
||
* compact format, which includes concatenating various components like the protected header,
|
||
* encrypted key, initialization vector, ciphertext, and authentication tag.
|
||
*
|
||
* @param params - The encryption parameters, including plaintext, JWE Protected Header,
|
||
* cryptographic key, and optional Key Manager and Crypto API instances.
|
||
* @returns A promise that resolves to a string representing the JWE in Compact Serialization
|
||
* format.
|
||
* @throws {@link CryptoError} if encryption fails or the input parameters are invalid.
|
||
*/
|
||
static async encrypt({
|
||
plaintext,
|
||
protectedHeader,
|
||
key,
|
||
keyManager = new import_crypto15.LocalKeyManager(),
|
||
crypto: crypto3 = new AgentCryptoApi(),
|
||
options = {}
|
||
}) {
|
||
const jwe = await FlattenedJwe.encrypt({ plaintext, protectedHeader, key, keyManager, crypto: crypto3, options });
|
||
return [jwe.protected, jwe.encrypted_key, jwe.iv, jwe.ciphertext, jwe.tag].join(".");
|
||
}
|
||
};
|
||
|
||
// src/hd-identity-vault.ts
|
||
function isEmptyString(obj) {
|
||
return typeof obj !== "string" || obj.trim().length === 0;
|
||
}
|
||
function isIdentityVaultBackup(obj) {
|
||
return typeof obj === "object" && obj !== null && "dateCreated" in obj && typeof obj.dateCreated === "string" && "size" in obj && typeof obj.size === "number" && "data" in obj && typeof obj.data === "string";
|
||
}
|
||
function isIdentityVaultStatus(obj) {
|
||
return typeof obj === "object" && obj !== null && "initialized" in obj && typeof obj.initialized === "boolean" && "lastBackup" in obj && "lastRestore" in obj;
|
||
}
|
||
var HdIdentityVault = class {
|
||
/**
|
||
* Constructs an instance of `HdIdentityVault`, initializing the key derivation factor and data
|
||
* store. It sets the default key derivation work factor and initializes the internal data store,
|
||
* either with the provided store or a default in-memory store. It also establishes the initial
|
||
* status of the vault as uninitialized and locked.
|
||
*
|
||
* @param params - Optional parameters when constructing a vault instance.
|
||
* @param params.keyDerivationWorkFactor - Optionally set the computational effort for key derivation.
|
||
* @param params.store - Optionally specify a custom key-value store for vault data.
|
||
*/
|
||
constructor({ keyDerivationWorkFactor, store } = {}) {
|
||
/** Provides cryptographic functions needed for secure storage and management of the vault. */
|
||
this.crypto = new AgentCryptoApi();
|
||
this._keyDerivationWorkFactor = keyDerivationWorkFactor ?? 21e4;
|
||
this._store = store ?? new import_common10.MemoryStore();
|
||
}
|
||
/**
|
||
* Creates a backup of the vault's current state, including the encrypted DID and content
|
||
* encryption key, and returns it as an `IdentityVaultBackup` object. The backup includes a
|
||
* Base64Url-encoded string representing the vault's encrypted data, encapsulating the
|
||
* {@link PortableDid}, the content encryption key, and the vault's status.
|
||
*
|
||
* This method ensures that the vault is initialized and unlocked before proceeding with the
|
||
* backup operation.
|
||
*
|
||
* @throws Error if the vault is not initialized or is locked, preventing the backup.
|
||
* @returns A promise that resolves to the `IdentityVaultBackup` object containing the vault's
|
||
* encrypted backup data.
|
||
*/
|
||
async backup() {
|
||
if (this.isLocked() || await this.isInitialized() === false) {
|
||
throw new Error(
|
||
"HdIdentityVault: Unable to proceed with the backup operation because the identity vault has not been initialized and unlocked. Please ensure the vault is properly initialized with a secure password before attempting to backup its contents."
|
||
);
|
||
}
|
||
const backupData = {
|
||
did: await this.getStoredDid(),
|
||
contentEncryptionKey: await this.getStoredContentEncryptionKey(),
|
||
status: await this.getStatus()
|
||
};
|
||
const backupDataString = import_common10.Convert.object(backupData).toBase64Url();
|
||
const backup = {
|
||
data: backupDataString,
|
||
dateCreated: (/* @__PURE__ */ new Date()).toISOString(),
|
||
size: backupDataString.length
|
||
};
|
||
await this.setStatus({ lastBackup: backup.dateCreated });
|
||
return backup;
|
||
}
|
||
/**
|
||
* Changes the password used to secure the vault.
|
||
*
|
||
* This method decrypts the existing content encryption key (CEK) with the old password, then
|
||
* re-encrypts it with the new password, updating the vault's stored encrypted CEK. It ensures
|
||
* that the vault is initialized and unlocks the vault if the password is successfully changed.
|
||
*
|
||
* @param params - Parameters required for changing the vault password.
|
||
* @param params.oldPassword - The current password used to unlock the vault.
|
||
* @param params.newPassword - The new password to replace the existing one.
|
||
* @throws Error if the vault is not initialized or the old password is incorrect.
|
||
* @returns A promise that resolves when the password change is complete.
|
||
*/
|
||
async changePassword({ oldPassword, newPassword }) {
|
||
if (await this.isInitialized() === false) {
|
||
throw new Error(
|
||
"HdIdentityVault: Unable to proceed with the change password operation because the identity vault has not been initialized. Please ensure the vault is properly initialized with a secure password before trying again."
|
||
);
|
||
}
|
||
await this.lock();
|
||
const cekJwe = await this.getStoredContentEncryptionKey();
|
||
let protectedHeader;
|
||
let contentEncryptionKey;
|
||
try {
|
||
let contentEncryptionKeyBytes;
|
||
({ plaintext: contentEncryptionKeyBytes, protectedHeader } = await CompactJwe.decrypt({
|
||
jwe: cekJwe,
|
||
key: import_common10.Convert.string(oldPassword).toUint8Array(),
|
||
crypto: this.crypto,
|
||
keyManager: new LocalKeyManager2()
|
||
}));
|
||
contentEncryptionKey = import_common10.Convert.uint8Array(contentEncryptionKeyBytes).toObject();
|
||
} catch (error) {
|
||
throw new Error(`HdIdentityVault: Unable to change the vault password due to an incorrectly entered old password.`);
|
||
}
|
||
const newCekJwe = await CompactJwe.encrypt({
|
||
key: import_common10.Convert.string(newPassword).toUint8Array(),
|
||
protectedHeader,
|
||
// Re-use the protected header from the original JWE.
|
||
plaintext: import_common10.Convert.object(contentEncryptionKey).toUint8Array(),
|
||
crypto: this.crypto,
|
||
keyManager: new LocalKeyManager2()
|
||
});
|
||
await this._store.set("contentEncryptionKey", newCekJwe);
|
||
this._contentEncryptionKey = contentEncryptionKey;
|
||
}
|
||
/**
|
||
* Retrieves the DID (Decentralized Identifier) associated with the vault.
|
||
*
|
||
* This method ensures the vault is initialized and unlocked before decrypting and returning the
|
||
* DID. The DID is stored encrypted and is decrypted using the vault's content encryption key.
|
||
*
|
||
* @throws Error if the vault is not initialized, is locked, or the DID cannot be decrypted.
|
||
* @returns A promise that resolves with a {@link BearerDid}.
|
||
*/
|
||
async getDid() {
|
||
if (this.isLocked()) {
|
||
throw new Error(`HdIdentityVault: Vault has not been initialized and unlocked.`);
|
||
}
|
||
const didJwe = await this.getStoredDid();
|
||
const { plaintext: portableDidBytes } = await CompactJwe.decrypt({
|
||
jwe: didJwe,
|
||
key: this._contentEncryptionKey,
|
||
crypto: this.crypto,
|
||
keyManager: new LocalKeyManager2()
|
||
});
|
||
const portableDid = import_common10.Convert.uint8Array(portableDidBytes).toObject();
|
||
if (!isPortableDid(portableDid)) {
|
||
throw new Error("HdIdentityVault: Unable to decode malformed DID in identity vault");
|
||
}
|
||
return await import_dids4.BearerDid.import({ portableDid });
|
||
}
|
||
/**
|
||
* Fetches the current status of the `HdIdentityVault`, providing details on whether it's
|
||
* initialized and the timestamps of the last backup and restore operations.
|
||
*
|
||
* @returns A promise that resolves with the current status of the `HdIdentityVault`, detailing
|
||
* its initialization, lock state, and the timestamps of the last backup and restore.
|
||
*/
|
||
async getStatus() {
|
||
const storedStatus = await this._store.get("vaultStatus");
|
||
if (!storedStatus) {
|
||
return {
|
||
initialized: false,
|
||
lastBackup: null,
|
||
lastRestore: null
|
||
};
|
||
}
|
||
const vaultStatus = import_common10.Convert.string(storedStatus).toObject();
|
||
if (!isIdentityVaultStatus(vaultStatus)) {
|
||
throw new Error("HdIdentityVault: Invalid IdentityVaultStatus object in store");
|
||
}
|
||
return vaultStatus;
|
||
}
|
||
/**
|
||
* Initializes the `HdIdentityVault` with a password and an optional recovery phrase.
|
||
*
|
||
* If a recovery phrase is not provided, a new one is generated. This process sets up the vault,
|
||
* deriving the necessary cryptographic keys and preparing the vault for use. It ensures the vault
|
||
* is ready to securely store and manage identity data.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const identityVault = new HdIdentityVault();
|
||
* const recoveryPhrase = await identityVault.initialize({
|
||
* password: 'your-secure-phrase'
|
||
* });
|
||
* console.log('Vault initialized. Recovery phrase:', recoveryPhrase);
|
||
* ```
|
||
*
|
||
* @param params - The initialization parameters.
|
||
* @param params.password - The password used to secure the vault.
|
||
* @param params.recoveryPhrase - An optional 12-word recovery phrase for key derivation. If
|
||
* omitted, a new recovery is generated.
|
||
* @returns A promise that resolves with the recovery phrase used during the initialization, which
|
||
* should be securely stored by the user.
|
||
*/
|
||
async initialize({ password, recoveryPhrase }) {
|
||
if (await this.isInitialized()) {
|
||
throw new Error(`HdIdentityVault: Vault has already been initialized.`);
|
||
}
|
||
if (isEmptyString(password)) {
|
||
throw new Error(
|
||
`HdIdentityVault: The password is required and cannot be blank. Please provide a ' +
|
||
'valid, non-empty password.`
|
||
);
|
||
}
|
||
if (recoveryPhrase && isEmptyString(recoveryPhrase)) {
|
||
throw new Error(
|
||
`HdIdentityVault: The password is required and cannot be blank. Please provide a ' +
|
||
'valid, non-empty password.`
|
||
);
|
||
}
|
||
recoveryPhrase ??= (0, import_bip39.generateMnemonic)(import_english.wordlist, 128);
|
||
if (!(0, import_bip39.validateMnemonic)(recoveryPhrase, import_english.wordlist)) {
|
||
throw new Error(
|
||
"HdIdentityVault: The provided recovery phrase is invalid. Please ensure that the recovery phrase is a correctly formatted series of 12 words."
|
||
);
|
||
}
|
||
const rootSeed = await (0, import_bip39.mnemonicToSeed)(recoveryPhrase);
|
||
const rootHdKey = HDKey.fromMasterSeed(rootSeed);
|
||
const vaultHdKey = rootHdKey.derive(`m/44'/0'/0'/0'/0'`);
|
||
const contentEncryptionKey = await this.crypto.deriveKey({
|
||
algorithm: "HKDF-512",
|
||
// key derivation function
|
||
baseKeyBytes: vaultHdKey.privateKey,
|
||
// input keying material
|
||
salt: "",
|
||
// empty salt because private key is sufficiently random
|
||
info: "vault_cek",
|
||
// non-secret application specific information
|
||
derivedKeyAlgorithm: "A256GCM"
|
||
// derived key algorithm
|
||
});
|
||
const saltInput = await this.crypto.deriveKeyBytes({
|
||
algorithm: "HKDF-512",
|
||
// key derivation function
|
||
baseKeyBytes: vaultHdKey.publicKey,
|
||
// input keying material
|
||
salt: "",
|
||
// empty salt because public key is sufficiently random
|
||
info: "vault_unlock_salt",
|
||
// non-secret application specific information
|
||
length: 256
|
||
// derived key length, in bits
|
||
});
|
||
const cekJweProtectedHeader = {
|
||
alg: "PBES2-HS512+A256KW",
|
||
enc: "A256GCM",
|
||
cty: "text/plain",
|
||
p2c: this._keyDerivationWorkFactor,
|
||
p2s: import_common10.Convert.uint8Array(saltInput).toBase64Url()
|
||
};
|
||
const cekJwe = await CompactJwe.encrypt({
|
||
key: import_common10.Convert.string(password).toUint8Array(),
|
||
protectedHeader: cekJweProtectedHeader,
|
||
plaintext: import_common10.Convert.object(contentEncryptionKey).toUint8Array(),
|
||
crypto: this.crypto,
|
||
keyManager: new LocalKeyManager2()
|
||
});
|
||
await this._store.set("contentEncryptionKey", cekJwe);
|
||
const identityHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/0'`);
|
||
const identityPrivateKey = await this.crypto.bytesToPrivateKey({
|
||
algorithm: "Ed25519",
|
||
privateKeyBytes: identityHdKey.privateKey
|
||
});
|
||
let signingHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/1'`);
|
||
const signingPrivateKey = await this.crypto.bytesToPrivateKey({
|
||
algorithm: "Ed25519",
|
||
privateKeyBytes: signingHdKey.privateKey
|
||
});
|
||
const deterministicKeyGenerator = new DeterministicKeyGenerator();
|
||
await deterministicKeyGenerator.addPredefinedKeys({
|
||
privateKeys: [identityPrivateKey, signingPrivateKey]
|
||
});
|
||
const did = await import_dids4.DidDht.create({
|
||
keyManager: deterministicKeyGenerator,
|
||
options: {
|
||
verificationMethods: [
|
||
{
|
||
algorithm: "Ed25519",
|
||
id: "sig",
|
||
purposes: ["assertionMethod", "authentication"]
|
||
}
|
||
// TODO: Enable this once DID DHT supports X25519 keys.
|
||
// {
|
||
// algorithm : 'X25519',
|
||
// id : 'enc',
|
||
// purposes : ['keyAgreement']
|
||
// }
|
||
]
|
||
}
|
||
});
|
||
const portableDid = await did.export();
|
||
const didJweProtectedHeader = {
|
||
alg: "dir",
|
||
enc: "A256GCM",
|
||
cty: "json"
|
||
};
|
||
const didJwe = await CompactJwe.encrypt({
|
||
key: contentEncryptionKey,
|
||
plaintext: import_common10.Convert.object(portableDid).toUint8Array(),
|
||
protectedHeader: didJweProtectedHeader,
|
||
crypto: this.crypto,
|
||
keyManager: new LocalKeyManager2()
|
||
});
|
||
await this._store.set("did", didJwe);
|
||
this._contentEncryptionKey = contentEncryptionKey;
|
||
await this.setStatus({ initialized: true });
|
||
return recoveryPhrase;
|
||
}
|
||
/**
|
||
* Determines whether the vault has been initialized.
|
||
*
|
||
* This method checks the vault's current status to determine if it has been
|
||
* initialized. Initialization is a prerequisite for most operations on the vault,
|
||
* ensuring that it is ready for use.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const isInitialized = await identityVault.isInitialized();
|
||
* console.log('Is the vault initialized?', isInitialized);
|
||
* ```
|
||
*
|
||
* @returns A promise that resolves to `true` if the vault has been initialized, otherwise `false`.
|
||
*/
|
||
async isInitialized() {
|
||
return this.getStatus().then(({ initialized }) => initialized);
|
||
}
|
||
/**
|
||
* Checks if the vault is currently locked.
|
||
*
|
||
* This method assesses the vault's current state to determine if it is locked.
|
||
* A locked vault restricts access to its contents, requiring the correct password
|
||
* to unlock and access the stored identity data. The vault must be unlocked to
|
||
* perform operations that access or modify its contents.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const isLocked = await identityVault.isLocked();
|
||
* console.log('Is the vault locked?', isLocked);
|
||
* ```
|
||
*
|
||
* @returns `true` if the vault is locked, otherwise `false`.
|
||
*/
|
||
isLocked() {
|
||
return !this._contentEncryptionKey;
|
||
}
|
||
/**
|
||
* Locks the `HdIdentityVault`, securing its contents by clearing the in-memory encryption key.
|
||
*
|
||
* This method ensures that the vault's sensitive data cannot be accessed without unlocking the
|
||
* vault again with the correct password. It's an essential security feature for safeguarding
|
||
* the vault's contents against unauthorized access.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const identityVault = new HdIdentityVault();
|
||
* await identityVault.lock();
|
||
* console.log('Vault is now locked.');
|
||
* ```
|
||
* @throws An error if the identity vault has not been initialized.
|
||
* @returns A promise that resolves when the vault is successfully locked.
|
||
*/
|
||
async lock() {
|
||
if (await this.isInitialized() === false) {
|
||
throw new Error(`HdIdentityVault: Lock operation failed. Vault has not been initialized.`);
|
||
}
|
||
if (this._contentEncryptionKey)
|
||
this._contentEncryptionKey.k = "";
|
||
this._contentEncryptionKey = void 0;
|
||
}
|
||
/**
|
||
* Restores the vault's data from a backup object, decrypting and reinitializing the vault's
|
||
* content with the provided backup data.
|
||
*
|
||
* This operation is crucial for data recovery scenarios, allowing users to regain access to their
|
||
* encrypted data using a previously saved backup and their password.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const identityVault = new HdIdentityVault();
|
||
* await identityVault.initialize({ password: 'your-secure-phrase' });
|
||
* // Create a backup of the vault's contents.
|
||
* const backup = await identityVault.backup();
|
||
* // Restore the vault with the same password.
|
||
* await identityVault.restore({ backup: backup, password: 'your-secure-phrase' });
|
||
* console.log('Vault restored successfully.');
|
||
* ```
|
||
*
|
||
* @param params - The parameters required for the restore operation.
|
||
* @param params.backup - The backup object containing the encrypted vault data.
|
||
* @param params.password - The password used to encrypt the backup, necessary for decryption.
|
||
* @returns A promise that resolves when the vault has been successfully restored.
|
||
* @throws An error if the backup object is invalid or if the password is incorrect.
|
||
*/
|
||
async restore({ backup, password }) {
|
||
if (!isIdentityVaultBackup(backup)) {
|
||
throw new Error(`HdIdentityVault: Restore operation failed due to invalid backup object.`);
|
||
}
|
||
let previousStatus;
|
||
let previousContentEncryptionKey;
|
||
let previousDid;
|
||
try {
|
||
previousDid = await this.getStoredDid();
|
||
previousContentEncryptionKey = await this.getStoredContentEncryptionKey();
|
||
previousStatus = await this.getStatus();
|
||
} catch {
|
||
throw new Error(
|
||
"HdIdentityVault: The restore operation cannot proceed because the existing vault contents are missing or inaccessible. If the problem persists consider re-initializing the vault and retrying the restore."
|
||
);
|
||
}
|
||
try {
|
||
const backupData = import_common10.Convert.base64Url(backup.data).toObject();
|
||
await this._store.set("did", backupData.did);
|
||
await this._store.set("contentEncryptionKey", backupData.contentEncryptionKey);
|
||
await this.setStatus(backupData.status);
|
||
await this.unlock({ password });
|
||
} catch (error) {
|
||
await this.setStatus(previousStatus);
|
||
await this._store.set("contentEncryptionKey", previousContentEncryptionKey);
|
||
await this._store.set("did", previousDid);
|
||
throw new Error(
|
||
"HdIdentityVault: Restore operation failed due to invalid backup data or an incorrect password. Please verify the password is correct for the provided backup and try again."
|
||
);
|
||
}
|
||
await this.setStatus({ lastRestore: (/* @__PURE__ */ new Date()).toISOString() });
|
||
}
|
||
/**
|
||
* Unlocks the vault by decrypting the stored content encryption key (CEK) using the provided
|
||
* password.
|
||
*
|
||
* This method is essential for accessing the vault's encrypted contents, enabling the decryption
|
||
* of stored data and the execution of further operations requiring the vault to be unlocked.
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const identityVault = new HdIdentityVault();
|
||
* await identityVault.initialize({ password: 'your-initial-phrase' });
|
||
* // Unlock the vault with the correct password before accessing its contents
|
||
* await identityVault.unlock({ password: 'your-initial-phrase' });
|
||
* console.log('Vault unlocked successfully.');
|
||
* ```
|
||
*
|
||
*
|
||
* @param params - The parameters required for the unlock operation.
|
||
* @param params.password - The password used to encrypt the vault's CEK, necessary for
|
||
* decryption.
|
||
* @returns A promise that resolves when the vault has been successfully unlocked.
|
||
* @throws An error if the vault has not been initialized or if the provided password is
|
||
* incorrect.
|
||
*/
|
||
async unlock({ password }) {
|
||
await this.lock();
|
||
const cekJwe = await this.getStoredContentEncryptionKey();
|
||
try {
|
||
const { plaintext: contentEncryptionKeyBytes } = await CompactJwe.decrypt({
|
||
jwe: cekJwe,
|
||
key: import_common10.Convert.string(password).toUint8Array(),
|
||
crypto: this.crypto,
|
||
keyManager: new LocalKeyManager2()
|
||
});
|
||
const contentEncryptionKey = import_common10.Convert.uint8Array(contentEncryptionKeyBytes).toObject();
|
||
this._contentEncryptionKey = contentEncryptionKey;
|
||
} catch (error) {
|
||
throw new Error(`HdIdentityVault: Unable to unlock the vault due to an incorrect password.`);
|
||
}
|
||
}
|
||
/**
|
||
* Retrieves the Decentralized Identifier (DID) associated with the identity vault from the vault
|
||
* store.
|
||
*
|
||
* This DID is encrypted in compact JWE format and needs to be decrypted after the vault is
|
||
* unlocked. The method is intended to be used internally within the HdIdentityVault class to access
|
||
* the encrypted PortableDid.
|
||
*
|
||
* @returns A promise that resolves to the encrypted DID stored in the vault as a compact JWE.
|
||
* @throws Will throw an error if the DID cannot be retrieved from the vault.
|
||
*/
|
||
async getStoredDid() {
|
||
const didJwe = await this._store.get("did");
|
||
if (!didJwe) {
|
||
throw new Error(
|
||
"HdIdentityVault: Unable to retrieve the DID record from the vault. Please check the vault status and if the problem persists consider re-initializing the vault and restoring the contents from a previous backup."
|
||
);
|
||
}
|
||
return didJwe;
|
||
}
|
||
/**
|
||
* Retrieves the encrypted Content Encryption Key (CEK) from the vault's storage.
|
||
*
|
||
* This CEK is used for encrypting and decrypting the vault's contents. It is stored as a
|
||
* compact JWE and should be decrypted with the user's password to be used for further
|
||
* cryptographic operations.
|
||
*
|
||
* @returns A promise that resolves to the stored CEK as a string in compact JWE format.
|
||
* @throws Will throw an error if the CEK cannot be retrieved, indicating potential issues with
|
||
* the vault's integrity or state.
|
||
*/
|
||
async getStoredContentEncryptionKey() {
|
||
const cekJwe = await this._store.get("contentEncryptionKey");
|
||
if (!cekJwe) {
|
||
throw new Error(
|
||
"HdIdentityVault: Unable to retrieve the Content Encryption Key record from the vault. Please check the vault status and if the problem persists consider re-initializing the vault and restoring the contents from a previous backup."
|
||
);
|
||
}
|
||
return cekJwe;
|
||
}
|
||
/**
|
||
* Updates the status of the `HdIdentityVault`, reflecting changes in its initialization, lock
|
||
* state, and the timestamps of the last backup and restore operations.
|
||
*
|
||
* This method directly manipulates the internal state stored in the vault's key-value store.
|
||
*
|
||
* @param params - The status properties to be updated.
|
||
* @param params.initialized - Updates the initialization state of the vault.
|
||
* @param params.lastBackup - Updates the timestamp of the last successful backup.
|
||
* @param params.lastRestore - Updates the timestamp of the last successful restore.
|
||
* @returns A promise that resolves to a boolean indicating successful status update.
|
||
* @throws Will throw an error if the status cannot be updated in the key-value store.
|
||
*/
|
||
async setStatus({ initialized, lastBackup, lastRestore }) {
|
||
let vaultStatus = await this.getStatus();
|
||
vaultStatus.initialized = initialized ?? vaultStatus.initialized;
|
||
vaultStatus.lastBackup = lastBackup ?? vaultStatus.lastBackup;
|
||
vaultStatus.lastRestore = lastRestore ?? vaultStatus.lastRestore;
|
||
await this._store.set("vaultStatus", JSON.stringify(vaultStatus));
|
||
return true;
|
||
}
|
||
};
|
||
|
||
// src/store-identity.ts
|
||
var import_common11 = require("@web5/common");
|
||
function isIdentityMetadata(obj) {
|
||
return !(!obj || typeof obj !== "object" || obj === null) && "name" in obj;
|
||
}
|
||
var DwnIdentityStore = class extends DwnDataStore {
|
||
constructor() {
|
||
super(...arguments);
|
||
this.name = "DwnIdentityStore";
|
||
/**
|
||
* Properties to use when writing and querying Identity records with the DWN store.
|
||
*/
|
||
this._recordProperties = {
|
||
dataFormat: "application/json",
|
||
schema: "https://identity.foundation/schemas/web5/identity-metadata"
|
||
};
|
||
}
|
||
async delete(params) {
|
||
return await super.delete(params);
|
||
}
|
||
async get(params) {
|
||
return await super.get(params);
|
||
}
|
||
async set(params) {
|
||
return await super.set(params);
|
||
}
|
||
async list(params) {
|
||
return await super.list(params);
|
||
}
|
||
async getAllRecords({ agent, tenantDid }) {
|
||
this._index.clear();
|
||
const { reply: queryReply } = await agent.dwn.processRequest({
|
||
author: tenantDid,
|
||
target: tenantDid,
|
||
messageType: DwnInterface.RecordsQuery,
|
||
messageParams: { filter: { ...this._recordProperties } }
|
||
});
|
||
let storedIdentities = [];
|
||
for (const record of queryReply.entries ?? []) {
|
||
if (!record.encodedData) {
|
||
throw new Error(`${this.name}: Expected 'encodedData' to be present in the DWN query result entry`);
|
||
}
|
||
const storedIdentity = import_common11.Convert.base64Url(record.encodedData).toObject();
|
||
if (isIdentityMetadata(storedIdentity)) {
|
||
const indexKey = `${tenantDid}${TENANT_SEPARATOR}${storedIdentity.uri}`;
|
||
this._index.set(indexKey, record.recordId);
|
||
this._cache.set(record.recordId, storedIdentity);
|
||
storedIdentities.push(storedIdentity);
|
||
}
|
||
}
|
||
return storedIdentities;
|
||
}
|
||
};
|
||
var InMemoryIdentityStore = class extends InMemoryDataStore {
|
||
constructor() {
|
||
super(...arguments);
|
||
this.name = "InMemoryIdentityStore";
|
||
}
|
||
async delete(params) {
|
||
return await super.delete(params);
|
||
}
|
||
async get(params) {
|
||
return await super.get(params);
|
||
}
|
||
async list(params) {
|
||
return await super.list(params);
|
||
}
|
||
async set(params) {
|
||
return await super.set(params);
|
||
}
|
||
};
|
||
|
||
// src/identity-api.ts
|
||
function isPortableIdentity(obj) {
|
||
return !(!obj || typeof obj !== "object" || obj === null) && "did" in obj && "metadata" in obj && isPortableDid(obj.did);
|
||
}
|
||
var AgentIdentityApi = class {
|
||
constructor({ agent, store } = {}) {
|
||
this._agent = agent;
|
||
this._store = store ?? new InMemoryIdentityStore();
|
||
}
|
||
/**
|
||
* Retrieves the `Web5PlatformAgent` execution context.
|
||
*
|
||
* @returns The `Web5PlatformAgent` instance that represents the current execution context.
|
||
* @throws Will throw an error if the `agent` instance property is undefined.
|
||
*/
|
||
get agent() {
|
||
if (this._agent === void 0) {
|
||
throw new Error("AgentIdentityApi: Unable to determine agent execution context.");
|
||
}
|
||
return this._agent;
|
||
}
|
||
set agent(agent) {
|
||
this._agent = agent;
|
||
}
|
||
async create({ metadata, didMethod = "dht", didOptions, store, tenant }) {
|
||
const bearerDid = await this.agent.did.create({
|
||
method: didMethod,
|
||
options: didOptions,
|
||
store,
|
||
tenant
|
||
});
|
||
const identity = new BearerIdentity({
|
||
did: bearerDid,
|
||
metadata: { ...metadata, uri: bearerDid.uri, tenant: tenant ?? bearerDid.uri }
|
||
});
|
||
if (store ?? true) {
|
||
await this._store.set({
|
||
id: identity.did.uri,
|
||
data: identity.metadata,
|
||
agent: this.agent,
|
||
tenant: identity.metadata.tenant,
|
||
preventDuplicates: false,
|
||
useCache: true
|
||
});
|
||
}
|
||
return identity;
|
||
}
|
||
async export({ didUri, tenant }) {
|
||
const bearerIdentity = await this.get({ didUri, tenant });
|
||
if (!bearerIdentity) {
|
||
throw new Error(`AgentIdentityApi: Failed to export due to Identity not found: ${didUri}`);
|
||
}
|
||
const portableIdentity = await bearerIdentity.export();
|
||
return portableIdentity;
|
||
}
|
||
async get({ didUri, tenant }) {
|
||
const storedIdentity = await this._store.get({ id: didUri, agent: this.agent, tenant, useCache: true });
|
||
if (!storedIdentity)
|
||
return void 0;
|
||
const storedDid = await this.agent.did.get({ didUri, tenant: storedIdentity.tenant });
|
||
if (!storedDid) {
|
||
throw new Error(`AgentIdentityApi: Identity is present in the store but DID is missing: ${didUri}`);
|
||
}
|
||
const identity = new BearerIdentity({ did: storedDid, metadata: storedIdentity });
|
||
return identity;
|
||
}
|
||
async import({ portableIdentity }) {
|
||
const storedDid = await this.agent.did.import({
|
||
portableDid: portableIdentity.portableDid,
|
||
tenant: portableIdentity.metadata.tenant
|
||
});
|
||
if (!storedDid) {
|
||
throw new Error(`AgentIdentityApi: Failed to import Identity: ${portableIdentity.metadata.uri}`);
|
||
}
|
||
const identity = new BearerIdentity({ did: storedDid, metadata: portableIdentity.metadata });
|
||
await this._store.set({
|
||
id: identity.did.uri,
|
||
data: identity.metadata,
|
||
agent: this.agent,
|
||
tenant: identity.metadata.tenant,
|
||
preventDuplicates: true,
|
||
useCache: true
|
||
});
|
||
return identity;
|
||
}
|
||
async list({ tenant } = {}) {
|
||
const storedIdentities = await this._store.list({ agent: this.agent, tenant });
|
||
const identities = [];
|
||
for (const metadata of storedIdentities) {
|
||
const identity = await this.get({ didUri: metadata.uri, tenant: metadata.tenant });
|
||
identities.push(identity);
|
||
}
|
||
return identities;
|
||
}
|
||
async manage({ portableIdentity }) {
|
||
const storedDid = await this.agent.did.get({
|
||
didUri: portableIdentity.metadata.uri,
|
||
tenant: portableIdentity.metadata.tenant
|
||
});
|
||
if (!storedDid) {
|
||
throw new Error(`AgentIdentityApi: Failed to manage Identity: ${portableIdentity.metadata.uri}`);
|
||
}
|
||
const identity = new BearerIdentity({ did: storedDid, metadata: portableIdentity.metadata });
|
||
await this._store.set({
|
||
id: identity.did.uri,
|
||
data: identity.metadata,
|
||
agent: this.agent,
|
||
preventDuplicates: true,
|
||
useCache: true
|
||
});
|
||
return identity;
|
||
}
|
||
};
|
||
|
||
// src/rpc-client.ts
|
||
var import_crypto19 = require("@web5/crypto");
|
||
|
||
// src/prototyping/clients/json-rpc.ts
|
||
var createJsonRpcRequest = (id, method, params) => {
|
||
return {
|
||
jsonrpc: "2.0",
|
||
id,
|
||
method,
|
||
params
|
||
};
|
||
};
|
||
var createJsonRpcSubscriptionRequest = (id, method, subscriptionId, params) => {
|
||
return {
|
||
jsonrpc: "2.0",
|
||
id,
|
||
method: `rpc.subscribe.${method}`,
|
||
params,
|
||
subscription: {
|
||
id: subscriptionId
|
||
}
|
||
};
|
||
};
|
||
function parseJson(text) {
|
||
try {
|
||
return JSON.parse(text);
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// src/prototyping/clients/http-dwn-rpc-client.ts
|
||
var import_crypto16 = require("@web5/crypto");
|
||
|
||
// src/prototyping/clients/dwn-server-info-cache-memory.ts
|
||
var import_ms3 = __toESM(require("ms"), 1);
|
||
var import_common12 = require("@web5/common");
|
||
var DwnServerInfoCacheMemory = class {
|
||
constructor({ ttl = "15m" } = {}) {
|
||
this.cache = new import_common12.TtlCache({ ttl: (0, import_ms3.default)(ttl) });
|
||
}
|
||
/**
|
||
* Retrieves a DWN ServerInfo entry from the cache.
|
||
*
|
||
* If the cached item has exceeded its TTL, it's scheduled for deletion and undefined is returned.
|
||
*
|
||
* @param dwnUrl - The DWN URL endpoint string used as the key for getting the entry.
|
||
* @returns The cached DWN ServerInfo entry or undefined if not found or expired.
|
||
*/
|
||
async get(dwnUrl) {
|
||
return this.cache.get(dwnUrl);
|
||
}
|
||
/**
|
||
* Stores a DWN ServerInfo entry in the cache with a TTL.
|
||
*
|
||
* @param dwnUrl - The DWN URL endpoint string used as the key for storing the entry.
|
||
* @param value - The DWN ServerInfo entry to be cached.
|
||
* @returns A promise that resolves when the operation is complete.
|
||
*/
|
||
async set(dwnUrl, value) {
|
||
this.cache.set(dwnUrl, value);
|
||
}
|
||
/**
|
||
* Deletes a DWN ServerInfo entry from the cache.
|
||
*
|
||
* @param dwnUrl - The DWN URL endpoint string used as the key for deletion.
|
||
* @returns A promise that resolves when the operation is complete.
|
||
*/
|
||
async delete(dwnUrl) {
|
||
this.cache.delete(dwnUrl);
|
||
}
|
||
/**
|
||
* Clears all entries from the cache.
|
||
*
|
||
* @returns A promise that resolves when the operation is complete.
|
||
*/
|
||
async clear() {
|
||
this.cache.clear();
|
||
}
|
||
/**
|
||
* This method is a no-op but exists to be consistent with other DWN ServerInfo Cache
|
||
* implementations.
|
||
*
|
||
* @returns A promise that resolves immediately.
|
||
*/
|
||
async close() {
|
||
}
|
||
};
|
||
|
||
// src/prototyping/clients/http-dwn-rpc-client.ts
|
||
var HttpDwnRpcClient = class {
|
||
constructor(serverInfoCache) {
|
||
this.serverInfoCache = serverInfoCache ?? new DwnServerInfoCacheMemory();
|
||
}
|
||
get transportProtocols() {
|
||
return ["http:", "https:"];
|
||
}
|
||
async sendDwnRequest(request) {
|
||
const requestId = import_crypto16.utils.randomUuid();
|
||
const jsonRpcRequest = createJsonRpcRequest(requestId, "dwn.processMessage", {
|
||
target: request.targetDid,
|
||
message: request.message
|
||
});
|
||
const fetchOpts = {
|
||
method: "POST",
|
||
headers: {
|
||
"dwn-request": JSON.stringify(jsonRpcRequest)
|
||
}
|
||
};
|
||
if (request.data) {
|
||
fetchOpts.headers["content-type"] = "application/octet-stream";
|
||
fetchOpts["body"] = request.data;
|
||
}
|
||
const resp = await fetch(request.dwnUrl, fetchOpts);
|
||
let dwnRpcResponse;
|
||
let dataStream;
|
||
const { headers } = resp;
|
||
if (headers.has("dwn-response")) {
|
||
const jsonRpcResponse = parseJson(headers.get("dwn-response"));
|
||
if (jsonRpcResponse == null) {
|
||
throw new Error(`failed to parse json rpc response. dwn url: ${request.dwnUrl}`);
|
||
}
|
||
dataStream = resp.body;
|
||
dwnRpcResponse = jsonRpcResponse;
|
||
} else {
|
||
const responseBody = await resp.text();
|
||
dwnRpcResponse = JSON.parse(responseBody);
|
||
}
|
||
if (dwnRpcResponse.error) {
|
||
const { code, message } = dwnRpcResponse.error;
|
||
throw new Error(`(${code}) - ${message}`);
|
||
}
|
||
const { reply } = dwnRpcResponse.result;
|
||
if (dataStream) {
|
||
reply["record"]["data"] = dataStream;
|
||
}
|
||
return reply;
|
||
}
|
||
async getServerInfo(dwnUrl) {
|
||
const serverInfo = await this.serverInfoCache.get(dwnUrl);
|
||
if (serverInfo) {
|
||
return serverInfo;
|
||
}
|
||
const url = new URL(dwnUrl);
|
||
url.pathname.endsWith("/") ? url.pathname += "info" : url.pathname += "/info";
|
||
try {
|
||
const response = await fetch(url.toString());
|
||
if (response.ok) {
|
||
const results = await response.json();
|
||
const serverInfo2 = {
|
||
registrationRequirements: results.registrationRequirements,
|
||
maxFileSize: results.maxFileSize,
|
||
webSocketSupport: results.webSocketSupport
|
||
};
|
||
this.serverInfoCache.set(dwnUrl, serverInfo2);
|
||
return serverInfo2;
|
||
} else {
|
||
throw new Error(`HTTP (${response.status}) - ${response.statusText}`);
|
||
}
|
||
} catch (error) {
|
||
throw new Error(`Error encountered while processing response from ${url.toString()}: ${error.message}`);
|
||
}
|
||
}
|
||
};
|
||
|
||
// src/prototyping/clients/web-socket-clients.ts
|
||
var import_crypto18 = require("@web5/crypto");
|
||
|
||
// src/prototyping/clients/json-rpc-socket.ts
|
||
var import_crypto17 = require("@web5/crypto");
|
||
var import_isomorphic_ws = __toESM(require("isomorphic-ws"), 1);
|
||
var CONNECT_TIMEOUT = 3e3;
|
||
var RESPONSE_TIMEOUT = 3e4;
|
||
var JsonRpcSocket = class _JsonRpcSocket {
|
||
constructor(socket, responseTimeout) {
|
||
this.socket = socket;
|
||
this.responseTimeout = responseTimeout;
|
||
this.messageHandlers = /* @__PURE__ */ new Map();
|
||
}
|
||
static async connect(url, options = {}) {
|
||
const { connectTimeout = CONNECT_TIMEOUT, responseTimeout = RESPONSE_TIMEOUT, onclose, onerror } = options;
|
||
const socket = new import_isomorphic_ws.default(url);
|
||
if (!onclose) {
|
||
socket.onclose = () => {
|
||
console.info(`JSON RPC Socket close ${url}`);
|
||
};
|
||
} else {
|
||
socket.onclose = onclose;
|
||
}
|
||
if (!onerror) {
|
||
socket.onerror = (error) => {
|
||
console.error(`JSON RPC Socket error ${url}`, error);
|
||
};
|
||
} else {
|
||
socket.onerror = onerror;
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
socket.addEventListener("open", () => {
|
||
const jsonRpcSocket = new _JsonRpcSocket(socket, responseTimeout);
|
||
socket.addEventListener("message", (event) => {
|
||
const jsonRpcResponse = parseJson(event.data);
|
||
const handler = jsonRpcSocket.messageHandlers.get(jsonRpcResponse.id);
|
||
if (handler) {
|
||
handler(event);
|
||
}
|
||
});
|
||
resolve(jsonRpcSocket);
|
||
});
|
||
socket.addEventListener("error", (error) => {
|
||
reject(error);
|
||
});
|
||
setTimeout(() => reject, connectTimeout);
|
||
});
|
||
}
|
||
close() {
|
||
this.socket.close();
|
||
}
|
||
/**
|
||
* Sends a JSON-RPC request through the socket and waits for a single response.
|
||
*/
|
||
async request(request) {
|
||
return new Promise((resolve, reject) => {
|
||
request.id ??= import_crypto17.utils.randomUuid();
|
||
const handleResponse = (event) => {
|
||
const jsonRpsResponse = parseJson(event.data);
|
||
if (jsonRpsResponse.id === request.id) {
|
||
this.messageHandlers.delete(request.id);
|
||
return resolve(jsonRpsResponse);
|
||
}
|
||
};
|
||
this.messageHandlers.set(request.id, handleResponse);
|
||
this.send(request);
|
||
setTimeout(() => {
|
||
this.messageHandlers.delete(request.id);
|
||
reject(new Error("request timed out"));
|
||
}, this.responseTimeout);
|
||
});
|
||
}
|
||
/**
|
||
* Sends a JSON-RPC request through the socket and keeps a listener open to read associated responses as they arrive.
|
||
* Returns a close method to clean up the listener.
|
||
*/
|
||
async subscribe(request, listener) {
|
||
if (!request.method.startsWith("rpc.subscribe.")) {
|
||
throw new Error("subscribe rpc requests must include the `rpc.subscribe` prefix");
|
||
}
|
||
if (!request.subscription) {
|
||
throw new Error("subscribe rpc requests must include subscribe options");
|
||
}
|
||
const subscriptionId = request.subscription.id;
|
||
const socketEventListener = (event) => {
|
||
const jsonRpcResponse = parseJson(event.data.toString());
|
||
if (jsonRpcResponse.id === subscriptionId) {
|
||
if (jsonRpcResponse.error !== void 0) {
|
||
this.messageHandlers.delete(subscriptionId);
|
||
this.closeSubscription(subscriptionId);
|
||
}
|
||
listener(jsonRpcResponse);
|
||
}
|
||
};
|
||
this.messageHandlers.set(subscriptionId, socketEventListener);
|
||
const response = await this.request(request);
|
||
if (response.error) {
|
||
this.messageHandlers.delete(subscriptionId);
|
||
return { response };
|
||
}
|
||
const close = async () => {
|
||
this.messageHandlers.delete(subscriptionId);
|
||
await this.closeSubscription(subscriptionId);
|
||
};
|
||
return {
|
||
response,
|
||
close
|
||
};
|
||
}
|
||
closeSubscription(id) {
|
||
const requestId = import_crypto17.utils.randomUuid();
|
||
const request = createJsonRpcSubscriptionRequest(requestId, "close", id, {});
|
||
return this.request(request);
|
||
}
|
||
/**
|
||
* Sends a JSON-RPC request through the socket. You must subscribe to a message listener separately to capture the response.
|
||
*/
|
||
send(request) {
|
||
this.socket.send(JSON.stringify(request));
|
||
}
|
||
};
|
||
|
||
// src/prototyping/clients/web-socket-clients.ts
|
||
var WebSocketDwnRpcClient = class _WebSocketDwnRpcClient {
|
||
get transportProtocols() {
|
||
return ["ws:", "wss:"];
|
||
}
|
||
static {
|
||
// a map of dwn host to WebSocket connection
|
||
this.connections = /* @__PURE__ */ new Map();
|
||
}
|
||
async sendDwnRequest(request, jsonRpcSocketOptions) {
|
||
const url = new URL(request.dwnUrl);
|
||
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
||
throw new Error(`Invalid websocket protocol ${url.protocol}`);
|
||
}
|
||
const hasConnection = _WebSocketDwnRpcClient.connections.has(url.host);
|
||
if (!hasConnection) {
|
||
try {
|
||
const socket = await JsonRpcSocket.connect(url.toString(), jsonRpcSocketOptions);
|
||
const subscriptions = /* @__PURE__ */ new Map();
|
||
_WebSocketDwnRpcClient.connections.set(url.host, { socket, subscriptions });
|
||
} catch (error) {
|
||
throw new Error(`Error connecting to ${url.host}: ${error.message}`);
|
||
}
|
||
}
|
||
const connection = _WebSocketDwnRpcClient.connections.get(url.host);
|
||
const { targetDid, message, subscriptionHandler } = request;
|
||
if (subscriptionHandler) {
|
||
return _WebSocketDwnRpcClient.subscriptionRequest(connection, targetDid, message, subscriptionHandler);
|
||
}
|
||
return _WebSocketDwnRpcClient.processMessage(connection, targetDid, message);
|
||
}
|
||
static async processMessage(connection, target, message) {
|
||
const requestId = import_crypto18.utils.randomUuid();
|
||
const request = createJsonRpcRequest(requestId, "dwn.processMessage", { target, message });
|
||
const { socket } = connection;
|
||
const response = await socket.request(request);
|
||
const { error, result } = response;
|
||
if (error !== void 0) {
|
||
throw new Error(`error sending DWN request: ${error.message}`);
|
||
}
|
||
return result.reply;
|
||
}
|
||
static async subscriptionRequest(connection, target, message, messageHandler) {
|
||
const requestId = import_crypto18.utils.randomUuid();
|
||
const subscriptionId = import_crypto18.utils.randomUuid();
|
||
const request = createJsonRpcSubscriptionRequest(requestId, "dwn.processMessage", subscriptionId, { target, message });
|
||
const { socket, subscriptions } = connection;
|
||
const { response, close } = await socket.subscribe(request, (response2) => {
|
||
const { result: result2, error: error2 } = response2;
|
||
if (error2) {
|
||
const subscription = subscriptions.get(subscriptionId);
|
||
if (subscription) {
|
||
subscription.close();
|
||
}
|
||
subscriptions.delete(subscriptionId);
|
||
return;
|
||
}
|
||
const { event } = result2;
|
||
messageHandler(event);
|
||
});
|
||
const { error, result } = response;
|
||
if (error) {
|
||
throw new Error(`could not subscribe via jsonrpc socket: ${error.message}`);
|
||
}
|
||
const { reply } = result;
|
||
if (reply.subscription && close) {
|
||
subscriptions.set(subscriptionId, { ...reply.subscription, close });
|
||
reply.subscription.close = close;
|
||
}
|
||
return reply;
|
||
}
|
||
};
|
||
|
||
// src/rpc-client.ts
|
||
var DidRpcMethod = /* @__PURE__ */ ((DidRpcMethod2) => {
|
||
DidRpcMethod2["Create"] = "did.create";
|
||
DidRpcMethod2["Resolve"] = "did.resolve";
|
||
return DidRpcMethod2;
|
||
})(DidRpcMethod || {});
|
||
var Web5RpcClient = class {
|
||
constructor(clients = []) {
|
||
this.transportClients = /* @__PURE__ */ new Map();
|
||
clients = [new HttpWeb5RpcClient(), new WebSocketWeb5RpcClient(), ...clients];
|
||
for (let client of clients) {
|
||
for (let transportScheme of client.transportProtocols) {
|
||
this.transportClients.set(transportScheme, client);
|
||
}
|
||
}
|
||
}
|
||
get transportProtocols() {
|
||
return Array.from(this.transportClients.keys());
|
||
}
|
||
async sendDidRequest(request) {
|
||
const url = new URL(request.url);
|
||
const transportClient = this.transportClients.get(url.protocol);
|
||
if (!transportClient) {
|
||
const error = new Error(`no ${url.protocol} transport client available`);
|
||
error.name = "NO_TRANSPORT_CLIENT";
|
||
throw error;
|
||
}
|
||
return transportClient.sendDidRequest(request);
|
||
}
|
||
sendDwnRequest(request) {
|
||
const url = new URL(request.dwnUrl);
|
||
const transportClient = this.transportClients.get(url.protocol);
|
||
if (!transportClient) {
|
||
const error = new Error(`no ${url.protocol} transport client available`);
|
||
error.name = "NO_TRANSPORT_CLIENT";
|
||
throw error;
|
||
}
|
||
return transportClient.sendDwnRequest(request);
|
||
}
|
||
async getServerInfo(dwnUrl) {
|
||
const url = new URL(dwnUrl);
|
||
const transportClient = this.transportClients.get(url.protocol);
|
||
if (!transportClient) {
|
||
const error = new Error(`no ${url.protocol} transport client available`);
|
||
error.name = "NO_TRANSPORT_CLIENT";
|
||
throw error;
|
||
}
|
||
return transportClient.getServerInfo(dwnUrl);
|
||
}
|
||
};
|
||
var HttpWeb5RpcClient = class extends HttpDwnRpcClient {
|
||
async sendDidRequest(request) {
|
||
const requestId = import_crypto19.utils.randomUuid();
|
||
const jsonRpcRequest = createJsonRpcRequest(requestId, request.method, {
|
||
data: request.data
|
||
});
|
||
const httpRequest = new Request(request.url, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify(jsonRpcRequest)
|
||
});
|
||
let jsonRpcResponse;
|
||
try {
|
||
const response = await fetch(httpRequest);
|
||
if (response.ok) {
|
||
jsonRpcResponse = await response.json();
|
||
if (jsonRpcResponse.error) {
|
||
const { code, message } = jsonRpcResponse.error;
|
||
throw new Error(`JSON RPC (${code}) - ${message}`);
|
||
}
|
||
} else {
|
||
throw new Error(`HTTP (${response.status}) - ${response.statusText}`);
|
||
}
|
||
} catch (error) {
|
||
throw new Error(`Error encountered while processing response from ${request.url}: ${error.message}`);
|
||
}
|
||
return jsonRpcResponse.result;
|
||
}
|
||
};
|
||
var WebSocketWeb5RpcClient = class extends WebSocketDwnRpcClient {
|
||
async sendDidRequest(_request) {
|
||
throw new Error(`not implemented for transports [${this.transportProtocols.join(", ")}]`);
|
||
}
|
||
async getServerInfo(_dwnUrl) {
|
||
throw new Error(`not implemented for transports [${this.transportProtocols.join(", ")}]`);
|
||
}
|
||
};
|
||
|
||
// src/sync-api.ts
|
||
var AgentSyncApi = class {
|
||
constructor({ agent, syncEngine }) {
|
||
this._syncEngine = syncEngine;
|
||
this._agent = agent;
|
||
}
|
||
/**
|
||
* Retrieves the `Web5PlatformAgent` execution context.
|
||
*
|
||
* @returns The `Web5PlatformAgent` instance that represents the current execution context.
|
||
* @throws Will throw an error if the `agent` instance property is undefined.
|
||
*/
|
||
get agent() {
|
||
if (this._agent === void 0) {
|
||
throw new Error("AgentSyncApi: Unable to determine agent execution context.");
|
||
}
|
||
return this._agent;
|
||
}
|
||
set agent(agent) {
|
||
this._agent = agent;
|
||
this._syncEngine.agent = agent;
|
||
}
|
||
async registerIdentity(params) {
|
||
await this._syncEngine.registerIdentity(params);
|
||
}
|
||
startSync(params) {
|
||
return this._syncEngine.startSync(params);
|
||
}
|
||
stopSync() {
|
||
this._syncEngine.stopSync();
|
||
}
|
||
};
|
||
|
||
// src/sync-engine-level.ts
|
||
var import_ms4 = __toESM(require("ms"), 1);
|
||
var import_level = require("level");
|
||
var import_ulidx = require("ulidx");
|
||
var import_common13 = require("@web5/common");
|
||
var import_dwn_sdk_js5 = require("@tbd54566975/dwn-sdk-js");
|
||
var is2xx = (code) => code >= 200 && code <= 299;
|
||
var is4xx = (code) => code >= 400 && code <= 499;
|
||
var SyncEngineLevel = class {
|
||
constructor({ agent, dataPath, db }) {
|
||
this._agent = agent;
|
||
this._db = db ? db : new import_level.Level(dataPath ?? "DATA/AGENT/SYNC_STORE");
|
||
this._ulidFactory = (0, import_ulidx.monotonicFactory)();
|
||
}
|
||
/**
|
||
* Retrieves the `Web5PlatformAgent` execution context.
|
||
*
|
||
* @returns The `Web5PlatformAgent` instance that represents the current execution context.
|
||
* @throws Will throw an error if the `agent` instance property is undefined.
|
||
*/
|
||
get agent() {
|
||
if (this._agent === void 0) {
|
||
throw new Error("SyncEngineLevel: Unable to determine agent execution context.");
|
||
}
|
||
return this._agent;
|
||
}
|
||
set agent(agent) {
|
||
this._agent = agent;
|
||
}
|
||
async clear() {
|
||
await this._db.clear();
|
||
}
|
||
async close() {
|
||
await this._db.close();
|
||
}
|
||
async pull() {
|
||
const syncPeerState = await this.getSyncPeerState({ syncDirection: "pull" });
|
||
await this.enqueueOperations({ syncDirection: "pull", syncPeerState });
|
||
const pullQueue = this.getPullQueue();
|
||
const pullJobs = await pullQueue.iterator().all();
|
||
const deleteOperations = [];
|
||
const errored = /* @__PURE__ */ new Set();
|
||
for (let job of pullJobs) {
|
||
const [key] = job;
|
||
const [did, dwnUrl, _, messageCid] = key.split("~");
|
||
if (errored.has(dwnUrl)) {
|
||
continue;
|
||
}
|
||
const messageExists = await this.messageExists(did, messageCid);
|
||
if (messageExists) {
|
||
deleteOperations.push({ type: "del", key });
|
||
continue;
|
||
}
|
||
const messagesGet = await this.agent.dwn.createMessage({
|
||
author: did,
|
||
messageType: DwnInterface.MessagesGet,
|
||
messageParams: {
|
||
messageCids: [messageCid]
|
||
}
|
||
});
|
||
let reply;
|
||
try {
|
||
reply = await this.agent.rpc.sendDwnRequest({
|
||
dwnUrl,
|
||
targetDid: did,
|
||
message: messagesGet
|
||
});
|
||
} catch (e) {
|
||
errored.add(dwnUrl);
|
||
continue;
|
||
}
|
||
for (let entry of reply.entries ?? []) {
|
||
if (entry.error || !entry.message) {
|
||
await this.addMessage(did, messageCid);
|
||
deleteOperations.push({ type: "del", key });
|
||
continue;
|
||
}
|
||
let dataStream;
|
||
if (isRecordsWrite(entry)) {
|
||
const { encodedData } = entry;
|
||
const message = entry.message;
|
||
if (encodedData) {
|
||
const dataBytes = import_common13.Convert.base64Url(encodedData).toUint8Array();
|
||
dataStream = import_dwn_sdk_js5.DataStream.fromBytes(dataBytes);
|
||
} else {
|
||
const recordsRead = await this.agent.dwn.createMessage({
|
||
author: did,
|
||
messageType: DwnInterface.RecordsRead,
|
||
messageParams: {
|
||
filter: {
|
||
recordId: message.recordId
|
||
}
|
||
}
|
||
});
|
||
const recordsReadReply = await this.agent.rpc.sendDwnRequest({
|
||
dwnUrl,
|
||
targetDid: did,
|
||
message: recordsRead.message
|
||
});
|
||
const { record, status: readStatus } = recordsReadReply;
|
||
if (is2xx(readStatus.code) && record) {
|
||
dataStream = import_common13.NodeStream.fromWebReadable({ readableStream: record.data });
|
||
} else if (readStatus.code >= 400) {
|
||
const pruneReply = await this.agent.dwn.processMessage({
|
||
targetDid: did,
|
||
message
|
||
});
|
||
if (pruneReply.status.code === 202 || pruneReply.status.code === 409) {
|
||
await this.addMessage(did, messageCid);
|
||
deleteOperations.push({ type: "del", key });
|
||
continue;
|
||
} else {
|
||
throw new Error(`SyncManager: Failed to sync tombstone for message '${messageCid}'`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
const pullReply = await this.agent.dwn.processMessage({
|
||
targetDid: did,
|
||
message: entry.message,
|
||
dataStream
|
||
});
|
||
if (pullReply.status.code === 202 || pullReply.status.code === 409) {
|
||
await this.addMessage(did, messageCid);
|
||
deleteOperations.push({ type: "del", key });
|
||
}
|
||
}
|
||
}
|
||
await pullQueue.batch(deleteOperations);
|
||
}
|
||
async push() {
|
||
const syncPeerState = await this.getSyncPeerState({ syncDirection: "push" });
|
||
await this.enqueueOperations({ syncDirection: "push", syncPeerState });
|
||
const pushQueue = this.getPushQueue();
|
||
const pushJobs = await pushQueue.iterator().all();
|
||
const deleteOperations = [];
|
||
const errored = /* @__PURE__ */ new Set();
|
||
for (let job of pushJobs) {
|
||
const [key] = job;
|
||
const [did, dwnUrl, _, messageCid] = key.split("~");
|
||
if (errored.has(dwnUrl)) {
|
||
continue;
|
||
}
|
||
const dwnMessage = await this.getDwnMessage({ author: did, messageCid });
|
||
if (!dwnMessage) {
|
||
deleteOperations.push({ type: "del", key });
|
||
await this.addMessage(did, messageCid);
|
||
continue;
|
||
}
|
||
try {
|
||
const reply = await this.agent.rpc.sendDwnRequest({
|
||
dwnUrl,
|
||
targetDid: did,
|
||
data: dwnMessage.data,
|
||
message: dwnMessage.message
|
||
});
|
||
if (reply.status.code === 202 || reply.status.code === 409) {
|
||
await this.addMessage(did, messageCid);
|
||
deleteOperations.push({ type: "del", key });
|
||
}
|
||
} catch {
|
||
errored.add(dwnUrl);
|
||
}
|
||
}
|
||
await pushQueue.batch(deleteOperations);
|
||
}
|
||
async registerIdentity({ did }) {
|
||
const registeredIdentities = this._db.sublevel("registeredIdentities");
|
||
await registeredIdentities.put(did, "");
|
||
}
|
||
startSync({ interval }) {
|
||
const intervalMilliseconds = (0, import_ms4.default)(interval);
|
||
return new Promise((resolve, reject) => {
|
||
const intervalSync = async () => {
|
||
if (this._syncIntervalId) {
|
||
clearInterval(this._syncIntervalId);
|
||
}
|
||
try {
|
||
await this.push();
|
||
await this.pull();
|
||
} catch (error) {
|
||
this.stopSync();
|
||
reject(error);
|
||
}
|
||
this._syncIntervalId = setInterval(intervalSync, intervalMilliseconds);
|
||
};
|
||
this._syncIntervalId = setInterval(intervalSync, intervalMilliseconds);
|
||
});
|
||
}
|
||
stopSync() {
|
||
if (this._syncIntervalId) {
|
||
clearInterval(this._syncIntervalId);
|
||
this._syncIntervalId = void 0;
|
||
}
|
||
}
|
||
async enqueueOperations({ syncDirection, syncPeerState }) {
|
||
for (let syncState of syncPeerState) {
|
||
const eventLog = await this.getDwnEventLog({
|
||
did: syncState.did,
|
||
dwnUrl: syncState.dwnUrl,
|
||
cursor: syncState.cursor,
|
||
syncDirection
|
||
});
|
||
const syncOperations = [];
|
||
for (let messageCid of eventLog) {
|
||
const watermark = this._ulidFactory();
|
||
const operationKey = [
|
||
syncState.did,
|
||
syncState.dwnUrl,
|
||
watermark,
|
||
messageCid
|
||
].join("~");
|
||
syncOperations.push({ type: "put", key: operationKey, value: "" });
|
||
}
|
||
if (syncOperations.length > 0) {
|
||
const syncQueue = syncDirection === "pull" ? this.getPullQueue() : this.getPushQueue();
|
||
await syncQueue.batch(syncOperations);
|
||
}
|
||
}
|
||
}
|
||
async getDwnEventLog({ did, dwnUrl, syncDirection, cursor }) {
|
||
let eventsReply = {};
|
||
if (syncDirection === "pull") {
|
||
const eventsGetMessage = await this.agent.dwn.createMessage({
|
||
author: did,
|
||
messageType: DwnInterface.EventsGet,
|
||
messageParams: { cursor }
|
||
});
|
||
try {
|
||
eventsReply = await this.agent.rpc.sendDwnRequest({
|
||
dwnUrl,
|
||
targetDid: did,
|
||
message: eventsGetMessage
|
||
});
|
||
} catch {
|
||
}
|
||
} else if (syncDirection === "push") {
|
||
const eventsGetDwnResponse = await this.agent.dwn.processRequest({
|
||
author: did,
|
||
target: did,
|
||
messageType: DwnInterface.EventsGet,
|
||
messageParams: { cursor }
|
||
});
|
||
eventsReply = eventsGetDwnResponse.reply;
|
||
}
|
||
const eventLog = eventsReply.entries ?? [];
|
||
if (eventsReply.cursor) {
|
||
this.setCursor(did, dwnUrl, syncDirection, eventsReply.cursor);
|
||
}
|
||
return eventLog;
|
||
}
|
||
async getDwnMessage({ author, messageCid }) {
|
||
let { reply } = await this.agent.dwn.processRequest({
|
||
author,
|
||
target: author,
|
||
messageType: DwnInterface.MessagesGet,
|
||
messageParams: {
|
||
messageCids: [messageCid]
|
||
}
|
||
});
|
||
if (!(reply.entries && reply.entries.length === 1)) {
|
||
return void 0;
|
||
}
|
||
const [messageEntry] = reply.entries;
|
||
const message = messageEntry.message;
|
||
if (!message) {
|
||
return void 0;
|
||
}
|
||
let dwnMessageWithBlob = { message };
|
||
if (isRecordsWrite(messageEntry)) {
|
||
if (messageEntry.encodedData) {
|
||
const dataBytes = import_common13.Convert.base64Url(messageEntry.encodedData).toUint8Array();
|
||
dwnMessageWithBlob.data = new Blob([dataBytes]);
|
||
} else {
|
||
let readResponse = await this.agent.dwn.processRequest({
|
||
author,
|
||
target: author,
|
||
messageType: DwnInterface.RecordsRead,
|
||
messageParams: { filter: { recordId: messageEntry.message.recordId } }
|
||
});
|
||
const reply2 = readResponse.reply;
|
||
if (is2xx(reply2.status.code) && reply2.record) {
|
||
dwnMessageWithBlob.data = await import_common13.NodeStream.consumeToBlob({ readable: reply2.record.data });
|
||
} else if (is4xx(reply2.status.code)) {
|
||
} else {
|
||
const { status: { code, detail } } = reply2;
|
||
throw new Error(`SyncEngineLevel: (${code}) Failed to read data associated with record ${messageEntry.message.recordId}. ${detail}}`);
|
||
}
|
||
}
|
||
}
|
||
return dwnMessageWithBlob;
|
||
}
|
||
async getSyncPeerState({ syncDirection }) {
|
||
const registeredIdentities = await this._db.sublevel("registeredIdentities").keys().all();
|
||
const syncPeerState = [];
|
||
for (let did of registeredIdentities) {
|
||
const dwnEndpointUrls = await getDwnServiceEndpointUrls(did, this.agent.did);
|
||
if (dwnEndpointUrls.length === 0) {
|
||
continue;
|
||
}
|
||
for (let dwnUrl of dwnEndpointUrls) {
|
||
const cursor = await this.getCursor(did, dwnUrl, syncDirection);
|
||
syncPeerState.push({ did, dwnUrl, cursor });
|
||
}
|
||
}
|
||
return syncPeerState;
|
||
}
|
||
async getCursor(did, dwnUrl, direction) {
|
||
const cursorKey = `${did}~${dwnUrl}~${direction}`;
|
||
const cursorsStore = this.getCursorStore();
|
||
try {
|
||
const cursorValue = await cursorsStore.get(cursorKey);
|
||
if (cursorValue) {
|
||
return JSON.parse(cursorValue);
|
||
}
|
||
} catch (error) {
|
||
if (error.notFound) {
|
||
return void 0;
|
||
}
|
||
}
|
||
}
|
||
async setCursor(did, dwnUrl, direction, cursor) {
|
||
const cursorKey = `${did}~${dwnUrl}~${direction}`;
|
||
const cursorsStore = this.getCursorStore();
|
||
await cursorsStore.put(cursorKey, JSON.stringify(cursor));
|
||
}
|
||
/**
|
||
* The message store is used to prevent "echoes" that occur during a sync pull operation.
|
||
* After a message is confirmed to already be synchronized on the local DWN, its CID is added
|
||
* to the message store to ensure that any subsequent pull attempts are skipped.
|
||
*/
|
||
async messageExists(did, messageCid) {
|
||
const messageStore = this.getMessageStore(did);
|
||
try {
|
||
await messageStore.get(messageCid);
|
||
return true;
|
||
} catch (error) {
|
||
if (error.notFound) {
|
||
return false;
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
async addMessage(did, messageCid) {
|
||
const messageStore = this.getMessageStore(did);
|
||
return await messageStore.put(messageCid, "");
|
||
}
|
||
getMessageStore(did) {
|
||
return this._db.sublevel("history").sublevel(did).sublevel("messages");
|
||
}
|
||
getCursorStore() {
|
||
return this._db.sublevel("cursors");
|
||
}
|
||
getPushQueue() {
|
||
return this._db.sublevel("pushQueue");
|
||
}
|
||
getPullQueue() {
|
||
return this._db.sublevel("pullQueue");
|
||
}
|
||
};
|
||
|
||
// src/test-harness.ts
|
||
var import_level2 = require("level");
|
||
var import_common14 = require("@web5/common");
|
||
var import_dwn_sdk_js6 = require("@tbd54566975/dwn-sdk-js");
|
||
var import_dids5 = require("@web5/dids");
|
||
var PlatformAgentTestHarness = class _PlatformAgentTestHarness {
|
||
constructor(params) {
|
||
this.agent = params.agent;
|
||
this.agentStores = params.agentStores;
|
||
this.didResolverCache = params.didResolverCache;
|
||
this.dwn = params.dwn;
|
||
this.dwnDataStore = params.dwnDataStore;
|
||
this.dwnEventLog = params.dwnEventLog;
|
||
this.dwnMessageStore = params.dwnMessageStore;
|
||
this.syncStore = params.syncStore;
|
||
this.vaultStore = params.vaultStore;
|
||
}
|
||
async clearStorage() {
|
||
this.agent.agentDid = void 0;
|
||
await this.didResolverCache.clear();
|
||
await this.dwnDataStore.clear();
|
||
await this.dwnEventLog.clear();
|
||
await this.dwnMessageStore.clear();
|
||
await this.syncStore.clear();
|
||
await this.vaultStore.clear();
|
||
if (this.agentStores === "memory") {
|
||
const { didApi, identityApi, keyManager } = _PlatformAgentTestHarness.useMemoryStores({ agent: this.agent });
|
||
this.agent.did = didApi;
|
||
this.agent.identity = identityApi;
|
||
this.agent.keyManager = keyManager;
|
||
}
|
||
}
|
||
async closeStorage() {
|
||
await this.didResolverCache.close();
|
||
await this.dwnDataStore.close();
|
||
await this.dwnEventLog.close();
|
||
await this.dwnMessageStore.close();
|
||
await this.syncStore.close();
|
||
await this.vaultStore.close();
|
||
}
|
||
async createAgentDid() {
|
||
this.agent.agentDid = await import_dids5.DidJwk.create({
|
||
options: { algorithm: "Ed25519" }
|
||
});
|
||
}
|
||
async createIdentity({ name, testDwnUrls }) {
|
||
const bearerIdentity = await this.agent.identity.create({
|
||
didMethod: "dht",
|
||
didOptions: {
|
||
services: [
|
||
{
|
||
id: "dwn",
|
||
type: "DecentralizedWebNode",
|
||
serviceEndpoint: testDwnUrls,
|
||
enc: "#enc",
|
||
sig: "#sig"
|
||
}
|
||
],
|
||
verificationMethods: [
|
||
{
|
||
algorithm: "Ed25519",
|
||
id: "sig",
|
||
purposes: ["assertionMethod", "authentication"]
|
||
},
|
||
{
|
||
algorithm: "secp256k1",
|
||
id: "enc",
|
||
purposes: ["keyAgreement"]
|
||
}
|
||
]
|
||
},
|
||
metadata: { name }
|
||
});
|
||
return bearerIdentity;
|
||
}
|
||
async preloadResolverCache({ didUri, resolutionResult }) {
|
||
await this.didResolverCache.set(didUri, resolutionResult);
|
||
}
|
||
static async setup({ agentClass, agentStores, testDataLocation }) {
|
||
agentStores ??= "memory";
|
||
testDataLocation ??= "__TESTDATA__";
|
||
const testDataPath = (path) => `${testDataLocation}/${path}`;
|
||
const cryptoApi = new AgentCryptoApi();
|
||
const rpcClient = new Web5RpcClient();
|
||
const {
|
||
agentVault,
|
||
didApi,
|
||
identityApi,
|
||
keyManager,
|
||
didResolverCache,
|
||
vaultStore
|
||
} = agentStores === "memory" ? _PlatformAgentTestHarness.useMemoryStores() : _PlatformAgentTestHarness.useDiskStores({ testDataLocation });
|
||
const dwnDataStore = new import_dwn_sdk_js6.DataStoreLevel({ blockstoreLocation: testDataPath("DWN_DATASTORE") });
|
||
const dwnEventLog = new import_dwn_sdk_js6.EventLogLevel({ location: testDataPath("DWN_EVENTLOG") });
|
||
const dwnEventStream = new import_dwn_sdk_js6.EventEmitterStream();
|
||
const dwnMessageStore = new import_dwn_sdk_js6.MessageStoreLevel({
|
||
blockstoreLocation: testDataPath("DWN_MESSAGESTORE"),
|
||
indexLocation: testDataPath("DWN_MESSAGEINDEX")
|
||
});
|
||
const dwn = await AgentDwnApi.createDwn({
|
||
dataPath: testDataLocation,
|
||
dataStore: dwnDataStore,
|
||
didResolver: didApi,
|
||
eventLog: dwnEventLog,
|
||
eventStream: dwnEventStream,
|
||
messageStore: dwnMessageStore
|
||
});
|
||
const dwnApi = new AgentDwnApi({ dwn });
|
||
const syncStore = new import_level2.Level(testDataPath("SYNC_STORE"));
|
||
const syncEngine = new SyncEngineLevel({ db: syncStore });
|
||
const syncApi = new AgentSyncApi({ syncEngine });
|
||
const agent = new agentClass({
|
||
agentVault,
|
||
cryptoApi,
|
||
didApi,
|
||
dwnApi,
|
||
identityApi,
|
||
keyManager,
|
||
rpcClient,
|
||
syncApi
|
||
});
|
||
return new _PlatformAgentTestHarness({
|
||
agent,
|
||
agentStores,
|
||
didResolverCache,
|
||
dwn,
|
||
dwnDataStore,
|
||
dwnEventLog,
|
||
dwnMessageStore,
|
||
syncStore,
|
||
vaultStore
|
||
});
|
||
}
|
||
static useDiskStores({ agent, testDataLocation }) {
|
||
const testDataPath = (path) => `${testDataLocation}/${path}`;
|
||
const vaultStore = new import_common14.LevelStore({ location: testDataPath("VAULT_STORE") });
|
||
const agentVault = new HdIdentityVault({ keyDerivationWorkFactor: 1, store: vaultStore });
|
||
const didResolverCache = new import_dids5.DidResolverCacheLevel({
|
||
location: testDataPath("DID_RESOLVERCACHE")
|
||
});
|
||
const didApi = new AgentDidApi({
|
||
agent,
|
||
didMethods: [import_dids5.DidDht, import_dids5.DidJwk],
|
||
resolverCache: didResolverCache,
|
||
store: new DwnDidStore()
|
||
});
|
||
const identityApi = new AgentIdentityApi({ agent, store: new DwnIdentityStore() });
|
||
const keyManager = new LocalKeyManager2({ agent, keyStore: new DwnKeyStore() });
|
||
return { agentVault, didApi, didResolverCache, identityApi, keyManager, vaultStore };
|
||
}
|
||
static useMemoryStores({ agent } = {}) {
|
||
const vaultStore = new import_common14.MemoryStore();
|
||
const agentVault = new HdIdentityVault({ keyDerivationWorkFactor: 1, store: vaultStore });
|
||
const didResolverCache = new DidResolverCacheMemory();
|
||
const didApi = new AgentDidApi({
|
||
agent,
|
||
didMethods: [import_dids5.DidDht, import_dids5.DidJwk],
|
||
resolverCache: didResolverCache,
|
||
store: new InMemoryDidStore()
|
||
});
|
||
const keyManager = new LocalKeyManager2({ agent, keyStore: new InMemoryKeyStore() });
|
||
const identityApi = new AgentIdentityApi({ agent, store: new InMemoryIdentityStore() });
|
||
return { agentVault, didApi, didResolverCache, identityApi, keyManager, vaultStore };
|
||
}
|
||
};
|
||
// Annotate the CommonJS export names for ESM import in node:
|
||
0 && (module.exports = {
|
||
AgentCryptoApi,
|
||
AgentDidApi,
|
||
AgentDwnApi,
|
||
AgentIdentityApi,
|
||
AgentSyncApi,
|
||
BearerIdentity,
|
||
DidInterface,
|
||
DidRpcMethod,
|
||
DwnConstant,
|
||
DwnDataStore,
|
||
DwnDateSort,
|
||
DwnDidStore,
|
||
DwnEncryptionAlgorithm,
|
||
DwnEventSubscriptionHandler,
|
||
DwnIdentityStore,
|
||
DwnInterface,
|
||
DwnKeyDerivationScheme,
|
||
DwnKeyStore,
|
||
DwnMessageSubscription,
|
||
DwnPaginationCursor,
|
||
DwnPublicKeyJwk,
|
||
DwnRecordSubscriptionHandler,
|
||
DwnSigner,
|
||
HdIdentityVault,
|
||
HttpWeb5RpcClient,
|
||
InMemoryDataStore,
|
||
InMemoryDidStore,
|
||
InMemoryIdentityStore,
|
||
InMemoryKeyStore,
|
||
LocalKeyManager,
|
||
PlatformAgentTestHarness,
|
||
SyncEngineLevel,
|
||
Web5RpcClient,
|
||
WebSocketWeb5RpcClient,
|
||
blobToIsomorphicNodeReadable,
|
||
dwnMessageConstructors,
|
||
getDwnServiceEndpointUrls,
|
||
getPaginationCursor,
|
||
getRecordAuthor,
|
||
getRecordMessageCid,
|
||
isDidRequest,
|
||
isDwnMessage,
|
||
isDwnRequest,
|
||
isIdentityMetadata,
|
||
isPortableIdentity,
|
||
isRecordsWrite,
|
||
webReadableToIsomorphicNodeReadable
|
||
});
|
||
/*! Bundled license information:
|
||
|
||
@noble/hashes/esm/utils.js:
|
||
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
||
|
||
@noble/curves/esm/abstract/utils.js:
|
||
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
||
|
||
@noble/curves/esm/abstract/modular.js:
|
||
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
||
|
||
@noble/curves/esm/abstract/curve.js:
|
||
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
||
|
||
@noble/curves/esm/abstract/edwards.js:
|
||
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
||
|
||
@noble/curves/esm/ed25519.js:
|
||
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
||
|
||
ed25519-keygen/hdkey.js:
|
||
(*! micro-ed25519-hdkey - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
||
*/
|
||
//# sourceMappingURL=index.js.map
|