lib/peer/src/identity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const text = @import("text.zig");
 3 
 4 const Ed25519 = std.crypto.sign.Ed25519;
 5 
 6 pub const key_bytes: usize = Ed25519.PublicKey.encoded_length;
 7 pub const text_bytes: usize = text.encodedLength(key_bytes);
 8 pub const PeerIdParseError = error{
 9     InvalidEncoding,
10     InvalidLength,
11 };
12 
13 pub const PeerId = struct {
14     key: [key_bytes]u8,
15 
16     pub const ParseError: type = PeerIdParseError;
17 
18     pub fn format(self: PeerId, output: []u8) text.EncodeError![]const u8 {
19         return text.encode(&self.key, output);
20     }
21 
22     pub fn parse(source: []const u8) ParseError!PeerId {
23         if (source.len != text_bytes) return error.InvalidLength;
24         var key: [key_bytes]u8 = undefined;
25         const decoded = text.decode(source, &key) catch return error.InvalidEncoding;
26         if (decoded != key.len) return error.InvalidLength;
27         return .{ .key = key };
28     }
29 
30     pub fn eql(self: PeerId, other: PeerId) bool {
31         return std.mem.eql(u8, &self.key, &other.key);
32     }
33 };
34 
35 pub const Identity = struct {
36     key_pair: Ed25519.KeyPair,
37 
38     pub const SeedError: type = error{InvalidSeed};
39 
40     pub fn generate(io: std.Io) Identity {
41         return .{ .key_pair = Ed25519.KeyPair.generate(io) };
42     }
43 
44     pub fn fromSeed(seed: [Ed25519.KeyPair.seed_length]u8) SeedError!Identity {
45         const pair = Ed25519.KeyPair.generateDeterministic(seed) catch
46             return error.InvalidSeed;
47         return .{ .key_pair = pair };
48     }
49 
50     pub fn id(self: Identity) PeerId {
51         return .{ .key = self.key_pair.public_key.toBytes() };
52     }
53 };
54 
55 test "Identity generation uses caller I/O" {
56     const identity = Identity.generate(std.testing.io);
57     try std.testing.expectEqualSlices(
58         u8,
59         &identity.key_pair.public_key.toBytes(),
60         &identity.id().key,
61     );
62 }
63 
64 test "RFC 8032 7.1 Ed25519 seed yields the documented PeerId text" {
65     var seed: [Ed25519.KeyPair.seed_length]u8 = undefined;
66     _ = try std.fmt.hexToBytes(
67         &seed,
68         "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
69     );
70     const identity = try Identity.fromSeed(seed);
71     var output: [text_bytes]u8 = undefined;
72     const actual = try identity.id().format(&output);
73     try std.testing.expectEqualStrings(
74         "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
75         actual,
76     );
77 }
78 
79 test "PeerId parse rejects lengths other than 43 characters" {
80     const canonical = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo";
81     try std.testing.expectError(error.InvalidLength, PeerId.parse(canonical[0..42]));
82     try std.testing.expectError(error.InvalidLength, PeerId.parse(canonical ++ "A"));
83 }
84 
85 test "RFC 4648 3.5 PeerId parse rejects noncanonical base64url" {
86     const canonical = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo";
87     var invalid = canonical.*;
88     invalid[0] = '+';
89     try std.testing.expectError(error.InvalidEncoding, PeerId.parse(&invalid));
90     invalid[0] = '/';
91     try std.testing.expectError(error.InvalidEncoding, PeerId.parse(&invalid));
92     invalid = canonical.*;
93     invalid[invalid.len - 1] = '=';
94     try std.testing.expectError(error.InvalidEncoding, PeerId.parse(&invalid));
95     invalid[invalid.len - 1] = 'p';
96     try std.testing.expectError(error.InvalidEncoding, PeerId.parse(&invalid));
97 }