@dwn-protocol/id-sdk
Version:
SDK for accessing the features and capabilities
299 lines (254 loc) • 10.1 kB
text/typescript
import type { KeyValueStore } from '../common/index.js';
import type { DidResolutionResult, DidResolverCache, PortableDid } from '../dids/index.js';
import { Level } from 'level';
import { Jose } from '../crypto/index.js';
import { Dwn, MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@dwn-protocol/id';
import { LevelStore, MemoryStore } from '../common/index.js';
import { DidIonMethod, DidKeyMethod, DidResolver, DidResolverCacheLevel } from '../dids/index.js';
import type { IDManagedAgent } from './types/agent.js';
import { LocalKms } from './kms-local.js';
import { DidManager } from './did-manager.js';
import { DwnManager } from './dwn-manager.js';
import { KeyManager } from './key-manager.js';
import { IDRpcClient } from './rpc-client.js';
import { AppDataVault } from './app-data-store.js';
import { SyncManagerLevel } from './sync-manager.js';
import { cryptoToPortableKeyPair } from './utils.js';
import { DidStoreDwn, DidStoreMemory } from './store-managed-did.js';
import { IdentityManager, ManagedIdentity } from './identity-manager.js';
import { IdentityStoreDwn, IdentityStoreMemory } from './store-managed-identity.js';
import { KeyStoreDwn, KeyStoreMemory, PrivateKeyStoreDwn, PrivateKeyStoreMemory } from './store-managed-key.js';
type CreateMethodOptions = {
agentClass: new (options: any) => IDManagedAgent
agentStores?: 'dwn' | 'memory';
testDataLocation?: string;
}
type TestManagedAgentOptions = {
agent: IDManagedAgent
agentStores: 'dwn' | 'memory';
appDataStore: KeyValueStore<string, any>;
didResolverCache: DidResolverCache;
dwn: Dwn;
dwnDataStore: DataStoreLevel;
dwnEventLog: EventLogLevel;
dwnMessageStore: MessageStoreLevel;
syncStore: Level;
}
export class TestManagedAgent {
agent: IDManagedAgent;
agentStores: 'dwn' | 'memory';
appDataStore: KeyValueStore<string, any>;
didResolverCache: DidResolverCache;
dwn: Dwn;
dwnDataStore: DataStoreLevel;
dwnEventLog: EventLogLevel;
dwnMessageStore: MessageStoreLevel;
syncStore: Level;
constructor(options: TestManagedAgentOptions) {
this.agent = options.agent;
this.agentStores = options.agentStores;
this.appDataStore = options.appDataStore;
this.didResolverCache = options.didResolverCache;
this.dwn = options.dwn;
this.dwnDataStore = options.dwnDataStore;
this.dwnEventLog = options.dwnEventLog;
this.dwnMessageStore = options.dwnMessageStore;
this.syncStore = options.syncStore;
}
async clearStorage(): Promise<void> {
this.agent.agentDid = undefined;
await this.appDataStore.clear();
await this.didResolverCache.clear();
await this.dwnDataStore.clear();
await this.dwnEventLog.clear();
await this.dwnMessageStore.clear();
await this.syncStore.clear();
/** Easiest way to start with fresh in-memory stores is to
* re-instantiate all of the managed agent components */
if (this.agentStores === 'memory') {
const { didManager, identityManager, keyManager } = TestManagedAgent.useMemoryStorage({ agent: this.agent });
this.agent.didManager = didManager;
this.agent.identityManager = identityManager;
this.agent.keyManager = keyManager;
}
}
async closeStorage(): Promise<void> {
await this.appDataStore.close();
await this.didResolverCache.close();
await this.dwnDataStore.close();
await this.dwnEventLog.close();
await this.dwnMessageStore.close();
await this.syncStore.close();
}
static async create(options: CreateMethodOptions): Promise<TestManagedAgent> {
let { agentClass, agentStores, testDataLocation } = options;
agentStores ??= 'memory';
testDataLocation ??= '__TESTDATA__';
const testDataPath = (path: string) => `${testDataLocation}/${path}`;
const { appData, appDataStore, didManager, didResolverCache, identityManager, keyManager } = (agentStores === 'memory')
? TestManagedAgent.useMemoryStorage()
: TestManagedAgent.useDiskStorage({ testDataLocation });
// Instantiate DID resolver.
const didMethodApis = [DidIonMethod, DidKeyMethod];
const didResolver = new DidResolver({
cache : didResolverCache,
didResolvers : didMethodApis
});
// Instantiate custom stores to use with DWN instance.
const dwnDataStore = new DataStoreLevel({ blockstoreLocation: testDataPath('DWN_DATASTORE') });
const dwnEventLog = new EventLogLevel({ location: testDataPath('DWN_EVENTLOG') });
const dwnMessageStore = new MessageStoreLevel({
blockstoreLocation : testDataPath('DWN_MESSAGESTORE'),
indexLocation : testDataPath('DWN_MESSAGEINDEX')
});
// Instantiate custom DWN instance.
const dwn = await Dwn.create({
eventLog : dwnEventLog,
dataStore : dwnDataStore,
// @ts-expect-error because the DidResolver implementation doesn't have the dump() method.
didResolver : didResolver,
messageStore : dwnMessageStore
});
// Instantiate a DwnManager using the custom DWN instance.
const dwnManager = new DwnManager({ dwn });
// Instantiate an RPC Client.
const rpcClient = new IDRpcClient();
// Instantiate a custom SyncManager and LevelDB-backed store.
const syncStore = new Level(testDataPath('SYNC_STORE'));
const syncManager = new SyncManagerLevel({ db: syncStore });
const agent = new agentClass({
agentDid: '',
appData,
didManager,
didResolver,
dwnManager,
identityManager,
keyManager,
rpcClient,
syncManager
});
return new TestManagedAgent({
agent,
agentStores,
appDataStore,
didResolverCache,
dwn,
dwnDataStore,
dwnEventLog,
dwnMessageStore,
syncStore,
});
}
async createAgentDid(): Promise<void> {
// Create an a DID and key set for the Agent.
const agentDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' });
const privateCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].privateKeyJwk! });
const publicCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].publicKeyJwk! });
const agentSigningKey = { privateKey: privateCryptoKey, publicKey: publicCryptoKey };
// Set the DID as the default signing key.
const alias = await this.agent.didManager.getDefaultSigningKey({ did: agentDid.did });
const defaultSigningKey = cryptoToPortableKeyPair({ cryptoKeyPair: agentSigningKey, keyData: { alias, kms: 'memory' } });
await this.agent.keyManager.setDefaultSigningKey({ key: defaultSigningKey });
// Set the DID as the Agent's DID.
this.agent.agentDid = agentDid.did;
}
public async createIdentity(options: {
keyAlgorithm?: 'Ed25519' | 'secp256k1';
testDwnUrls: string[]
}): Promise<{ did: PortableDid, identity: ManagedIdentity }> {
// Default to generating Ed25519 keys.
const { keyAlgorithm, testDwnUrls } = options;
const didOptions = await DidIonMethod.generateDwnOptions({
signingKeyAlgorithm : keyAlgorithm,
serviceEndpointNodes : testDwnUrls
});
// Create a PortableDid.
const did = await DidIonMethod.create({
anchor: false,
...didOptions
});
// Create a ManagedIdentity.
const identity: ManagedIdentity = {
did : did.did,
name : 'Test'
};
return { did, identity };
}
private static useDiskStorage(options: { agent?: IDManagedAgent, testDataLocation: string }) {
const { agent, testDataLocation } = options;
const testDataPath = (path: string) => `${testDataLocation}/${path}`;
const appDataStore = new LevelStore(testDataPath('APPDATA'));
const appData = new AppDataVault({
keyDerivationWorkFactor : 1,
store : appDataStore
});
const didManager = new DidManager({
agent,
didMethods : [DidIonMethod, DidKeyMethod],
store : new DidStoreDwn()
});
const didResolverCache = new DidResolverCacheLevel({
location: testDataPath('DID_RESOLVERCACHE')
});
const identityManager = new IdentityManager({
agent,
store: new IdentityStoreDwn()
});
const localKmsDwn = new LocalKms({
agent,
kmsName : 'local',
keyStore : new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/dwn/kms-key' }),
privateKeyStore : new PrivateKeyStoreDwn()
});
const localKmsMemory = new LocalKms({
agent,
kmsName: 'memory'
});
const keyManager = new KeyManager({
agent,
kms: {
local : localKmsDwn,
memory : localKmsMemory
},
store: new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/dwn/managed-key' })
});
return { appData, appDataStore, didManager, didResolverCache, identityManager, keyManager };
}
private static useMemoryStorage(options?: { agent: IDManagedAgent }) {
const { agent } = options ?? {};
const appDataStore = new MemoryStore<string, any>();
const appData = new AppDataVault({
keyDerivationWorkFactor : 1,
store : appDataStore
});
const didManager = new DidManager({
agent,
didMethods : [DidIonMethod, DidKeyMethod],
store : new DidStoreMemory()
});
const didResolverCache = new MemoryStore<string, DidResolutionResult | void>();
const identityManager = new IdentityManager({
agent,
store: new IdentityStoreMemory()
});
const localKmsDwn = new LocalKms({
agent,
kmsName : 'local',
keyStore : new KeyStoreMemory(),
privateKeyStore : new PrivateKeyStoreMemory()
});
const localKmsMemory = new LocalKms({
agent,
kmsName: 'memory'
});
const keyManager = new KeyManager({
agent,
kms: {
local : localKmsDwn,
memory : localKmsMemory
},
store: new KeyStoreMemory()
});
return { appData, appDataStore, didManager, didResolverCache, identityManager, keyManager };
}
}