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
44 changes: 42 additions & 2 deletions src/model/account/PublicAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* limitations under the License.
*/

import {NetworkType} from '../blockchain/NetworkType';
import {Address} from './Address';
import { KeyPair, convert } from 'nem2-library';
import { NetworkType } from '../blockchain/NetworkType';
import { Address } from './Address';

/**
* The public account structure contains account's address and public key.
Expand Down Expand Up @@ -53,6 +54,44 @@ export class PublicAccount {
return new PublicAccount(publicKey, address);
}

/**
* Verify a signature.
*
* @param {PublicAccount} publicAccount - The public account to use for verification.
* @param {string} data - The data to verify.
* @param {string} signature - The signature to verify.
*
* @return {boolean} - True if the signature is valid, false otherwise.
*/
static verifySignature(publicAccount: PublicAccount, data: string, signature: string): boolean {
if (!publicAccount || !data || !signature) {
throw new Error('Missing argument');
}

if (signature.length !== 128) {
throw new Error('Signature length is incorrect');
}

if (!convert.isHexString(signature)) {
throw new Error('Signature must be hexadecimal only');
}

// Convert signature key to Uint8Array
const convertedSignature = convert.hexToUint8(signature);

let convertedData;

// Convert data to hex if data is not hex
if (!convert.isHexString(data)) {
convertedData = convert.utf8ToHex(data);
}

// Convert to Uint8Array
convertedData = convert.hexToUint8(convertedData);

return KeyPair.verify(publicAccount.publicKey, convertedData, convertedSignature);
}

/**
* Compares public accounts for equality.
* @param publicAccount
Expand All @@ -61,4 +100,5 @@ export class PublicAccount {
equals(publicAccount: PublicAccount) {
return this.publicKey === publicAccount.publicKey && this.address.plain() === publicAccount.address.plain();
}

}
86 changes: 83 additions & 3 deletions test/model/account/PublicAccount.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
* limitations under the License.
*/

import {expect} from 'chai';
import {PublicAccount} from '../../../src/model/account/PublicAccount';
import {NetworkType} from '../../../src/model/blockchain/NetworkType';
import { expect } from 'chai';
import { PublicAccount } from '../../../src/model/account/PublicAccount';
import { NetworkType } from '../../../src/model/blockchain/NetworkType';

describe('PublicAccount', () => {
const publicKey = 'b4f12e7c9f6946091e2cb8b6d3a12b50d17ccbbf646386ea27ce2946a7423dcf';
Expand All @@ -27,3 +27,83 @@ describe('PublicAccount', () => {
expect(publicAccount.address.plain()).to.be.equal('SARNASAS2BIAB6LMFA3FPMGBPGIJGK6IJETM3ZSP');
});
});

describe('Signature verification', () => {
it('Can verify a signature', () => {
// Arrange:'
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
NetworkType.MIJIN_TEST);
const data = 'I am so so so awesome as always';
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA508'; // tslint:disable-line

// Act & Assert:
expect(PublicAccount.verifySignature(signerPublicAccount, data, signature)).equal(true);
});

it('Throw error if signature has invalid length', () => {
// Arrange:
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
NetworkType.MIJIN_TEST);
const data = 'I am so so so awesome as always';
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C5486'; // tslint:disable-line

// Act & Assert:
expect(() => { PublicAccount.verifySignature(signerPublicAccount, data, signature); }).to.throw('Signature length is incorrect');
});

it('Throw error if signature is not strictly hexadecimal', () => {
// Arrange:
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
NetworkType.MIJIN_TEST);
const data = 'I am so so so awesome as always';
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F35a1wwwww';// tslint:disable-line

// Act & Assert:
expect(() => { PublicAccount.verifySignature(signerPublicAccount, data, signature); })
.to.throw('Signature must be hexadecimal only');
});

it('Return false if wrong public key provided', () => {
// Arrange:
const signerPublicAccount = PublicAccount.createFromPublicKey('12816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB509',
NetworkType.MIJIN_TEST);
const data = 'I am so so so awesome as always';
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA508';// tslint:disable-line

// Act & Assert:
expect(PublicAccount.verifySignature(signerPublicAccount, data, signature)).equal(false);
});

it('Return false if data is not corresponding to signature provided', () => {
// Arrange:
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
NetworkType.MIJIN_TEST);
const data = 'I am awesome as always';
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA508';// tslint:disable-line

// Act & Assert:
expect(PublicAccount.verifySignature(signerPublicAccount, data, signature)).equal(false);
});

it('Return false if signature is not corresponding to data provided', () => {
// Arrange:
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
NetworkType.MIJIN_TEST);
const data = 'I am so so so awesome as always';
const signature = 'A01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA509';// tslint:disable-line

// Act & Assert:
expect(PublicAccount.verifySignature(signerPublicAccount, data, signature)).equal(false);
});

it('Throw error if signature verification is missing a parameter', () => {
// Arrange:
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
NetworkType.MIJIN_TEST);
const data = '';
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA508';// tslint:disable-line

// Act & Assert:
expect(() => { PublicAccount.verifySignature(signerPublicAccount, data, signature); }).to.throw('Missing argument');
});
});