I am currently writing a zig implementation of age, and one of the thing it do is allowing for multiple type of Identity (i.e, Scrypt, X25519, ssh-keys, etc…) to be use as the encryption and decryption keys for a given file. Since its designed to be extensible I couldn’t use tagged unions because 3rd party Identity using the library wouldn’t be able to use the provided encrypt/decrypt function.
In my current implementation, the encrypt/decrypt function have the following signature (simplifies compare to the actual implementation)
encrypt(plain_text: []const u8, recipients: []const AnyIdentity) []const u8
decrypt(encrypted_text: []const u8, identities: []const AnyIdentity) []const u8
all specific Identity type need to be convert into AnyIdentity
interface (which i learn about from here), however because the interface relies on a *const anyopaque
the specific Identity need to be on the same scope as when the Any interface is use.
So something like this wouldn’t work
var identities = ArrayList(AnyIdentity).init(alloc);
for (secret_keys) |key| {
if (key.prefix == "x25519") {
// X25519Identity.parse(key) return a stack allocated memory
const x25519 = X25519Identity.parse(key);
// .any() return AnyIdentity with a context pointer pointed to x25519
identities.append(x25519.any());
}
// else if ... for other types of supported keys
}
// x25519 go out of scope and AnyIdentity's context now pointed to invalid memory
// and the encryption would produce a file that can't be decrypt
encrypt("hello world", identities.items);
I have to do it like this
var x25519_identities = ArrayList(X25519Identity).init(alloc);
// more ArrayList(Identities).init() for supported keys
for (secret_keys) |key| {
if (key.prefix == "x25519"){
x25519_identities.append(X25519Identity.parse(key));
}
// else if ... for other types of supported keys
}
var any_identities = ArrayList(AnyIdentity).init(alloc);
for (x25519_identities) |identity| {
identities.append(identity.any());
}
// repeat the block above for any extra specific identities (i.e scrypt, ssh, etc...)
encrypt("hello world", any_identities.items);
As you can see it is quite unwieldy.
I have a few idea:
- Use
anytype
:if the provided encrypt/decrypt function take inanytype
instead of[]const AnyIdentity
then i could do something like thisencrypt(encrypted_text, &.{x25519_identity, scrypt_identity, etc...})
but i can’t doArrayList(anytype)
to use a for loop to parse a list of inputs.anytype
can’t be use since its compile time only. - Use an allocator to duplicate the identity on the heap and free it later.
I don’t really want to do number 2 since it feel dirty (entirely out of my preconception that heap allocated memory should be avoids), how should i approach this?