Skip to content

Commit

Permalink
refactor: update Keystore types
Browse files Browse the repository at this point in the history
  • Loading branch information
rygine committed Jan 23, 2024
1 parent c5fe2d2 commit 3ab0700
Show file tree
Hide file tree
Showing 18 changed files with 272 additions and 97 deletions.
10 changes: 5 additions & 5 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { KeystoreAuthenticator } from './authn'
import { Flatten } from './utils/typedefs'
import BackupClient, { BackupType } from './message-backup/BackupClient'
import { createBackupClient } from './message-backup/BackupClientFactory'
import { Keystore } from './keystore'
import {
KeyGeneratorKeystoreProvider,
KeystoreProvider,
Expand All @@ -47,6 +46,7 @@ import { packageName, version } from './snapInfo.json'
import { ExtractDecodedType } from './types/client'
import type { WalletClient } from 'viem'
import { Contacts } from './Contacts'
import { KeystoreInterfaces } from './keystore/rpcDefinitions'
const { Compression } = proto

// eslint-disable @typescript-eslint/explicit-module-boundary-types
Expand Down Expand Up @@ -147,7 +147,7 @@ export type KeyStoreOptions = {
* The client will attempt to use each one in sequence until one successfully
* returns a Keystore instance
*/
keystoreProviders: KeystoreProvider[]
keystoreProviders: KeystoreProvider<KeystoreInterfaces>[]
/**
* Enable the Keystore to persist conversations in the provided storage interface
*/
Expand Down Expand Up @@ -252,7 +252,7 @@ export function defaultOptions(opts?: Partial<ClientOptions>): ClientOptions {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default class Client<ContentTypes = any> {
address: string
keystore: Keystore
keystore: KeystoreInterfaces
apiClient: ApiClient
contacts: Contacts
publicKeyBundle: PublicKeyBundle
Expand All @@ -271,7 +271,7 @@ export default class Client<ContentTypes = any> {
publicKeyBundle: PublicKeyBundle,
apiClient: ApiClient,
backupClient: BackupClient,
keystore: Keystore
keystore: KeystoreInterfaces
) {
this.knownPublicKeyBundles = new Map<
string,
Expand Down Expand Up @@ -857,7 +857,7 @@ async function bootstrapKeystore(
opts: ClientOptions,
apiClient: ApiClient,
wallet: Signer | null
): Promise<Keystore> {
) {
for (const provider of opts.keystoreProviders) {
try {
return await provider.newKeystore(opts, apiClient, wallet ?? undefined)
Expand Down
6 changes: 3 additions & 3 deletions src/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { bytesToHex } from './crypto/utils'
import { sha256 } from './crypto/encryption'
import { ContentTypeId } from './MessageContent'
import { dateToNs, nsToDate } from './utils'
import { Keystore } from './keystore'
import { buildDecryptV1Request, getResultOrThrow } from './utils/keystore'
import { KeystoreInterfaces } from './keystore/rpcDefinitions'

const headerBytesAndCiphertext = (
msg: proto.Message
Expand Down Expand Up @@ -105,7 +105,7 @@ export class MessageV1 extends MessageBase implements proto.MessageV1 {
}

async decrypt(
keystore: Keystore,
keystore: KeystoreInterfaces,
myPublicKeyBundle: PublicKeyBundle
): Promise<Uint8Array> {
const responses = (
Expand Down Expand Up @@ -151,7 +151,7 @@ export class MessageV1 extends MessageBase implements proto.MessageV1 {
}

static async encode(
keystore: Keystore,
keystore: KeystoreInterfaces,
payload: Uint8Array,
sender: PublicKeyBundle,
recipient: PublicKeyBundle,
Expand Down
13 changes: 9 additions & 4 deletions src/authn/KeystoreAuthenticator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { authn } from '@xmtp/proto'
import { Keystore } from '../keystore'
import { dateToNs } from '../utils'
import Token from './Token'
import {
KeystoreInterface,
KeystoreInterfaces,
} from '../keystore/rpcDefinitions'

const wrapToken = (token: authn.Token): Token => {
if (token instanceof Token) {
Expand All @@ -10,10 +13,12 @@ const wrapToken = (token: authn.Token): Token => {
return new Token(token)
}

export default class KeystoreAuthenticator {
private keystore: Keystore
export default class KeystoreAuthenticator<
T extends KeystoreInterfaces = KeystoreInterface,
> {
private keystore: T

constructor(keystore: Keystore) {
constructor(keystore: T) {
this.keystore = keystore
}

Expand Down
6 changes: 3 additions & 3 deletions src/conversations/JobRunner.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { keystore } from '@xmtp/proto'
import { Mutex } from 'async-mutex'
import { Keystore } from '../keystore'
import Long from 'long'
import { dateToNs, nsToDate } from '../utils'
import { KeystoreInterfaces } from '../keystore/rpcDefinitions'

const CLOCK_SKEW_OFFSET_MS = 10000

Expand All @@ -13,10 +13,10 @@ type UpdateJob<T> = (lastRun: Date | undefined) => Promise<T>
export default class JobRunner {
readonly jobType: JobType
readonly mutex: Mutex
readonly keystore: Keystore
readonly keystore: KeystoreInterfaces
disableOffset: boolean = false

constructor(jobType: JobType, keystore: Keystore) {
constructor(jobType: JobType, keystore: KeystoreInterfaces) {
this.jobType = jobType
this.mutex = new Mutex()
this.keystore = keystore
Expand Down
5 changes: 3 additions & 2 deletions src/keystore/InMemoryKeystore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from './../crypto/PrivateKeyBundle'
import { InvitationV1, SealedInvitation } from './../Invitation'
import { PrivateKey, PublicKeyBundle } from '../crypto'
import { Keystore, TopicData } from './interfaces'
import { TopicData } from './interfaces'
import { decryptV1, encryptV1, encryptV2, decryptV2 } from './encryption'
import { KeystoreError } from './errors'
import {
Expand Down Expand Up @@ -34,6 +34,7 @@ import {
userPreferencesEncrypt,
generateUserPreferencesTopic,
} from '../crypto/selfEncryption'
import { KeystoreInterface } from '..'

const { ErrorCode } = keystore

Expand All @@ -57,7 +58,7 @@ async function deriveKey(
)
}

export default class InMemoryKeystore implements Keystore {
export default class InMemoryKeystore implements KeystoreInterface {
private v1Keys: PrivateKeyBundleV1
private v2Keys: PrivateKeyBundleV2 // Do I need this?
private v1Store: V1Store
Expand Down
38 changes: 16 additions & 22 deletions src/keystore/SnapKeystore.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
import { Keystore } from './interfaces'
import { SnapMeta, snapRPC } from './snapHelpers'
import type { XmtpEnv } from '../Client'
import { apiDefs } from './rpcDefinitions'

async function getResponse<T extends keyof Keystore>(
method: T,
req: Uint8Array | null,
meta: SnapMeta,
snapId: string
): Promise<(typeof apiDefs)[T]['res']> {
return snapRPC(method, apiDefs[method], req, meta, snapId)
}
import {
SnapKeystoreApiEntries,
SnapKeystoreApiRequestValues,
SnapKeystoreInterface,
snapApiDefs,
} from './rpcDefinitions'

export function SnapKeystore(
walletAddress: string,
env: XmtpEnv,
snapId: string
): Keystore {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const generatedMethods: any = {}
) {
const generatedMethods: Partial<SnapKeystoreInterface> = {}

const snapMeta: SnapMeta = {
walletAddress,
env,
}

for (const [method, apiDef] of Object.entries(apiDefs)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
generatedMethods[method] = async (req: any) => {
if (!apiDef.req) {
return getResponse(method as keyof Keystore, null, snapMeta, snapId)
for (const [method, rpc] of Object.entries(
snapApiDefs
) as SnapKeystoreApiEntries) {
generatedMethods[method] = async (req?: SnapKeystoreApiRequestValues) => {
if (!rpc.req) {
return snapRPC(method, rpc, undefined, snapMeta, snapId)
}

return getResponse(method as keyof Keystore, req, snapMeta, snapId)
return snapRPC(method, rpc, req, snapMeta, snapId)
}
}

Expand All @@ -42,5 +36,5 @@ export function SnapKeystore(
async getAccountAddress() {
return walletAddress
},
}
} as SnapKeystoreInterface
}
1 change: 0 additions & 1 deletion src/keystore/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export { default as InMemoryKeystore } from './InMemoryKeystore'
export { SnapKeystore } from './SnapKeystore'
export { apiDefs as keystoreApiDefs } from './rpcDefinitions'
export { V1Store, V2Store } from './conversationStores'
export * from './encryption'
export * from './errors'
Expand Down
1 change: 1 addition & 0 deletions src/keystore/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { WithoutUndefined } from '../utils/typedefs'
/**
* A Keystore is responsible for holding the user's XMTP private keys and using them to encrypt/decrypt/sign messages.
* Keystores are instantiated using a `KeystoreProvider`
* @deprecated Use `KeystoreInterface` instead
*/
export interface Keystore {
/**
Expand Down
4 changes: 2 additions & 2 deletions src/keystore/providers/KeyGeneratorKeystoreProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { KeystoreProviderUnavailableError } from './errors'
import { buildPersistenceFromOptions } from './helpers'
import NetworkKeyManager from './NetworkKeyManager'
import type { Signer } from '../../types/Signer'
import type { Keystore } from '../interfaces'
import type { KeystoreProvider, KeystoreProviderOptions } from './interfaces'
import { KeystoreInterface } from '../rpcDefinitions'

/**
* KeyGeneratorKeystoreProvider will create a new XMTP `PrivateKeyBundle` and persist it to the network
Expand All @@ -19,7 +19,7 @@ export default class KeyGeneratorKeystoreProvider implements KeystoreProvider {
opts: KeystoreProviderOptions,
apiClient: ApiClient,
wallet?: Signer
): Promise<Keystore> {
): Promise<KeystoreInterface> {
if (!wallet) {
throw new KeystoreProviderUnavailableError(
'Wallet required to generate new keys'
Expand Down
4 changes: 2 additions & 2 deletions src/keystore/providers/NetworkKeystoreProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { KeystoreProvider, KeystoreProviderOptions } from './interfaces'
import NetworkKeyLoader from './NetworkKeyManager'
import { KeystoreProviderUnavailableError } from './errors'
import TopicPersistence from '../persistence/TopicPersistence'
import { Keystore } from '../interfaces'
import InMemoryKeystore from '../InMemoryKeystore'
import { buildPersistenceFromOptions } from './helpers'
import { KeystoreInterface } from '../rpcDefinitions'

/**
* NetworkKeystoreProvider will look on the XMTP network for an `EncryptedPrivateKeyBundle`
Expand All @@ -18,7 +18,7 @@ export default class NetworkKeystoreProvider implements KeystoreProvider {
opts: KeystoreProviderOptions,
apiClient: ApiClient,
wallet?: Signer
): Promise<Keystore> {
): Promise<KeystoreInterface> {
if (!wallet) {
throw new KeystoreProviderUnavailableError('No wallet provided')
}
Expand Down
8 changes: 5 additions & 3 deletions src/keystore/providers/SnapProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { KeystoreProviderUnavailableError } from './errors'
import { Keystore } from '../interfaces'
import { KeystoreProvider, KeystoreProviderOptions } from './interfaces'
import { SnapKeystore } from '../SnapKeystore'
import {
Expand All @@ -17,6 +16,7 @@ import { PrivateKeyBundleV1, decodePrivateKeyBundle } from '../../crypto'
import KeyGeneratorKeystoreProvider from './KeyGeneratorKeystoreProvider'
import type { XmtpEnv } from '../../Client'
import { semverGreaterThan } from '../../utils/semver'
import { SnapKeystoreInterface } from '../rpcDefinitions'
const { GetKeystoreStatusResponse_KeystoreStatus: KeystoreStatus } = keystore

export const SNAP_LOCAL_ORIGIN = 'local:http://localhost:8080'
Expand All @@ -27,7 +27,9 @@ export const SNAP_LOCAL_ORIGIN = 'local:http://localhost:8080'
* 2. Check if the user has already setup the Snap with the appropriate keys
* 3. If not, will get keys from the network or create new keys and store them in the Snap
*/
export default class SnapKeystoreProvider implements KeystoreProvider {
export default class SnapKeystoreProvider
implements KeystoreProvider<SnapKeystoreInterface>
{
snapId: string
snapVersion?: string

Expand All @@ -40,7 +42,7 @@ export default class SnapKeystoreProvider implements KeystoreProvider {
opts: KeystoreProviderOptions,
apiClient: ApiClient,
wallet?: Signer
): Promise<Keystore> {
) {
if (!wallet) {
throw new KeystoreProviderUnavailableError('No wallet provided')
}
Expand Down
4 changes: 2 additions & 2 deletions src/keystore/providers/StaticKeystoreProvider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { KeystoreProviderUnavailableError } from './errors'
import { Keystore } from '../interfaces'
import type { KeystoreProvider, KeystoreProviderOptions } from './interfaces'
import InMemoryKeystore from '../InMemoryKeystore'
import {
decodePrivateKeyBundle,
PrivateKeyBundleV2,
} from '../../crypto/PrivateKeyBundle'
import { buildPersistenceFromOptions } from './helpers'
import { KeystoreInterface } from '../rpcDefinitions'

/**
* StaticKeystoreProvider will look for a `privateKeyOverride` in the provided options,
Expand All @@ -16,7 +16,7 @@ import { buildPersistenceFromOptions } from './helpers'
* the client to continue iterating through the `KeystoreProviders` list.
*/
export default class StaticKeystoreProvider implements KeystoreProvider {
async newKeystore(opts: KeystoreProviderOptions): Promise<Keystore> {
async newKeystore(opts: KeystoreProviderOptions): Promise<KeystoreInterface> {
const { privateKeyOverride } = opts
if (!privateKeyOverride) {
throw new KeystoreProviderUnavailableError(
Expand Down
8 changes: 5 additions & 3 deletions src/keystore/providers/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { XmtpEnv, PreEventCallbackOptions } from '../../Client'
import type { Signer } from '../../types/Signer'
import type { Keystore } from '../interfaces'
import type { ApiClient } from '../../ApiClient'
import { Persistence } from '../persistence'
import { KeystoreInterface, KeystoreInterfaces } from '../rpcDefinitions'

export type KeystoreProviderOptions = {
env: XmtpEnv
Expand All @@ -16,10 +16,12 @@ export type KeystoreProviderOptions = {
* A Keystore Provider is responsible for either creating a Keystore instance or throwing a KeystoreUnavailableError
* It is typically used once on application startup to bootstrap the Keystore and load/decrypt the user's private keys
*/
export interface KeystoreProvider {
export interface KeystoreProvider<
T extends KeystoreInterfaces = KeystoreInterface,
> {
newKeystore(
opts: KeystoreProviderOptions,
apiClient: ApiClient,
wallet?: Signer
): Promise<Keystore>
): Promise<T>
}
Loading

0 comments on commit 3ab0700

Please sign in to comment.