Skip to content
Permalink
Newer
Older
100644 330 lines (285 sloc) 8.65 KB
1
/* eslint no-bitwise: ["error", {"allow": ["<<"]}] */
2
3
import base32 from 'base32.js';
4
import crc from 'crc';
5
import isUndefined from 'lodash/isUndefined';
6
import isNull from 'lodash/isNull';
7
import isString from 'lodash/isString';
8
import { verifyChecksum } from './util/checksum';
9
10
const versionBytes = {
11
ed25519PublicKey: 6 << 3, // G (when encoded in base32)
Jan 30, 2017
12
ed25519SecretSeed: 18 << 3, // S
13
med25519PublicKey: 12 << 3, // M
14
preAuthTx: 19 << 3, // T
15
sha256Hash: 23 << 3, // X
16
signedPayload: 15 << 3 // P
17
};
18
19
const strkeyTypes = {
20
G: 'ed25519PublicKey',
21
S: 'ed25519SecretSeed',
22
M: 'med25519PublicKey',
23
T: 'preAuthTx',
24
X: 'sha256Hash',
25
P: 'signedPayload'
26
};
27
Jan 19, 2017
28
/**
29
* StrKey is a helper class that allows encoding and decoding Stellar keys
30
* to/from strings, i.e. between their binary (Buffer, xdr.PublicKey, etc.) and
31
* string (i.e. "GABCD...", etc.) representations.
Jan 19, 2017
32
*/
33
export class StrKey {
34
/**
35
* Encodes `data` to strkey ed25519 public key.
36
*
37
* @param {Buffer} data raw data to encode
38
* @returns {string} "G..." representation of the key
Jan 19, 2017
39
*/
Jan 30, 2017
40
static encodeEd25519PublicKey(data) {
41
return encodeCheck('ed25519PublicKey', data);
Jan 19, 2017
42
}
43
44
/**
Jan 30, 2017
45
* Decodes strkey ed25519 public key to raw data.
46
*
47
* If the parameter is a muxed account key ("M..."), this will only encode it
48
* as a basic Ed25519 key (as if in "G..." format).
49
*
50
* @param {string} data "G..." (or "M...") key representation to decode
51
* @returns {Buffer} raw key
Jan 19, 2017
52
*/
Jan 30, 2017
53
static decodeEd25519PublicKey(data) {
54
return decodeCheck('ed25519PublicKey', data);
Jan 19, 2017
55
}
56
57
/**
Jan 30, 2017
58
* Returns true if the given Stellar public key is a valid ed25519 public key.
Jan 19, 2017
59
* @param {string} publicKey public key to check
60
* @returns {boolean}
61
*/
Jan 30, 2017
62
static isValidEd25519PublicKey(publicKey) {
63
return isValid('ed25519PublicKey', publicKey);
Jan 19, 2017
64
}
65
66
/**
Jan 30, 2017
67
* Encodes data to strkey ed25519 seed.
Jan 19, 2017
68
* @param {Buffer} data data to encode
69
* @returns {string}
70
*/
Jan 30, 2017
71
static encodeEd25519SecretSeed(data) {
72
return encodeCheck('ed25519SecretSeed', data);
Jan 19, 2017
73
}
74
75
/**
Jan 30, 2017
76
* Decodes strkey ed25519 seed to raw data.
77
* @param {string} address data to decode
Jan 19, 2017
78
* @returns {Buffer}
79
*/
80
static decodeEd25519SecretSeed(address) {
81
return decodeCheck('ed25519SecretSeed', address);
Jan 19, 2017
82
}
83
84
/**
Jan 30, 2017
85
* Returns true if the given Stellar secret key is a valid ed25519 secret seed.
86
* @param {string} seed seed to check
Jan 19, 2017
87
* @returns {boolean}
88
*/
Jan 30, 2017
89
static isValidEd25519SecretSeed(seed) {
90
return isValid('ed25519SecretSeed', seed);
Jan 19, 2017
91
}
92
93
/**
94
* Encodes data to strkey med25519 public key.
95
* @param {Buffer} data data to encode
96
* @returns {string}
97
*/
98
static encodeMed25519PublicKey(data) {
99
return encodeCheck('med25519PublicKey', data);
100
}
101
102
/**
103
* Decodes strkey med25519 public key to raw data.
104
* @param {string} address data to decode
105
* @returns {Buffer}
106
*/
107
static decodeMed25519PublicKey(address) {
108
return decodeCheck('med25519PublicKey', address);
109
}
110
111
/**
112
* Returns true if the given Stellar public key is a valid med25519 public key.
113
* @param {string} publicKey public key to check
114
* @returns {boolean}
115
*/
116
static isValidMed25519PublicKey(publicKey) {
117
return isValid('med25519PublicKey', publicKey);
118
}
119
Jan 19, 2017
120
/**
121
* Encodes data to strkey preAuthTx.
Jan 19, 2017
122
* @param {Buffer} data data to encode
123
* @returns {string}
124
*/
125
static encodePreAuthTx(data) {
126
return encodeCheck('preAuthTx', data);
Jan 19, 2017
127
}
128
129
/**
130
* Decodes strkey PreAuthTx to raw data.
131
* @param {string} address data to decode
Jan 19, 2017
132
* @returns {Buffer}
133
*/
134
static decodePreAuthTx(address) {
135
return decodeCheck('preAuthTx', address);
Jan 19, 2017
136
}
137
138
/**
Jan 30, 2017
139
* Encodes data to strkey sha256 hash.
Jan 19, 2017
140
* @param {Buffer} data data to encode
141
* @returns {string}
142
*/
Jan 30, 2017
143
static encodeSha256Hash(data) {
144
return encodeCheck('sha256Hash', data);
Jan 19, 2017
145
}
146
147
/**
Jan 30, 2017
148
* Decodes strkey sha256 hash to raw data.
149
* @param {string} address data to decode
Jan 19, 2017
150
* @returns {Buffer}
151
*/
152
static decodeSha256Hash(address) {
153
return decodeCheck('sha256Hash', address);
154
}
155
156
/**
157
* Encodes raw data to strkey signed payload (P...).
158
* @param {Buffer} data data to encode
159
* @returns {string}
160
*/
161
static encodeSignedPayload(data) {
162
return encodeCheck('signedPayload', data);
163
}
164
165
/**
166
* Decodes strkey signed payload (P...) to raw data.
167
* @param {string} address address to decode
168
* @returns {Buffer}
169
*/
170
static decodeSignedPayload(address) {
171
return decodeCheck('signedPayload', address);
172
}
173
174
/**
175
* Checks validity of alleged signed payload (P...) strkey address.
176
* @param {string} address signer key to check
177
* @returns {boolean}
178
*/
179
static isValidSignedPayload(address) {
180
return isValid('signedPayload', address);
181
}
182
183
static getVersionByteForPrefix(address) {
184
return strkeyTypes[address[0]];
Jan 19, 2017
185
}
186
}
187
188
/**
189
* Sanity-checks whether or not a strkey *appears* valid.
190
*
191
* @param {string} versionByteName the type of strkey to expect in `encoded`
192
* @param {string} encoded the strkey to validate
193
*
194
* @return {Boolean} whether or not the `encoded` strkey appears valid for the
195
* `versionByteName` strkey type (see `versionBytes`, above).
196
*
197
* @note This isn't a *definitive* check of validity, but rather a best-effort
198
* check based on (a) input length, (b) whether or not it can be decoded,
199
* and (c) output length.
200
*/
Jan 19, 2017
201
function isValid(versionByteName, encoded) {
202
if (!isString(encoded)) {
Jan 19, 2017
203
return false;
204
}
205
206
// basic length checks on the strkey lengths
207
switch (versionByteName) {
208
case 'ed25519PublicKey': // falls through
209
case 'ed25519SecretSeed': // falls through
210
case 'preAuthTx': // falls through
211
case 'sha256Hash':
212
if (encoded.length !== 56) {
213
return false;
214
}
215
break;
216
217
case 'med25519PublicKey':
218
if (encoded.length !== 69) {
219
return false;
220
}
221
break;
222
223
case 'signedPayload':
224
if (encoded.length < 56 || encoded.length > 165) {
225
return false;
226
}
227
break;
228
229
default:
Jan 19, 2017
230
return false;
231
}
232
233
let decoded = '';
234
try {
235
decoded = decodeCheck(versionByteName, encoded);
Jan 19, 2017
236
} catch (err) {
237
return false;
238
}
239
240
// basic length checks on the resulting buffer sizes
241
switch (versionByteName) {
242
case 'ed25519PublicKey': // falls through
243
case 'ed25519SecretSeed': // falls through
244
case 'preAuthTx': // falls through
245
case 'sha256Hash':
246
return decoded.length === 32;
247
248
case 'med25519PublicKey':
249
return decoded.length === 40; // +8 bytes for the ID
250
251
case 'signedPayload':
252
return (
253
// 32 for the signer, +4 for the payload size, then either +4 for the
254
// min or +64 for the max payload
255
decoded.length >= 32 + 4 + 4 && decoded.length <= 32 + 4 + 64
256
);
257
258
default:
259
return false;
260
}
Jan 19, 2017
261
}
262
263
export function decodeCheck(versionByteName, encoded) {
264
if (!isString(encoded)) {
265
throw new TypeError('encoded argument must be of type String');
266
}
267
268
const decoded = base32.decode(encoded);
269
const versionByte = decoded[0];
270
const payload = decoded.slice(0, -2);
271
const data = payload.slice(1);
272
const checksum = decoded.slice(-2);
273
274
if (encoded !== base32.encode(decoded)) {
275
throw new Error('invalid encoded string');
276
}
277
278
const expectedVersion = versionBytes[versionByteName];
279
280
if (isUndefined(expectedVersion)) {
281
throw new Error(
282
`${versionByteName} is not a valid version byte name. ` +
283
`Expected one of ${Object.keys(versionBytes).join(', ')}`
285
}
286
287
if (versionByte !== expectedVersion) {
288
throw new Error(
289
`invalid version byte. expected ${expectedVersion}, got ${versionByte}`
290
);
291
}
293
const expectedChecksum = calculateChecksum(payload);
294
295
if (!verifyChecksum(expectedChecksum, checksum)) {
296
throw new Error(`invalid checksum`);
297
}
298
299
return Buffer.from(data);
300
}
301
302
export function encodeCheck(versionByteName, data) {
303
if (isNull(data) || isUndefined(data)) {
304
throw new Error('cannot encode null data');
305
}
306
307
const versionByte = versionBytes[versionByteName];
308
309
if (isUndefined(versionByte)) {
310
throw new Error(
311
`${versionByteName} is not a valid version byte name. ` +
312
`Expected one of ${Object.keys(versionBytes).join(', ')}`
314
}
315
data = Buffer.from(data);
317
const versionBuffer = Buffer.from([versionByte]);
318
const payload = Buffer.concat([versionBuffer, data]);
319
const checksum = calculateChecksum(payload);
320
const unencoded = Buffer.concat([payload, checksum]);
321
322
return base32.encode(unencoded);
323
}
324
325
// Computes the CRC16-XModem checksum of `payload` in little-endian order
326
function calculateChecksum(payload) {
327
const checksum = Buffer.alloc(2);
328
checksum.writeUInt16LE(crc.crc16xmodem(payload), 0);
329
return checksum;