Skip to content
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
57 changes: 52 additions & 5 deletions api-specs/openrpc-signing-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,17 @@
"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",
"type": "string",
"description": "Internal txId used by the Wallet Gateway to store the transaction."
}
},
"required": ["tx", "txHash", "publicKey"]
"required": ["tx", "txHash", "keyIdentifier"]
}
}
],
Expand Down Expand Up @@ -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",
Expand Down
11 changes: 10 additions & 1 deletion core/signing-blockdaemon/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,19 @@ export default class BlockdaemonSigningDriver implements SigningDriverInterface
params: SignTransactionParams
): Promise<SignTransactionResult> => {
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 {
Expand Down
5 changes: 4 additions & 1 deletion core/signing-fireblocks/src/fireblocks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
23 changes: 17 additions & 6 deletions core/signing-fireblocks/src/fireblocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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<FireblocksTransaction> {
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)
Expand All @@ -338,7 +349,7 @@ export class FireblocksHandler {
rawMessageData: {
messages: [
{
content: tx,
content: txHash,
derivationPath: key.derivationPath,
},
],
Expand Down
4 changes: 3 additions & 1 deletion core/signing-fireblocks/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion core/signing-fireblocks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion core/signing-internal/src/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
13 changes: 11 additions & 2 deletions core/signing-internal/src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,18 @@ export class InternalSigningDriver implements SigningDriverInterface {
): Promise<SignTransactionResult> => {
// 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(
Expand All @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions core/signing-lib/src/SigningDriverStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export interface SigningDriverStore {
keyId: string
): Promise<SigningKey | undefined>
getSigningKeyByPublicKey(publicKey: string): Promise<SigningKey | undefined>
getSigningKeyByName(
userId: string,
name: string
): Promise<SigningKey | undefined>
listSigningTransactionsByTxIdsAndPublicKeys(
txIds: string[],
publicKeys: string[]
Expand Down
40 changes: 33 additions & 7 deletions core/signing-lib/src/rpc-gen/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -127,7 +153,7 @@ export type Keys = Key[]
export interface SignTransactionParams {
tx: Tx
txHash: TxHash
publicKey: PublicKey
keyIdentifier: KeyIdentifier
internalTxId?: InternalTxId
[k: string]: any
}
Expand Down
2 changes: 1 addition & 1 deletion core/signing-participant/src/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test('transaction signature', async () => {
.signTransaction({
tx: TEST_TRANSACTION,
txHash: TEST_TRANSACTION_HASH,
publicKey: '',
keyIdentifier: { publicKey: '' },
})
expect(tx.status).toBe('signed')
})
13 changes: 13 additions & 0 deletions core/signing-store-sql/src/store-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ export class StoreSql implements SigningDriverStore, AuthAware<StoreSql> {
return result ? toSigningKey(result) : undefined
}

async getSigningKeyByName(
userId: string,
name: string
): Promise<SigningKey | undefined> {
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<void> {
const serialized = fromSigningKey(key, userId)

Expand Down
20 changes: 15 additions & 5 deletions wallet-gateway/remote/src/user-api/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ export const userController = (
const { signature } = await driver.signTransaction({
tx: '',
txHash: hash,
publicKey: key.publicKey,
keyIdentifier: {
publicKey: key.publicKey,
},
})

return signature
Expand Down Expand Up @@ -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,
})

Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -543,7 +551,9 @@ export const userController = (
let result = await driver.signTransaction({
tx: preparedTransaction,
txHash: preparedTransactionHash,
publicKey: wallet.publicKey,
keyIdentifier: {
publicKey: wallet.publicKey,
},
internalTxId,
})

Expand Down
Loading