Skip to content

Commit b8c2b20

Browse files
authored
feat(MemoryAccount): Add validation of keypair (#594)
* feat(MemoryAccount): Add validation of keypair. Cover by tests * fix(Account): condition in selectAccount
1 parent 9aaa05c commit b8c2b20

File tree

6 files changed

+104
-12
lines changed

6 files changed

+104
-12
lines changed

es/account/memory.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import Account from './'
2626
import * as Crypto from '../utils/crypto'
27+
import { isHex } from '../utils/string'
2728

2829
const secrets = new WeakMap()
2930

@@ -37,7 +38,7 @@ async function address (format = Crypto.ADDRESS_FORMAT.api) {
3738

3839
function setSecret (keyPair) {
3940
secrets.set(this, {
40-
secretKey: Buffer.from(keyPair.secretKey, 'hex'),
41+
secretKey: Buffer.isBuffer(keyPair.secretKey) ? keyPair.secretKey : Buffer.from(keyPair.secretKey, 'hex'),
4142
publicKey: keyPair.publicKey
4243
})
4344
}
@@ -48,7 +49,11 @@ function validateKeyPair (keyPair) {
4849
keyPair = { publicKey: keyPair.pub, secretKey: keyPair.priv }
4950
}
5051
if (!keyPair.secretKey || !keyPair.publicKey) throw new Error('KeyPair must must have "secretKey", "publicKey" properties')
51-
if (typeof keyPair.publicKey !== 'string' || keyPair.publicKey.indexOf('ak_') === -1) throw new Error('Public Key must be a string with "ak_" prefix')
52+
if (typeof keyPair.publicKey !== 'string' || keyPair.publicKey.indexOf('ak_') === -1) throw new Error('Public Key must be a base58c string with "ak_" prefix')
53+
if (
54+
!Buffer.isBuffer(keyPair.secretKey) &&
55+
(typeof keyPair.secretKey === 'string' && !isHex(keyPair.secretKey))
56+
) throw new Error('Secret key must be hex string or Buffer')
5257
}
5358

5459
/**

es/account/selector.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async function address ({ onAccount } = {}) {
4949
* @example selectAccount('ak_xxxxxxxx')
5050
*/
5151
function selectAccount (address) {
52-
if (address && !assertedType(address, 'ak', true)) throw new Error(`Invalid account address`)
52+
if (!address || !assertedType(address, 'ak', true)) throw new Error(`Invalid account address`)
5353
this.Selector.address = address
5454
}
5555

es/utils/crypto.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,13 +454,15 @@ export function decryptPubKey (password, encrypted) {
454454
* @rtype (data: String, type: String) => String, throws: Error
455455
* @param {String} data - ae data
456456
* @param {String} type - Prefix
457+
* @param forceError
457458
* @return {String} Payload
458459
*/
459460
export function assertedType (data, type, forceError = false) {
460461
if (RegExp(`^${type}_.+$`).test(data)) {
461462
return data.split('_')[1]
462463
} else {
463464
if (!forceError) throw Error(`Data doesn't match expected type ${type}`)
465+
return false
464466
}
465467
}
466468

es/utils/keystore.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import nacl from 'tweetnacl'
22
import uuid from 'uuid'
33

44
import { encodeBase58Check, isBase64 } from './crypto'
5+
import { isHex } from './string'
56

67
/**
78
* KeyStore module
@@ -70,15 +71,6 @@ function decryptXsalsa20Poly1305 ({ ciphertext, key, nonce }) {
7071
return res
7172
}
7273

73-
/**
74-
* Check whether a string is valid hex.
75-
* @param {string} str String to validate.
76-
* @return {boolean} True if the string is valid hex, false otherwise.
77-
*/
78-
function isHex (str) {
79-
return !!(str.length % 2 === 0 && str.match(/^[0-9a-f]+$/i))
80-
}
81-
8274
/**
8375
* Convert a string to a Buffer. If encoding is not specified, hex-encoding
8476
* will be used if the input is valid hex. If the input is valid base64 but

es/utils/string.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ export function snakeOrKebabToPascal (s) {
4646
export function pascalToSnake (s) {
4747
return s.replace(/[A-Z]/g, match => `_${R.toLower(match)}`)
4848
}
49+
50+
/**
51+
* Check whether a string is valid hex.
52+
* @param {string} str String to validate.
53+
* @return {boolean} True if the string is valid hex, false otherwise.
54+
*/
55+
export function isHex (str) {
56+
return !!(str.length % 2 === 0 && str.match(/^[0-9a-f]+$/i))
57+
}

test/unit/memory-account.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* ISC License (ISC)
3+
* Copyright (c) 2018 aeternity developers
4+
*
5+
* Permission to use, copy, modify, and/or distribute this software for any
6+
* purpose with or without fee is hereby granted, provided that the above
7+
* copyright notice and this permission notice appear in all copies.
8+
*
9+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10+
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11+
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12+
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13+
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14+
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15+
* PERFORMANCE OF THIS SOFTWARE.
16+
*/
17+
18+
import '../'
19+
import { describe, it } from 'mocha'
20+
import MemoryAccount from '../../es/account/memory'
21+
import { generateKeyPair } from '../../es/utils/crypto'
22+
23+
const testAcc = generateKeyPair()
24+
25+
describe('MemoryAccount', function () {
26+
describe('Fail on invalid params', () => {
27+
it('Fail on empty keypair', async () => {
28+
try {
29+
MemoryAccount({})
30+
} catch (e) {
31+
e.message.should.be.equal('KeyPair must be an object')
32+
}
33+
})
34+
it('Fail on empty keypair object', async () => {
35+
try {
36+
MemoryAccount({ keypair: {} })
37+
} catch (e) {
38+
e.message.should.be.equal('KeyPair must must have "secretKey", "publicKey" properties')
39+
}
40+
})
41+
it('Fail on invalid secret key', async () => {
42+
try {
43+
MemoryAccount({
44+
keypair: {
45+
publicKey: testAcc.publicKey,
46+
secretKey: ' '
47+
}
48+
})
49+
} catch (e) {
50+
e.message.should.be.equal('Secret key must be hex string or Buffer')
51+
}
52+
})
53+
it('Fail on invalid publicKey', async () => {
54+
try {
55+
MemoryAccount({
56+
keypair: {
57+
publicKey: ' ',
58+
secretKey: testAcc.secretKey
59+
}
60+
})
61+
} catch (e) {
62+
e.message.should.be.equal('Public Key must be a base58c string with "ak_" prefix')
63+
}
64+
})
65+
})
66+
it('Init with secretKey as hex string', async () => {
67+
const acc = MemoryAccount({
68+
keypair: {
69+
publicKey: testAcc.publicKey,
70+
secretKey: testAcc.secretKey
71+
}
72+
})
73+
return acc.address().should.eventually.be.equal(testAcc.publicKey)
74+
})
75+
it('Init with secretKey as hex Buffer', async () => {
76+
const acc = MemoryAccount({
77+
keypair: {
78+
publicKey: testAcc.publicKey,
79+
secretKey: Buffer.from(testAcc.secretKey, 'hex')
80+
}
81+
})
82+
return acc.address().should.eventually.be.equal(testAcc.publicKey)
83+
})
84+
})

0 commit comments

Comments
 (0)