From 22e942909f878692dac9b66058ce07c19eae60a7 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 16 Feb 2024 08:20:59 -0300 Subject: [PATCH] crypto: add ecdsa malleability checks (#21) * crypto: add ecdsa malleability checks Signed-off-by: Ignacio Hagopian * Implement receipts (#22) * types: add receipt structs Signed-off-by: Ignacio Hagopian * types/receipt: add logs bloom calculation Signed-off-by: Ignacio Hagopian * blockchain: add receipt generation Signed-off-by: Ignacio Hagopian --------- Signed-off-by: Ignacio Hagopian --------- Signed-off-by: Ignacio Hagopian --- build.zig.zon | 4 +-- src/blockchain/blockchain.zig | 31 +++++++----------- src/blockchain/vm.zig | 20 +++++++++-- src/crypto/ecdsa.zig | 10 ++++++ src/signer/signer.zig | 7 +++- src/types/block.zig | 2 +- src/types/receipt.zig | 62 +++++++++++++++++++++++++++++++++++ src/types/types.zig | 9 +++-- 8 files changed, 117 insertions(+), 28 deletions(-) create mode 100644 src/types/receipt.zig diff --git a/build.zig.zon b/build.zig.zon index ea83da7..44c212e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,8 +7,8 @@ .hash = "1220ebf62505957cdd9c87b9c517ceba60c46f5c9578a504ab3299b3970cc3ab0a1d", }, .@"zig-eth-secp256k1" = .{ - .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz", - .hash = "1220fcf062f8ee89b343e1588ac3cc002f37ee3f72841dd7f9493d9c09acad7915a3", + .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-4.tar.gz", + .hash = "1220c2dbdc5ddd85906c8858f2046b52870f4fe793e0c9af50b8591d10a3f267e250", }, .httpz = .{ .url = "https://github.com/karlseguin/http.zig/archive/1a82beb0dfc22e6fc38e9918e323f8fbd3cb78a3.tar.gz", diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index d8c92b8..3e432da 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -22,6 +22,8 @@ const StateDB = @import("../state/state.zig").StateDB; const Hash32 = types.Hash32; const Bytes32 = types.Bytes32; const Address = types.Address; +const Receipt = types.Receipt; +const Log = types.Log; const TxSigner = signer.TxSigner; const VM = vm.VM; const Keccak256 = std.crypto.hash.sha3.Keccak256; @@ -176,7 +178,11 @@ pub const Blockchain = struct { const exec_tx_result = try processTransaction(allocator, env, tx); gas_available -= exec_tx_result.gas_used; - // TODO: make receipt and add to receipt tree. + // Create receipt. + const cumm_gas_used = block.header.gas_limit - gas_available; + const receipt = Receipt.init(tx, exec_tx_result.success, cumm_gas_used, &[_]Log{}); + _ = receipt; + // TODO: do tx logs aggregation. } @@ -220,7 +226,7 @@ pub const Blockchain = struct { return .{ .sender_address = sender_address, .effective_gas_price = effective_gas_price }; } - fn processTransaction(allocator: Allocator, env: Environment, tx: transaction.Tx) !struct { gas_used: u64 } { + fn processTransaction(allocator: Allocator, env: Environment, tx: transaction.Tx) !struct { success: bool, gas_used: u64 } { if (!validateTransaction(tx)) return error.InvalidTransaction; @@ -297,7 +303,7 @@ pub const Blockchain = struct { env.state.destroyAccount(address); } - return .{ .gas_used = total_gas_used }; + return .{ .success = output.success, .gas_used = total_gas_used }; } fn validateTransaction(tx: transaction.Tx) bool { @@ -397,25 +403,10 @@ pub const Blockchain = struct { return padded_address; } - const MessageCallOutput = struct { - gas_left: u64, - refund_counter: u64, - // logs: Union[Tuple[()], Tuple[Log, ...]] TODO - // accounts_to_delete: AddressKeySet, // TODO (delete?) - // error TODO (required for future receipts) - }; - - fn processMessageCall(message: Message, env: Environment) !MessageCallOutput { + fn processMessageCall(message: Message, env: Environment) !vm.MessageCallOutput { var vm_instance = VM.init(env); defer vm_instance.deinit(); - const result = try vm_instance.processMessageCall(message); - defer { - if (result.release) |release| release(&result); - } - return .{ - .gas_left = @intCast(result.gas_left), - .refund_counter = @intCast(result.gas_refund), - }; + return try vm_instance.processMessageCall(message); } }; diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index d7b5b56..931be2a 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -62,7 +62,7 @@ pub const VM = struct { } // processMessageCall executes a message call. - pub fn processMessageCall(self: *VM, msg: Message) !evmc.struct_evmc_result { + pub fn processMessageCall(self: *VM, msg: Message) !MessageCallOutput { const evmc_message: evmc.struct_evmc_message = .{ .kind = if (msg.target != null) evmc.EVMC_CALL else evmc.EVMC_CREATE, .flags = 0, @@ -81,7 +81,15 @@ pub const VM = struct { .code_address = toEVMCAddress(msg.code_address), }; - return EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); + const result = EVMOneHost.call(@ptrCast(self), @ptrCast(&evmc_message)); + defer { + if (result.release) |release| release(&result); + } + return .{ + .gas_left = @intCast(result.gas_left), + .refund_counter = @intCast(result.gas_refund), + .success = result.status_code == evmc.EVMC_SUCCESS, + }; } }; @@ -459,3 +467,11 @@ fn toEVMCUint256Be(num: u256) evmc.evmc_uint256be { }, }; } + +pub const MessageCallOutput = struct { + success: bool, + gas_left: u64, + refund_counter: u64, + // logs: Union[Tuple[()], Tuple[Log, ...]] TODO + // accounts_to_delete: AddressKeySet, // TODO (delete?) +}; diff --git a/src/crypto/ecdsa.zig b/src/crypto/ecdsa.zig index 57c0f1f..77cfcec 100644 --- a/src/crypto/ecdsa.zig +++ b/src/crypto/ecdsa.zig @@ -25,6 +25,16 @@ pub const Signer = struct { } }; +pub fn validateSignatureFields(r: u256, s: u256) !void { + if (r > secp256k1.Secp256k1.order) { + return error.InvalidR; + } + // Malleability check. + if (s > secp256k1.Secp256k1.order / 2) { + return error.InvalidS; + } +} + // The following test values where generated using geth, as a reference. const hashed_msg = common.comptimeHexToBytes("05e0e0ff09b01e5626daac3165b82afa42be29197b82e8a5a8800740ee7519d2"); const private_key = common.comptimeHexToBytes("f457cd3bd0186e342d243ea40ad78fe8e81743f90852e87074e68d8c94c2a42e"); diff --git a/src/signer/signer.zig b/src/signer/signer.zig index f51fc31..b52eb54 100644 --- a/src/signer/signer.zig +++ b/src/signer/signer.zig @@ -42,9 +42,10 @@ pub const TxSigner = struct { var sig: ecdsa.Signature = undefined; - // TODO: solve malleability problem. sig[64] = switch (tx) { Tx.LegacyTx => |itx| blk: { + try ecdsa.validateSignatureFields(itx.r, itx.s); + std.mem.writeIntSlice(u256, sig[0..32], itx.r, std.builtin.Endian.Big); std.mem.writeIntSlice(u256, sig[32..64], itx.s, std.builtin.Endian.Big); @@ -58,11 +59,15 @@ pub const TxSigner = struct { break :blk @intCast(itx.v - v_eip155); }, Tx.AccessListTx => |itx| blk: { + try ecdsa.validateSignatureFields(itx.r, itx.s); + std.mem.writeIntSlice(u256, sig[0..32], itx.r, std.builtin.Endian.Big); std.mem.writeIntSlice(u256, sig[32..64], itx.s, std.builtin.Endian.Big); break :blk @intCast(itx.y_parity); }, Tx.FeeMarketTx => |itx| blk: { + try ecdsa.validateSignatureFields(itx.r, itx.s); + std.mem.writeIntSlice(u256, sig[0..32], itx.r, std.builtin.Endian.Big); std.mem.writeIntSlice(u256, sig[32..64], itx.s, std.builtin.Endian.Big); break :blk @intCast(itx.y_parity); diff --git a/src/types/block.zig b/src/types/block.zig index 19ca2df..43d981c 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -6,11 +6,11 @@ const Arena = std.heap.ArenaAllocator; const Withdrawal = types.Withdrawal; const Tx = types.Tx; const Hash32 = types.Hash32; +const LogsBloom = types.LogsBloom; const Bytes32 = types.Bytes32; const Address = types.Address; pub const empty_uncle_hash: types.Hash32 = [_]u8{ 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71 }; -pub const LogsBloom = [256]u8; pub const BlockHeader = struct { parent_hash: Hash32, diff --git a/src/types/receipt.zig b/src/types/receipt.zig new file mode 100644 index 0000000..f622612 --- /dev/null +++ b/src/types/receipt.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const types = @import("types.zig"); +const crypto = @import("../crypto/crypto.zig"); +const Allocator = std.mem.Allocator; +const hasher = crypto.hasher; +const Hash32 = types.Hash32; +const Address = types.Address; +const LogsBloom = types.LogsBloom; +const TxTypes = types.TxTypes; + +pub const Receipt = struct { + tx_type: TxTypes, + + succeeded: bool, + cumulative_gas_used: u64, + bloom: LogsBloom, + logs: []Log, + + pub fn init(tx_type: TxTypes, succeeded: bool, cumulative_gas_used: u64, logs: []Log) Receipt { + return Receipt{ + .tx_type = tx_type, + .succeeded = succeeded, + .cumulative_gas_used = cumulative_gas_used, + .bloom = calculateLogsBloom(logs), + .logs = logs, + }; + } + + fn calculateLogsBloom(logs: []Log) LogsBloom { + var logs_bloom = std.mem.zeroes(LogsBloom); + + for (logs) |log| { + addToBloom(&logs_bloom, &log.address); + for (log.topics) |topic| { + addToBloom(&logs_bloom, &topic); + } + } + + return logs_bloom; + } + + fn addToBloom(bloom: *LogsBloom, value: []const u8) void { + const hash = hasher.keccak256(value); + + inline for (0..3) |i| { + const hash_16bit_word = hash[i * 2 .. (i + 1) * 2].*; + const bit_to_set = @as(u11, @intCast(std.mem.readInt(u16, &hash_16bit_word, std.builtin.Endian.Big) & 0x07FF)); + const bit_index = 0x07FF - bit_to_set; + + const byte_index = bit_index / 8; + const bit_value = @as(u8, 1) << (7 - @as(u3, @intCast(bit_index % 8))); + + bloom[byte_index] |= bit_value; + } + } +}; + +pub const Log = struct { + address: Address, + topics: []Hash32, + data: []u8, +}; diff --git a/src/types/types.zig b/src/types/types.zig index d64f3ae..959381a 100644 --- a/src/types/types.zig +++ b/src/types/types.zig @@ -7,13 +7,18 @@ pub const Bytes31 = [31]u8; pub const Address = [20]u8; // Blocks -pub const block = @import("block.zig"); +const block = @import("block.zig"); pub const empty_uncle_hash = block.empty_uncle_hash; pub const Block = block.Block; pub const BlockHeader = block.BlockHeader; -pub const LogsBloom = block.LogsBloom; pub const Withdrawal = @import("withdrawal.zig").Withdrawal; +// Receipt & Logs +const receipt = @import("receipt.zig"); +pub const Receipt = receipt.Receipt; +pub const Log = receipt.Log; +pub const LogsBloom = [256]u8; + // Transactions const transaction = @import("transaction.zig"); pub const AccessListTuple = transaction.AccessListTuple;