diff --git a/BitcoinKernel.NET.sln b/BitcoinKernel.NET.sln
index 9814736..b23123f 100644
--- a/BitcoinKernel.NET.sln
+++ b/BitcoinKernel.NET.sln
@@ -9,6 +9,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{B3
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F8E63A4F-7D3E-4B2A-9C1D-8A5F6E9B3C2D}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BitcoinKernel.Tests", "tests\BitcoinKernel.Tests\BitcoinKernel.Tests.csproj", "{BC90EFB4-1692-CBCC-EF52-778255F591E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicUsage", "examples\BasicUsage\BasicUsage.csproj", "{0E2DDF4A-A1FE-5424-03EA-7A8E76751354}"
@@ -23,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockProcessing", "examples
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BitcoinKernel.Core.Tests", "tests\BitcoinKernel.Core.Tests\BitcoinKernel.Core.Tests.csproj", "{267842B2-D915-4B9E-8448-F9B5816D4A0A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kernel-bindings-test-handler", "tools\kernel-bindings-test-handler\kernel-bindings-test-handler.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -117,6 +121,18 @@ Global
{267842B2-D915-4B9E-8448-F9B5816D4A0A}.Release|x64.Build.0 = Release|Any CPU
{267842B2-D915-4B9E-8448-F9B5816D4A0A}.Release|x86.ActiveCfg = Release|Any CPU
{267842B2-D915-4B9E-8448-F9B5816D4A0A}.Release|x86.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -129,6 +145,7 @@ Global
{D6F509B1-C990-0533-2DD1-CFFBA7506249} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{23E19BC3-8829-42BE-BCB4-A2050BE04975} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
{267842B2-D915-4B9E-8448-F9B5816D4A0A} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {F8E63A4F-7D3E-4B2A-9C1D-8A5F6E9B3C2D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B999E480-512A-4C50-9574-4AECEAC73E1C}
diff --git a/README.md b/README.md
index 8a3e19a..791c93e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# BitcoinKernel.NET
.NET bindings and high-level library for [libbitcoinkernel](https://github.com/bitcoin/bitcoin/tree/master/src/kernel), providing access to Bitcoin Core's consensus and validation logic.
+
+
⚠️🚧 This library is still under contruction. ⚠️🚧
This library uses [libbitcoinkernel](https://github.com/bitcoin/bitcoin/tree/master/src/kernel) which is in an experimental state, do not use for production purposes.
@@ -68,6 +70,19 @@ Explore the [examples](examples/) directory for complete working samples:
- **[BasicUsage](examples/BasicUsage/)** - Getting started with the high-level API
- **[BlockProcessing](examples/BlockProcessing/)** - Block validation and chain management
+## Tools
+
+### Kernel Bindings Test Handler
+
+A conformance test handler for Kernel bindings Test handler framework, see [tools/kernel-bindings-test-handler](tools/kernel-bindings-test-handler/) for details.
+
+**Usage:**
+```bash
+dotnet run --project tools/kernel-bindings-test-handler
+```
+
+The handler communicates via stdin/stdout and is designed for automated conformance testing.
+
## Building from Source
### Prerequisites
diff --git a/tools/kernel-bindings-test-handler/Handlers/ScriptVerifyHandler.cs b/tools/kernel-bindings-test-handler/Handlers/ScriptVerifyHandler.cs
new file mode 100644
index 0000000..e55a479
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/Handlers/ScriptVerifyHandler.cs
@@ -0,0 +1,186 @@
+using BitcoinKernel.Core;
+using BitcoinKernel.Core.Abstractions;
+using BitcoinKernel.Core.Exceptions;
+using BitcoinKernel.Core.ScriptVerification;
+using BitcoinKernel.Interop.Enums;
+using BitcoinKernel.TestHandler.Protocol;
+
+namespace BitcoinKernel.TestHandler.Handlers;
+
+///
+/// Handles script_pubkey.verify method requests.
+///
+public class ScriptVerifyHandler
+{
+ private readonly KernelContext _context;
+
+ public ScriptVerifyHandler(KernelContext context)
+ {
+ _context = context;
+ }
+
+ ///
+ /// Handles a script verification request.
+ ///
+ public Response Handle(string requestId, ScriptVerifyParams parameters)
+ {
+ try
+ {
+ // Parse input data
+ var scriptPubKey = ScriptPubKey.FromHex(parameters.ScriptPubKeyHex);
+ var transaction = Transaction.FromHex(parameters.TxHex);
+
+ // Parse spent outputs if provided
+ var spentOutputs = new List();
+ if (parameters.SpentOutputs != null && parameters.SpentOutputs.Any())
+ {
+ foreach (var output in parameters.SpentOutputs)
+ {
+ var outputScriptPubKey = ScriptPubKey.FromHex(output.ScriptPubKeyHex);
+ spentOutputs.Add(new TxOut(outputScriptPubKey, output.Amount));
+ }
+ }
+
+ // Parse flags
+ var flags = ParseFlags(parameters.Flags);
+
+ // Verify the script
+ ScriptVerifier.VerifyScript(
+ scriptPubKey,
+ parameters.Amount,
+ transaction,
+ parameters.InputIndex,
+ spentOutputs,
+ flags
+ );
+
+ // Success
+ return new Response
+ {
+ Id = requestId,
+ Success = new { }
+ };
+ }
+ catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "inputIndex")
+ {
+ return new Response
+ {
+ Id = requestId,
+ Error = new ErrorResponse
+ {
+ Type = "ScriptVerify",
+ Variant = "TxInputIndex"
+ }
+ };
+ }
+ catch (ScriptVerificationException ex)
+ {
+ return new Response
+ {
+ Id = requestId,
+ Error = new ErrorResponse
+ {
+ Type = "ScriptVerify",
+ Variant = MapScriptVerifyStatus(ex.Status)
+ }
+ };
+ }
+ catch (Exception
+#if DEBUG
+ ex
+#endif
+ )
+ {
+ // Log to stderr for debugging (can be disabled in production)
+#if DEBUG
+ Console.Error.WriteLine($"Exception: {ex.GetType().Name}: {ex.Message}");
+ Console.Error.WriteLine($"StackTrace: {ex.StackTrace}");
+#endif
+
+ // Generic error for unexpected exceptions
+ return new Response
+ {
+ Id = requestId,
+ Error = new ErrorResponse
+ {
+ Type = "ScriptVerify",
+ Variant = "Invalid"
+ }
+ };
+ }
+ }
+
+ ///
+ /// Parses flags from either uint or string format.
+ ///
+ private ScriptVerificationFlags ParseFlags(object? flags)
+ {
+ if (flags == null)
+ return ScriptVerificationFlags.None;
+
+ // Handle numeric flags
+ if (flags is uint or int or long)
+ {
+ return (ScriptVerificationFlags)Convert.ToUInt32(flags);
+ }
+
+ // Handle System.Text.Json JsonElement
+ if (flags.GetType().Name == "JsonElement")
+ {
+ var jsonElement = (System.Text.Json.JsonElement)flags;
+ if (jsonElement.ValueKind == System.Text.Json.JsonValueKind.Number)
+ {
+ return (ScriptVerificationFlags)jsonElement.GetUInt32();
+ }
+ else if (jsonElement.ValueKind == System.Text.Json.JsonValueKind.String)
+ {
+ return ParseFlagString(jsonElement.GetString() ?? string.Empty);
+ }
+ }
+
+ // Handle string flags
+ if (flags is string flagStr)
+ {
+ return ParseFlagString(flagStr);
+ }
+
+ return ScriptVerificationFlags.None;
+ }
+
+ ///
+ /// Parses a string flag name to ScriptVerificationFlags.
+ ///
+ private ScriptVerificationFlags ParseFlagString(string flagStr)
+ {
+ return flagStr.ToUpperInvariant() switch
+ {
+ "VERIFY_NONE" or "NONE" => ScriptVerificationFlags.None,
+ "VERIFY_P2SH" or "P2SH" => ScriptVerificationFlags.P2SH,
+ "VERIFY_DERSIG" or "DERSIG" => ScriptVerificationFlags.DerSig,
+ "VERIFY_NULLDUMMY" or "NULLDUMMY" => ScriptVerificationFlags.NullDummy,
+ "VERIFY_CHECKLOCKTIMEVERIFY" or "CHECKLOCKTIMEVERIFY" => ScriptVerificationFlags.CheckLockTimeVerify,
+ "VERIFY_CHECKSEQUENCEVERIFY" or "CHECKSEQUENCEVERIFY" => ScriptVerificationFlags.CheckSequenceVerify,
+ "VERIFY_WITNESS" or "WITNESS" => ScriptVerificationFlags.Witness,
+ "VERIFY_TAPROOT" or "TAPROOT" => ScriptVerificationFlags.Taproot,
+ "VERIFY_ALL" or "ALL" => ScriptVerificationFlags.All,
+ "VERIFY_ALL_PRE_TAPROOT" or "ALL_PRE_TAPROOT" => ScriptVerificationFlags.AllPreTaproot,
+ _ => throw new ArgumentException($"Unknown flag: {flagStr}")
+ };
+ }
+
+ ///
+ /// Maps ScriptVerifyStatus to error variant strings.
+ ///
+ private string MapScriptVerifyStatus(ScriptVerifyStatus status)
+ {
+ return status switch
+ {
+ ScriptVerifyStatus.ERROR_TX_INPUT_INDEX => "TxInputIndex",
+ ScriptVerifyStatus.ERROR_INVALID_FLAGS => "InvalidFlags",
+ ScriptVerifyStatus.ERROR_INVALID_FLAGS_COMBINATION => "InvalidFlagsCombination",
+ ScriptVerifyStatus.ERROR_SPENT_OUTPUTS_MISMATCH => "SpentOutputsMismatch",
+ ScriptVerifyStatus.ERROR_SPENT_OUTPUTS_REQUIRED => "SpentOutputsRequired",
+ _ => "Invalid"
+ };
+ }
+}
diff --git a/tools/kernel-bindings-test-handler/Program.cs b/tools/kernel-bindings-test-handler/Program.cs
new file mode 100644
index 0000000..ccd89e1
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/Program.cs
@@ -0,0 +1,146 @@
+using System.Text.Json;
+using BitcoinKernel.Core;
+using BitcoinKernel.TestHandler.Handlers;
+using BitcoinKernel.TestHandler.Protocol;
+
+namespace BitcoinKernel.TestHandler;
+
+///
+/// Test handler for Bitcoin Kernel conformance tests.
+/// Implements the JSON-RPC-like protocol for testing bindings.
+///
+class Program
+{
+ static async Task Main(string[] args)
+ {
+ // Initialize kernel context
+ using var context = new KernelContext();
+ var scriptVerifyHandler = new ScriptVerifyHandler(context);
+
+ // Configure JSON serialization options
+ var jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ WriteIndented = false
+ };
+
+ try
+ {
+ // Read requests line-by-line from stdin
+ string? line;
+ while ((line = await Console.In.ReadLineAsync()) != null)
+ {
+ if (string.IsNullOrWhiteSpace(line))
+ continue;
+
+ Response response;
+ try
+ {
+ // Parse the request
+ var request = JsonSerializer.Deserialize(line, jsonOptions);
+
+ if (request == null)
+ {
+ response = new Response
+ {
+ Id = "unknown",
+ Error = new ErrorResponse
+ {
+ Type = "InvalidRequest"
+ }
+ };
+ }
+ else
+ {
+ response = HandleRequest(request, scriptVerifyHandler, jsonOptions);
+ }
+ }
+ catch (JsonException)
+ {
+ response = new Response
+ {
+ Id = "unknown",
+ Error = new ErrorResponse
+ {
+ Type = "InvalidRequest"
+ }
+ };
+ }
+
+ // Write response to stdout
+ var responseJson = JsonSerializer.Serialize(response, jsonOptions);
+ await Console.Out.WriteLineAsync(responseJson);
+ await Console.Out.FlushAsync();
+ }
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ await Console.Error.WriteLineAsync($"Fatal error: {ex.Message}");
+ return 1;
+ }
+ }
+
+ ///
+ /// Routes the request to the appropriate handler.
+ ///
+ private static Response HandleRequest(Request request, ScriptVerifyHandler scriptVerifyHandler, JsonSerializerOptions jsonOptions)
+ {
+ try
+ {
+ switch (request.Method)
+ {
+ case "script_pubkey.verify":
+ if (request.Params == null)
+ {
+ return new Response
+ {
+ Id = request.Id,
+ Error = new ErrorResponse
+ {
+ Type = "InvalidParams"
+ }
+ };
+ }
+
+ var scriptVerifyParams = JsonSerializer.Deserialize(request.Params.Value, jsonOptions);
+
+ if (scriptVerifyParams == null)
+ {
+ return new Response
+ {
+ Id = request.Id,
+ Error = new ErrorResponse
+ {
+ Type = "InvalidParams"
+ }
+ };
+ }
+
+ return scriptVerifyHandler.Handle(request.Id, scriptVerifyParams);
+
+ default:
+ return new Response
+ {
+ Id = request.Id,
+ Error = new ErrorResponse
+ {
+ Type = "MethodNotFound"
+ }
+ };
+ }
+ }
+ catch (Exception)
+ {
+ return new Response
+ {
+ Id = request.Id,
+ Error = new ErrorResponse
+ {
+ Type = "InternalError"
+ }
+ };
+ }
+ }
+}
diff --git a/tools/kernel-bindings-test-handler/Protocol/Request.cs b/tools/kernel-bindings-test-handler/Protocol/Request.cs
new file mode 100644
index 0000000..0d75185
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/Protocol/Request.cs
@@ -0,0 +1,55 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace BitcoinKernel.TestHandler.Protocol;
+
+///
+/// Represents a request from the test runner.
+///
+public class Request
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ [JsonPropertyName("method")]
+ public string Method { get; set; } = string.Empty;
+
+ [JsonPropertyName("params")]
+ public JsonElement? Params { get; set; }
+}
+
+///
+/// Parameters for script_pubkey.verify method.
+///
+public class ScriptVerifyParams
+{
+ [JsonPropertyName("script_pubkey_hex")]
+ public string ScriptPubKeyHex { get; set; } = string.Empty;
+
+ [JsonPropertyName("amount")]
+ public long Amount { get; set; }
+
+ [JsonPropertyName("tx_hex")]
+ public string TxHex { get; set; } = string.Empty;
+
+ [JsonPropertyName("input_index")]
+ public uint InputIndex { get; set; }
+
+ [JsonPropertyName("spent_outputs")]
+ public List? SpentOutputs { get; set; }
+
+ [JsonPropertyName("flags")]
+ public object? Flags { get; set; } // Can be uint or string
+}
+
+///
+/// Represents a spent output.
+///
+public class SpentOutput
+{
+ [JsonPropertyName("script_pubkey_hex")]
+ public string ScriptPubKeyHex { get; set; } = string.Empty;
+
+ [JsonPropertyName("amount")]
+ public long Amount { get; set; }
+}
diff --git a/tools/kernel-bindings-test-handler/Protocol/Response.cs b/tools/kernel-bindings-test-handler/Protocol/Response.cs
new file mode 100644
index 0000000..1c49f2a
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/Protocol/Response.cs
@@ -0,0 +1,33 @@
+using System.Text.Json.Serialization;
+
+namespace BitcoinKernel.TestHandler.Protocol;
+
+///
+/// Represents a response to the test runner.
+///
+public class Response
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ [JsonPropertyName("success")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public object? Success { get; set; }
+
+ [JsonPropertyName("error")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public ErrorResponse? Error { get; set; }
+}
+
+///
+/// Represents an error response.
+///
+public class ErrorResponse
+{
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = string.Empty;
+
+ [JsonPropertyName("variant")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? Variant { get; set; }
+}
diff --git a/tools/kernel-bindings-test-handler/README.md b/tools/kernel-bindings-test-handler/README.md
new file mode 100644
index 0000000..9825654
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/README.md
@@ -0,0 +1,154 @@
+# Kernel Bindings Test Handler
+
+This is a conformance test handler for the BitcoinKernel.NET library.
+It implements the protocol specification [kernel-bindings-test handler-spec](https://github.com/stringintech/kernel-bindings-tests/blob/main/docs/handler-spec.md) for testing Bitcoin Kernel bindings via stdin/stdout JSON-RPC-like communication.
+
+## Overview
+
+The handler is a console application that:
+- Reads JSON requests line-by-line from stdin
+- Processes each request using the BitcoinKernel.Core library
+- Writes JSON responses to stdout
+- Exits cleanly when stdin closes
+
+## Protocol
+
+### Communication
+- **Input**: JSON requests on stdin (one per line)
+- **Output**: JSON responses on stdout (one per line)
+
+### Request Format
+```json
+{
+ "id": "unique-request-id",
+ "method": "method_name",
+ "params": { /* method-specific parameters */ }
+}
+```
+
+### Response Format
+**Success:**
+```json
+{
+ "id": "unique-request-id",
+ "success": { /* result */ }
+}
+```
+
+**Error:**
+```json
+{
+ "id": "unique-request-id",
+ "error": {
+ "type": "error_category",
+ "variant": "specific_error"
+ }
+}
+```
+
+## Supported Methods
+
+### `script_pubkey.verify`
+
+Verifies a Bitcoin script pubkey against a transaction input.
+
+**Parameters:**
+- `script_pubkey` (string): Hex-encoded script pubkey
+- `amount` (number): Amount of the output being spent
+- `transaction` (string): Hex-encoded transaction
+- `input_index` (number): Index of the transaction input to verify
+- `spent_outputs` (array, optional): Array of spent outputs
+ - Each output contains: `script_pubkey` (string), `amount` (number)
+- `script_verify_flags` (number): Script verification flags
+
+**Success Response:**
+```json
+{
+ "id": "test-id",
+ "success": {}
+}
+```
+
+**Error Variants:**
+- `TxInputIndex`: Input index is out of bounds
+- `InvalidFlags`: Invalid verification flags
+- `InvalidFlagsCombination`: Invalid flag combination
+- `SpentOutputsMismatch`: Spent outputs count doesn't match input count
+- `SpentOutputsRequired`: Spent outputs required but not provided
+- `Invalid`: Script verification failed
+
+## Building
+
+### Build for Development
+```bash
+dotnet build
+```
+
+### Build Release Binary
+```bash
+./build.sh
+```
+
+This creates a compiled binary at `bin/kernel-bindings-test-handler` that can be invoked directly without `dotnet run`.
+
+## Running
+
+### Option 1: Run with dotnet (development)
+```bash
+dotnet run --project tools/kernel-bindings-test-handler
+```
+
+### Option 2: Run compiled binary (production)
+```bash
+./tools/kernel-bindings-test-handler/bin/kernel-bindings-test-handler
+```
+
+### invoke via Bitcoin Kernel Binding Conformance Tests framework.
+Clone the repo and build.
+
+Execute the test:
+```bash
+ ./build/runner --handler /Users/arjan/Projects/BitcoinKernel.NET/tools/kernel-bindings-test-handler/bin/kernel-bindings-test-handler
+```
+
+## Testing
+
+The handler is designed to be used with conformance test suite. Example:
+
+```bash
+echo '{"id":"1","method":"script_pubkey.verify","params":{...}}' | \
+ ./bin/kernel-bindings-test-handler
+```
+
+## Project Structure
+
+```
+tools/kernel-bindings-test-handler/
+|-- kernel-bindings-test-handler.csproj
+|-- Program.cs # Main entry point and request router
+|-- Protocol/
+| |-- Request.cs # Request message definitions
+| |-- Response.cs # Response message definitions
+|-- Handlers/
+| |-- ScriptVerifyHandler.cs # Script verification handler
+|-- Bin/
+| |-- Compiled binaries useable for the Bitcoin Kernel Binding Conformance Tests framework
+```
+
+## Dependencies
+
+- BitcoinKernel.Core: The core library being tested
+- System.Text.Json: JSON serialization
+
+## Error Handling
+
+The handler maps BitcoinKernel.Core exceptions to protocol error responses:
+
+| Exception | Error Type | Error Variant |
+|-----------|-----------|---------------|
+| ArgumentOutOfRangeException (inputIndex) | ScriptVerify | TxInputIndex |
+| ScriptVerificationException (ERROR_INVALID_FLAGS) | ScriptVerify | InvalidFlags |
+| ScriptVerificationException (ERROR_INVALID_FLAGS_COMBINATION) | ScriptVerify | InvalidFlagsCombination |
+| ScriptVerificationException (ERROR_SPENT_OUTPUTS_MISMATCH) | ScriptVerify | SpentOutputsMismatch |
+| ScriptVerificationException (ERROR_SPENT_OUTPUTS_REQUIRED) | ScriptVerify | SpentOutputsRequired |
+| Any other exception | ScriptVerify | Invalid |
diff --git a/tools/kernel-bindings-test-handler/build.sh b/tools/kernel-bindings-test-handler/build.sh
new file mode 100755
index 0000000..b2760e5
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/build.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# Build the handler as a self-contained executable
+
+PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+OUTPUT_DIR="$PROJECT_DIR/bin"
+
+echo "Building kernel-bindings-test-handler..."
+
+# Create output directory
+mkdir -p "$OUTPUT_DIR"
+
+# Build release version
+dotnet publish "$PROJECT_DIR/kernel-bindings-test-handler.csproj" \
+ -c Release \
+ -o "$OUTPUT_DIR" \
+ --self-contained false \
+ /p:PublishSingleFile=false
+
+if [ $? -eq 0 ]; then
+ echo "✅ Build successful"
+ echo "Binary location: $OUTPUT_DIR/kernel-bindings-test-handler"
+ echo ""
+ echo "Run with: $OUTPUT_DIR/kernel-bindings-test-handler"
+else
+ echo "❌ Build failed"
+ exit 1
+fi
diff --git a/tools/kernel-bindings-test-handler/kernel-bindings-test-handler.csproj b/tools/kernel-bindings-test-handler/kernel-bindings-test-handler.csproj
new file mode 100644
index 0000000..45fef66
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/kernel-bindings-test-handler.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net9.0
+ BitcoinKernel.TestHandler
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/kernel-bindings-test-handler/test.sh b/tools/kernel-bindings-test-handler/test.sh
new file mode 100755
index 0000000..5a867b3
--- /dev/null
+++ b/tools/kernel-bindings-test-handler/test.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# Simple test script to verify the handler responds correctly
+
+# Example test case (you'll need to replace with actual test data)
+echo "Testing kernel-bindings-test-handler..."
+
+# Build the handler
+dotnet build tools/kernel-bindings-test-handler/kernel-bindings-test-handler.csproj > /dev/null 2>&1
+
+if [ $? -ne 0 ]; then
+ echo "❌ Build failed"
+ exit 1
+fi
+
+echo "✅ Build successful"
+
+# Test 1: Invalid method
+echo '{"id":"test-1","method":"invalid.method","params":{}}' | \
+ dotnet run --project tools/kernel-bindings-test-handler 2>/dev/null | \
+ grep -q '"error"' && echo "✅ Test 1 passed: Invalid method returns error" || echo "❌ Test 1 failed"
+
+
+echo ""
+echo "Handler is ready to use with conformance test suites."
+echo "Run with: dotnet run --project tools/kernel-bindings-test-handler"