Skip to content

Commit

Permalink
feat: added parameter validation for signWithAddress
Browse files Browse the repository at this point in the history
  • Loading branch information
andreabadesso committed Jan 15, 2025
1 parent a7a11b6 commit ecb47ea
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 73 deletions.
254 changes: 185 additions & 69 deletions packages/hathor-rpc-handler/__tests__/rpcMethods/signWithAddress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,100 +10,216 @@ import {
TriggerResponseTypes,
RpcMethods,
SignWithAddressRpcRequest,
TriggerTypes,
RpcResponseTypes,
} from '../../src/types';
import { signWithAddress } from '../../src/rpcMethods/signWithAddress';
import { InvalidParamsError } from '../../src/errors';
import { InvalidParamsError, PromptRejectedError } from '../../src/errors';

describe('signWithAddress parameter validation', () => {
describe('signWithAddress', () => {
const mockWallet = {
getNetwork: jest.fn().mockReturnValue('testnet'),
getAddressAtIndex: jest.fn().mockResolvedValue('test-address'),
getAddressPathForIndex: jest.fn().mockResolvedValue('test-path'),
signMessageWithAddress: jest.fn().mockResolvedValue('test-signature'),
} as unknown as HathorWallet;

const mockTriggerHandler = jest.fn().mockResolvedValue({
type: TriggerResponseTypes.SignMessageWithAddressConfirmationResponse,
data: {
accepted: true,
pinCode: '1234'
}
});
const mockTriggerHandler = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
});

it('should reject when network is missing', async () => {
const invalidRequest = {
method: RpcMethods.SignWithAddress,
params: {
message: 'test-message',
addressIndex: 0,
// network is missing
},
} as SignWithAddressRpcRequest;

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});
describe('parameter validation', () => {
it('should reject when method is missing', async () => {
const invalidRequest = {
params: {
network: 'testnet',
message: 'test-message',
addressIndex: 0,
},
} as SignWithAddressRpcRequest;

it('should reject when message is empty', async () => {
const invalidRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: '',
addressIndex: 0,
},
} as SignWithAddressRpcRequest;

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});
await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});

it('should reject when addressIndex is negative', async () => {
const invalidRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: 'test-message',
addressIndex: -1,
},
} as SignWithAddressRpcRequest;
it('should reject when method is invalid', async () => {
const invalidRequest = {
method: 'invalid_method',
params: {
network: 'testnet',
message: 'test-message',
addressIndex: 0,
},
} as unknown as SignWithAddressRpcRequest;

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});

it('should reject when network is missing', async () => {
const invalidRequest = {
method: RpcMethods.SignWithAddress,
params: {
message: 'test-message',
addressIndex: 0,
// network is missing
},
} as SignWithAddressRpcRequest;

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});

it('should reject when message is empty', async () => {
const invalidRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: '',
addressIndex: 0,
},
} as SignWithAddressRpcRequest;

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});

it('should reject when addressIndex is negative', async () => {
const invalidRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: 'test-message',
addressIndex: -1,
},
} as SignWithAddressRpcRequest;

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
it('should reject when addressIndex is not an integer', async () => {
const invalidRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: 'test-message',
addressIndex: 1.5,
},
} as SignWithAddressRpcRequest;

await expect(
signWithAddress(invalidRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(InvalidParamsError);
});
});

it('should accept valid parameters', async () => {
const validRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
describe('functionality', () => {
it('should accept valid parameters and sign message', async () => {
const validRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: 'test-message',
addressIndex: 0,
},
} as SignWithAddressRpcRequest;

mockTriggerHandler
.mockResolvedValueOnce({
type: TriggerResponseTypes.SignMessageWithAddressConfirmationResponse,
data: true,
})
.mockResolvedValueOnce({
type: TriggerResponseTypes.PinRequestResponse,
data: {
accepted: true,
pinCode: '1234',
},
});

const result = await signWithAddress(validRequest, mockWallet, {}, mockTriggerHandler);

expect(result).toBeDefined();
expect(result.type).toBe(RpcResponseTypes.SendWithAddressResponse);
expect(result.response).toEqual({
message: 'test-message',
addressIndex: 0,
},
} as SignWithAddressRpcRequest;
signature: 'test-signature',
address: {
address: 'test-address',
index: 0,
addressPath: 'test-path',
info: undefined,
},
});

mockTriggerHandler
.mockResolvedValueOnce({
type: TriggerResponseTypes.SignMessageWithAddressConfirmationResponse,
data: true,
})
.mockResolvedValueOnce({
type: TriggerResponseTypes.PinRequestResponse,
expect(mockTriggerHandler).toHaveBeenCalledWith({
type: TriggerTypes.SignMessageWithAddressConfirmationPrompt,
method: validRequest.method,
data: {
accepted: true,
pinCode: '1234',
address: {
address: 'test-address',
index: 0,
addressPath: 'test-path',
info: undefined,
},
message: 'test-message',
},
}, {});
});

it('should throw PromptRejectedError if user rejects sign confirmation', async () => {
const validRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: 'test-message',
addressIndex: 0,
},
} as SignWithAddressRpcRequest;

mockTriggerHandler.mockResolvedValueOnce({
type: TriggerResponseTypes.SignMessageWithAddressConfirmationResponse,
data: false,
});

await expect(
signWithAddress(validRequest, mockWallet, {}, mockTriggerHandler)
).resolves.toBeDefined();
await expect(
signWithAddress(validRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(PromptRejectedError);
});

it('should throw PromptRejectedError if user rejects PIN prompt', async () => {
const validRequest = {
method: RpcMethods.SignWithAddress,
params: {
network: 'testnet',
message: 'test-message',
addressIndex: 0,
},
} as SignWithAddressRpcRequest;

mockTriggerHandler
.mockResolvedValueOnce({
type: TriggerResponseTypes.SignMessageWithAddressConfirmationResponse,
data: true,
})
.mockResolvedValueOnce({
type: TriggerResponseTypes.PinRequestResponse,
data: {
accepted: false,
},
});

await expect(
signWithAddress(validRequest, mockWallet, {}, mockTriggerHandler)
).rejects.toThrow(PromptRejectedError);
});
});
});
14 changes: 10 additions & 4 deletions packages/hathor-rpc-handler/src/rpcMethods/signWithAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ import {
SignWithAddressRpcRequest,
RpcResponseTypes,
SignWithAddressResponse,
RpcMethods,
} from '../types';
import { PromptRejectedError, InvalidParamsError } from '../errors';
import { validateNetwork } from '../helpers';
import { AddressInfoObject } from '@hathor/wallet-lib/lib/wallet/types';

const signWithAddressSchema = z.object({
network: z.string().min(1),
message: z.string().min(1),
addressIndex: z.number().int().nonnegative(),
method: z.literal(RpcMethods.SignWithAddress),
params: z.object({
network: z.string().min(1),
message: z.string().min(1),
addressIndex: z.number().int().nonnegative(),
}),
});

/**
Expand All @@ -50,7 +54,9 @@ export async function signWithAddress(
promptHandler: TriggerHandler,
) {
try {
const params = signWithAddressSchema.parse(rpcRequest.params);
const validatedRequest = signWithAddressSchema.parse(rpcRequest);
const { params } = validatedRequest;

validateNetwork(wallet, params.network);

const base58: string = await wallet.getAddressAtIndex(params.addressIndex);
Expand Down

0 comments on commit ecb47ea

Please sign in to comment.