Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(precompile programs): ed25519 verify #575

Merged
merged 16 commits into from
Feb 28, 2025
2 changes: 2 additions & 0 deletions src/runtime/program/lib.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub const system_program = @import("system_program/lib.zig");

pub const test_program_execute = @import("test_program_execute.zig");

pub const precompile_programs = @import("precompile_programs/lib.zig");
296 changes: 296 additions & 0 deletions src/runtime/program/precompile_programs/ed25519.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
const std = @import("std");
const builtin = @import("builtin");
const sig = @import("../../../sig.zig");
const precompile_programs = sig.runtime.program.precompile_programs;

const PrecompileProgramError = precompile_programs.PrecompileProgramError;
const getInstructionValue = precompile_programs.getInstructionValue;
const getInstructionData = precompile_programs.getInstructionData;

const Ed25519 = std.crypto.sign.Ed25519;

pub const ED25519_DATA_START = ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE +
ED25519_SIGNATURE_OFFSETS_START;
pub const ED25519_PUBKEY_SERIALIZED_SIZE = 32;
pub const ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE = 14;
pub const ED25519_SIGNATURE_OFFSETS_START = 2;
pub const ED25519_SIGNATURE_SERIALIZED_SIZE = 64;

comptime {
std.debug.assert(ED25519_PUBKEY_SERIALIZED_SIZE == Ed25519.PublicKey.encoded_length);
std.debug.assert(ED25519_SIGNATURE_SERIALIZED_SIZE == Ed25519.Signature.encoded_length);
std.debug.assert(ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE == @sizeOf(Ed25519SignatureOffsets));
}

pub const Ed25519SignatureOffsets = extern struct {
/// Offset to ed25519 signature of 64 bytes.
signature_offset: u16 = 0,
/// Instruction index to find signature.
signature_instruction_idx: u16 = 0,
/// Offset to public key of 32 bytes.
pubkey_offset: u16 = 0,
/// Instruction index to find public key.
pubkey_instruction_idx: u16 = 0,
/// Offset to start of message data.
message_data_offset: u16 = 0,
/// Size of message data.
message_data_size: u16 = 0,
/// Index of instruction data to get message data.
message_instruction_idx: u16 = 0,
};

// TODO: support verify_strict feature https://github.com/anza-xyz/agave/pull/1876/
// https://github.com/anza-xyz/agave/blob/a8aef04122068ec36a7af0721e36ee58efa0bef2/sdk/src/ed25519_instruction.rs#L88
// https://github.com/firedancer-io/firedancer/blob/af74882ffb2c24783a82718dbc5111a94e1b5f6f/src/flamenco/runtime/program/fd_precompiles.c#L118
pub fn verify(
current_instruction_data: []const u8,
all_instruction_datas: []const []const u8,
) PrecompileProgramError!void {
const data = current_instruction_data;
if (data.len < ED25519_DATA_START) {
if (data.len == 2 and data[0] == 0) return;
return error.InvalidInstructionDataSize;
}

const n_signatures = data[0];
if (n_signatures == 0) return error.InvalidInstructionDataSize;

const expected_data_size: u64 = ED25519_SIGNATURE_OFFSETS_START +
@as(u64, n_signatures) * ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE;
if (data.len < expected_data_size) return error.InvalidInstructionDataSize;

for (0..n_signatures) |i| {
const offset = ED25519_SIGNATURE_OFFSETS_START +
i * ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE;

const sig_offsets: *align(1) const Ed25519SignatureOffsets = @ptrCast(data.ptr + offset);

const signature = try getInstructionValue(
Ed25519.Signature,
data,
all_instruction_datas,
sig_offsets.signature_instruction_idx,
sig_offsets.signature_offset,
);
const pubkey = try getInstructionValue(
Ed25519.PublicKey,
data,
all_instruction_datas,
sig_offsets.pubkey_instruction_idx,
sig_offsets.pubkey_offset,
);
const msg = try getInstructionData(
sig_offsets.message_data_size,
data,
all_instruction_datas,
sig_offsets.message_instruction_idx,
sig_offsets.message_data_offset,
);
signature.verify(msg, pubkey.*) catch return error.InvalidSignature;
}
}

// https://github.com/anza-xyz/agave/blob/a8aef04122068ec36a7af0721e36ee58efa0bef2/sdk/src/ed25519_instruction.rs#L35
pub fn newInstruction(
allocator: std.mem.Allocator,
keypair: Ed25519.KeyPair,
message: []const u8,
) !sig.core.Instruction {
if (!builtin.is_test) @compileError("newInstruction is only for use in tests");
std.debug.assert(message.len < std.math.maxInt(u16));

const signature = try keypair.sign(message, null);

const num_signatures: u8 = 1;
const pubkey_offset = ED25519_DATA_START;
const signature_offset = pubkey_offset + ED25519_PUBKEY_SERIALIZED_SIZE;
const message_data_offset = signature_offset + ED25519_SIGNATURE_SERIALIZED_SIZE;

const offsets: Ed25519SignatureOffsets = .{
.signature_offset = signature_offset,
.signature_instruction_idx = std.math.maxInt(u16),
.pubkey_offset = pubkey_offset,
.pubkey_instruction_idx = std.math.maxInt(u16),
.message_data_offset = message_data_offset,
.message_data_size = @intCast(message.len),
.message_instruction_idx = std.math.maxInt(u16),
};

var instruction_data = try std.ArrayList(u8).initCapacity(
allocator,
message_data_offset + message.len,
);
errdefer instruction_data.deinit();

Check warning on line 123 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L123

Added line #L123 was not covered by tests

// add 2nd byte for padding, so that offset structure is aligned
instruction_data.appendSliceAssumeCapacity(&.{ num_signatures, 0 });
instruction_data.appendSliceAssumeCapacity(std.mem.asBytes(&offsets));
std.debug.assert(instruction_data.items.len == pubkey_offset);
instruction_data.appendSliceAssumeCapacity(&keypair.public_key.toBytes());
std.debug.assert(instruction_data.items.len == signature_offset);
instruction_data.appendSliceAssumeCapacity(&signature.toBytes());
std.debug.assert(instruction_data.items.len == message_data_offset);
instruction_data.appendSliceAssumeCapacity(message);

return .{
.program_id = sig.runtime.ids.PRECOMPILE_ED25519_PROGRAM_ID,
.accounts = &.{},
.data = try instruction_data.toOwnedSlice(),
};
}

// https://github.com/anza-xyz/agave/blob/a8aef04122068ec36a7af0721e36ee58efa0bef2/sdk/src/ed25519_instruction.rs#L258
fn testCase(
num_signatures: u16,
offsets: Ed25519SignatureOffsets,
) PrecompileProgramError!void {
if (!builtin.is_test) @compileError("testCase is only for use in tests");

var instruction_data: [ED25519_DATA_START]u8 align(2) = undefined;
@memcpy(instruction_data[0..2], std.mem.asBytes(&num_signatures));
@memcpy(instruction_data[2..], std.mem.asBytes(&offsets));

return try verify(&instruction_data, &.{&(.{0} ** 100)});
}

// https://github.com/anza-xyz/agave/blob/a8aef04122068ec36a7af0721e36ee58efa0bef2/sdk/src/ed25519_instruction.rs#L279
test "ed25519 invalid offsets" {
const allocator = std.testing.allocator;
var instruction_data = try std.ArrayListAligned(u8, 2).initCapacity(
allocator,
ED25519_DATA_START,
);
defer instruction_data.deinit();

const offsets: Ed25519SignatureOffsets = .{};

// Set up instruction data with invalid size
instruction_data.appendSliceAssumeCapacity(std.mem.asBytes(&1));
instruction_data.appendSliceAssumeCapacity(std.mem.asBytes(&offsets));
try instruction_data.resize(instruction_data.items.len - 1);

try std.testing.expectEqual(

Check warning on line 172 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L172

Added line #L172 was not covered by tests
verify(instruction_data.items, &.{}),
error.InvalidInstructionDataSize,
);

// invalid signature instruction index
const invalid_signature_offsets: Ed25519SignatureOffsets = .{
.signature_instruction_idx = 1,
};
try std.testing.expectEqual(

Check warning on line 181 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L181

Added line #L181 was not covered by tests
testCase(1, invalid_signature_offsets),
error.InvalidDataOffsets,
);

// invalid message instruction index
const invalid_message_offsets: Ed25519SignatureOffsets = .{
.message_instruction_idx = 1,
};
try std.testing.expectEqual(

Check warning on line 190 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L190

Added line #L190 was not covered by tests
testCase(1, invalid_message_offsets),
error.InvalidDataOffsets,
);

// invalid public key instruction index
const invalid_pubkey_offsets: Ed25519SignatureOffsets = .{
.pubkey_instruction_idx = 1,
};
try std.testing.expectEqual(

Check warning on line 199 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L199

Added line #L199 was not covered by tests
testCase(1, invalid_pubkey_offsets),
error.InvalidDataOffsets,
);
}

// https://github.com/anza-xyz/agave/blob/a8aef04122068ec36a7af0721e36ee58efa0bef2/sdk/src/ed25519_instruction.rs#L326
test "ed25519 message data offsets" {
{
const offsets: Ed25519SignatureOffsets = .{
.message_data_offset = 99,
.message_data_size = 1,
};
try std.testing.expectEqual(

Check warning on line 212 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L212

Added line #L212 was not covered by tests
testCase(1, offsets),
error.InvalidSignature,
);
}

{
const offsets: Ed25519SignatureOffsets = .{
.message_data_offset = 100,
.message_data_size = 1,
};
try std.testing.expectEqual(

Check warning on line 223 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L223

Added line #L223 was not covered by tests
testCase(1, offsets),
error.InvalidDataOffsets,
);
}

{
const offsets: Ed25519SignatureOffsets = .{
.message_data_offset = 100,
.message_data_size = 1000,
};
try std.testing.expectEqual(

Check warning on line 234 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L234

Added line #L234 was not covered by tests
testCase(1, offsets),
error.InvalidDataOffsets,
);
}

{
const offsets: Ed25519SignatureOffsets = .{
.message_data_offset = std.math.maxInt(u16),
.message_data_size = std.math.maxInt(u16),
};
try std.testing.expectEqual(
testCase(1, offsets),
error.InvalidDataOffsets,
);
}
}

// https://github.com/anza-xyz/agave/blob/a8aef04122068ec36a7af0721e36ee58efa0bef2/sdk/src/ed25519_instruction.rs#L369
test "ed25519 pubkey offset" {
{
const offsets: Ed25519SignatureOffsets = .{
.pubkey_offset = std.math.maxInt(u16),
};
try std.testing.expectEqual(

Check warning on line 258 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L258

Added line #L258 was not covered by tests
testCase(1, offsets),
error.InvalidDataOffsets,
);
}

{
const offsets: Ed25519SignatureOffsets = .{
.pubkey_offset = 100 - ED25519_PUBKEY_SERIALIZED_SIZE + 1,
};
try std.testing.expectEqual(
testCase(1, offsets),
error.InvalidDataOffsets,
);
}
}

// https://github.com/anza-xyz/agave/blob/a8aef04122068ec36a7af0721e36ee58efa0bef2/sdk/src/ed25519_instruction.rs#L389-L390
test "ed25519 signature offset" {
{
const offsets: Ed25519SignatureOffsets = .{
.signature_offset = std.math.maxInt(u16),
};
try std.testing.expectEqual(

Check warning on line 281 in src/runtime/program/precompile_programs/ed25519.zig

View check run for this annotation

Codecov / codecov/patch

src/runtime/program/precompile_programs/ed25519.zig#L281

Added line #L281 was not covered by tests
testCase(1, offsets),
error.InvalidDataOffsets,
);
}

{
const offsets: Ed25519SignatureOffsets = .{
.signature_offset = 100 - ED25519_SIGNATURE_SERIALIZED_SIZE + 1,
};
try std.testing.expectEqual(
testCase(1, offsets),
error.InvalidDataOffsets,
);
}
}
Loading