Skip to content

Commit e3c7b5c

Browse files
author
Aleix Morgadas
authored
Merge pull request #14 from nemtech/task/10/verify-signature
Task/10/verify signature
2 parents 94bc0d6 + 7977ea4 commit e3c7b5c

File tree

5 files changed

+145
-6
lines changed

5 files changed

+145
-6
lines changed

src/model/account/Account.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,15 @@ export class Account {
131131
public signCosignatureTransaction(cosignatureTransaction: CosignatureTransaction): CosignatureSignedTransaction {
132132
return cosignatureTransaction.signWith(this);
133133
}
134+
135+
/**
136+
* Sign raw data
137+
* @param data - Data to be signed
138+
* @return {string} - Signed data result
139+
*/
140+
public signData(data: string): string {
141+
return convert.uint8ToHex(KeyPair.sign(this.keyPair,
142+
convert.hexToUint8(convert.utf8ToHex(data)),
143+
));
144+
}
134145
}

src/model/account/PublicAccount.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {NetworkType} from '../blockchain/NetworkType';
18-
import {Address} from './Address';
17+
import { convert, KeyPair } from 'nem2-library';
18+
import { NetworkType } from '../blockchain/NetworkType';
19+
import { Address } from './Address';
20+
21+
const Hash512 = 64;
1922

2023
/**
2124
* The public account structure contains account's address and public key.
@@ -53,6 +56,36 @@ export class PublicAccount {
5356
return new PublicAccount(publicKey, address);
5457
}
5558

59+
/**
60+
* Verify a signature.
61+
*
62+
* @param {string} data - The data to verify.
63+
* @param {string} signature - The signature to verify.
64+
*
65+
* @return {boolean} - True if the signature is valid, false otherwise.
66+
*/
67+
public verifySignature(data: string, signature: string): boolean {
68+
if (!signature) {
69+
throw new Error('Missing argument');
70+
}
71+
72+
if (signature.length / 2 !== Hash512) {
73+
throw new Error('Signature length is incorrect');
74+
}
75+
76+
if (!convert.isHexString(signature)) {
77+
throw new Error('Signature must be hexadecimal only');
78+
}
79+
80+
// Convert signature key to Uint8Array
81+
const convertedSignature = convert.hexToUint8(signature);
82+
83+
// Convert to Uint8Array
84+
const convertedData = convert.hexToUint8(convert.utf8ToHex(data));
85+
86+
return KeyPair.verify(convert.hexToUint8(this.publicKey), convertedData, convertedSignature);
87+
}
88+
5689
/**
5790
* Compares public accounts for equality.
5891
* @param publicAccount
@@ -61,4 +94,5 @@ export class PublicAccount {
6194
equals(publicAccount: PublicAccount) {
6295
return this.publicKey === publicAccount.publicKey && this.address.plain() === publicAccount.address.plain();
6396
}
97+
6498
}

test/model/account/Account.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import {expect} from 'chai';
1818
import {Account} from '../../../src/model/account/Account';
1919
import {NetworkType} from '../../../src/model/blockchain/NetworkType';
20+
import { PublicAccount } from '../../../src/model/model';
2021

2122
describe('Account', () => {
2223
const accountInformation = {
@@ -45,4 +46,27 @@ describe('Account', () => {
4546
expect(account.address).to.not.be.equal(undefined);
4647
});
4748

49+
describe('signData', () => {
50+
it('utf-8', () => {
51+
const account = Account.createFromPrivateKey(
52+
'AB860ED1FE7C91C02F79C02225DAC708D7BD13369877C1F59E678CC587658C47',
53+
NetworkType.MIJIN_TEST,
54+
);
55+
const publicAccount = account.publicAccount;
56+
const signed = account.signData('catapult rocks!');
57+
expect(publicAccount.verifySignature('catapult rocks!', signed))
58+
.to.be.true;
59+
});
60+
61+
it('hexa', () => {
62+
const account = Account.createFromPrivateKey(
63+
'AB860ED1FE7C91C02F79C02225DAC708D7BD13369877C1F59E678CC587658C47',
64+
NetworkType.MIJIN_TEST,
65+
);
66+
const publicAccount = account.publicAccount;
67+
const signed = account.signData('0xAA');
68+
expect(publicAccount.verifySignature('0xAA', signed))
69+
.to.be.true;
70+
});
71+
});
4872
});

test/model/account/PublicAccount.spec.ts

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {expect} from 'chai';
18-
import {PublicAccount} from '../../../src/model/account/PublicAccount';
19-
import {NetworkType} from '../../../src/model/blockchain/NetworkType';
17+
import { expect } from 'chai';
18+
import { PublicAccount } from '../../../src/model/account/PublicAccount';
19+
import { NetworkType } from '../../../src/model/blockchain/NetworkType';
2020

2121
describe('PublicAccount', () => {
2222
const publicKey = 'b4f12e7c9f6946091e2cb8b6d3a12b50d17ccbbf646386ea27ce2946a7423dcf';
@@ -27,3 +27,73 @@ describe('PublicAccount', () => {
2727
expect(publicAccount.address.plain()).to.be.equal('SARNASAS2BIAB6LMFA3FPMGBPGIJGK6IJETM3ZSP');
2828
});
2929
});
30+
31+
describe('Signature verification', () => {
32+
it('Can verify a signature', () => {
33+
// Arrange:'
34+
const signerPublicAccount = PublicAccount.createFromPublicKey(
35+
'1464953393CE96A08ABA6184601FD08864E910696B060FF7064474726E666CA8',
36+
NetworkType.MIJIN_TEST);
37+
const data = 'I am so so so awesome as always';
38+
const signature = '2092660F5BD4AE832B2E290F34A76B41506EE473B02FD7FD468B32C80C945CF60A0D60D005FA9B2DB3AD3212F8028C1449D3DCF81C9FAB3EB4975A7409D8D802'; // tslint:disable-line
39+
40+
// Act & Assert:
41+
expect(signerPublicAccount.verifySignature(data, signature)).to.be.true;
42+
});
43+
44+
it('Throw error if signature has invalid length', () => {
45+
// Arrange:
46+
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
47+
NetworkType.MIJIN_TEST);
48+
const data = 'I am so so so awesome as always';
49+
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C5486'; // tslint:disable-line
50+
51+
// Act & Assert:
52+
expect(() => { signerPublicAccount.verifySignature(data, signature); }).to.throw('Signature length is incorrect');
53+
});
54+
55+
it('Throw error if signature is not strictly hexadecimal', () => {
56+
// Arrange:
57+
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
58+
NetworkType.MIJIN_TEST);
59+
const data = 'I am so so so awesome as always';
60+
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F35a1wwwww';// tslint:disable-line
61+
62+
// Act & Assert:
63+
expect(() => { signerPublicAccount.verifySignature(data, signature); })
64+
.to.throw('Signature must be hexadecimal only');
65+
});
66+
67+
it('Return false if wrong public key provided', () => {
68+
// Arrange:
69+
const signerPublicAccount = PublicAccount.createFromPublicKey('12816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB509',
70+
NetworkType.MIJIN_TEST);
71+
const data = 'I am so so so awesome as always';
72+
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA508';// tslint:disable-line
73+
74+
// Act & Assert:
75+
expect(signerPublicAccount.verifySignature(data, signature)).to.be.false;
76+
});
77+
78+
it('Return false if data is not corresponding to signature provided', () => {
79+
// Arrange:
80+
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
81+
NetworkType.MIJIN_TEST);
82+
const data = 'I am awesome as always';
83+
const signature = 'B01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA508';// tslint:disable-line
84+
85+
// Act & Assert:
86+
expect(signerPublicAccount.verifySignature(data, signature)).to.be.false;
87+
});
88+
89+
it('Return false if signature is not corresponding to data provided', () => {
90+
// Arrange:
91+
const signerPublicAccount = PublicAccount.createFromPublicKey('22816F825B4CACEA334723D51297D8582332D8B875A5829908AAE85831ABB508',
92+
NetworkType.MIJIN_TEST);
93+
const data = 'I am so so so awesome as always';
94+
const signature = 'A01DCA6484026C2ECDF3C822E64DEAAFC15EBCCE337EEE209C28513CB5351CDED8863A8E7B855CD471B55C91FAE611C548625C9A5916A555A24F72F3526FA509';// tslint:disable-line
95+
96+
// Act & Assert:
97+
expect(signerPublicAccount.verifySignature(data, signature)).to.be.false;
98+
});
99+
});

tslint.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"no-switch-case-fall-through": true,
7474
"no-trailing-whitespace": true,
7575
"no-unnecessary-initializer": true,
76-
"no-unused-expression": true,
76+
"no-unused-expression": false,
7777
"no-use-before-declare": true,
7878
"no-var-keyword": true,
7979
"object-literal-sort-keys": false,

0 commit comments

Comments
 (0)