| ... | ... | @@ -2167,9 +2167,12 @@ pub const Signature = union(enum) { |
| 2167 | 2167 | packet_length: u32, |
| 2168 | 2168 | version: u8, |
| 2169 | 2169 | type_id: Pgp.Type, |
| 2170 | | pk_algorithm: Pgp.PublicKeyAlgorithm, |
| 2170 | material: Material, |
| 2171 | 2171 | hash_algorithm: Pgp.HashAlgorithm, |
| 2172 | keyid: [16]u8, |
| 2173 | creation_time: time.DateTime, |
| 2172 | 2174 | signed_hash_value_prefix: [4]u8, |
| 2175 | pubkey: ?PubKey, |
| 2173 | 2176 | valid: ?bool, |
| 2174 | 2177 | |
| 2175 | 2178 | /// https://datatracker.ietf.org/doc/html/rfc9580#name-signature-types |
| ... | ... | @@ -2245,6 +2248,77 @@ pub const Signature = union(enum) { |
| 2245 | 2248 | }; |
| 2246 | 2249 | } |
| 2247 | 2250 | }; |
| 2251 | |
| 2252 | pub const SubType = enum(u8) { |
| 2253 | signature_creation_time = 2, |
| 2254 | signature_expiration_time = 3, |
| 2255 | exportable_certification = 4, |
| 2256 | trust_signature = 5, |
| 2257 | revocable_expression = 6, |
| 2258 | revocable = 7, |
| 2259 | key_expiration_time = 9, |
| 2260 | preferred_v1_seipd_ciphers = 11, |
| 2261 | issuer_key_id = 16, |
| 2262 | notation_data = 20, |
| 2263 | preferred_hash_algorithms = 21, |
| 2264 | preferred_compression_algorithms = 22, |
| 2265 | key_server_preferences = 23, |
| 2266 | preferred_key_server = 24, |
| 2267 | primary_user_id = 25, |
| 2268 | policy_uri = 26, |
| 2269 | key_flags = 27, |
| 2270 | signer_user_id = 28, |
| 2271 | revocation_reason = 29, |
| 2272 | features = 30, |
| 2273 | signature_target = 31, |
| 2274 | embedded_signature = 32, |
| 2275 | issuer_fingerprint = 33, |
| 2276 | intended_recipient_fingerprint = 35, |
| 2277 | preferred_aead_ciphersuites = 39, |
| 2278 | _, |
| 2279 | }; |
| 2280 | |
| 2281 | pub const Material = union(PublicKeyAlgorithm) { |
| 2282 | reserved: void, |
| 2283 | rsa_encrypt_or_sign: []const u8, |
| 2284 | rsa_encrypt: void, |
| 2285 | rsa_sign: void, |
| 2286 | elgamal_encrypt: void, |
| 2287 | dsa: void, |
| 2288 | ecdh: void, |
| 2289 | ecdsa: void, |
| 2290 | x25519: void, |
| 2291 | x448: void, |
| 2292 | ed25519: void, |
| 2293 | ed448: void, |
| 2294 | }; |
| 2295 | |
| 2296 | const PubKey = struct { |
| 2297 | version: u8, |
| 2298 | creation_time: time.DateTime, |
| 2299 | days_valid: u16, |
| 2300 | material: PubKey.Material, |
| 2301 | |
| 2302 | const Material = union(Pgp.PublicKeyAlgorithm) { |
| 2303 | reserved: void, |
| 2304 | rsa_encrypt_or_sign: RSA, |
| 2305 | rsa_encrypt: void, |
| 2306 | rsa_sign: void, |
| 2307 | elgamal_encrypt: void, |
| 2308 | dsa: void, |
| 2309 | ecdh: void, |
| 2310 | ecdsa: void, |
| 2311 | x25519: void, |
| 2312 | x448: void, |
| 2313 | ed25519: void, |
| 2314 | ed448: void, |
| 2315 | |
| 2316 | const RSA = struct { |
| 2317 | n: []const u8, |
| 2318 | e: []const u8, |
| 2319 | }; |
| 2320 | }; |
| 2321 | }; |
| 2248 | 2322 | }; |
| 2249 | 2323 | |
| 2250 | 2324 | pub const Ssh = struct { |
| ... | ... | @@ -2264,57 +2338,159 @@ pub const Signature = union(enum) { |
| 2264 | 2338 | return .none; |
| 2265 | 2339 | } |
| 2266 | 2340 | if (std.mem.startsWith(u8, pem_sig, "-----BEGIN PGP SIGNATURE-----\n \n ") and (std.mem.endsWith(u8, pem_sig, "\n -----END PGP SIGNATURE-----\n ") or std.mem.endsWith(u8, pem_sig, "\n -----END PGP SIGNATURE-----"))) { |
| 2267 | | const pembody = pem_sig[33 .. std.mem.indexOf(u8, pem_sig, "\n =") orelse std.mem.indexOf(u8, pem_sig, "\n -----END").?]; |
| 2341 | const pembody = pem_sig[33..std.mem.indexOf(u8, pem_sig, "\n -----END").?]; |
| 2268 | 2342 | const sigcontent = pembody[0..std.mem.lastIndexOf(u8, pembody, "\n ").?]; |
| 2269 | 2343 | var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent); |
| 2270 | 2344 | var skip = nio.SkipReader(void).from(&fixed, "\n "); |
| 2271 | 2345 | var b64r = nio.Base64Reader(void).from(&skip); |
| 2272 | 2346 | |
| 2347 | var message = extras.ManyArrayList(u8).init(allocator); |
| 2348 | defer message.deinit(); |
| 2349 | try message.appendSlice(try message.add(), content_plus_gpgsig); |
| 2350 | message.lengths.items.len = 0; |
| 2351 | { |
| 2352 | var iter = std.mem.splitScalar(u8, content_plus_gpgsig, '\n'); |
| 2353 | while (iter.next()) |line| { |
| 2354 | try message.lengths.append(allocator, line.len + 1); |
| 2355 | } |
| 2356 | var skipping = false; |
| 2357 | var i: usize = 0; |
| 2358 | while (i < message.lengths.items.len) : (i += 1) { |
| 2359 | if (!skipping and std.mem.startsWith(u8, message.items(i), "gpgsig ")) { |
| 2360 | skipping = true; |
| 2361 | message.remove(i); |
| 2362 | i -= 1; |
| 2363 | continue; |
| 2364 | } |
| 2365 | if (!skipping) { |
| 2366 | continue; |
| 2367 | } |
| 2368 | if (skipping and std.mem.startsWith(u8, message.items(i), " ")) { |
| 2369 | message.remove(i); |
| 2370 | i -= 1; |
| 2371 | continue; |
| 2372 | } |
| 2373 | break; |
| 2374 | } |
| 2375 | } |
| 2376 | |
| 2377 | var signed_data: nio.AllocatingWriter = .init(allocator); |
| 2378 | defer signed_data.deinit(); |
| 2379 | try signed_data.writeAll(message.list.items); |
| 2380 | |
| 2273 | 2381 | const packet_type: packed struct { id: u6, format: u1, reserved: u1 } = @bitCast(try b64r.readByte()); |
| 2274 | 2382 | if (packet_type.reserved != 1) return .unrecognized; |
| 2275 | 2383 | if (packet_type.format != 1) return .unrecognized; // legacy non-OpenPGP format |
| 2276 | 2384 | if (packet_type.id != 2) return .unrecognized; // not a signature |
| 2277 | 2385 | |
| 2278 | | var packet_len_partial = false; |
| 2279 | | const packet_len: u32 = blk: { |
| 2280 | | const oct1: u32 = try b64r.readByte(); |
| 2281 | | if (oct1 <= 191) break :blk oct1; |
| 2282 | | if (oct1 >= 224 and oct1 < 255) packet_len_partial = true; |
| 2283 | | if (oct1 >= 224 and oct1 < 255) break :blk @as(u32, 1) << @intCast(oct1 & 0x1F); |
| 2284 | | const oct2: u32 = try b64r.readByte(); |
| 2285 | | if (oct1 <= 223) break :blk ((oct1 - 192) << 8) + (oct2) + 192; |
| 2286 | | const oct3: u32 = try b64r.readByte(); |
| 2287 | | const oct4: u32 = try b64r.readByte(); |
| 2288 | | const oct5: u32 = try b64r.readByte(); |
| 2289 | | std.debug.assert(oct1 == 255); |
| 2290 | | break :blk (oct2 << 24) | (oct3 << 16) | (oct4 << 8) | oct5; |
| 2291 | | }; |
| 2386 | _, const packet_len = pgp_read_packet_len(&b64r) catch return .unrecognized; |
| 2292 | 2387 | |
| 2293 | 2388 | const sig_version = try b64r.readByte(); |
| 2389 | try signed_data.writeAll(&.{sig_version}); |
| 2294 | 2390 | var type_id: Pgp.Type = .reserved; |
| 2295 | 2391 | var pk_algo: Pgp.PublicKeyAlgorithm = .reserved; |
| 2296 | 2392 | var hash_algo: Pgp.HashAlgorithm = .reserved; |
| 2393 | var creation_time: [4]u8 = @splat(0); |
| 2394 | var keyid: [8]u8 = @splat(0); |
| 2297 | 2395 | var signed_hash_value_prefix: [2]u8 = @splat(0); |
| 2298 | 2396 | |
| 2397 | if (sig_version == 3) blk: { |
| 2398 | const hashed_len = try b64r.readByte(); |
| 2399 | if (hashed_len != 5) break :blk; |
| 2400 | type_id = @enumFromInt(try b64r.readByte()); |
| 2401 | creation_time = try b64r.readArray(4); |
| 2402 | keyid = try b64r.readArray(8); |
| 2403 | pk_algo = @enumFromInt(try b64r.readByte()); |
| 2404 | hash_algo = @enumFromInt(try b64r.readByte()); |
| 2405 | signed_hash_value_prefix = try b64r.readArray(2); |
| 2406 | } |
| 2299 | 2407 | if (sig_version == 4) { |
| 2300 | 2408 | type_id = @enumFromInt(try b64r.readByte()); |
| 2301 | 2409 | pk_algo = @enumFromInt(try b64r.readByte()); |
| 2302 | 2410 | hash_algo = @enumFromInt(try b64r.readByte()); |
| 2411 | try signed_data.writeAll(&.{ @intFromEnum(type_id), @intFromEnum(pk_algo), @intFromEnum(hash_algo) }); |
| 2303 | 2412 | const subpacket_len_hashed = try b64r.readInt(u16, .big); |
| 2304 | | try b64r.skipBytes(subpacket_len_hashed, .{}); // TODO read this |
| 2413 | try signed_data.writeInt(u16, subpacket_len_hashed, .big); |
| 2414 | const subpacket_bytes = try b64r.readAlloc(allocator, subpacket_len_hashed); |
| 2415 | try signed_data.writeAll(subpacket_bytes); |
| 2416 | try signed_data.writeAll(&.{0x04}); |
| 2417 | try signed_data.writeAll(&.{0xFF}); |
| 2418 | try signed_data.writeInt(u32, @truncate(signed_data.items.len - 2 - message.list.items.len), .big); |
| 2419 | { |
| 2420 | var lr = nio.FixedBufferStream([]u8).init(subpacket_bytes); |
| 2421 | while (lr.rest().len > 0) { |
| 2422 | const subpacket_len = try pgp_read_subpacket_len(&lr); |
| 2423 | const subpacket_typeid: Pgp.SubType = @enumFromInt(try lr.readByte() & 127); |
| 2424 | if (subpacket_typeid == .signature_creation_time and subpacket_len == 1 + 4) { |
| 2425 | creation_time = try lr.readArray(4); |
| 2426 | continue; |
| 2427 | } |
| 2428 | if (subpacket_typeid == .issuer_key_id and subpacket_len == 1 + 8) { |
| 2429 | keyid = try lr.readArray(8); |
| 2430 | continue; |
| 2431 | } |
| 2432 | try lr.skipBytes(subpacket_len - 1, .{}); |
| 2433 | } |
| 2434 | } |
| 2305 | 2435 | const subpacket_len_unhashed = try b64r.readInt(u16, .big); |
| 2306 | | try b64r.skipBytes(subpacket_len_unhashed, .{}); // TODO read this |
| 2436 | { |
| 2437 | try b64r.skipBytes(subpacket_len_unhashed, .{}); // TODO read this |
| 2438 | } |
| 2307 | 2439 | signed_hash_value_prefix = try b64r.readArray(2); |
| 2308 | 2440 | } |
| 2441 | if (sig_version == 6) { |
| 2442 | type_id = @enumFromInt(try b64r.readByte()); |
| 2443 | pk_algo = @enumFromInt(try b64r.readByte()); |
| 2444 | hash_algo = @enumFromInt(try b64r.readByte()); |
| 2445 | const subpacket_len_hashed = try b64r.readInt(u32, .big); |
| 2446 | { |
| 2447 | try b64r.skipBytes(subpacket_len_hashed, .{}); // TODO read this |
| 2448 | } |
| 2449 | const subpacket_len_unhashed = try b64r.readInt(u32, .big); |
| 2450 | { |
| 2451 | try b64r.skipBytes(subpacket_len_unhashed, .{}); // TODO read this |
| 2452 | } |
| 2453 | signed_hash_value_prefix = try b64r.readArray(2); |
| 2454 | } |
| 2455 | |
| 2456 | const material = try pgp_read_signature_material(&b64r, allocator, pk_algo); |
| 2457 | |
| 2458 | var valid: ?bool = null; |
| 2459 | _ = &valid; |
| 2460 | var pubkey_t: ?Pgp.PubKey = null; |
| 2461 | |
| 2462 | // PGP keys are not stored in-band, need to fetch them and cache them |
| 2463 | // TODO check https://keys.openpgp.org |
| 2464 | // TODO check https://keyserver.ubuntu.com |
| 2465 | // TODO check manually uploaded keys in database |
| 2466 | if (known_pgp_keys.get(&extras.to_HEX(keyid))) |pubkey_bytes| blk: { |
| 2467 | const ns = std.crypto.Certificate.rsa; |
| 2468 | var pubkey_fbs: nio.FixedBufferStream([]const u8) = .init(pubkey_bytes); |
| 2469 | pubkey_t = pgp_parse_pubkey(&pubkey_fbs, allocator) catch break :blk; |
| 2470 | if (pubkey_t.?.material != pk_algo) { |
| 2471 | valid = false; |
| 2472 | break :blk; |
| 2473 | } |
| 2474 | switch (pubkey_t.?.material) { |
| 2475 | .rsa_encrypt_or_sign => |*m| { |
| 2476 | const pubkey = ns.PublicKey.fromBytes(m.e, m.n) catch break :blk; |
| 2477 | valid = if (pgp_rsa_verify(m.n.len, material.rsa_encrypt_or_sign, signed_data.items, pubkey, hash_algo)) true else |err| if (err == error.unrecognized) null else false; |
| 2478 | }, |
| 2479 | else => {}, |
| 2480 | } |
| 2481 | } |
| 2309 | 2482 | |
| 2310 | 2483 | return .{ .pgp = .{ |
| 2311 | 2484 | .packet_length = packet_len, |
| 2312 | 2485 | .version = sig_version, |
| 2313 | 2486 | .type_id = type_id, |
| 2314 | | .pk_algorithm = pk_algo, |
| 2487 | .material = material, |
| 2315 | 2488 | .hash_algorithm = hash_algo, |
| 2489 | .keyid = extras.to_HEX(keyid), |
| 2490 | .creation_time = .initUnix(std.mem.readInt(u32, &creation_time, .big)), |
| 2316 | 2491 | .signed_hash_value_prefix = extras.to_hex(signed_hash_value_prefix), |
| 2317 | | .valid = null, |
| 2492 | .pubkey = pubkey_t, |
| 2493 | .valid = valid, |
| 2318 | 2494 | } }; |
| 2319 | 2495 | } |
| 2320 | 2496 | if (std.mem.startsWith(u8, pem_sig, "-----BEGIN SSH SIGNATURE-----\n") and std.mem.endsWith(u8, pem_sig, "\n -----END SSH SIGNATURE-----")) { |
| ... | ... | @@ -2429,6 +2605,126 @@ pub const Signature = union(enum) { |
| 2429 | 2605 | } |
| 2430 | 2606 | return .unrecognized; |
| 2431 | 2607 | } |
| 2608 | |
| 2609 | fn pgp_read_packet_len(r: anytype) !struct { bool, u32 } { |
| 2610 | const oct1: u32 = try r.readByte(); |
| 2611 | if (oct1 <= 191) return .{ false, oct1 }; |
| 2612 | if (oct1 >= 224 and oct1 < 255) return .{ true, @as(u32, 1) << @intCast(oct1 & 0x1F) }; |
| 2613 | const oct2: u32 = try r.readByte(); |
| 2614 | if (oct1 <= 223) return .{ false, ((oct1 - 192) << 8) + (oct2) + 192 }; |
| 2615 | const oct3: u32 = try r.readByte(); |
| 2616 | const oct4: u32 = try r.readByte(); |
| 2617 | const oct5: u32 = try r.readByte(); |
| 2618 | std.debug.assert(oct1 == 255); |
| 2619 | return .{ false, (oct2 << 24) | (oct3 << 16) | (oct4 << 8) | oct5 }; |
| 2620 | } |
| 2621 | |
| 2622 | fn pgp_read_subpacket_len(r: anytype) !u32 { |
| 2623 | const oct1: u32 = try r.readByte(); |
| 2624 | if (oct1 < 192) return oct1; |
| 2625 | const oct2: u32 = try r.readByte(); |
| 2626 | if (oct1 < 255) return ((oct1 - 192) << 8) + (oct2) + 192; |
| 2627 | const oct3: u32 = try r.readByte(); |
| 2628 | const oct4: u32 = try r.readByte(); |
| 2629 | const oct5: u32 = try r.readByte(); |
| 2630 | return (oct2 << 24) | (oct3 << 16) | (oct4 << 8) | oct5; |
| 2631 | } |
| 2632 | |
| 2633 | fn pgp_parse_pubkey(r: anytype, allocator: std.mem.Allocator) !Pgp.PubKey { |
| 2634 | const packet_type: packed struct { id: u6, format: u1, reserved: u1 } = @bitCast(try r.readByte()); |
| 2635 | if (packet_type.reserved != 1) return error.unrecognized; |
| 2636 | if (packet_type.format != 1) return error.unrecognized; // legacy non-OpenPGP format |
| 2637 | if (packet_type.id != 6) return error.unrecognized; // not a public key |
| 2638 | _, const packet_len = pgp_read_packet_len(r) catch return error.unrecognized; |
| 2639 | var lr = nio.LimitedReader(void).from(r, packet_len); |
| 2640 | const key_version = try lr.readByte(); |
| 2641 | if (key_version == 3) { |
| 2642 | // TODO |
| 2643 | } |
| 2644 | if (key_version == 4) { |
| 2645 | const creation_time = try lr.readArray(4); |
| 2646 | const pk_algo: Pgp.PublicKeyAlgorithm = @enumFromInt(try lr.readByte()); |
| 2647 | const material = try pgp_parse_pubkey_material(r, allocator, pk_algo); |
| 2648 | return .{ |
| 2649 | .version = key_version, |
| 2650 | .creation_time = .initUnix(std.mem.readInt(u32, &creation_time, .big)), |
| 2651 | .days_valid = 0, |
| 2652 | .material = material, |
| 2653 | }; |
| 2654 | } |
| 2655 | if (key_version == 6) { |
| 2656 | // TODO |
| 2657 | } |
| 2658 | return .{ |
| 2659 | .version = key_version, |
| 2660 | .creation_time = .initUnix(0), |
| 2661 | .days_valid = 0, |
| 2662 | .material = .reserved, |
| 2663 | }; |
| 2664 | } |
| 2665 | |
| 2666 | fn pgp_parse_pubkey_material(r: anytype, allocator: std.mem.Allocator, pk_algo: Pgp.PublicKeyAlgorithm) !Pgp.PubKey.Material { |
| 2667 | switch (pk_algo) { |
| 2668 | .rsa_encrypt_or_sign => { |
| 2669 | const n_len = try r.readInt(u16, .big); |
| 2670 | const n = try r.readAlloc(allocator, (n_len + 7) / 8); |
| 2671 | const e_len = try r.readInt(u16, .big); |
| 2672 | const e = try r.readAlloc(allocator, (e_len + 7) / 8); |
| 2673 | return .{ .rsa_encrypt_or_sign = .{ .n = n, .e = e } }; |
| 2674 | }, |
| 2675 | inline else => |t| return @unionInit(Pgp.PubKey.Material, @tagName(t), {}), |
| 2676 | _ => return .reserved, |
| 2677 | } |
| 2678 | } |
| 2679 | |
| 2680 | fn pgp_read_signature_material(r: anytype, allocator: std.mem.Allocator, pk_algo: Pgp.PublicKeyAlgorithm) !Pgp.Material { |
| 2681 | switch (pk_algo) { |
| 2682 | .rsa_encrypt_or_sign => { |
| 2683 | const len = try r.readInt(u16, .big); |
| 2684 | const bytes = try r.readAlloc(allocator, (len + 7) / 8); |
| 2685 | return .{ .rsa_encrypt_or_sign = bytes }; |
| 2686 | }, |
| 2687 | inline else => |t| return @unionInit(Pgp.Material, @tagName(t), {}), |
| 2688 | _ => return .reserved, |
| 2689 | } |
| 2690 | } |
| 2691 | |
| 2692 | // https://datatracker.ietf.org/doc/html/rfc9580#name-rsa |
| 2693 | // An implementation SHOULD NOT encrypt, sign, or verify using RSA keys of a size less than 3072 bits. |
| 2694 | // An implementation that decrypts a message using an RSA secret key of a size less than 3072 bits SHOULD generate a deprecation warning that the key is too weak for modern use. |
| 2695 | fn pgp_rsa_verify(modulus_len_r: usize, sig: []const u8, message: []const u8, pubkey: std.crypto.Certificate.rsa.PublicKey, hash_algorithm: Pgp.HashAlgorithm) !void { |
| 2696 | return switch (modulus_len_r) { |
| 2697 | inline 384, 512 => |modulus_len| pgp_rsa_verify_inner(modulus_len, sig, message, pubkey, hash_algorithm), |
| 2698 | else => error.unrecognized, |
| 2699 | }; |
| 2700 | } |
| 2701 | |
| 2702 | fn pgp_rsa_verify_inner(comptime modulus_len: usize, sig: []const u8, message: []const u8, pubkey: std.crypto.Certificate.rsa.PublicKey, hash_algorithm: Pgp.HashAlgorithm) !void { |
| 2703 | const ns = std.crypto.Certificate.rsa; |
| 2704 | const signature = ns.PKCS1v1_5Signature.fromBytes(modulus_len, sig); |
| 2705 | return switch (hash_algorithm) { |
| 2706 | .reserved => error.unrecognized, |
| 2707 | .md5 => error.unrecognized, //ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.Md5), |
| 2708 | .sha1 => ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.Sha1), |
| 2709 | .ripemd160 => error.unrecognized, |
| 2710 | .sha2_224 => ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.sha2.Sha224), |
| 2711 | .sha2_256 => ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.sha2.Sha256), |
| 2712 | .sha2_384 => ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.sha2.Sha384), |
| 2713 | .sha2_512 => ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.sha2.Sha512), |
| 2714 | .sha3_256 => error.unrecognized, //ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.sha3.Sha3_256), |
| 2715 | .sha3_512 => error.unrecognized, //ns.PKCS1v1_5Signature.verify(modulus_len, signature, message, pubkey, std.crypto.hash.sha3.Sha3_512), |
| 2716 | _ => error.unrecognized, |
| 2717 | }; |
| 2718 | } |
| 2719 | |
| 2720 | // curl https://keys.openpgp.org/vks/v1/by-keyid/<KEYID> | head -n -2 | tail +4 | tr -d '\n' | base64 -d | xxd -p |
| 2721 | pub const known_pgp_keys: std.StaticStringMap([]const u8) = blk: { |
| 2722 | @setEvalBranchQuota(std.math.maxInt(u32)); |
| 2723 | break :blk .initComptime(.{ |
| 2724 | // GitHub <noreply@github.com> |
| 2725 | .{ "B5690EEEBB952194", &extras.from_hex("c6c14d0465a6c576011000b237ee2f88540ce904282abd00d96d2b19d3286d3270399b28d903e97b06f206cb8bdeb356e4e987ad6c170213ca36a508c8ecddabc3284b1a134eea8b92ca1a862c442ff5f69f284e07147d1a4633fe823d4779d870b45a150a1226855f3adeb5a2990b5336d601e8696e04fc10a967f09b0436ad3ed270e55908b487643da069aa960573b24c26559f0b7b2bf8320ee137ab30c99ee6a197e49290f9f94ed9d9756eac2ce44927e0e336f694c44b268b3678201cef8f440ac5f4b7b3817ed17e62a8232178b15ec9b646b52abd4e3b3a43ad65e021bc061e9db0b7a6bf4585ae4dc6805411afb5d4c2bd5ba39c461c2fc55094acc511e854d26720bffd5a574d3b6b4653aaa54e7018c6cfdf3a67f607aa3970a5f2b17bfb58003c6fe8f901504c1b1512bbb08b8256a20890b90c8ec0022515effb76f6b15991a9bffc96dbe2f782e2fbc5a1ca1551ac6b28658ad410e576be3f589280a4970f245e09b5a8b5fa807a6a4fc92236e4b1e1dd271723a3607474ab481685a23c007e0c5b3d3110e9a748b0a1c24287eb9cbacd6b750e7860c688e38ad1a615de38840c6028b9ab6435fdaaf39128b23d5ff65682a801f760b243edd7129ded620a12b9a914c1c8cbc8c9e483d0d6cd9d5305386a57c92c96b4a6f695da6ef00581409b09b3d166601716006040da9a7e2316829faac120722c51515137c39564ab04b01916cb0011010001") }, |
| 2726 | }); |
| 2727 | }; |
| 2432 | 2728 | }; |
| 2433 | 2729 | |
| 2434 | 2730 | pub fn findFirstUnset(set: std.bit_set.DynamicBitSetUnmanaged, after: usize) ?usize { |