diff --git a/docs/node/CATEGORIES/13-misc.md b/docs/node/CATEGORIES/13-misc.md index 45a2bc0ed3..efa5018597 100644 --- a/docs/node/CATEGORIES/13-misc.md +++ b/docs/node/CATEGORIES/13-misc.md @@ -22,3 +22,23 @@ interface misc { setMockTime (ts: number): Promise } ``` + +# verifyMessage + +Verify a signed message. + +```ts title="client.misc.verifyMessage()" +interface misc { + verifyMessage (address: string, signature: string, message: string): Promise +} +``` + +# signMessageWithPrivKey + +Sign a message with the private key of an address + +```ts title="client.misc.signMessageWithPrivKey()" +interface misc { + signMessageWithPrivKey (privkey: string, message: string): Promise +} +``` diff --git a/packages/jellyfish-api-core/__tests__/category/misc/signMessageWithPrivKey.test.ts b/packages/jellyfish-api-core/__tests__/category/misc/signMessageWithPrivKey.test.ts new file mode 100644 index 0000000000..1d19b553a9 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/misc/signMessageWithPrivKey.test.ts @@ -0,0 +1,36 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { ContainerAdapterClient } from '../../container_adapter_client' +import { RpcApiError } from '@defichain/jellyfish-api-core' + +describe('Verify message', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if invalid private key', async () => { + const privkey = await client.wallet.dumpPrivKey(await client.wallet.getNewAddress()) + const promise = client.misc.signMessageWithPrivKey(privkey.substr(1, 7), 'test') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -5, + message: 'Invalid private key', + method: 'signmessagewithprivkey' + } + }) + }) + + it('should sign with private key', async () => { + const privkey = await client.wallet.dumpPrivKey(await client.wallet.getNewAddress()) + const promise = client.misc.signMessageWithPrivKey(privkey, 'test') + await expect(promise).toBeTruthy() + }) +}) diff --git a/packages/jellyfish-api-core/__tests__/category/misc/verifyMessage.test.ts b/packages/jellyfish-api-core/__tests__/category/misc/verifyMessage.test.ts new file mode 100644 index 0000000000..f555905ff8 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/misc/verifyMessage.test.ts @@ -0,0 +1,70 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { ContainerAdapterClient } from '../../container_adapter_client' +import { RegTestFoundationKeys } from '@defichain/jellyfish-network' +import { RpcApiError } from '@defichain/jellyfish-api-core' + +describe('Verify message', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + const message = 'test' + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if invalid address', async () => { + const promise = client.misc.verifyMessage('test', 'ICqlzHuredAz6XN7bVsB09/FGtGbRX+nUv+E9qz44rQ8DRi/zHpDGuMs2U6EtnGapv7r1V7cIdJ2ui9TMaaCNvA=', message) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -3, + message: 'Invalid address', + method: 'verifymessage' + } + }) + }) + + it('should throw error if invalid signature', async () => { + const promise = client.misc.verifyMessage('mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU', 'ICqlzHuredAz6XN7bVsB09/FGtGbRX+nUv+E9qz44rQ8DRi/zHpDGuMs2U6EtnGapv7r1V7cIdJ2ui9TMaaCNvA', message) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -5, + message: 'Malformed base64 encoding', + method: 'verifymessage' + } + }) + }) + + it('should fail if incorrect keypair is used', async () => { + const keyPair = RegTestFoundationKeys[0].owner + const otherKeyPair = RegTestFoundationKeys[1].owner + + const signedString = await client.misc.signMessageWithPrivKey(keyPair.privKey, message) + + const isCorrect = await client.misc.verifyMessage(otherKeyPair.address, signedString, message) + expect(isCorrect).toStrictEqual(false) + }) + + it('should fail to verify message if incorrect message', async () => { + const keyPair = RegTestFoundationKeys[0].owner + const signedString = await client.misc.signMessageWithPrivKey(keyPair.privKey, message) + + const isCorrect = await client.misc.verifyMessage(keyPair.address, signedString, 'test1') + expect(isCorrect).toStrictEqual(false) + }) + + it('should verify message', async () => { + const keyPair = RegTestFoundationKeys[0].owner + const signedString = await client.misc.signMessageWithPrivKey(keyPair.privKey, message) + + const isCorrect = await client.misc.verifyMessage(keyPair.address, signedString, message) + expect(isCorrect).toStrictEqual(true) + }) +}) diff --git a/packages/jellyfish-api-core/src/category/misc.ts b/packages/jellyfish-api-core/src/category/misc.ts index 814e34b417..2e8e102c86 100644 --- a/packages/jellyfish-api-core/src/category/misc.ts +++ b/packages/jellyfish-api-core/src/category/misc.ts @@ -20,4 +20,27 @@ export class Misc { const timestamp = ts.toString().length === 13 ? Math.floor(ts / 1e3) : ts return await this.client.call('setmocktime', [timestamp], 'number') } + + /** + * Verify a signed message + * + * @param {string} address DeFi address to use for this signature + * @param {string} signature Base 63 encoded signature + * @param {string} message The message that was signed + * @return Promise Is valid signature of the provided address + */ + async verifyMessage (address: string, signature: string, message: string): Promise { + return await this.client.call('verifymessage', [address, signature, message], 'number') + } + + /** + * Sign a message with the private key of an address + * + * @param {string} privkey The private key to sign the message with + * @param {string} message The message to create a signature of + * @return Promise The signature of the message encoded in base 64 + */ + async signMessageWithPrivKey (privkey: string, message: string): Promise { + return await this.client.call('signmessagewithprivkey', [privkey, message], 'number') + } }