Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Keystore types #524

Merged
merged 2 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
28 changes: 23 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,30 @@ export {
buildUserInviteTopic,
buildUserPrivateStoreTopic,
} from './utils'
export { Keystore, InMemoryKeystore, TopicData } from './keystore'
export {
Keystore,
InMemoryKeystore,
TopicData,
keystoreApiDefs,
} from './keystore'
apiDefs as keystoreApiDefs,
snapApiDefs as snapKeystoreApiDefs,
KeystoreApiDefs,
KeystoreApiEntries,
KeystoreApiMethods,
KeystoreApiRequestEncoders,
KeystoreApiRequestValues,
KeystoreApiResponseDecoders,
KeystoreInterface,
KeystoreInterfaceRequestValues,
KeystoreInterfaces,
KeystoreRPC,
KeystoreRPCCodec,
SnapKeystoreApiDefs,
SnapKeystoreApiEntries,
SnapKeystoreApiMethods,
SnapKeystoreApiRequestEncoders,
SnapKeystoreApiRequestValues,
SnapKeystoreApiResponseDecoders,
SnapKeystoreInterface,
SnapKeystoreInterfaceRequestValues,
} from './keystore/rpcDefinitions'
export {
KeystoreProvider,
KeyGeneratorKeystoreProvider,
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
Loading