Skip to content

Commit

Permalink
feat(jellyfish-api-core): add verifyMessage and signMessageWithPrivKe…
Browse files Browse the repository at this point in the history
…y RPCs (#1905)

<!--  Thanks for sending a pull request! -->

#### What this PR does / why we need it:

Adds verifyMessage and signMessageWithPrivKey RPCs and tests.

#### Which issue(s) does this PR fixes?:
<!--
(Optional) Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes part of #48
  • Loading branch information
shohamc1 authored Jan 12, 2023
1 parent 3aeeb67 commit 45d18d5
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 0 deletions.
20 changes: 20 additions & 0 deletions docs/node/CATEGORIES/13-misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,23 @@ interface misc {
setMockTime (ts: number): Promise<void>
}
```

# verifyMessage

Verify a signed message.

```ts title="client.misc.verifyMessage()"
interface misc {
verifyMessage (address: string, signature: string, message: string): Promise<boolean>
}
```

# signMessageWithPrivKey

Sign a message with the private key of an address

```ts title="client.misc.signMessageWithPrivKey()"
interface misc {
signMessageWithPrivKey (privkey: string, message: string): Promise<string>
}
```
Original file line number Diff line number Diff line change
@@ -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()
})
})
Original file line number Diff line number Diff line change
@@ -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)
})
})
23 changes: 23 additions & 0 deletions packages/jellyfish-api-core/src/category/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean> Is valid signature of the provided address
*/
async verifyMessage (address: string, signature: string, message: string): Promise<boolean> {
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<string> The signature of the message encoded in base 64
*/
async signMessageWithPrivKey (privkey: string, message: string): Promise<string> {
return await this.client.call('signmessagewithprivkey', [privkey, message], 'number')
}
}

0 comments on commit 45d18d5

Please sign in to comment.