1//! multicodec key parsing
2//!
3//! parses multicodec-prefixed public keys from DID documents.
4//! extracts key type and raw key bytes.
5//!
6//! see: https://github.com/multiformats/multicodec
7
8const std = @import("std");
9
10/// supported key types for AT Protocol
11pub const KeyType = enum {
12 secp256k1, // ES256K - used by most AT Protocol accounts
13 p256, // ES256 - also supported
14};
15
16/// parsed public key with type and raw bytes
17pub const PublicKey = struct {
18 key_type: KeyType,
19 /// raw compressed public key (33 bytes for secp256k1/p256)
20 raw: []const u8,
21};
22
23/// multicodec prefixes (unsigned varint encoding)
24/// secp256k1-pub: 0xe7 = 231, varint encoded as 0xe7 0x01 (2 bytes)
25/// p256-pub: 0x1200 = 4608, varint encoded as 0x80 0x24 (2 bytes)
26/// parse a multicodec-prefixed public key
27/// returns the key type and a slice pointing to the raw key bytes
28pub fn parsePublicKey(data: []const u8) !PublicKey {
29 if (data.len < 2) return error.TooShort;
30
31 // check for secp256k1-pub (varint 0xe7 = 231 encoded as 0xe7 0x01)
32 if (data.len >= 2 and data[0] == 0xe7 and data[1] == 0x01) {
33 const raw = data[2..];
34 if (raw.len != 33) return error.InvalidKeyLength;
35 return .{
36 .key_type = .secp256k1,
37 .raw = raw,
38 };
39 }
40
41 // check for p256-pub (varint 0x1200 = 4608 encoded as 0x80 0x24)
42 if (data.len >= 2 and data[0] == 0x80 and data[1] == 0x24) {
43 const raw = data[2..];
44 if (raw.len != 33) return error.InvalidKeyLength;
45 return .{
46 .key_type = .p256,
47 .raw = raw,
48 };
49 }
50
51 return error.UnsupportedKeyType;
52}
53
54// === tests ===
55
56test "parse secp256k1 key" {
57 // 0xe7 0x01 prefix (varint) + 33-byte compressed key
58 var data: [35]u8 = undefined;
59 data[0] = 0xe7;
60 data[1] = 0x01;
61 data[2] = 0x02; // compressed point prefix
62 @memset(data[3..], 0xaa);
63
64 const key = try parsePublicKey(&data);
65 try std.testing.expectEqual(KeyType.secp256k1, key.key_type);
66 try std.testing.expectEqual(@as(usize, 33), key.raw.len);
67}
68
69test "parse p256 key" {
70 // 0x80 0x24 prefix + 33-byte compressed key
71 var data: [35]u8 = undefined;
72 data[0] = 0x80;
73 data[1] = 0x24;
74 data[2] = 0x03; // compressed point prefix
75 @memset(data[3..], 0xbb);
76
77 const key = try parsePublicKey(&data);
78 try std.testing.expectEqual(KeyType.p256, key.key_type);
79 try std.testing.expectEqual(@as(usize, 33), key.raw.len);
80}
81
82test "reject unsupported key type" {
83 const data = [_]u8{ 0xff, 0x02, 0x00 };
84 try std.testing.expectError(error.UnsupportedKeyType, parsePublicKey(&data));
85}
86
87test "reject too short" {
88 const data = [_]u8{0xe7};
89 try std.testing.expectError(error.TooShort, parsePublicKey(&data));
90}