-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScriptVerifier.cs
More file actions
157 lines (139 loc) · 5.91 KB
/
ScriptVerifier.cs
File metadata and controls
157 lines (139 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System;
using System.Runtime.InteropServices;
using BitcoinKernel.Core.Exceptions;
using BitcoinKernel.Interop;
using BitcoinKernel.Interop.Enums;
using BitcoinKernel.Core.Abstractions;
namespace BitcoinKernel.Core.ScriptVerification;
/// <summary>
/// Handles script verification operations.
/// </summary>
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>
/// <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="inputIndex">The index of the transaction input to verify within the transaction.</param>
/// <param name="spentOutputs">The outputs being spent by the transaction .</param>
/// <param name="flags">Script verification flags to use.</param>
/// <exception cref="ScriptVerificationException">Thrown when verification fails with an error status.</exception>
public static bool VerifyScript(
ScriptPubKey scriptPubkey,
long amount,
Transaction transaction,
uint inputIndex,
List<TxOut> spentOutputs,
ScriptVerificationFlags flags = ScriptVerificationFlags.All)
{
var inputCount = transaction.InputCount;
if (inputIndex >= inputCount)
{
throw new ArgumentOutOfRangeException(nameof(inputIndex),
$"Input index {inputIndex} is out of bounds for transaction with {inputCount} inputs");
}
if (spentOutputs.Any() && spentOutputs.Count != inputCount)
{
throw new ScriptVerificationException(
ScriptVerifyStatus.ERROR_SPENT_OUTPUTS_MISMATCH,
$"Spent outputs count ({spentOutputs.Count}) does not match transaction input count ({inputCount})");
}
if (((uint)flags & ~(uint)ScriptVerificationFlags.All) != 0)
{
throw new ScriptVerificationException(
ScriptVerifyStatus.ERROR_INVALID_FLAGS,
$"Invalid script verification flags: 0x{flags:X}");
}
// Create spent outputs
IntPtr precomputedDataPtr = IntPtr.Zero;
if (spentOutputs.Any())
{
var kernelSpentOutputs = spentOutputs.Select(utxo => utxo.Handle).ToArray();
precomputedDataPtr = NativeMethods.PrecomputedTransactionDataCreate(
transaction.Handle,
kernelSpentOutputs,
(nuint)spentOutputs.Count);
}
// Verify script
// Allocate memory for status byte
IntPtr statusPtr = Marshal.AllocHGlobal(1);
try
{
int result = NativeMethods.ScriptPubkeyVerify(
scriptPubkey.Handle,
amount,
transaction.Handle,
precomputedDataPtr,
inputIndex,
(uint)flags,
statusPtr);
byte statusCode = Marshal.ReadByte(statusPtr);
var status = (ScriptVerifyStatus)statusCode;
// Check for errors
if (status != ScriptVerifyStatus.OK)
{
throw new ScriptVerificationException(status, $"Script verification failed: {status}");
}
// Even if status is OK, result==0 means script verification failed
if (result == 0)
{
throw new ScriptVerificationException(status, "Script verification failed");
}
}
finally
{
Marshal.FreeHGlobal(statusPtr);
if (precomputedDataPtr != IntPtr.Zero)
{
NativeMethods.PrecomputedTransactionDataDestroy(precomputedDataPtr);
}
}
return true;
}
}