From d11b780ea999e3fbb38b689f05b2d832706750a0 Mon Sep 17 00:00:00 2001 From: Jonatan Chaverri Date: Fri, 20 Sep 2024 16:00:41 -0400 Subject: [PATCH] Add OP_SHA256 (#72) Co-authored-by: Jonatan Chaverri --- README.md | 2 +- src/script/engine.zig | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 107b156..150052b 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ pie showData | opWithin | 0xa5 | ✅ | Returns 1 if x is within the specified range (left-inclusive), 0 otherwise. | | opRipeMd160 | 0xa6 | ❗ | The input is hashed using RIPEMD-160. | | opSha1 | 0xa7 | ❗ | The input is hashed using SHA-1. | -| opSha256 | 0xa8 | ❗ | The input is hashed using SHA-256. | +| opSha256 | 0xa8 | ✅ | The input is hashed using SHA-256. | | opHash160 | 0xa9 | ✅ | The input is hashed twice: first with SHA-256 and then with RIPEMD-160. | | opHash256 | 0xaa | ❗ | The input is hashed two times with SHA-256. | | opCodeSeparator | 0xab | ❗ | All of the signature checking words will only match signatures to the data after the most recently-executed opCODESEPARATOR. | diff --git a/src/script/engine.zig b/src/script/engine.zig index 3da6ce6..da09304 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -10,6 +10,7 @@ const Opcode = @import("opcodes/constant.zig").Opcode; const isUnnamedPushNDataOpcode = @import("opcodes/constant.zig").isUnnamedPushNDataOpcode; const EngineError = @import("lib.zig").EngineError; const ripemd160 = @import("bitcoin-primitives").hashes.Ripemd160; +const Sha256 = std.crypto.hash.sha2.Sha256; /// Engine is the virtual machine that executes Bitcoin scripts pub const Engine = struct { /// The script being executed @@ -144,6 +145,7 @@ pub const Engine = struct { Opcode.OP_MAX => try arithmetic.opMax(self), Opcode.OP_WITHIN => try arithmetic.opWithin(self), Opcode.OP_RIPEMD160 => try self.opRipemd160(), + Opcode.OP_SHA256 => try self.opSha256(), Opcode.OP_HASH160 => try self.opHash160(), Opcode.OP_CHECKSIG => try self.opCheckSig(), Opcode.OP_NIP => try self.opNip(), @@ -381,6 +383,21 @@ pub const Engine = struct { try self.opVerify(); } + /// OP_Sha256: The input is hashed with SHA-256. + /// + /// # Returns + /// - `EngineError`: If an error occurs during execution + fn opSha256(self: *Engine) !void { + const arr = try self.stack.pop(); + defer self.allocator.free(arr); + + // Create a digest buffer to hold the hash result + var hash: [Sha256.digest_length]u8 = undefined; + Sha256.hash(arr, &hash, .{}); + + try self.stack.pushByteArray(&hash); + } + /// OP_HASH160: Hash the top stack item with SHA256 and RIPEMD160 /// /// # Returns @@ -801,3 +818,26 @@ test "Script execution OP_RIPEMD160" { try std.testing.expectEqualSlices(u8, &expected_output, result); } } + +test "Script execution - OP_SHA256" { + const allocator = std.testing.allocator; + + const script_bytes = [_]u8{ Opcode.OP_1.toBytes(), Opcode.OP_SHA256.toBytes() }; + const script = Script.init(&script_bytes); + + var engine = Engine.init(allocator, script, .{}); + defer engine.deinit(); // Ensure engine is deinitialized and memory is freed + + try engine.execute(); + + try std.testing.expectEqual(1, engine.stack.len()); + + const hash_bytes = try engine.stack.pop(); // Pop the result + defer engine.allocator.free(hash_bytes); // Free the popped byte array + + try std.testing.expectEqual(Sha256.digest_length, hash_bytes.len); + + var expected_hash: [Sha256.digest_length]u8 = undefined; + Sha256.hash(&[_]u8{1}, &expected_hash, .{}); + try std.testing.expectEqualSlices(u8, expected_hash[0..], hash_bytes); +}