diff --git a/ironfish/src/rpc/clients/client.ts b/ironfish/src/rpc/clients/client.ts index 84bb664d48..4dcce51d15 100644 --- a/ironfish/src/rpc/clients/client.ts +++ b/ironfish/src/rpc/clients/client.ts @@ -172,6 +172,7 @@ import type { UseAccountResponse, } from '../routes' import { ApiNamespace } from '../routes/namespaces' +import { AddSignatureRequest, AddSignatureResponse } from '../routes/wallet/addSignature' export abstract class RpcClient { abstract request( @@ -555,6 +556,15 @@ export abstract class RpcClient { ).waitForEnd() }, + addSignature: ( + params: AddSignatureRequest, + ): Promise> => { + return this.request( + `${ApiNamespace.wallet}/addSignature`, + params, + ).waitForEnd() + }, + createTransaction: ( params: CreateTransactionRequest, ): Promise> => { diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/addSignature.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/addSignature.test.ts.fixture new file mode 100644 index 0000000000..47c40f17fa --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/addSignature.test.ts.fixture @@ -0,0 +1,32 @@ +{ + "Route wallet/addSignature should return error if signature is not a valid hex": [ + { + "value": { + "version": 4, + "id": "0e9f1f07-ee9d-4746-a4e8-131d89d71345", + "name": "addSignatureAccount2", + "spendingKey": "21557df84c37fa55363c77ffe99a6d01a1c1938f06b805b34a2d2f3442016d3c", + "viewKey": "4d8f669c3667195906dcc4e636983d653e6042267fffb3b7ed1a00e8aeacf7011382ac8e43e6f446f3751dca9640eb7d684b88c181feec8f1399863b0e389c95", + "incomingViewKey": "9f13a57239aba9daade572166257ae494807e6436a9eaaf5bf312855e225d906", + "outgoingViewKey": "5c156963119e3fbdccd84d08622eae4dbbc36ada56e30682188a338fe9be0ed6", + "publicAddress": "608569ba934ef7bb6c6bc99e653b7ab18eb17bf02602c8fc489c3286d1065f2c", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + }, + "scanningEnabled": true, + "proofAuthorizingKey": "0b3bcdc835ee3709c9a30cad80c36d0d4f5d5712349d6bbfd34da9ed405bbc02" + }, + "head": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + } + } + ] +} \ No newline at end of file diff --git a/ironfish/src/rpc/routes/wallet/addSignature.test.ts b/ironfish/src/rpc/routes/wallet/addSignature.test.ts new file mode 100644 index 0000000000..9d57c1ca49 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/addSignature.test.ts @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { RawTransactionSerde } from '../../../primitives' +import { useAccountFixture } from '../../../testUtilities' +import { createRawTransaction } from '../../../testUtilities/helpers/transaction' +import { createRouteTest } from '../../../testUtilities/routeTest' + +describe('Route wallet/addSignature', () => { + const routeTest = createRouteTest(true) + + it('should return error if signature is not a valid hex', async () => { + const account = await useAccountFixture(routeTest.node.wallet, 'addSignatureAccount') + const rawTransaction = await createRawTransaction({ + wallet: routeTest.node.wallet, + from: account, + }) + + const response = await routeTest.client.wallet.buildTransaction({ + rawTransaction: RawTransactionSerde.serialize(rawTransaction).toString('hex'), + account: account.name, + }) + + expect(response.status).toBe(200) + expect(response.content.unsignedTransaction).toBeDefined() + + const invalidSignature = 'invalid' + + await expect( + routeTest.client.wallet.addSignature({ + unsignedTransaction: response.content.unsignedTransaction, + signature: invalidSignature, + }), + ).rejects.toThrow('Invalid signature length') + }) +}) diff --git a/ironfish/src/rpc/routes/wallet/addSignature.ts b/ironfish/src/rpc/routes/wallet/addSignature.ts new file mode 100644 index 0000000000..122712f97a --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/addSignature.ts @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { UnsignedTransaction } from '@ironfish/rust-nodejs' +import * as yup from 'yup' +import { ApiNamespace } from '../namespaces' +import { routes } from '../router' +import { AssertHasRpcContext } from '../rpcContext' + +export type AddSignatureRequest = { + unsignedTransaction: string + signature: string +} + +export type AddSignatureResponse = { + transaction: string +} + +export const AddSignatureRequestSchema: yup.ObjectSchema = yup + .object({ + unsignedTransaction: yup.string().defined(), + signature: yup.string().defined(), + }) + .defined() + +export const AddSignatureResponseSchema: yup.ObjectSchema = yup + .object({ + transaction: yup.string().defined(), + }) + .defined() + +routes.register( + `${ApiNamespace.wallet}/addSignature`, + AddSignatureRequestSchema, + (request, node): void => { + AssertHasRpcContext(request, node, 'wallet') + const unsignedTransaction = new UnsignedTransaction( + Buffer.from(request.data.unsignedTransaction, 'hex'), + ) + + const buffer = Buffer.from(request.data.signature, 'hex') + + if (buffer.length !== 64) { + throw new Error('Invalid signature length') + } + + const serialized = unsignedTransaction.addSignature(buffer) + + request.end({ + transaction: serialized.toString('hex'), + }) + }, +) diff --git a/ironfish/src/rpc/routes/wallet/index.ts b/ironfish/src/rpc/routes/wallet/index.ts index 446008ccb1..742c56b7a2 100644 --- a/ironfish/src/rpc/routes/wallet/index.ts +++ b/ironfish/src/rpc/routes/wallet/index.ts @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -export * from './setAccountHead' +export * from './addSignature' export * from './addTransaction' export * from './buildTransaction' export * from './burnAsset' @@ -12,10 +12,10 @@ export * from './createTransaction' export * from './estimateFeeRates' export * from './exportAccount' export * from './getAccountNotesStream' -export * from './getAccounts' export * from './getAccountStatus' export * from './getAccountTransaction' export * from './getAccountTransactions' +export * from './getAccounts' export * from './getAccountsStatus' export * from './getAsset' export * from './getAssets' @@ -38,6 +38,7 @@ export * from './renameAccount' export * from './rescan' export * from './resetAccount' export * from './sendTransaction' +export * from './setAccountHead' export * from './setScanning' export * from './types' export * from './use'