-
Notifications
You must be signed in to change notification settings - Fork 72
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
Wrapped Keys #513
Wrapped Keys #513
Conversation
… and add `AcssParams` to `AccessControlConditions` type for accuracy
…t implementation - Responsible for communicating with off-chain storage that is authenticated with a valid LIT session signature
- Methods are responsible for performing generate, signMessage, signTransaction operations using LIT actions - `evm` and `solana` type `network` use our pre-defined actions IPFS CIDs - `custom` type `network` requires the caller provide either LIT action source code or an IPFS CID where the source can be found - Interfaces for executing the LIT actions directly are not exported from the package, so we can change our LIT actions APIs without making breaking changes to the top-level client APIs - Consistent error and response handling is executed for all LIT action executions using methods in `utils` in `lit-actions-client`.
- Update constants for multiple target network support - Move interfaces to more standard-named `types` file - Rename tests to `utils.spec.ts` since it only tests util functions - Update util functions imperative style to use declarative methods for better readability - Update getPkpAddressFromSessionSig() util function to permit more than 3 capabilities to be present without throwing an error - Update `getPkpAccessControlCondition()` to actually return _one condition_, not an array with one condition in it. Type updated accordingly.
…txHash return in solana action
…private key action with JSDoc comment
- Glues together service-client and lit-action-client appropriately with public API surface area to be exposed to consumers of the package - Updated JSDocs on exported functions to reference types instead of repeating mostly the same strings on each function's JSDoc - Added `getEncryptedKeyMetadata` method to facilitate arbitrary usage of the encrypted key without needing to use our built-in logic/assumptions around LIT actions, and to allow callers to fetch the `address` and other metadata that is being persisted to the backend - Remove legacy `wrapped-keys.ts` file as its logic has been migrated into `api`
…cit. - Namespaced API methods and constants separately from exported types - Only exported types, interfaces and constants that are part of the API layer that end consumers will interact with
…e -- it breaks a ton of code in the `canonicalFormatter` - Replaced type on `getPkpAccessControlCondition()` with `AccsDefaultParams`, a narrower type instead. - Added `EthereumLitTransaction` type to exports
…they don't play nicely with string union types.
…-keys API - Fixed missing logic in `generatePrivateKey` for handling `custom` case - Added some code to testCustomSignTransactionWrappedKey to bring it closer to doing what its title implies it does. It fails.
- Replace try/catch at top-level `tinny-operations` so that failed tests don't...fail. But they still do ?!
…onfusion with the `algo` key on sigs returned from the LIT network nodes
…pes in favour of composition for the 'custom' case
…nclude `litNetwork` rather than the entire LitNodeClient instance. Clarify types of `litNetwork` returned from previously persisted data
The local-tests/tests/testImportWrappedKey.ts test is importing a Solana key but says the This test also isn't really testing if the key was imported correctly, but I think we'd have to use either |
export const api = { | ||
exportPrivateKey, | ||
generatePrivateKey, | ||
getEncryptedKeyMetadata, | ||
importPrivateKey, | ||
signMessageWithEncryptedKey, | ||
signTransactionWithEncryptedKey, | ||
storeEncryptedKeyMetadata, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious why we're wrapping this in an object, does this make it not tree shakeable if a user is only using one or a few of these methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's still tree-shakeable; bundlers will only include the values from the exported object that the consumer actually uses, and these are all specific functions, rather than classes with mixed dependencies.
I actually had considered adding a createClient({litNodeClient, pkpSessionSigs})
method to the module exports, which would eliminate those 2 arguments from all of our API functions, simplify all of our typescript types and make our function signature more concise and easy to understand -- but decided not to because the return value of createClient()
would not have been tree-shakeable and would have always included all of the functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If both are tree-shakable then what's the reason behind export const api = {..}
instead of directly exporting the individual functions? @MaximusHaximus
SignMessageParams; | ||
|
||
interface BaseLitTransaction { | ||
chain: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we could restrict this to the supported EVM networks for EthereumLitTransaction
s and mainnet-beta | testnet | devnet
for Solana txs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure for later, can you cut a ticket for tracking this @spacesailor24 and comment here
…ts` and eliminate circular type imports
@@ -82,7 +82,7 @@ const { | |||
clusterApiUrl(unsignedTransaction.chain), | |||
'confirmed' | |||
); | |||
await solanaConnection.sendRawTransaction(transaction.serialize()); | |||
await solanaConnection.sendRawTransaction(transaction.serialize()); // FIXME: Shouldn't this return the tx hash for consistency with the evm action? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it should, however, in my testing waiting for the transaction hash always resulted in the Lit Action timing out. I'm going to add the following code to the docs to show users how to obtain the tx hash/receipt outside of the Lit Action:
// Assuming signature is in base64, convert it back to byte array for querying
const byteSignature = Buffer.from(signature, 'base64');
// Wait for confirmation and fetch the transaction details
const confirmation = await solanaConnection.confirmTransaction(byteSignature);
console.log('Transaction confirmation status:', confirmation.value);
// Get the transaction receipt
const receipt = await solanaConnection.getTransaction(byteSignature.toString('base64'), { commitment: 'confirmed' });
console.log('Transaction receipt:', receipt);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can't keep the return values and behaviours of the functions consistent, it might make more sense to split them into separately named methods at the API layer. It's already a bit funky that we have a union type for the transaction -- if the return values and behaviour of the actual LIT actions differ as well, keeping them under a single method name in our API is probably hurting more than helping consumers of the SDK.
Can you look into this approach and see if it will work for us? @spacesailor24
https://solana.stackexchange.com/a/24
Are there tests for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments
@@ -31,7 +28,7 @@ export const testEthereumBroadcastTransactionGeneratedKey = async ( | |||
|
|||
const { pkpAddress, generatedPublicKey } = await generatePrivateKey({ | |||
pkpSessionSigs, | |||
network: NETWORK_EVM, | |||
network: 'evm', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Why remove the constant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So once we started restricting network
to be specific values using a string union type, I discovered that if someone used the const
to call our method, the typescript checker would throw an error TS2322: Type string is not assignable to type Network
-- because the type of the constant is string
, decides that it is too generate -- it's not a specific string literal.
So then I tried defining these as:
export const NETWORK_EVM: Network = 'evm';
export const NETWORK_SOLANA: Network = 'solana';
That did fix the consumers code's typechecking, but then, inside the SDK I started getting TS checker errors in the lit-actions-client/constants.ts
file where we define the LIT_ACTION_CID_REPOSITORY
:
TS2739: Type Readonly<{ [x: string]: 'QmdYUhPCCK5hpDWMK1NiDLNLG6RZQy61QE4J7dBm1Y2nbA' | 'QmSi9GL2weCFEP1SMAUw5PDpZRr436Zt3tLUNrSECPA5dT'; }> is missing the following properties from type Readonly<Record<Network, string>>: evm, solana
Even though the entry clearly has the correct keys in it, defined by their constants :(
import { LitCidRepository } from './types';
import { NETWORK_EVM, NETWORK_SOLANA } from '../constants';
import { Network } from '../types';
export type LitActionType =
| 'signTransaction'
| 'signMessage'
| 'generateEncryptedKey';
export type LitCidRepositoryEntry = Readonly<Record<Network, string>>;
export type LitCidRepository = Readonly<
Record<LitActionType, LitCidRepositoryEntry>
>;
const LIT_ACTION_CID_REPOSITORY: LitCidRepository = {
signTransaction: Object.freeze({
[NETWORK_EVM]: 'QmdYUhPCCK5hpDWMK1NiDLNLG6RZQy61QE4J7dBm1Y2nbA',
[NETWORK_SOLANA]: 'QmSi9GL2weCFEP1SMAUw5PDpZRr436Zt3tLUNrSECPA5dT',
}),
signMessage: Object.freeze({
[NETWORK_EVM]: 'QmTMGcyp77NeppGaqF2DmE1F8GXTSxQYzXCrbE7hNudUWx',
[NETWORK_SOLANA]: 'QmYQC6cd4EMvyB4XPkfEEAwNXJupRZWU5JsTCUrjey4ovp',
}),
generateEncryptedKey: Object.freeze({
[NETWORK_EVM]: 'QmaoPMSqcze3NW3KSA75ecWSkcmWT1J7kVr8LyJPCKRvHd',
[NETWORK_SOLANA]: 'QmdRBXYLYvcNHrChmsZ2jFDY8dA99CcSdqHo3p1ES3UThL',
}),
};
export { LIT_ACTION_CID_REPOSITORY };
In the end, I discovered that I had to use string literals in the LIT_ACTION_CID_REPOSITORY to make TS happy; I couldn't use our consts as keys. Then I was faced with a conundrum; now external consumers would be using constants that we didn't use ourselves. Since TS can't use runtime data as types, I went the opposite direction and exported the string union type. I did some digging around the net to try and identify a solution that would let me use the same consts that consumers used to define the keys on our repository, but couldn't find a solution and it was taking quite a lot of time at that point.
Given that these are user-facing arguments, maybe it'd be fine for our internal files to use string constants when defining actual records, but external consumers still have constants to use if they want to? I've pushed a commit that makes that change -- take a look and LMK what you think. I also added exported constants for keyType
using the same pattern.
packages/types/src/lib/types.ts
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have we changed these to interfaces do we plan to extend it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing changed -- our ESLint rule configuration (and SDK-wide convention) enforces that we always use interfaces unless we are using features that require a type. I ran eslint --fix
on the file and it fixed the entries that should have been interfaces :). See first commit in the pr here: 21cfaf3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then EsLint is not being run in the CI? @MaximusHaximus @Ansonhkg ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right, there is no mutative CI that changes files, and not everyone has their editors set to run eslint --fix
on save, so these things happen ™️. It's been on the roadmap to implement for a few months.
packages/wrapped-keys/src/lib/litActions/solana/src/signTransactionWithSolanaEncryptedKey.js
Show resolved
Hide resolved
…ute correct keyType in `generatePrivateKey()`
Not directly; they are used by all of the other tests, so are extensively tested alreeady |
Addressed feedback and responded to some questions with questions @DashKash54 :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
4be606e
into
feature/lit-3125-sdk-for-wrapping-up-walletx-2
Description
keyType
andpublicKey
keys to the persisted metadata, and requires they are provided when consumers call the methods that require themlit-actions-client
contains all code specific to interacting with the lit actions,service-client
contains all of the same for interactions with the wrapped keys backend, andapi
contains definitions of all top-level API methods that will be exposed to the consumer of the packageindex.ts
rather than blanket exporting everything in the package with * exports. The goal here is to facilitate us being able to modify the LIT action APIs without making breaking changes to the top-level API methods that consumers are using as we need to.customXXXX
methods, in favour of us instead add a basic documentation blurb to the JSDocs for prospective integrators that shows them how easy it is to get the private key metadata from the wrapped keys service, and then submit it to any custom LIT action, using the LitNodeClient instance that they already have in scope. Thecustom
behaviour in the SDK exposes internals of LIT actions to our consumer API, and would cause us to get tickets against the wrapped-keys SDK that are actually issues with custom LIT Action implementations on top of making the API worse for consumers of the package who want to use 'supported' networks.e.g.:
Type of change
How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
Checklist: