aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nkeys.zig271
-rw-r--r--src/znk.zig24
2 files changed, 160 insertions, 135 deletions
diff --git a/src/nkeys.zig b/src/nkeys.zig
index 8493941..aac326c 100644
--- a/src/nkeys.zig
+++ b/src/nkeys.zig
@@ -9,9 +9,15 @@ const testing = std.testing;
9 9
10pub const InvalidPrefixByteError = error{InvalidPrefixByte}; 10pub const InvalidPrefixByteError = error{InvalidPrefixByte};
11pub const InvalidEncodingError = error{InvalidEncoding}; 11pub const InvalidEncodingError = error{InvalidEncoding};
12pub const InvalidPrivateKeyError = error{InvalidPrivateKey};
12pub const InvalidSeedError = error{InvalidSeed}; 13pub const InvalidSeedError = error{InvalidSeed};
14pub const InvalidSignatureError = error{InvalidSignature};
13pub const NoNkeySeedFoundError = error{NoNkeySeedFound}; 15pub const NoNkeySeedFoundError = error{NoNkeySeedFound};
14pub const NoNkeyUserSeedFoundError = error{NoNkeyUserSeedFound}; 16pub const NoNkeyUserSeedFoundError = error{NoNkeyUserSeedFound};
17pub const DecodeError = InvalidPrefixByteError || base32.DecodeError || crc16.InvalidChecksumError;
18pub const SeedDecodeError = DecodeError || InvalidSeedError || crypto.errors.IdentityElementError;
19pub const PrivateKeyDecodeError = DecodeError || InvalidPrivateKeyError || crypto.errors.IdentityElementError;
20pub const SignError = crypto.errors.IdentityElementError || crypto.errors.WeakPublicKeyError || crypto.errors.KeyMismatchError;
15 21
16pub const KeyTypePrefixByte = enum(u8) { 22pub const KeyTypePrefixByte = enum(u8) {
17 seed = 18 << 3, // S 23 seed = 18 << 3, // S
@@ -41,93 +47,93 @@ pub const PublicPrefixByte = enum(u8) {
41pub const SeedKeyPair = struct { 47pub const SeedKeyPair = struct {
42 const Self = @This(); 48 const Self = @This();
43 49
44 seed: text_seed, 50 prefix: PublicPrefixByte,
51 kp: Ed25519.KeyPair,
45 52
46 pub fn generate(prefix: PublicPrefixByte) Self { 53 pub fn generate(prefix: PublicPrefixByte) crypto.errors.IdentityElementError!Self {
47 var raw_seed: [Ed25519.seed_length]u8 = undefined; 54 var raw_seed: [Ed25519.seed_length]u8 = undefined;
48 crypto.random.bytes(&raw_seed); 55 crypto.random.bytes(&raw_seed);
49 defer wipeBytes(&raw_seed); 56 defer wipeBytes(&raw_seed);
50 57 return Self{ .prefix = prefix, .kp = try Ed25519.KeyPair.create(raw_seed) };
51 return Self{ .seed = encodeSeed(prefix, &raw_seed) };
52 }
53
54 pub fn fromTextSeed(seed: *const text_seed) SeedDecodeError!Self {
55 var decoded = try decodeSeed(seed);
56 decoded.wipe();
57 return Self{ .seed = seed.* };
58 }
59
60 pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) Self {
61 return Self{ .seed = encodeSeed(prefix, raw_seed) };
62 } 58 }
63 59
64 fn rawSeed(self: *const Self) SeedDecodeError![Ed25519.seed_length]u8 { 60 pub fn fromTextSeed(text: *const text_seed) SeedDecodeError!Self {
65 return (try decodeSeed(&self.seed)).seed; 61 var decoded = try decode(2, Ed25519.seed_length, text);
66 } 62 defer decoded.wipe(); // gets copied
67
68 fn keys(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!Ed25519.KeyPair {
69 return Ed25519.KeyPair.create(try rawSeed(self));
70 }
71 63
72 pub fn privateKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!text_private { 64 var key_ty_prefix = decoded.prefix[0] & 0b11111000;
73 var kp = try self.keys(); 65 var entity_ty_prefix = (decoded.prefix[0] << 5) | (decoded.prefix[1] >> 3);
74 defer wipeKeyPair(&kp);
75 return encodePrivate(&kp.secret_key);
76 }
77 66
78 pub fn publicKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!text_public { 67 if (key_ty_prefix != @enumToInt(KeyTypePrefixByte.seed))
79 var decoded = try decodeSeed(&self.seed); 68 return error.InvalidSeed;
80 defer decoded.wipe();
81 var kp = try Ed25519.KeyPair.create(decoded.seed);
82 defer wipeKeyPair(&kp);
83 return encodePublic(decoded.prefix, &kp.public_key);
84 }
85 69
86 pub fn intoPublicKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!PublicKey { 70 return Self{
87 var decoded = try decodeSeed(&self.seed); 71 .prefix = try PublicPrefixByte.fromU8(entity_ty_prefix),
88 defer decoded.wipe(); 72 .kp = try Ed25519.KeyPair.create(decoded.data),
89 var kp = try Ed25519.KeyPair.create(decoded.seed);
90 defer wipeKeyPair(&kp);
91 return PublicKey{
92 .prefix = decoded.prefix,
93 .key = kp.public_key,
94 }; 73 };
95 } 74 }
96 75
97 pub const SignError = SeedDecodeError || crypto.errors.IdentityElementError || crypto.errors.WeakPublicKeyError; 76 pub fn fromRawSeed(
77 prefix: PublicPrefixByte,
78 raw_seed: *const [Ed25519.seed_length]u8,
79 ) crypto.errors.IdentityElementError!Self {
80 return Self{ .prefix = prefix, .kp = try Ed25519.KeyPair.create(raw_seed.*) };
81 }
98 82
99 pub fn sign( 83 pub fn sign(
100 self: *const Self, 84 self: *const Self,
101 msg: []const u8, 85 msg: []const u8,
102 ) SignError![Ed25519.signature_length]u8 { 86 ) SignError![Ed25519.signature_length]u8 {
103 var kp = try self.keys(); 87 return Ed25519.sign(msg, self.kp, null);
104 defer wipeKeyPair(&kp);
105 return Ed25519.sign(msg, kp, null) catch |e| switch (e) {
106 error.KeyMismatch => unreachable, // would mean that self.keys() has an incorrect implementation
107 error.WeakPublicKey => error.WeakPublicKey,
108 error.IdentityElement => error.IdentityElement,
109 };
110 } 88 }
111 89
112 pub fn verify( 90 pub fn verify(
113 self: *const Self, 91 self: *const Self,
114 msg: []const u8, 92 msg: []const u8,
115 sig: [Ed25519.signature_length]u8, 93 sig: [Ed25519.signature_length]u8,
116 ) !void { 94 ) InvalidSignatureError!void {
117 var kp = try self.keys(); 95 Ed25519.verify(sig, msg, self.kp.public_key) catch return error.InvalidSignature;
118 defer wipeKeyPair(&kp);
119 Ed25519.verify(sig, msg, kp.public_key) catch return error.InvalidSignature;
120 } 96 }
121 97
122 pub fn wipe(self: *Self) void { 98 pub fn asTextSeed(self: *const Self) text_seed {
123 wipeBytes(&self.seed); 99 const full_prefix = &[_]u8{
100 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(self.prefix) >> 5),
101 (@enumToInt(self.prefix) & 0b00011111) << 3,
102 };
103 const seed = self.kp.secret_key[0..Ed25519.seed_length];
104 return encode(full_prefix.len, seed.len, full_prefix, seed);
105 }
106
107 pub fn asTextPrivateKey(self: *const Self) text_private {
108 return encode(1, self.kp.secret_key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key);
109 }
110
111 pub fn asTextPublicKey(self: *const Self) text_public {
112 return encode(1, self.kp.public_key.len, &[_]u8{@enumToInt(self.prefix)}, &self.kp.public_key);
124 } 113 }
125 114
126 fn wipeKeyPair(kp: *Ed25519.KeyPair) void { 115 pub fn asPublicKey(self: *const Self) PublicKey {
127 wipeBytes(&kp.secret_key); 116 return PublicKey{
117 .prefix = self.prefix,
118 .key = self.kp.public_key,
119 };
120 }
121
122 pub fn asPrivateKey(self: *const Self) PrivateKey {
123 return PrivateKey{ .kp = self.kp };
124 }
125
126 pub fn wipe(self: *Self) void {
127 self.prefix = .account;
128 wipeKeyPair(&self.kp);
128 } 129 }
129}; 130};
130 131
132fn wipeKeyPair(kp: *Ed25519.KeyPair) void {
133 wipeBytes(&kp.public_key);
134 wipeBytes(&kp.secret_key);
135}
136
131fn wipeBytes(bs: []u8) void { 137fn wipeBytes(bs: []u8) void {
132 for (bs) |*b| b.* = 0; 138 for (bs) |*b| b.* = 0;
133} 139}
@@ -138,7 +144,7 @@ pub const PublicKey = struct {
138 prefix: PublicPrefixByte, 144 prefix: PublicPrefixByte,
139 key: [Ed25519.public_length]u8, 145 key: [Ed25519.public_length]u8,
140 146
141 pub fn fromTextPublicKey(text: *const text_public) DecodeError!PublicKey { 147 pub fn fromTextPublicKey(text: *const text_public) DecodeError!Self {
142 var decoded = try decode(1, Ed25519.public_length, text); 148 var decoded = try decode(1, Ed25519.public_length, text);
143 defer decoded.wipe(); // gets copied 149 defer decoded.wipe(); // gets copied
144 return PublicKey{ 150 return PublicKey{
@@ -147,15 +153,15 @@ pub const PublicKey = struct {
147 }; 153 };
148 } 154 }
149 155
150 pub fn publicKey(self: *const Self) text_public { 156 pub fn asTextPublicKey(self: *const Self) text_public {
151 return encodePublic(self.prefix, &self.key); 157 return encode(1, self.key.len, &[_]u8{@enumToInt(self.prefix)}, &self.key);
152 } 158 }
153 159
154 pub fn verify( 160 pub fn verify(
155 self: *const Self, 161 self: *const Self,
156 msg: []const u8, 162 msg: []const u8,
157 sig: [Ed25519.signature_length]u8, 163 sig: [Ed25519.signature_length]u8,
158 ) !void { 164 ) InvalidSignatureError!void {
159 Ed25519.verify(sig, msg, self.key) catch return error.InvalidSignature; 165 Ed25519.verify(sig, msg, self.key) catch return error.InvalidSignature;
160 } 166 }
161 167
@@ -165,6 +171,57 @@ pub const PublicKey = struct {
165 } 171 }
166}; 172};
167 173
174pub const PrivateKey = struct {
175 const Self = @This();
176
177 kp: Ed25519.KeyPair,
178
179 pub fn fromTextPrivateKey(text: *const text_private) PrivateKeyDecodeError!Self {
180 var decoded = try decode(1, Ed25519.secret_length, text);
181 defer decoded.wipe(); // gets copied
182 if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private))
183 return error.InvalidPrivateKey;
184 return PrivateKey{ .kp = Ed25519.KeyPair.fromSecretKey(decoded.data) };
185 }
186
187 pub fn asSeedKeyPair(self: *const Self, prefix: PublicPrefixByte) SeedKeyPair {
188 return SeedKeyPair{
189 .prefix = prefix,
190 .kp = self.kp,
191 };
192 }
193
194 pub fn asPublicKey(self: *const Self, prefix: PublicPrefixByte) PublicKey {
195 return PublicKey{
196 .prefix = prefix,
197 .key = self.kp.public_key,
198 };
199 }
200
201 pub fn asTextPrivateKey(self: *const Self) text_private {
202 return encode(1, self.kp.secret_key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key);
203 }
204
205 pub fn sign(
206 self: *const Self,
207 msg: []const u8,
208 ) SignError![Ed25519.signature_length]u8 {
209 return Ed25519.sign(msg, self.kp, null);
210 }
211
212 pub fn verify(
213 self: *const Self,
214 msg: []const u8,
215 sig: [Ed25519.signature_length]u8,
216 ) InvalidSignatureError!void {
217 Ed25519.verify(sig, msg, self.kp.public_key) catch return error.InvalidSignature;
218 }
219
220 pub fn wipe(self: *Self) void {
221 wipeKeyPair(&self.kp);
222 }
223};
224
168// One prefix byte, two CRC bytes 225// One prefix byte, two CRC bytes
169const binary_private_size = 1 + Ed25519.secret_length + 2; 226const binary_private_size = 1 + Ed25519.secret_length + 2;
170// One prefix byte, two CRC bytes 227// One prefix byte, two CRC bytes
@@ -180,14 +237,6 @@ pub const text_private = [text_private_len]u8;
180pub const text_public = [text_public_len]u8; 237pub const text_public = [text_public_len]u8;
181pub const text_seed = [text_seed_len]u8; 238pub const text_seed = [text_seed_len]u8;
182 239
183fn encodePublic(prefix: PublicPrefixByte, key: *const [Ed25519.public_length]u8) text_public {
184 return encode(1, key.len, &[_]u8{@enumToInt(prefix)}, key);
185}
186
187fn encodePrivate(key: *const [Ed25519.secret_length]u8) text_private {
188 return encode(1, key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, key);
189}
190
191fn encoded_key(comptime prefix_len: usize, comptime data_len: usize) type { 240fn encoded_key(comptime prefix_len: usize, comptime data_len: usize) type {
192 return [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8; 241 return [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8;
193} 242}
@@ -213,16 +262,6 @@ fn encode(
213 return text; 262 return text;
214} 263}
215 264
216pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) text_seed {
217 const full_prefix = &[_]u8{
218 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5),
219 (@enumToInt(prefix) & 0b00011111) << 3,
220 };
221 return encode(full_prefix.len, src.len, full_prefix, src);
222}
223
224pub const DecodeError = InvalidPrefixByteError || base32.DecodeError || crc16.InvalidChecksumError;
225
226fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type { 265fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type {
227 return struct { 266 return struct {
228 const Self = @This(); 267 const Self = @This();
@@ -255,36 +294,6 @@ fn decode(
255 }; 294 };
256} 295}
257 296
258pub const DecodedSeed = struct {
259 const Self = @This();
260
261 prefix: PublicPrefixByte,
262 seed: [Ed25519.seed_length]u8,
263
264 pub fn wipe(self: *Self) void {
265 self.prefix = .account;
266 wipeBytes(&self.seed);
267 }
268};
269
270pub const SeedDecodeError = DecodeError || InvalidSeedError;
271
272pub fn decodeSeed(text: *const text_seed) SeedDecodeError!DecodedSeed {
273 var decoded = try decode(2, Ed25519.seed_length, text);
274 defer decoded.wipe(); // gets copied
275
276 var key_ty_prefix = decoded.prefix[0] & 0b11111000;
277 var entity_ty_prefix = (decoded.prefix[0] << 5) | (decoded.prefix[1] >> 3);
278
279 if (key_ty_prefix != @enumToInt(KeyTypePrefixByte.seed))
280 return error.InvalidSeed;
281
282 return DecodedSeed{
283 .prefix = try PublicPrefixByte.fromU8(entity_ty_prefix),
284 .seed = decoded.data,
285 };
286}
287
288pub fn isValidEncoding(text: []const u8) bool { 297pub fn isValidEncoding(text: []const u8) bool {
289 if (text.len < 4) return false; 298 if (text.len < 4) return false;
290 var made_crc: u16 = 0; 299 var made_crc: u16 = 0;
@@ -307,7 +316,7 @@ pub fn isValidEncoding(text: []const u8) bool {
307} 316}
308 317
309pub fn isValidSeed(text: *const text_seed) bool { 318pub fn isValidSeed(text: *const text_seed) bool {
310 var res = decodeSeed(text) catch return false; 319 var res = SeedKeyPair.fromTextSeed(text) catch return false;
311 res.wipe(); 320 res.wipe();
312 return true; 321 return true;
313} 322}
@@ -400,7 +409,7 @@ pub fn parseDecoratedNkey(contents: []const u8) NoNkeySeedFoundError!SeedKeyPair
400 409
401pub fn parseDecoratedUserNkey(contents: []const u8) (NoNkeySeedFoundError || NoNkeyUserSeedFoundError)!SeedKeyPair { 410pub fn parseDecoratedUserNkey(contents: []const u8) (NoNkeySeedFoundError || NoNkeyUserSeedFoundError)!SeedKeyPair {
402 var key = try parseDecoratedNkey(contents); 411 var key = try parseDecoratedNkey(contents);
403 if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound; 412 if (!mem.startsWith(u8, &key.asTextSeed(), "SU")) return error.NoNkeyUserSeedFound;
404 defer key.wipe(); 413 defer key.wipe();
405 return key; 414 return key;
406} 415}
@@ -409,36 +418,49 @@ test {
409 testing.refAllDecls(@This()); 418 testing.refAllDecls(@This());
410 testing.refAllDecls(SeedKeyPair); 419 testing.refAllDecls(SeedKeyPair);
411 testing.refAllDecls(PublicKey); 420 testing.refAllDecls(PublicKey);
421 testing.refAllDecls(PrivateKey);
412} 422}
413 423
414test { 424test {
415 var key_pair = SeedKeyPair.generate(PublicPrefixByte.server); 425 var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server);
416 defer key_pair.wipe(); 426 defer key_pair.wipe();
417 427
418 var decoded_seed = try decodeSeed(&key_pair.seed); 428 var decoded_seed = try SeedKeyPair.fromTextSeed(&key_pair.asTextSeed());
419 defer decoded_seed.wipe(); 429 defer decoded_seed.wipe();
420 var encoded_second_time = encodeSeed(decoded_seed.prefix, &decoded_seed.seed); 430 try testing.expect(isValidEncoding(&decoded_seed.asTextSeed()));
421 defer wipeBytes(&encoded_second_time);
422 try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time);
423 try testing.expect(isValidEncoding(&key_pair.seed));
424 431
425 var pub_key_str_a = try key_pair.publicKey(); 432 var pub_key_str_a = key_pair.asTextPublicKey();
426 defer wipeBytes(&pub_key_str_a); 433 defer wipeBytes(&pub_key_str_a);
427 var priv_key_str = try key_pair.privateKey(); 434 var priv_key_str = key_pair.asTextPrivateKey();
428 defer wipeBytes(&priv_key_str); 435 defer wipeBytes(&priv_key_str);
429 try testing.expect(pub_key_str_a.len != 0); 436 try testing.expect(pub_key_str_a.len != 0);
430 try testing.expect(priv_key_str.len != 0); 437 try testing.expect(priv_key_str.len != 0);
431 try testing.expect(isValidEncoding(&pub_key_str_a)); 438 try testing.expect(isValidEncoding(&pub_key_str_a));
432 try testing.expect(isValidEncoding(&priv_key_str)); 439 try testing.expect(isValidEncoding(&priv_key_str));
433 440
434 var pub_key = try key_pair.intoPublicKey(); 441 var pub_key = key_pair.asPublicKey();
435 defer pub_key.wipe(); 442 defer pub_key.wipe();
436 var pub_key_str_b = pub_key.publicKey(); 443 var pub_key_str_b = pub_key.asTextPublicKey();
437 defer wipeBytes(&pub_key_str_b); 444 defer wipeBytes(&pub_key_str_b);
438 try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); 445 try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b);
439} 446}
440 447
441test { 448test "encode" {
449 var raw_key: [32]u8 = undefined;
450 crypto.random.bytes(&raw_key);
451
452 // encode(
453}
454
455test "parse decorated JWT (bad)" {
456 try testing.expectEqualStrings("foo", parseDecoratedJwt("foo"));
457}
458
459test "parse decorated seed (bad)" {
460 try testing.expectError(error.NoNkeySeedFound, parseDecoratedNkey("foo"));
461}
462
463test "parse decorated seed and JWT" {
442 const creds = 464 const creds =
443 \\-----BEGIN NATS USER JWT----- 465 \\-----BEGIN NATS USER JWT-----
444 \\eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJUWEg1TUxDNTdPTUJUQURYNUJNU0RLWkhSQUtXUFM0TkdHRFFPVlJXRzUyRFdaUlFFVERBIiwiaWF0IjoxNjIxNTgyOTU1LCJpc3MiOiJBQ1ZUQVZMQlFKTklQRjdNWFZWSlpZUFhaTkdFQUZMWVpTUjJSNVRZNk9ESjNSTTRYV0FDNUVFRiIsIm5hbWUiOiJ0ZXN0Iiwic3ViIjoiVUJHSlhLRkVWUlFEM05LM0lDRVc1Q0lDSzM1NkdESVZORkhaRUU0SzdMMkRYWTdORVNQVlFVNEwiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e30sInN1YnMiOi0xLCJkYXRhIjotMSwicGF5bG9hZCI6LTEsInR5cGUiOiJ1c2VyIiwidmVyc2lvbiI6Mn19.OhPLDZflyJ_keg2xBRDHZZhG5x_Qf_Yb61k9eHLs9zLRf0_ETwMd0PNZI_isuBhXYevobXHVoYA3oxvMVGlDCQ 466 \\eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJUWEg1TUxDNTdPTUJUQURYNUJNU0RLWkhSQUtXUFM0TkdHRFFPVlJXRzUyRFdaUlFFVERBIiwiaWF0IjoxNjIxNTgyOTU1LCJpc3MiOiJBQ1ZUQVZMQlFKTklQRjdNWFZWSlpZUFhaTkdFQUZMWVpTUjJSNVRZNk9ESjNSTTRYV0FDNUVFRiIsIm5hbWUiOiJ0ZXN0Iiwic3ViIjoiVUJHSlhLRkVWUlFEM05LM0lDRVc1Q0lDSzM1NkdESVZORkhaRUU0SzdMMkRYWTdORVNQVlFVNEwiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e30sInN1YnMiOi0xLCJkYXRhIjotMSwicGF5bG9hZCI6LTEsInR5cGUiOiJ1c2VyIiwidmVyc2lvbiI6Mn19.OhPLDZflyJ_keg2xBRDHZZhG5x_Qf_Yb61k9eHLs9zLRf0_ETwMd0PNZI_isuBhXYevobXHVoYA3oxvMVGlDCQ
@@ -458,7 +480,10 @@ test {
458 const seed = "SUAGIEYODKBBTUMOB666Z5KA4FCWAZV7HWSGRHOD7MK6UM5IYLWLACH7DQ"; 480 const seed = "SUAGIEYODKBBTUMOB666Z5KA4FCWAZV7HWSGRHOD7MK6UM5IYLWLACH7DQ";
459 481
460 var got_kp = try parseDecoratedUserNkey(creds); 482 var got_kp = try parseDecoratedUserNkey(creds);
461 try testing.expectEqualStrings(seed, &got_kp.seed); 483 try testing.expectEqualStrings(seed, &got_kp.asTextSeed());
484
485 got_kp = try parseDecoratedNkey(creds);
486 try testing.expectEqualStrings(seed, &got_kp.asTextSeed());
462 487
463 var got_jwt = parseDecoratedJwt(creds); 488 var got_jwt = parseDecoratedJwt(creds);
464 try testing.expectEqualStrings(jwt, got_jwt); 489 try testing.expectEqualStrings(jwt, got_jwt);
diff --git a/src/znk.zig b/src/znk.zig
index 5a5ab5e..7cb16c6 100644
--- a/src/znk.zig
+++ b/src/znk.zig
@@ -147,12 +147,12 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi
147 147
148 try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); 148 try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate();
149 } else { 149 } else {
150 var kp = nkeys.SeedKeyPair.generate(ty.?); 150 var kp = try nkeys.SeedKeyPair.generate(ty.?);
151 defer kp.wipe(); 151 defer kp.wipe();
152 try stdout.writeAll(&kp.seed); 152 try stdout.writeAll(&kp.asTextSeed());
153 try stdout.writeAll("\n"); 153 try stdout.writeAll("\n");
154 154
155 var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); 155 var public_key = kp.asTextPublicKey();
156 if (pub_out) { 156 if (pub_out) {
157 try stdout.writeAll(&public_key); 157 try stdout.writeAll(&public_key);
158 try stdout.writeAll("\n"); 158 try stdout.writeAll("\n");
@@ -377,18 +377,18 @@ const PrefixKeyGenerator = struct {
377 }; 377 };
378 } 378 }
379 379
380 fn generatePrivate(self: *Self) void { 380 fn generatePrivate(self: *Self) !void {
381 while (true) { 381 while (true) {
382 if (self.done.load(.SeqCst)) return; 382 if (self.done.load(.SeqCst)) return;
383 383
384 var kp = nkeys.SeedKeyPair.generate(self.ty); 384 var kp = try nkeys.SeedKeyPair.generate(self.ty);
385 defer kp.wipe(); 385 defer kp.wipe();
386 var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); 386 var public_key = kp.asTextPublicKey();
387 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; 387 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue;
388 388
389 if (self.done.xchg(true, .SeqCst)) return; // another thread is already done 389 if (self.done.xchg(true, .SeqCst)) return; // another thread is already done
390 390
391 info("{s}", .{kp.seed}); 391 info("{s}", .{kp.asTextSeed()});
392 info("{s}", .{public_key}); 392 info("{s}", .{public_key});
393 393
394 return; 394 return;
@@ -432,16 +432,16 @@ pub const Nkey = union(enum) {
432 432
433 const Self = @This(); 433 const Self = @This();
434 434
435 pub fn publicKey(self: *const Self) !nkeys.text_public { 435 pub fn publicKey(self: *const Self) nkeys.text_public {
436 return switch (self.*) { 436 return switch (self.*) {
437 .seed_key_pair => |*kp| try kp.publicKey(), 437 .seed_key_pair => |*kp| kp.asTextPublicKey(),
438 .public_key => |*pk| pk.publicKey(), 438 .public_key => |*pk| pk.asTextPublicKey(),
439 }; 439 };
440 } 440 }
441 441
442 pub fn intoPublicKey(self: *const Self) !nkeys.PublicKey { 442 pub fn intoPublicKey(self: *const Self) nkeys.PublicKey {
443 return switch (self.*) { 443 return switch (self.*) {
444 .seed_key_pair => |*kp| try kp.intoPublicKey(), 444 .seed_key_pair => |*kp| kp.asPublicKey(),
445 .public_key => |pk| pk, 445 .public_key => |pk| pk,
446 }; 446 };
447 } 447 }