Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
platform: linux_amd64

env:
TEST_VERSION: '0.0.2'
TEST_VERSION: '0.0.3-alpha.4'
TEST_REPO: 'stringintech/kernel-bindings-tests'
TEST_DIR: '.conformance-tests'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using BitcoinKernel.Core.Abstractions;
using BitcoinKernel.Core.Exceptions;
using BitcoinKernel.Interop;

namespace BitcoinKernel.Core.ScriptVerification;

/// <summary>
/// Holds precomputed transaction data used to accelerate repeated script verification
/// across multiple inputs of the same transaction.
/// Required when <c>btck_ScriptVerificationFlags_TAPROOT</c> is set.
/// </summary>
public sealed class PrecomputedTransactionData : IDisposable
{
private IntPtr _handle;
private bool _disposed;

/// <summary>
/// Creates precomputed transaction data for the given transaction.
/// </summary>
/// <param name="transaction">The transaction being verified.</param>
/// <param name="spentOutputs">
/// The outputs being spent by the transaction inputs. Required when the TAPROOT
/// verification flag is set. Must match the transaction input count if provided.
/// </param>
public PrecomputedTransactionData(Transaction transaction, IReadOnlyList<TxOut>? spentOutputs = null)
{
ArgumentNullException.ThrowIfNull(transaction);

IntPtr[] handles = spentOutputs is { Count: > 0 }
? spentOutputs.Select(o => o.Handle).ToArray()
: Array.Empty<IntPtr>();

_handle = NativeMethods.PrecomputedTransactionDataCreate(
transaction.Handle,
handles,
(nuint)handles.Length);

if (_handle == IntPtr.Zero)
throw new KernelException("Failed to create precomputed transaction data");
}

internal IntPtr Handle
{
get
{
ThrowIfDisposed();
return _handle;
}
}

public void Dispose()
{
if (!_disposed && _handle != IntPtr.Zero)
{
NativeMethods.PrecomputedTransactionDataDestroy(_handle);
_handle = IntPtr.Zero;
_disposed = true;
}
GC.SuppressFinalize(this);
}

~PrecomputedTransactionData() => Dispose();

private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(PrecomputedTransactionData));
}
}
49 changes: 49 additions & 0 deletions src/BitcoinKernel.Core/ScriptVerification/ScriptVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,55 @@ namespace BitcoinKernel.Core.ScriptVerification;
public static class ScriptVerifier
{

/// <summary>
/// Verifies a script pubkey using externally-managed precomputed transaction data.
/// Use this overload when the <see cref="PrecomputedTransactionData"/> is created
/// separately and reused across multiple inputs of the same transaction.
/// </summary>
/// <param name="scriptPubkey">The output script to verify against.</param>
/// <param name="amount">The amount of the output being spent.</param>
/// <param name="transaction">The transaction containing the input to verify.</param>
/// <param name="precomputedTxData">Optional externally-managed precomputed transaction data. Required for Taproot.</param>
/// <param name="inputIndex">The index of the transaction input to verify.</param>
/// <param name="flags">Script verification flags to use.</param>
/// <returns>True if the script is valid, false if invalid.</returns>
/// <exception cref="ScriptVerificationException">Thrown when verification fails with an error status.</exception>
public static bool VerifyScript(
ScriptPubKey scriptPubkey,
long amount,
Transaction transaction,
PrecomputedTransactionData? precomputedTxData,
uint inputIndex,
ScriptVerificationFlags flags = ScriptVerificationFlags.All)
{
IntPtr precomputedPtr = precomputedTxData?.Handle ?? IntPtr.Zero;

IntPtr statusPtr = Marshal.AllocHGlobal(1);
try
{
int result = NativeMethods.ScriptPubkeyVerify(
scriptPubkey.Handle,
amount,
transaction.Handle,
precomputedPtr,
inputIndex,
(uint)flags,
statusPtr);

byte statusCode = Marshal.ReadByte(statusPtr);
var status = (ScriptVerifyStatus)statusCode;

if (status != ScriptVerifyStatus.OK)
throw new ScriptVerificationException(status, $"Script verification failed: {status}");

return result != 0;
}
finally
{
Marshal.FreeHGlobal(statusPtr);
}
}

/// <summary>
/// Verifies a script pubkey against a transaction input, throwing an exception on error.
/// </summary>
Expand Down
Loading
Loading