Skip to content

Commit 70abfbf

Browse files
author
Ian Tan
committed
test(): add basic tests for util.ts
1 parent 3114d97 commit 70abfbf

File tree

10 files changed

+255
-174
lines changed

10 files changed

+255
-174
lines changed

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ const config = {
33
'^.+\\.(t|j)s$': 'ts-jest',
44
},
55
testMatch: [
6-
'<rootDir>/src/**/__tests__/**/*.ts?(x)',
7-
'<rootDir>/src/**/?(*.)+(spec|test).ts?(x)',
6+
// '<rootDir>/src/**/__tests__/**/*.ts',
7+
'<rootDir>/src/**/?(*.)+(spec|test).ts',
88
],
99
moduleDirectories: ['src', 'node_modules'],
1010
moduleFileExtensions: ['js', 'ts'],

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"dependencies": {
1212
"bn.js": "^4.11.8",
1313
"bsert": "^0.0.3",
14-
"crypto-js": "^3.1.9-1",
1514
"elliptic": "^6.4.0",
15+
"hash.js": "^1.1.5",
1616
"hmac-drbg": "^1.0.1",
1717
"node-fetch": "^2.2.0",
1818
"valid-url": "^1.0.9"

src/__tests__/keypairs.fixtures.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
export const pairs = [
2+
{
3+
private: 'b776d8f068d11b3c3f5b94db0fb30efea05b73ddb9af1bbd5da8182d94245f0b',
4+
public:
5+
'04cfa555bb63231d167f643f1a23ba66e6ca1458d416ddb9941e95b5fd28df0ac513075403c996efbbc15d187868857e31cf7be4d109b4f8cb3fd40499839f150a',
6+
digest: '533eb365e9c33754c7d7cf437d15fbdfb708c65f330253ea65b9ba2ba553fffe',
7+
},
8+
{
9+
private: '24180e6b0c3021aedb8f5a86f75276ee6fc7ff46e67e98e716728326102e91c9',
10+
public:
11+
'04163fa604c65aebeb7048c5548875c11418d6d106a20a0289d67b59807abdd299d4cf0efcf07e96e576732dae122b9a8ac142214a6bc133b77aa5b79ba46b3e20',
12+
digest: 'cd10769d6ea5b71d13b90a4c810ce050814fcd9dc1a257087aa00cb9e4f50fe3',
13+
},
14+
{
15+
private: 'af71626e38926401a6d2fd8fdf91c97f785b8fb2b867e7f8a884351e59ee9aa6',
16+
public:
17+
'0485c34ff11ea1e06f44d35afe3cc1748b6b122bb06df021a4767db4ef5fbcf1cdbed73c821d89724b59a0fc5e2f2e3e1a9c121d698fff36d750ee463117438a14',
18+
digest: 'f900af5c4b116e050687ce9116d9f99608a64b2005215670014ca04482e0dc76',
19+
},
20+
{
21+
private: 'b94289721618d1a8100bea8502fee149bae7313fcbaebf5ad6a867d557e82971',
22+
public:
23+
'04c7d47ad99dd1db85c9d0b6abe89ffc137f230c991a33890c05436a14974543a29d58bc4655269db6bcd6ea27adca287f956355c64d676cefcfea14051b5239c9',
24+
digest: 'f3b24aed190c68ca93fd03ca268e234003da7a08bc400cae93d470ca2d6a4c91',
25+
},
26+
{
27+
private: 'c577711506abf9dbdabd09c5ae66492e77d6450e8d739ce6493728e781150236',
28+
public:
29+
'0484929ab70d2b39703319a94144802ea9b9a7c8c1a673b10f01738db5b5c40ea8f534bc21ef1b167ffcac7d1a087ed5be2a5d1fa720bc016fbfbd9fb869aee4c6',
30+
digest: 'e4908def64f5255f313a8d137b478be9af5b34052ec73f1bca1dcf18f046351f',
31+
},
32+
{
33+
private: 'e9521f5f9f2ac16f4e22681d43f2c1e15163ffe56e5912d57540dd59d71f2877',
34+
public:
35+
'04db0535d0e4337e5ada6d87b07569308f3f3de19aad15ba1f95108e96f855e17195e5b86773f7570645832048cb5e13e1721784ecce6bf69a0342f873e3afef58',
36+
digest: 'd2a8c89cf55bb366d3631aedd39cc1bad2a9938b6b42847a0b1ec9ac79c5c31c',
37+
},
38+
{
39+
private: 'def6ac38d746349abd8a28903c6b98b06156f6f3925d1c32dec9964541fbbfa1',
40+
public:
41+
'04dc5ca31dff767cb18f92d208fd74e36e800508d3b88bee61680bb7cefa0515943f2801d25bbce9c6a65721427f2443532980a0f7b7aae71b1fe0501179b8f641',
42+
digest: 'cb72209dc219fea538f6d134c571555d6201e90a5cc60dc1e85db227b934311e',
43+
},
44+
{
45+
private: '93fe1b32f4f1358d6809982bce53b9e95c083eb889d14b0c2d0f96012074dc6b',
46+
public:
47+
'04987f1f6a2fe56cd0c2461f2920e3e0d9bd9de96a8febf9246e187e057c6141c494b697455dccefa29361b976698594082ed962da42bce09ef0ce59bd05510dd4',
48+
digest: '672f8403890d6640ba82e1b67bd976037efa0f373aaa9d8fb281c98c3910b889',
49+
},
50+
{
51+
private: 'efaa6048789fa0282302a5620a2fb0e2c60095a54c42b8312646bbbe1d2ec801',
52+
public:
53+
'04645a3995ff3309a36d1ec0991fd87ece2ee36e8887f0d5b304accb9152fe7a603aba24fefa904741e0fd67db24432a38153ecdc496ceec38eab1b7c2d8405de9',
54+
digest: 'ed65e2933beba968f04857532ea8fb93a0f086fc35fa41e8f6cb458867bea380',
55+
},
56+
{
57+
private: 'ca7de577b3e6968da27088d22e918c039a96af3d4821b7e103560fb6ca1185c4',
58+
public:
59+
'04f3e03a4ac451a78254fa3d056448db6cbc29d2ef429228c8435141a126c0c07f2446c68270016322e93a6a14f2e7c557bfdcc74e122a013498b7f1ebe008cc30',
60+
digest: 'dee77df5370c94e61bb300588b6e4653dd2b76bf48862064f2f2ec1202f7f489',
61+
},
62+
];

src/__tests__/util.spec.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import elliptic from 'elliptic';
2+
import hashjs from 'hash.js';
3+
import {pairs} from './keypairs.fixtures';
4+
import * as util from '../util';
5+
import * as schnorr from '../schnorr';
6+
7+
const secp256k1 = elliptic.ec('secp256k1');
8+
9+
describe('utils', () => {
10+
it('should be able to generate a valid 32-byte private key', () => {
11+
const pk = util.generatePrivateKey();
12+
13+
expect(pk.slice(2)).toHaveLength(64);
14+
expect(util.verifyPrivateKey(pk)).toBeTruthy();
15+
});
16+
17+
it('should recover a public key from a private key', () => {
18+
pairs.forEach(({private: priv, public: expected}) => {
19+
const actual = util.getPubKeyFromPrivateKey(priv);
20+
expect(actual).toEqual(expected);
21+
});
22+
});
23+
24+
it('should convert a public key to an address', () => {
25+
pairs.forEach(({public: pub, digest}) => {
26+
const expected = digest.slice(0, 40);
27+
const actual = util.getAddressFromPublicKey(pub);
28+
expect(actual).toEqual(expected);
29+
});
30+
});
31+
32+
it('should be able to recover an address from a private key', () => {
33+
const pk = util.generatePrivateKey();
34+
const publicKey = secp256k1
35+
.keyFromPrivate(pk, 'hex')
36+
.getPublic(false, 'hex');
37+
const hash = hashjs.sha256().update(publicKey).digest('hex');
38+
const expected = hash.slice(0, 40);
39+
const actual = util.getAddressFromPrivateKey(pk);
40+
41+
expect(actual).toHaveLength(40);
42+
expect(actual).toEqual(hash.slice(0, 40));
43+
expect(actual).toEqual(expected);
44+
});
45+
46+
it('should sign messages correctly', () => {
47+
const privateKey = pairs[1].private;
48+
const publicKey = secp256k1
49+
.keyFromPrivate(privateKey, 'hex')
50+
.getPublic(false, 'hex');
51+
52+
const tx = {
53+
version: 8,
54+
nonce: 8,
55+
to: pairs[0].digest.slice(0, 40),
56+
from: pairs[1].digest.slice(0, 40),
57+
pubKey: publicKey,
58+
amount: 888,
59+
gasPrice: 8,
60+
gasLimit: 88,
61+
code: '',
62+
data: '',
63+
};
64+
65+
const encodedTx = util.encodeTransaction(tx);
66+
const sig = schnorr.sign(
67+
encodedTx,
68+
new Buffer(privateKey, 'hex'),
69+
new Buffer(publicKey, 'hex'),
70+
);
71+
const res = schnorr.verify(encodedTx, sig, new Buffer(publicKey, 'hex'));
72+
73+
expect(res).toBeTruthy();
74+
});
75+
});

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Zilliqa from './zilliqa';
2+
export { default as Zilliqa } from './zilliqa';
3+
4+
if (typeof window !== 'undefined' && typeof window.Zilliqa === 'undefined') {
5+
window.Zilliqa = Zilliqa;
6+
}
7+

src/schnorr.ts

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@
2121
import assert from 'bsert';
2222
import elliptic from 'elliptic';
2323
import BN from 'bn.js';
24+
import hashjs from 'hash.js';
2425
import DRBG from 'hmac-drbg';
25-
import sha256 from 'crypto-js/sha256';
2626
import Signature from 'elliptic/lib/elliptic/ec/signature';
2727

2828
const curve = elliptic.ec('secp256k1').curve;
29+
// Public key is a point (x, y) on the curve.
30+
// Each coordinate requires 32 bytes.
31+
// In its compressed form it suffices to store the x co-ordinate
32+
// and the sign for y.
33+
// Hence a total of 33 bytes.
34+
const PUBKEY_COMPRESSED_SIZE_BYTES = 33;
2935

3036
/**
3137
* Hash (r | M).
@@ -36,30 +42,34 @@ const curve = elliptic.ec('secp256k1').curve;
3642
*/
3743

3844
export const hash = (q: BN, pubkey: Buffer, msg: Buffer) => {
39-
const totalLength = 66 + msg.byteLength; // 33 q + 33 pubkey + variable msgLen
45+
const sha256 = hashjs.sha256();
46+
const totalLength = PUBKEY_COMPRESSED_SIZE_BYTES * 2 + msg.byteLength; // 33 q + 33 pubkey + variable msgLen
4047
const Q = q.toArrayLike(Buffer, 'be', 33);
4148
const B = Buffer.allocUnsafe(totalLength);
4249

4350
Q.copy(B, 0);
4451
pubkey.copy(B, 33);
4552
msg.copy(B, 66);
4653

47-
return new BN(sha256.digest(B));
54+
return new BN(sha256.update(B).digest('hex'));
4855
};
4956

5057
/**
51-
* Sign message.
58+
* sign
59+
*
5260
* @param {Buffer} msg
5361
* @param {Buffer} key
54-
* @param {Buffer} pubNonce
62+
* @param {Buffer} pubkey
63+
* @param {Buffer} pubNonce?
64+
*
5565
* @returns {Signature}
5666
*/
5767
export const sign = (
5868
msg: Buffer,
5969
key: Buffer,
6070
pubkey: Buffer,
6171
pubNonce?: Buffer,
62-
) => {
72+
): Signature => {
6373
const prv = new BN(key);
6474
const drbg = getDRBG(msg, key, pubNonce);
6575
const len = curve.n.byteLength();
@@ -77,38 +87,43 @@ export const sign = (
7787
};
7888

7989
/**
80-
* Sign message.
90+
* trySign
8191
*
8292
* @param {Buffer} msg
83-
* @param {BN} priv
84-
* @param {BN} k
85-
* @param {Buffer} pn
93+
* @param {BN} prv - private key
94+
* @param {BN} k - DRBG-generated random number
95+
* @param {Buffer} pn - optional
96+
* @param {Buffer)} pubKey - public key
8697
*
87-
* @returns {Signature|null}
98+
* @returns {Signature | null =>}
8899
*/
89-
const trySign = (msg: Buffer, prv: BN, k: BN, pn: Buffer, pubKey: Buffer) => {
100+
const trySign = (msg: Buffer, prv: BN, k: BN, pn: Buffer, pubKey: Buffer): Signature | null => {
90101
if (prv.isZero()) throw new Error('Bad private key.');
91102

92103
if (prv.gte(curve.n)) throw new Error('Bad private key.');
93104

105+
// 1a. check that k is not 0
94106
if (k.isZero()) return null;
95-
107+
// 1b. check that k is < the order of the group
96108
if (k.gte(curve.n)) return null;
97109

110+
// 2. Compute commitment Q = kG, where g is the base point
98111
const Q = curve.g.mul(k);
112+
// convert the commitment to octets first
99113
const compressedQ = new BN(Q.encodeCompressed());
100114

101-
const Q = curve.g.mul(k);
102-
const compressedQ = new BN(Q.encodeCompressed());
103-
115+
// 3. Compute the challenge r = H(Q || pubKey || msg)
104116
const r = hash(compressedQ, pubKey, msg);
105117
const h = r.clone();
106118

107119
if (h.isZero()) return null;
108120

109-
if (h.gte(curve.n)) return null;
121+
if (h.eq(curve.n)) return null;
110122

123+
// 4. Compute s = k - r * prv
124+
// 4a. Compute r * prv
111125
let s = h.imul(prv);
126+
// 4b. Compute s = k - r * prv mod n
112127
s = k.isub(s);
113128
s = s.umod(curve.n);
114129

@@ -119,13 +134,20 @@ const trySign = (msg: Buffer, prv: BN, k: BN, pn: Buffer, pubKey: Buffer) => {
119134

120135
/**
121136
* Verify signature.
137+
*
122138
* @param {Buffer} msg
123139
* @param {Buffer} signature
124140
* @param {Buffer} key
125-
* @returns {Buffer}
141+
*
142+
* @returns {boolean}
143+
*
144+
* 1. Check if r,s is in [1, ..., order-1]
145+
* 2. Compute Q = sG + r*kpub
146+
* 3. If Q = O (the neutral point), return 0;
147+
* 4. r' = H(Q, kpub, m)
148+
* 5. return r' == r
126149
*/
127-
128-
export const verify = (msg: Buffer, signature: Buffer, key: Buffer) => {
150+
export const verify = (msg: Buffer, signature: Signature, key: Buffer) => {
129151
const sig = new Signature(signature);
130152

131153
if (sig.s.gte(curve.n)) throw new Error('Invalid S value.');
@@ -159,8 +181,8 @@ export const alg = Buffer.from('Schnorr+SHA256 ', 'ascii');
159181
* Instantiate an HMAC-DRBG.
160182
*
161183
* @param {Buffer} msg
162-
* @param {Buffer} priv
163-
* @param {Buffer} data
184+
* @param {Buffer} priv - used as entropy input
185+
* @param {Buffer} data - used as nonce
164186
*
165187
* @returns {DRBG}
166188
*/
@@ -177,14 +199,22 @@ export const getDRBG = (msg: Buffer, priv: Buffer, data?: Buffer) => {
177199

178200
alg.copy(pers, 32);
179201

180-
return new DRBG(sha256, priv, msg, pers);
202+
// return new DRBG(sha256, priv, msg, pers);
203+
return new DRBG({
204+
hash: hashjs.sha256,
205+
entropy: priv,
206+
nonce: msg,
207+
pers,
208+
});
181209
};
182210

183211
/**
184212
* Generate pub+priv nonce pair.
213+
*
185214
* @param {Buffer} msg
186215
* @param {Buffer} priv
187216
* @param {Buffer} data
217+
*
188218
* @returns {Buffer}
189219
*/
190220

0 commit comments

Comments
 (0)