diff --git a/api-specs/openrpc-signing-api.json b/api-specs/openrpc-signing-api.json index 5795ff153..eefb3d50a 100644 --- a/api-specs/openrpc-signing-api.json +++ b/api-specs/openrpc-signing-api.json @@ -25,10 +25,9 @@ "type": "string", "description": "Hash of the prepared transaction that will be signed." }, - "publicKey": { - "title": "publicKey", - "type": "string", - "description": "Public key to use to sign the transaction." + "keyIdentifier": { + "$ref": "#/components/schemas/KeyIdentifier", + "description": "Identifier for the key to use for signing. At least one of publicKey or id must be provided." }, "internalTxId": { "title": "internalTxId", @@ -36,7 +35,7 @@ "description": "Internal txId used by the Wallet Gateway to store the transaction." } }, - "required": ["tx", "txHash", "publicKey"] + "required": ["tx", "txHash", "keyIdentifier"] } } ], @@ -279,6 +278,54 @@ "type": "null", "description": "Represents a null value, used in responses where no data is returned." }, + "KeyIdentifierWithPublicKey": { + "title": "KeyIdentifierWithPublicKey", + "type": "object", + "properties": { + "publicKey": { + "title": "publicKey", + "type": "string", + "description": "Public key to use to sign the transaction." + }, + "id": { + "title": "id", + "type": "string", + "description": "Unique identifier for the key" + } + }, + "required": ["publicKey"], + "description": "Key identifier with publicKey (id is optional)." + }, + "KeyIdentifierWithId": { + "title": "KeyIdentifierWithId", + "type": "object", + "properties": { + "publicKey": { + "title": "publicKey", + "type": "string", + "description": "Public key to use to sign the transaction." + }, + "id": { + "title": "id", + "type": "string", + "description": "Unique identifier for the key" + } + }, + "required": ["id"], + "description": "Key identifier with id (publicKey is optional)." + }, + "KeyIdentifier": { + "title": "KeyIdentifier", + "oneOf": [ + { + "$ref": "#/components/schemas/KeyIdentifierWithPublicKey" + }, + { + "$ref": "#/components/schemas/KeyIdentifierWithId" + } + ], + "description": "Identifier for a key. At least one of publicKey or id must be provided (both can also be provided)." + }, "Key": { "title": "Key", "type": "object", diff --git a/core/signing-blockdaemon/src/index.ts b/core/signing-blockdaemon/src/index.ts index a9bf87606..31003dfff 100644 --- a/core/signing-blockdaemon/src/index.ts +++ b/core/signing-blockdaemon/src/index.ts @@ -49,10 +49,19 @@ export default class BlockdaemonSigningDriver implements SigningDriverInterface params: SignTransactionParams ): Promise => { try { + if (!params.keyIdentifier.publicKey) { + return { + error: 'key_not_found', + error_description: + 'The provided key identifier must include a publicKey.', + } + } const tx = await this.client.signTransaction({ tx: params.tx, txHash: params.txHash, - publicKey: params.publicKey, + keyIdentifier: { + publicKey: params.keyIdentifier.publicKey, + }, internalTxId: params.internalTxId, }) return { diff --git a/core/signing-fireblocks/src/fireblocks.test.ts b/core/signing-fireblocks/src/fireblocks.test.ts index 52257d990..689804d27 100644 --- a/core/signing-fireblocks/src/fireblocks.test.ts +++ b/core/signing-fireblocks/src/fireblocks.test.ts @@ -38,7 +38,10 @@ describe('fireblocks handler', () => { const transaction = await handler.signTransaction( userId, TEST_TRANSACTION_HASH, - '02fefbcc9aebc8a479f211167a9f564df53aefd603a8662d9449a98c1ead2eba' + { + publicKey: + '02fefbcc9aebc8a479f211167a9f564df53aefd603a8662d9449a98c1ead2eba', + } ) expect(transaction).toBeDefined() const foundTransaction = await handler.getTransaction( diff --git a/core/signing-fireblocks/src/fireblocks.ts b/core/signing-fireblocks/src/fireblocks.ts index 82a95c87c..6279e0851 100644 --- a/core/signing-fireblocks/src/fireblocks.ts +++ b/core/signing-fireblocks/src/fireblocks.ts @@ -8,7 +8,11 @@ import { VaultAccount, } from '@fireblocks/ts-sdk' import { pino } from 'pino' -import { SigningStatus, CC_COIN_TYPE } from '@canton-network/core-signing-lib' +import { + SigningStatus, + CC_COIN_TYPE, + KeyIdentifier, +} from '@canton-network/core-signing-lib' import { z } from 'zod' const RawMessageSchema = z.object({ @@ -307,19 +311,26 @@ export class FireblocksHandler { } /** * Sign a transaction using a public key - * @param tx - The transaction to sign, as a string - * @param publicKey - The public key to use for signing + * @param userId - id of a user to get respective client and keys + * @param txHash - Hash of the transaction to sign + * @param keyIdentifier - The key identifier (must include publicKey) * @param externalTxId - The transaction ID assigned by the Wallet Gateway * @return The transaction object from Fireblocks */ public async signTransaction( userId: string | undefined, - tx: string, - publicKey: string, + txHash: string, + keyIdentifier: KeyIdentifier, externalTxId?: string ): Promise { try { const client = this.getClient(userId) + if (!keyIdentifier.publicKey) { + throw new Error( + 'Public key is required for Fireblocks signing provider' + ) + } + const publicKey = keyIdentifier.publicKey if (!this.keyInfoByPublicKey.has(publicKey)) { // refresh the keycache await this.getPublicKeys(userId) @@ -338,7 +349,7 @@ export class FireblocksHandler { rawMessageData: { messages: [ { - content: tx, + content: txHash, derivationPath: key.derivationPath, }, ], diff --git a/core/signing-fireblocks/src/index.test.ts b/core/signing-fireblocks/src/index.test.ts index b40cee4b3..8769453f2 100644 --- a/core/signing-fireblocks/src/index.test.ts +++ b/core/signing-fireblocks/src/index.test.ts @@ -197,7 +197,9 @@ test('transaction signature', async () => { const tx = await controller.signTransaction({ tx: TEST_TRANSACTION, txHash: TEST_TRANSACTION_HASH, - publicKey: key.publicKey, + keyIdentifier: { + publicKey: key.publicKey, + }, }) throwWhenRpcError(tx) diff --git a/core/signing-fireblocks/src/index.ts b/core/signing-fireblocks/src/index.ts index 218cac1b2..1b7889cb2 100644 --- a/core/signing-fireblocks/src/index.ts +++ b/core/signing-fireblocks/src/index.ts @@ -85,7 +85,7 @@ export default class FireblocksSigningDriver implements SigningDriverInterface { const tx = await this.fireblocks.signTransaction( userId, params.txHash, - params.publicKey, + params.keyIdentifier, params.internalTxId ) return { diff --git a/core/signing-internal/src/controller.test.ts b/core/signing-internal/src/controller.test.ts index 1dc04e045..53df2f578 100644 --- a/core/signing-internal/src/controller.test.ts +++ b/core/signing-internal/src/controller.test.ts @@ -78,7 +78,9 @@ test('transaction signature', async () => { const tx = await controller.signTransaction({ tx: TEST_TRANSACTION, txHash: TEST_TRANSACTION_HASH, - publicKey: key.publicKey, + keyIdentifier: { + publicKey: key.publicKey, + }, }) if (isRpcError(tx)) { diff --git a/core/signing-internal/src/controller.ts b/core/signing-internal/src/controller.ts index 66a355978..2964d73dc 100644 --- a/core/signing-internal/src/controller.ts +++ b/core/signing-internal/src/controller.ts @@ -75,9 +75,18 @@ export class InternalSigningDriver implements SigningDriverInterface { ): Promise => { // TODO: validate transaction here + if (!params.keyIdentifier.publicKey) { + return Promise.resolve({ + error: 'key_not_found', + error_description: + 'The provided key identifier must include a publicKey.', + }) + } + const key = await this.store.getSigningKeyByPublicKey( - params.publicKey + params.keyIdentifier.publicKey ) + if (key?.privateKey && _userId) { const txId = randomUUID() const signature = signTransactionHash( @@ -90,7 +99,7 @@ export class InternalSigningDriver implements SigningDriverInterface { id: txId, hash: params.txHash, signature, - publicKey: params.publicKey, + publicKey: params.keyIdentifier.publicKey, createdAt: now, status: 'signed', updatedAt: now, diff --git a/core/signing-lib/src/SigningDriverStore.ts b/core/signing-lib/src/SigningDriverStore.ts index a1026c069..a38f37a2f 100644 --- a/core/signing-lib/src/SigningDriverStore.ts +++ b/core/signing-lib/src/SigningDriverStore.ts @@ -13,6 +13,10 @@ export interface SigningDriverStore { keyId: string ): Promise getSigningKeyByPublicKey(publicKey: string): Promise + getSigningKeyByName( + userId: string, + name: string + ): Promise listSigningTransactionsByTxIdsAndPublicKeys( txIds: string[], publicKeys: string[] diff --git a/core/signing-lib/src/rpc-gen/typings.ts b/core/signing-lib/src/rpc-gen/typings.ts index 22706409e..e17ca4daf 100644 --- a/core/signing-lib/src/rpc-gen/typings.ts +++ b/core/signing-lib/src/rpc-gen/typings.ts @@ -21,6 +21,38 @@ export type TxHash = string * */ export type PublicKey = string +/** + * + * Unique identifier for the key + * + */ +export type Id = string +/** + * + * Key identifier with publicKey (id is optional). + * + */ +export interface KeyIdentifierWithPublicKey { + publicKey: PublicKey + id?: Id + [k: string]: any +} +/** + * + * Key identifier with id (publicKey is optional). + * + */ +export interface KeyIdentifierWithId { + publicKey?: PublicKey + id: Id + [k: string]: any +} +/** + * + * Identifier for the key to use for signing. At least one of publicKey or id must be provided. + * + */ +export type KeyIdentifier = KeyIdentifierWithPublicKey | KeyIdentifierWithId /** * * Internal txId used by the Wallet Gateway to store the transaction. @@ -106,12 +138,6 @@ export interface ObjectOfTransactionsUOtaZpXE { transactions?: Transactions [k: string]: any } -/** - * - * Unique identifier for the key - * - */ -export type Id = string export interface Key { id: Id name: Name @@ -127,7 +153,7 @@ export type Keys = Key[] export interface SignTransactionParams { tx: Tx txHash: TxHash - publicKey: PublicKey + keyIdentifier: KeyIdentifier internalTxId?: InternalTxId [k: string]: any } diff --git a/core/signing-participant/src/controller.test.ts b/core/signing-participant/src/controller.test.ts index 408ed7de4..3e00e3e71 100644 --- a/core/signing-participant/src/controller.test.ts +++ b/core/signing-participant/src/controller.test.ts @@ -28,7 +28,7 @@ test('transaction signature', async () => { .signTransaction({ tx: TEST_TRANSACTION, txHash: TEST_TRANSACTION_HASH, - publicKey: '', + keyIdentifier: { publicKey: '' }, }) expect(tx.status).toBe('signed') }) diff --git a/core/signing-store-sql/src/store-sql.ts b/core/signing-store-sql/src/store-sql.ts index 778243db9..326b5cc4f 100644 --- a/core/signing-store-sql/src/store-sql.ts +++ b/core/signing-store-sql/src/store-sql.ts @@ -75,6 +75,19 @@ export class StoreSql implements SigningDriverStore, AuthAware { return result ? toSigningKey(result) : undefined } + async getSigningKeyByName( + userId: string, + name: string + ): Promise { + const result = await this.db + .selectFrom('signingKeys') + .selectAll() + .where('userId', '=', userId) + .where('name', '=', name) + .executeTakeFirst() + return result ? toSigningKey(result) : undefined + } + async setSigningKey(userId: string, key: SigningKey): Promise { const serialized = fromSigningKey(key, userId) diff --git a/wallet-gateway/remote/src/user-api/controller.ts b/wallet-gateway/remote/src/user-api/controller.ts index c5fd4e8af..bc142d0a1 100644 --- a/wallet-gateway/remote/src/user-api/controller.ts +++ b/wallet-gateway/remote/src/user-api/controller.ts @@ -203,7 +203,9 @@ export const userController = ( const { signature } = await driver.signTransaction({ tx: '', txHash: hash, - publicKey: key.publicKey, + keyIdentifier: { + publicKey: key.publicKey, + }, }) return signature @@ -282,7 +284,9 @@ export const userController = ( await driver.signTransaction({ tx: Buffer.from(txPayload).toString('base64'), txHash: transactions.multiHash, - publicKey: key.publicKey, + keyIdentifier: { + publicKey: key.publicKey, + }, internalTxId, }) @@ -376,7 +380,9 @@ export const userController = ( transactions.multiHash, 'base64' ).toString('hex'), - publicKey: key.publicKey, + keyIdentifier: { + publicKey: key.publicKey, + }, }) if (status === 'signed') { const { signature } = await driver.getTransaction({ @@ -500,7 +506,9 @@ export const userController = ( const signature = await driver.signTransaction({ tx: preparedTransaction, txHash: preparedTransactionHash, - publicKey: wallet.publicKey, + keyIdentifier: { + publicKey: wallet.publicKey, + }, }) if (!signature.signature) { @@ -543,7 +551,9 @@ export const userController = ( let result = await driver.signTransaction({ tx: preparedTransaction, txHash: preparedTransactionHash, - publicKey: wallet.publicKey, + keyIdentifier: { + publicKey: wallet.publicKey, + }, internalTxId, })