• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nats-io / nats.java / #1906

17 Mar 2025 04:10PM UTC coverage: 95.82% (-0.03%) from 95.846%
#1906

push

github

web-flow
Merge pull request #1290 from nats-io/bouncy-castle-for-main

Replace ed25519 with BouncyCastle

37 of 40 new or added lines in 1 file covered. (92.5%)

1 existing line in 1 file now uncovered.

11463 of 11963 relevant lines covered (95.82%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.61
/src/main/java/io/nats/client/NKey.java
1
// Copyright 2018 The NATS Authors
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
// you may not use this file except in compliance with the License.
4
// You may obtain a copy of the License at:
5
//
6
// http://www.apache.org/licenses/LICENSE-2.0
7
//
8
// Unless required by applicable law or agreed to in writing, software
9
// distributed under the License is distributed on an "AS IS" BASIS,
10
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
// See the License for the specific language governing permissions and
12
// limitations under the License.
13

14
package io.nats.client;
15

16
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
17
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
18
import org.bouncycastle.crypto.signers.Ed25519Signer;
19

20
import java.io.ByteArrayOutputStream;
21
import java.io.IOException;
22
import java.nio.ByteBuffer;
23
import java.nio.ByteOrder;
24
import java.security.*;
25
import java.util.Arrays;
26

27
import static io.nats.client.support.Encoding.base32Decode;
28
import static io.nats.client.support.Encoding.base32Encode;
29
import static io.nats.client.support.RandomUtils.SRAND;
30

31
class DecodedSeed {
1✔
32
    int prefix;
33
    byte[] bytes;
34
}
35

36
/**
37
 * <p>
38
 * The NATS ecosystem will be moving to Ed25519 keys for identity,
39
 * authentication and authorization for entities such as Accounts, Users,
40
 * Servers and Clusters.
41
 * </p>
42
 * <p>
43
 * NKeys are based on the Ed25519 standard. This signing algorithm provides for
44
 * the use of public and private keys to sign and verify data. NKeys is designed
45
 * to formulate keys in a much friendlier fashion referencing work done in
46
 * cryptocurrencies, specifically Stellar. Bitcoin and others use a form of
47
 * Base58 (or Base58Check) to encode raw keys. Stellar utilizes a more
48
 * traditional Base32 with a CRC16 and a version or prefix byte. NKeys utilizes
49
 * a similar format with one or two prefix bytes. The base32 encoding of these
50
 * prefixes will yield friendly human readable prefixes, e.g. 'N' = server, 'C'
51
 * = cluster, 'O' = operator, 'A' = account, and 'U' = user to help developers
52
 * and administrators quickly identify key types.
53
 * </p>
54
 * <p>
55
 * Each NKey is generated from 32 bytes. These bytes are called the seed and are
56
 * encoded, in the NKey world, into a string starting with the letter 'S', with
57
 * a second character indicating the key’s type, e.g. "SU" is a seed for a u
58
 * er key pair, "SA" is a seed for an account key pair. The seed can be used t
59
 *  create the Ed25519 public/private key pair and should be protected as a p
60
 * ivate key. It is equivalent to the private key for a PGP key pair, or the m
61
 * ster password for your password vault.
62
 * </p>
63
 * <p>
64
 * Ed25519 uses the seed bytes to generate a key pair. The pair contains a
65
 * private key, which can be used to sign data, and a public key which can be
66
 * used to verify a signature. The public key can be distributed, and is not
67
 * considered secret.
68
 * </p>
69
 * <p>
70
 * The NKey libraries encode 32 byte public keys using Base32 and a CRC16
71
 * checksum plus a prefix based on the key type, e.g. U for a user key.
72
 * </p>
73
 * <p>
74
 * The NKey libraries have support for exporting a 64 byte private key. This
75
 * data is encoded into a string starting with the prefix ‘P’ for private. The
76
 * 64 bytes in a private key consists of the 32 bytes of the seed followed by
77
 * he 32 bytes of the public key. Essentially, the private key is redundant sin
78
 * e you can get it back from the seed alone. The NATS team recommends sto
79
 * ing the 32 byte seed and letting the NKey library regenerate anything els
80
 *  it needs for signing.
81
 * </p>
82
 * <p>
83
 * The existence of both a seed and a private key can result in confusion. It is
84
 * reasonable to simply think of Ed25519 as having a public key and a private
85
 * seed, and ignore the longer private key concept. In fact, the NKey libraries
86
 * generally expect you to create an NKey from either a public key, to use for
87
 * verification, or a seed, to use for signing.
88
 * </p>
89
 * <p>
90
 * The NATS system will utilize public NKeys for identification, the NATS system
91
 * will never store or even have access to any private keys or seeds.
92
 * Authentication will utilize a challenge-response mechanism based on a
93
 * collection of random bytes called a nonce.
94
 * </p>
95
 * <p>
96
 * Version note - 2.2.0 provided string arguments for seeds, this is not as safe
97
 * as char arrays, so in 2.3.0 we have included a breaking change to char arrays.
98
 * While this is not the proper version choice, NKeys aren't widely used, if at all yet,
99
 * so we are making the change on a minor jump.
100
 * </p>
101
 */
102
public class NKey {
103

104
    /**
105
     * NKeys use a prefix byte to indicate their intended owner: 'N' = server, 'C' =
106
     * cluster, 'A' = account, and 'U' = user. 'P' is used for private keys. The
107
     * NKey class formalizes these into the enum NKey.Type.
108
     */
109
    public enum Type {
1✔
110
        /** A user NKey. */
111
        USER(PREFIX_BYTE_USER),
1✔
112
        /** An account NKey. */
113
        ACCOUNT(PREFIX_BYTE_ACCOUNT),
1✔
114
        /** A server NKey. */
115
        SERVER(PREFIX_BYTE_SERVER),
1✔
116
        /** An operator NKey. */
117
        OPERATOR(PREFIX_BYTE_OPERATOR),
1✔
118
        /** A cluster NKey. */
119
        CLUSTER(PREFIX_BYTE_CLUSTER),
1✔
120
        /** A private NKey. */
121
        PRIVATE(PREFIX_BYTE_PRIVATE);
1✔
122

123
        private final int prefix;
124

125
        Type(int prefix) {
1✔
126
            this.prefix = prefix;
1✔
127
        }
1✔
128

129
        public static Type fromPrefix(int prefix) {
130
            if (prefix == PREFIX_BYTE_ACCOUNT) {
1✔
131
                return ACCOUNT;
1✔
132
            } else if (prefix == PREFIX_BYTE_SERVER) {
1✔
133
                return SERVER;
1✔
134
            } else if (prefix == PREFIX_BYTE_USER) {
1✔
135
                return USER;
1✔
136
            } else if (prefix == PREFIX_BYTE_CLUSTER) {
1✔
137
                return CLUSTER;
1✔
138
            } else if (prefix == PREFIX_BYTE_PRIVATE) {
1✔
139
                return ACCOUNT;
1✔
140
            } else if (prefix == PREFIX_BYTE_OPERATOR) {
1✔
141
                return OPERATOR;
1✔
142
            }
143

144
            throw new IllegalArgumentException("Unknown prefix");
1✔
145
        }
146
    }
147

148
    // PrefixByteSeed is the prefix byte used for encoded NATS Seeds
149
    private static final int PREFIX_BYTE_SEED = 18 << 3; // Base32-encodes to 'S...'
150

151
    // PrefixBytePrivate is the prefix byte used for encoded NATS Private keys
152
    static final int PREFIX_BYTE_PRIVATE = 15 << 3; // Base32-encodes to 'P...'
153

154
    // PrefixByteServer is the prefix byte used for encoded NATS Servers
155
    static final int PREFIX_BYTE_SERVER = 13 << 3; // Base32-encodes to 'N...'
156

157
    // PrefixByteCluster is the prefix byte used for encoded NATS Clusters
158
    static final int PREFIX_BYTE_CLUSTER = 2 << 3; // Base32-encodes to 'C...'
159

160
    // PrefixByteAccount is the prefix byte used for encoded NATS Accounts
161
    static final int PREFIX_BYTE_ACCOUNT = 0; // Base32-encodes to 'A...'
162

163
    // PrefixByteUser is the prefix byte used for encoded NATS Users
164
    static final int PREFIX_BYTE_USER = 20 << 3; // Base32-encodes to 'U...'
165

166
    // PrefixByteOperator is the prefix byte used for encoded NATS Operators
167
    static final int PREFIX_BYTE_OPERATOR = 14 << 3; // Base32-encodes to 'O...'
168

169
    private static final int ED25519_PUBLIC_KEYSIZE = 32;
170
    private static final int ED25519_PRIVATE_KEYSIZE = 64;
171
    private static final int ED25519_SEED_SIZE = 32;
172

173
    // XModem CRC based on the go version of NKeys
174
    private final static int[] crc16table = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108,
1✔
175
        0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294,
176
        0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420,
177
        0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
178
        0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df,
179
        0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed,
180
        0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33,
181
        0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5,
182
        0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97,
183
        0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
184
        0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2,
185
        0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
186
        0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e,
187
        0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa,
188
        0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615,
189
        0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
190
        0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75,
191
        0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b,
192
        0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d,
193
        0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };
194

195
    static int crc16(byte[] bytes) {
196
        int crc = 0;
1✔
197

198
        for (byte b : bytes) {
1✔
199
            crc = ((crc << 8) & 0xffff) ^ crc16table[((crc >> 8) ^ (b & 0xFF)) & 0x00FF];
1✔
200
        }
201

202
        return crc;
1✔
203
    }
204

205
    private static boolean notValidPublicPrefixByte(int prefix) {
206
        switch (prefix) {
1✔
207
            case PREFIX_BYTE_SERVER:
208
            case PREFIX_BYTE_CLUSTER:
209
            case PREFIX_BYTE_OPERATOR:
210
            case PREFIX_BYTE_ACCOUNT:
211
            case PREFIX_BYTE_USER:
212
                return false;
1✔
213
        }
214
        return true;
1✔
215
    }
216

217
    static char[] removePaddingAndClear(char[] withPad) {
218
        int i;
219

220
        for (i=withPad.length-1;i>=0;i--) {
1✔
221
            if (withPad[i] != '=') {
1✔
222
                break;
1✔
223
            }
224
        }
225
        char[] withoutPad = new char[i+1];
1✔
226
        System.arraycopy(withPad, 0, withoutPad, 0, withoutPad.length);
1✔
227

228
        for (int j=0; j<withPad.length;j++) {
1✔
229
            withPad[j] = '\0';
1✔
230
        }
231

232
        return withoutPad;
1✔
233
    }
234

235
    static char[] encode(Type type, byte[] src) throws IOException {
236
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
1✔
237

238
        bytes.write(type.prefix);
1✔
239
        bytes.write(src);
1✔
240

241
        int crc = crc16(bytes.toByteArray());
1✔
242
        byte[] littleEndian = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) crc).array();
1✔
243

244
        bytes.write(littleEndian);
1✔
245

246
        char[] withPad = base32Encode(bytes.toByteArray());
1✔
247
        return removePaddingAndClear(withPad);
1✔
248
    }
249

250
    static char[] encodeSeed(Type type, byte[] src) throws IOException {
251
        if (src.length != ED25519_PRIVATE_KEYSIZE && src.length != ED25519_SEED_SIZE) {
1✔
252
            throw new IllegalArgumentException("Source is not the correct size for an ED25519 seed");
1✔
253
        }
254

255
        // In order to make this human printable for both bytes, we need to do a little
256
        // bit manipulation to setup for base32 encoding which takes 5 bits at a time.
257
        int b1 = PREFIX_BYTE_SEED | (type.prefix >> 5);
1✔
258
        int b2 = (type.prefix & 31) << 3; // 31 = 00011111
1✔
259

260
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
1✔
261

262
        bytes.write(b1);
1✔
263
        bytes.write(b2);
1✔
264
        bytes.write(src);
1✔
265

266
        int crc = crc16(bytes.toByteArray());
1✔
267
        byte[] littleEndian = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) crc).array();
1✔
268

269
        bytes.write(littleEndian);
1✔
270

271
        char[] withPad = base32Encode(bytes.toByteArray());
1✔
272
        return removePaddingAndClear(withPad);
1✔
273
    }
274

275
    static byte[] decode(char[] src) {
276
        byte[] raw = base32Decode(src);
1✔
277
        if (raw.length < 4) {
1✔
278
            throw new IllegalArgumentException("Invalid encoding for source string");
1✔
279
        }
280

281
        byte[] crcBytes = Arrays.copyOfRange(raw, raw.length - 2, raw.length);
1✔
282
        byte[] dataBytes = Arrays.copyOfRange(raw, 0, raw.length - 2);
1✔
283

284
        int crc = ByteBuffer.wrap(crcBytes).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
1✔
285
        int actual = crc16(dataBytes);
1✔
286

287
        if (actual != crc) {
1✔
288
            throw new IllegalArgumentException("CRC is invalid");
1✔
289
        }
290

291
        return dataBytes;
1✔
292
    }
293

294
    static byte[] decode(Type expectedType, char[] src, boolean safe) {
295
        byte[] raw = decode(src);
1✔
296
        byte[] dataBytes = Arrays.copyOfRange(raw, 1, raw.length);
1✔
297
        Type type = NKey.Type.fromPrefix(raw[0] & 0xFF);
1✔
298

299
        if (type != expectedType) {
1✔
300
            if (safe) {
1✔
301
                return null;
1✔
302
            }
303
            throw new IllegalArgumentException("Unexpected type");
1✔
304
        }
305

306
        return dataBytes;
1✔
307
    }
308

309
    static DecodedSeed decodeSeed(char[] seed) {
310
        byte[] raw = decode(seed);
1✔
311

312
        // Need to do the reverse here to get back to internal representation.
313
        int b1 = raw[0] & 248; // 248 = 11111000
1✔
314
        int b2 = (raw[0] & 7) << 5 | ((raw[1] & 248) >> 3); // 7 = 00000111
1✔
315

316
        if (b1 != PREFIX_BYTE_SEED) {
1✔
317
            throw new IllegalArgumentException("Invalid encoding");
1✔
318
        }
319

320
        if (notValidPublicPrefixByte(b2)) {
1✔
321
            throw new IllegalArgumentException("Invalid encoded prefix byte");
×
322
        }
323

324
        byte[] dataBytes = Arrays.copyOfRange(raw, 2, raw.length);
1✔
325
        DecodedSeed retVal = new DecodedSeed();
1✔
326
        retVal.prefix = b2;
1✔
327
        retVal.bytes = dataBytes;
1✔
328
        return retVal;
1✔
329
    }
330

331
    private static NKey createPair(Type type, SecureRandom random) throws IOException {
332
        byte[] seed = new byte[ED25519_SEED_SIZE];
1✔
333
        if (random == null) {
1✔
334
            SRAND.nextBytes(seed);
1✔
335
        }
336
        else {
NEW
337
            random.nextBytes(seed);
×
338
        }
339
        return createPair(type, seed);
1✔
340
    }
341

342
    private static NKey createPair(Type type, byte[] seed) throws IOException {
343
        Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(seed);
1✔
344
        Ed25519PublicKeyParameters publicKey = privateKey.generatePublicKey();
1✔
345

346
        byte[] pubBytes = publicKey.getEncoded();
1✔
347

348
        byte[] bytes = new byte[pubBytes.length + seed.length];
1✔
349
        System.arraycopy(seed, 0, bytes, 0, seed.length);
1✔
350
        System.arraycopy(pubBytes, 0, bytes, seed.length, pubBytes.length);
1✔
351

352
        char[] encoded = encodeSeed(type, bytes);
1✔
353
        return new NKey(type, null, encoded);
1✔
354
    }
355

356
    /**
357
     * Create an Account NKey from the provided random number generator.
358
     * If no random is provided, SecureRandom() will be used to create one.
359
     * The new NKey contains the private seed, which should be saved in a secure location.
360
     * @param random A secure random provider
361
     * @return the new Nkey
362
     * @throws IOException if the seed cannot be encoded to a string
363
     * @throws NoSuchProviderException if the default secure random cannot be created
364
     * @throws NoSuchAlgorithmException if the default secure random cannot be created
365
     */
366
    public static NKey createAccount(SecureRandom random)
367
        throws IOException, NoSuchProviderException, NoSuchAlgorithmException {
368
        return createPair(Type.ACCOUNT, random);
1✔
369
    }
370

371
    /**
372
     * Create a Cluster NKey from the provided random number generator.
373
     * If no random is provided, SecureRandom() will be used to create one.
374
     * The new NKey contains the private seed, which should be saved in a secure location.
375
     * @param random A secure random provider
376
     * @return the new Nkey
377
     * @throws IOException if the seed cannot be encoded to a string
378
     * @throws NoSuchProviderException if the default secure random cannot be created
379
     * @throws NoSuchAlgorithmException if the default secure random cannot be created
380
     */
381
    public static NKey createCluster(SecureRandom random)
382
        throws IOException, NoSuchProviderException, NoSuchAlgorithmException {
383
        return createPair(Type.CLUSTER, random);
1✔
384
    }
385

386
    /**
387
     * Create an Operator NKey from the provided random number generator.
388
     * If no random is provided, SecureRandom() will be used to create one.
389
     * The new NKey contains the private seed, which should be saved in a secure location.
390
     * @param random A secure random provider
391
     * @return the new Nkey
392
     * @throws IOException if the seed cannot be encoded to a string
393
     * @throws NoSuchProviderException if the default secure random cannot be created
394
     * @throws NoSuchAlgorithmException if the default secure random cannot be created
395
     */
396
    public static NKey createOperator(SecureRandom random)
397
        throws IOException, NoSuchProviderException, NoSuchAlgorithmException {
398
        return createPair(Type.OPERATOR, random);
1✔
399
    }
400

401
    /**
402
     * Create a Server NKey from the provided random number generator.
403
     * If no random is provided, SecureRandom() will be used to create one.
404
     * The new NKey contains the private seed, which should be saved in a secure location.
405
     * @param random A secure random provider
406
     * @return the new Nkey
407
     * @throws IOException if the seed cannot be encoded to a string
408
     * @throws NoSuchProviderException if the default secure random cannot be created
409
     * @throws NoSuchAlgorithmException if the default secure random cannot be created
410
     */
411
    public static NKey createServer(SecureRandom random)
412
        throws IOException, NoSuchProviderException, NoSuchAlgorithmException {
413
        return createPair(Type.SERVER, random);
1✔
414
    }
415

416
    /**
417
     * Create a User NKey from the provided random number generator.
418
     * If no random is provided, SecureRandom() will be used to create one.
419
     * The new NKey contains the private seed, which should be saved in a secure location.
420
     *
421
     * @param random A secure random provider
422
     * @return the new Nkey
423
     * @throws IOException if the seed cannot be encoded to a string
424
     * @throws NoSuchProviderException if the default secure random cannot be created
425
     * @throws NoSuchAlgorithmException if the default secure random cannot be created
426
     */
427
    public static NKey createUser(SecureRandom random)
428
        throws IOException, NoSuchProviderException, NoSuchAlgorithmException {
429
        return createPair(Type.USER, random);
1✔
430
    }
431

432
    /**
433
     * Create an NKey object from the encoded public key. This NKey can be used for verification but not for signing.
434
     * @param publicKey the string encoded public key
435
     * @return the new Nkey
436
     */
437
    public static NKey fromPublicKey(char[] publicKey) {
438
        byte[] raw = decode(publicKey);
1✔
439
        int prefix = raw[0] & 0xFF;
1✔
440

441
        if (notValidPublicPrefixByte(prefix)) {
1✔
442
            throw new IllegalArgumentException("Not a valid public NKey");
1✔
443
        }
444

445
        Type type = NKey.Type.fromPrefix(prefix);
1✔
446
        return new NKey(type, publicKey, null);
1✔
447
    }
448

449
    /**
450
     * Creates an NKey object from a string encoded seed. This NKey can be used to sign or verify.
451
     * @param seed the string encoded seed, see {@link NKey#getSeed() getSeed()}
452
     * @return the Nkey
453
     */
454
    public static NKey fromSeed(char[] seed) {
455
        DecodedSeed decoded = decodeSeed(seed); // Should throw on bad seed
1✔
456

457
        if (decoded.bytes.length == ED25519_PRIVATE_KEYSIZE) {
1✔
458
            return new NKey(Type.fromPrefix(decoded.prefix), null, seed);
×
459
        } else {
460
            try {
461
                return createPair(Type.fromPrefix(decoded.prefix), decoded.bytes);
1✔
462
            } catch (Exception e) {
×
463
                throw new IllegalArgumentException("Bad seed value", e);
×
464
            }
465
        }
466
    }
467

468
    /**
469
     * @param src the encoded public key
470
     * @return true if the public key is an account public key
471
     */
472
    public static boolean isValidPublicAccountKey(char[] src) {
473
        return decode(Type.ACCOUNT, src, true) != null;
1✔
474
    }
475

476
    /**
477
     * @param src the encoded public key
478
     * @return true if the public key is a cluster public key
479
     */
480
    public static boolean isValidPublicClusterKey(char[] src) {
481
        return decode(Type.CLUSTER, src, true) != null;
1✔
482
    }
483

484
    /**
485
     * @param src the encoded public key
486
     * @return true if the public key is an operator public key
487
     */
488
    public static boolean isValidPublicOperatorKey(char[] src) {
489
        return decode(Type.OPERATOR, src, true) != null;
1✔
490
    }
491

492
    /**
493
     * @param src the encoded public key
494
     * @return true if the public key is a server public key
495
     */
496
    public static boolean isValidPublicServerKey(char[] src) {
497
        return decode(Type.SERVER, src, true) != null;
1✔
498
    }
499

500
    /**
501
     * @param src the encoded public key
502
     * @return true if the public key is a user public key
503
     */
504
    public static boolean isValidPublicUserKey(char[] src) {
505
        return decode(Type.USER, src, true) != null;
1✔
506
    }
507

508
    /**
509
     * The seed or private key per the Ed25519 spec, encoded with encodeSeed.
510
     */
511
    private final char[] privateKeyAsSeed;
512

513
    /**
514
     * The public key, maybe null. Used for public only NKeys.
515
     */
516
    private final char[] publicKey;
517

518
    private final Type type;
519

520
    private NKey(Type t, char[] publicKey, char[] privateKey) {
1✔
521
        this.type = t;
1✔
522
        this.privateKeyAsSeed = privateKey;
1✔
523
        this.publicKey = publicKey;
1✔
524
    }
1✔
525

526
    /**
527
     * Clear the seed and public key char arrays by zero-ing them out.
528
     * The nkey is unusable after this operation.
529
     */
530
    public void clear() {
531
        if (privateKeyAsSeed != null) {
1✔
532
            for (int i=0; i< privateKeyAsSeed.length ; i++) {
1✔
533
                privateKeyAsSeed[i] = 0;
1✔
534
            }
535
        }
536
        if (publicKey != null) {
1✔
537
            for (int i=0; i< publicKey.length ; i++) {
1✔
538
                publicKey[i] = 0;
1✔
539
            }
540
        }
541
    }
1✔
542

543
    /**
544
     * @return the string encoded seed for this NKey
545
     */
546
    public char[] getSeed() {
547
        if (privateKeyAsSeed == null) {
1✔
548
            throw new IllegalStateException("Public-only NKey");
1✔
549
        }
550
        DecodedSeed decoded = decodeSeed(privateKeyAsSeed);
1✔
551
        byte[] seedBytes = new byte[ED25519_SEED_SIZE];
1✔
552
        System.arraycopy(decoded.bytes, 0, seedBytes, 0, seedBytes.length);
1✔
553
        try {
554
            return encodeSeed(Type.fromPrefix(decoded.prefix), seedBytes);
1✔
555
        } catch (Exception e) {
×
556
            throw new IllegalStateException("Unable to create seed.", e);
×
557
        }
558
    }
559

560
    /**
561
     * @return the encoded public key for this NKey
562
     *
563
     * @throws GeneralSecurityException if there is an encryption problem
564
     * @throws IOException              if there is a problem encoding the public
565
     *                                  key
566
     */
567
    public char[] getPublicKey() throws GeneralSecurityException, IOException {
568
        if (publicKey != null) {
1✔
569
            return publicKey;
1✔
570
        }
571
        return encode(this.type, getKeyPair().getPublic().getEncoded());
1✔
572
    }
573

574
    /**
575
     * @return the encoded private key for this NKey
576
     *
577
     * @throws GeneralSecurityException if there is an encryption problem
578
     * @throws IOException              if there is a problem encoding the key
579
     */
580
    public char[] getPrivateKey() throws GeneralSecurityException, IOException {
581
        if (privateKeyAsSeed == null) {
1✔
582
            throw new IllegalStateException("Public-only NKey");
1✔
583
        }
584

585
        DecodedSeed decoded = decodeSeed(privateKeyAsSeed);
1✔
586
        return encode(Type.PRIVATE, decoded.bytes);
1✔
587
    }
588

589
    /**
590
     * @return A Java security keypair that represents this NKey in Java security
591
     *         form.
592
     *
593
     * @throws GeneralSecurityException if there is an encryption problem
594
     * @throws IOException              if there is a problem encoding or decoding
595
     */
596
    public KeyPair getKeyPair() throws GeneralSecurityException, IOException {
597
        if (privateKeyAsSeed == null) {
1✔
598
            throw new IllegalStateException("Public-only NKey");
1✔
599
        }
600

601
        DecodedSeed decoded = decodeSeed(privateKeyAsSeed);
1✔
602
        byte[] seedBytes = new byte[ED25519_SEED_SIZE];
1✔
603
        byte[] pubBytes = new byte[ED25519_PUBLIC_KEYSIZE];
1✔
604

605
        System.arraycopy(decoded.bytes, 0, seedBytes, 0, seedBytes.length);
1✔
606
        System.arraycopy(decoded.bytes, seedBytes.length, pubBytes, 0, pubBytes.length);
1✔
607

608
        Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(seedBytes);
1✔
609
        Ed25519PublicKeyParameters publicKey = new Ed25519PublicKeyParameters(pubBytes);
1✔
610

611
        return new KeyPair(new PublicKeyWrapper(publicKey), new PrivateKeyWrapper(privateKey));
1✔
612
    }
613

614
    /**
615
     * @return the Type of this NKey
616
     */
617
    public Type getType() {
618
        return type;
1✔
619
    }
620

621
    /**
622
     * Sign arbitrary binary input.
623
     *
624
     * @param input the bytes to sign
625
     * @return the signature for the input from the NKey
626
     *
627
     * @throws GeneralSecurityException if there is an encryption problem
628
     * @throws IOException              if there is a problem reading the data
629
     */
630
    public byte[] sign(byte[] input) throws GeneralSecurityException, IOException {
631
        Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(getKeyPair().getPrivate().getEncoded());
1✔
632
        Ed25519Signer signer = new Ed25519Signer();
1✔
633
        signer.init(true, privateKey);
1✔
634
        signer.update(input, 0, input.length);
1✔
635
        return signer.generateSignature();
1✔
636
    }
637

638
    /**
639
     * Verify a signature.
640
     *
641
     * @param input     the bytes that were signed
642
     * @param signature the bytes for the signature
643
     * @return true if the signature matches this keys signature for the input.
644
     *
645
     * @throws GeneralSecurityException if there is an encryption problem
646
     * @throws IOException              if there is a problem reading the data
647
     */
648
    public boolean verify(byte[] input, byte[] signature) throws GeneralSecurityException, IOException {
649
        Ed25519PublicKeyParameters publicKey;
650
        if (privateKeyAsSeed != null) {
1✔
651
            publicKey = new Ed25519PublicKeyParameters(getKeyPair().getPublic().getEncoded());
1✔
652
        } else {
653
            char[] encodedPublicKey = getPublicKey();
1✔
654
            byte[] decodedPublicKey = decode(this.type, encodedPublicKey, false);
1✔
655
            //noinspection DataFlowIssue // decode will throw instead of return null
656
            publicKey = new Ed25519PublicKeyParameters(decodedPublicKey);
1✔
657
        }
658

659
        Ed25519Signer signer = new Ed25519Signer();
1✔
660
        signer.init(false, publicKey);
1✔
661
        signer.update(input, 0, input.length);
1✔
662
        return signer.verifySignature(signature);
1✔
663
    }
664

665
    @Override
666
    public boolean equals(Object o) {
667
        if (o == this)
1✔
668
            return true;
1✔
669
        if (!(o instanceof NKey)) {
1✔
670
            return false;
1✔
671
        }
672

673
        NKey otherNKey = (NKey) o;
1✔
674

675
        if (this.type != otherNKey.type) {
1✔
676
            return false;
1✔
677
        }
678

679
        if (this.privateKeyAsSeed == null) {
1✔
680
            return Arrays.equals(this.publicKey, otherNKey.publicKey);
1✔
681
        }
682

683
        return Arrays.equals(this.privateKeyAsSeed, otherNKey.privateKeyAsSeed);
1✔
684
    }
685

686
    @Override
687
    public int hashCode() {
688
        int result = 17;
1✔
689
        result = 31 * result + this.type.prefix;
1✔
690

691
        if (this.privateKeyAsSeed == null) {
1✔
692
            result = 31 * result + Arrays.hashCode(this.publicKey);
1✔
693
        } else {
694
            result = 31 * result + Arrays.hashCode(this.privateKeyAsSeed);
1✔
695
        }
696
        return result;
1✔
697
    }
698
}
699

700
abstract class KeyWrapper implements Key {
1✔
701
    @Override
702
    public String getAlgorithm() {
NEW
703
        return "EdDSA";
×
704
    }
705

706
    @Override
707
    public String getFormat() {
NEW
708
        return "PKCS#8";
×
709
    }
710
}
711

712
class PublicKeyWrapper extends KeyWrapper implements PublicKey {
713
    final Ed25519PublicKeyParameters publicKey;
714

715
    public PublicKeyWrapper(Ed25519PublicKeyParameters publicKey) {
1✔
716
        this.publicKey = publicKey;
1✔
717
    }
1✔
718

719
    @Override
720
    public byte[] getEncoded() {
721
        return publicKey.getEncoded();
1✔
722
    }
723
}
724

725
class PrivateKeyWrapper extends KeyWrapper implements PrivateKey {
726
    final Ed25519PrivateKeyParameters privateKey;
727

728
    public PrivateKeyWrapper(Ed25519PrivateKeyParameters privateKey) {
1✔
729
        this.privateKey = privateKey;
1✔
730
    }
1✔
731

732
    @Override
733
    public byte[] getEncoded() {
734
        return privateKey.getEncoded();
1✔
735
    }
736
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc