From c764065ec95a6babd1f8f999e516f502e6292a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sehyun=20Chung=20=E2=9C=8C=EF=B8=8E?= <41171808+sehyunc@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:54:14 -0800 Subject: [PATCH] docs: add externally managed wallet sections --- .../technical-reference/typescript-sdk.md | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) diff --git a/docs.renegade.fi/docs/technical-reference/typescript-sdk.md b/docs.renegade.fi/docs/technical-reference/typescript-sdk.md index a2bc08d6..dbd251e3 100644 --- a/docs.renegade.fi/docs/technical-reference/typescript-sdk.md +++ b/docs.renegade.fi/docs/technical-reference/typescript-sdk.md @@ -95,6 +95,9 @@ The `Config` object is responsible for storing Renegade wallet state and interna ### Auth Config The `AuthConfig` object is specifically used to interact with the relayer using API keys. If you are using a managed Renegade wallet, you will not need to interact with this object. If you are a permissioned user with API keys, you should follow the instructions [here](#createauthconfig) to create an `AuthConfig` object. +### External Config +The `ExternalConfig` object is used to interact with the relayer using an Externally Managed Wallet. The main difference between this and the `Config` object is that the `ExternalConfig` object does not contain a derivation key. Instead, the user will derive required fields of a wallet, such as the keychain, and custody the wallet secrets themselves. Learn more about Externally Managed Wallets [here](#external-key-management). + ### Actions This SDK exposes actions that are used to interact with a relayer, such as fetching a wallet's state or placing an order. Actions that access private wallet data require API authorization headers, which are automatically added for you when using the SDK. Browse the list of available actions [below](#actions-1). @@ -248,6 +251,87 @@ export const config = createAuthConfig({ import { type AuthConfig } from '@renegade-fi/node' ``` +### createExternalConfig + +Creates a configuration object for interacting with the relayer using an externally managed wallet. The required secrets should be obtained from [generateWalletSecrets](#generating-wallet-secrets) first. + +**Import** +```typescript +import { createExternalConfig } from "@renegade-fi/node" +``` + +**Parameters** +- `signMessage`: `(message: string) => Promise` + - Function that signs messages using ECDSA secp256k1 + - Must return signature as hex string with recovery bit (r[32] || s[32] || v[1]) + - Must use raw message (no Ethereum message prefix) +- `publicKey`: `string` + - The public key corresponding to your signing function + - Used to verify signatures during API requests +- `symmetricKey`: `string` + - Generated symmetric key for API authentication + - Obtained from `generateWalletSecrets` +- `walletId`: `string` + - Unique identifier for your wallet + - Obtained from `generateWalletSecrets` +- `relayerUrl`: `string` + - HTTP URL of the relayer +- `websocketUrl`: `string` + - WebSocket URL of the relayer + +**Basic Example** +```typescript +const config = createExternalConfig({ + signMessage, + publicKey: "0x04800db50009a01fab58a239f204ca14e85682ca0991cb6914f34c4fbd0131eedb54d0ccbe392922e57486b031779bf8b6feab57971c2c406df291c0ab9c529a3d", + symmetricKey: walletSecrets.symmetric_key, + walletId: walletSecrets.wallet_id, + relayerUrl: "https://testnet.cluster0.renegade.fi:3000", + websocketUrl: "wss://testnet.cluster0.renegade.fi:4000", +}); +``` + +**Signing Function Example** +```typescript +// Example signing function implementation +const signMessage = async (message: string) => { + // Validate input + if (!isHex(message)) { + throw new Error("Message must be a hex string"); + } + + // Hash the raw message (no Ethereum prefix) + const hashedMessage = keccak256(message); + + try { + // Generate secp256k1 signature + const sig = await secp.signAsync( + hashedMessage.slice(2), + signingKey.slice(2), + { lowS: true, extraEntropy: false } + ); + + // Format as r[32] || s[32] || v[1] + return concatHex([ + numberToHex(sig.r, { size: 32 }), + numberToHex(sig.s, { size: 32 }), + numberToHex(sig.recovery ? 1 : 0, { size: 1 }), + ]); + } catch (error) { + throw new Error(`Failed to sign message: ${error.message}`); + } +}; +``` + +:::warning Important +The `signMessage` function and `publicKey` must form a valid signing pair. The relayer will verify that signatures produced by `signMessage` can be verified using `publicKey`. +::: + +**Return Type** +```typescript +import { type ExternalConfig } from '@renegade-fi/node' +``` + ## Actions ### createWallet @@ -1076,6 +1160,170 @@ An error may be thrown if: - neither `baseAmount` nor `quoteAmount` is provided - the API request authorization is incorrect / missing +## External Key Management + +Renegade supports external key management as an alternative to the default managed wallet approach. This allows you to maintain complete control over your wallet's cryptographic secrets rather than having them derived and managed by the SDK. + +### Why Use External Key Management? + +External key management is ideal for: +- Users requiring complete control over key generation and storage +- Custom key management infrastructure integration +- Advanced security requirements where keys must be stored in specific hardware/systems +- Scenarios where key backup and recovery needs to be handled manually + +### Key Components + +The external key management flow consists of three main components: + +1. **Wallet Secrets** - A set of cryptographic materials you generate and store: + - Wallet ID & symmetric key: Used for API authentication + - Blinder & share seeds: Used for wallet encryption + - Match key: Used during order matching + +2. **ExternalConfig** - A configuration object that connects your externally managed keys to the SDK + - Does not store or derive keys internally + - Requires you to provide signing capabilities and public keys + +3. **Actions** - Standard SDK actions that work with your external keys + - Require wallet secrets for initial wallet creation/lookup + - Support key rotation for ongoing security + +### Generating Wallet Secrets + +The `generateWalletSecrets` function creates the cryptographic materials needed for an externally managed wallet. + +**Import** +```typescript +import { generateWalletSecrets } from "@renegade-fi/node" +``` + +**Parameters** +- `signer`: A function that generates a secp256k1 signature for a given message + - Input: Unhashed message as hex string + - Output: Signature as hex string with recovery bit + - Must NOT prefix with Ethereum signed message header + +**Example** +```typescript +// Create a signer function that generates a secp256k1 signature for a given message +const signer = async (message: string) => { + // Hash the raw message (do not add Ethereum message prefix) + const hashedMessage = keccak256(message); + + // Sign the hash with your private key + const sig = await secp.signAsync( + hashedMessage.slice(2), + env.PRIVATE_KEY.slice(2), + { lowS: true, extraEntropy: false } + ); + + // Format signature as r[32] || s[32] || v[1] + return concatHex([ + numberToHex(sig.r, { size: 32 }), // r component + numberToHex(sig.s, { size: 32 }), // s component + numberToHex(sig.recovery ? 1 : 0, { size: 1 }) // recovery bit + ]); +}; + +const walletSecrets = await generateWalletSecrets(signer); +``` + +**Return Value** +```typescript +type GeneratedSecrets { + /** Identifies your wallet to the relayer */ + wallet_id: string + + /** Used to generate blinding values for wallet state encryption */ + blinder_seed: `0x${string}` + + /** Used to generate secret shares for wallet state encryption */ + share_seed: `0x${string}` + + /** Used to authenticate API requests to the relayer */ + symmetric_key: `0x${string}` + + /** Used during order matching process */ + sk_match: `0x${string}` +} +``` + +:::warning Security Best Practices +- Store wallet secrets in secure, encrypted storage +- Never expose secrets in logs or client-side code +- Back up secrets securely - they cannot be recovered if lost +- Consider using a hardware security module (HSM) for production deployments +::: + +:::info +Some Ethereum libraries (e.g. `viem`) automatically prefix messages with "\x19Ethereum Signed Message:\n". This will break signature verification - ensure your signer uses the raw message. +::: + +### Wallet Creation / Lookup + +Once you have generated wallet secrets, you can use them to create a wallet or lookup an existing wallet. The `createWallet` and `lookupWallet` actions require wallet secrets to be provided since they are externally managed. Each of these actions accepts an object containing the blinder seed, share seed, and match key as the second parameter. + +```js +const walletSecrets = await generateWalletSecrets(signer); +const parameters: CreateWalletParameters = { + blinderSeed: walletSecrets.blinder_seed, + shareSeed: walletSecrets.share_seed, + skMatch: walletSecrets.sk_match, +}; + +// Create the wallet using the secrets +await createWallet(config, parameters); +``` + +:::note +Make sure you are using an `ExternalConfig` object when calling `createWallet` or `lookupWallet` with wallet secrets. Otherwise, the SDK will attempt to rederive the wallet secrets from wallet seeds and fail. +::: + +### Actions + +Once you have created an Externally Managed Wallet, you can use it to interact with the relayer. When using an Externally Managed Wallet, you should instantiate an [`ExternalConfig`](#createexternalconfig) object instead of a `Config` object and pass it as the first parameter actions. Otherwise, usage is the same as described in the [actions](#actions-1) section above. + + +### Key Rotation + +Key rotation is a security best practice that involves updating your wallet's cryptographic keys. When using the default internally managed wallet, the SDK automatically rotates keys after each wallet update. For externally managed wallets, you'll need to handle key rotation manually. + +#### Implementation + +Keys can be rotated during any wallet update operation by providing a new public key. Your connected relayer will: +1. Verify the new public key is valid +2. Update the wallet's keychain +3. Use the new key for future operations + +**Example** +```typescript +// Generate new signing keypair +const newSigningKey = generateNewSigningKey(); // Your key generation logic +const newPublicKey = derivePublicKey(newSigningKey); + +// Execute operation with key rotation +const { taskId } = await deposit(config, { + // Key rotation parameter + newPublicKey: newPublicKey, + + // Normal deposit parameters + fromAddr: account.address, + mint: token.address, + amount: depositAmount, + permitNonce: nonce, + permitDeadline: deadline, + permit: signature, +}); + +// Update config with new public key +config.setPublicKey(newPublicKey); +``` + +:::info +Key rotation is supported in all wallet update operations including deposits, withdrawals, and order placement. +::: + ## Examples ### Create a wallet