Skip to content

Commit b5cd99e

Browse files
committed
Add APO hashing
1 parent 6b85b00 commit b5cd99e

File tree

6 files changed

+97
-36
lines changed

6 files changed

+97
-36
lines changed

coinlib/lib/src/tx/inputs/taproot_key_input.dart

-4
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ class TaprootKeyInput extends TaprootInput {
5151
required ECPrivateKey key,
5252
}) {
5353

54-
if (details.hashType.requiresApo) {
55-
throw CannotSignInput("A Taproot key-spend doesn't support APO");
56-
}
57-
5854
// Check key corresponds to matching prevOut
5955
final program = details.program;
6056
if (program is! P2TR || key.pubkey.xonly != program.tweakedKey) {

coinlib/lib/src/tx/inputs/taproot_script_input.dart

+3-11
Original file line numberDiff line numberDiff line change
@@ -100,25 +100,17 @@ class TaprootScriptInput extends TaprootInput {
100100
);
101101

102102
/// Creates a [SchnorrInputSignature] to be used for the input's script data.
103-
/// Uses the [details] plus an optional [codeSeperatorPos] for the last
104-
/// executed position of the script.
103+
/// The leaf hash of the [tapscript] is added to the details.
105104
///
106105
/// [InputSigHashOption.anyPrevOut] or
107106
/// [InputSigHashOption.anyPrevOutAnyScript] can be used, but it must be
108107
/// assured that the tapscript has a signature operation for a BIP118 APO key
109108
/// as this is not checked by this method.
110109
SchnorrInputSignature createScriptSignature({
111-
required TaprootKeySignDetails details,
110+
required TaprootScriptSignDetails details,
112111
required ECPrivateKey key,
113-
int codeSeperatorPos = 0xFFFFFFFF,
114112
}) => createInputSignature(
115-
details: TaprootScriptSignDetails(
116-
tx: details.tx,
117-
inputN: details.inputN,
118-
prevOuts: details.prevOuts,
119-
leafHash: TapLeaf(tapscript).hash,
120-
codeSeperatorPos: codeSeperatorPos,
121-
),
113+
details: details.addLeafHash(TapLeaf(tapscript).hash),
122114
key: key,
123115
);
124116

coinlib/lib/src/tx/sighash/taproot_signature_hasher.dart

+27-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:typed_data';
22
import 'package:coinlib/src/common/serial.dart';
33
import 'package:coinlib/src/crypto/hash.dart';
44
import 'package:coinlib/src/tx/sign_details.dart';
5+
import 'package:coinlib/src/tx/transaction.dart';
56
import 'precomputed_signature_hashes.dart';
67
import 'signature_hasher.dart';
78

@@ -20,7 +21,11 @@ final class TaprootSignatureHasher extends SignatureHasher with Writable {
2021
: txHashes = TransactionSignatureHashes(details.tx),
2122
prevOutHashes = details.hashType.allInputs
2223
? PrevOutSignatureHashes(details.prevOuts)
23-
: null;
24+
: null {
25+
if (details is TaprootScriptSignDetails) {
26+
throw CannotSignInput("Missing leaf hash for tapscript sign details");
27+
}
28+
}
2429

2530
@override
2631
void write(Writer writer) {
@@ -49,12 +54,23 @@ final class TaprootSignatureHasher extends SignatureHasher with Writable {
4954
// Data specific to spending input
5055
writer.writeUInt8(extFlag << 1);
5156

52-
if (hashType.anyOneCanPay) {
53-
thisInput.prevOut.write(writer);
54-
details.prevOuts.first.write(writer);
55-
writer.writeUInt32(thisInput.sequence);
56-
} else {
57+
if (hashType.allInputs) {
5758
writer.writeUInt32(inputN);
59+
} else {
60+
61+
// ANYONECANPAY commits to the prevout point
62+
if (hashType.anyOneCanPay) {
63+
thisInput.prevOut.write(writer);
64+
}
65+
66+
// Commit to the output value and script unless ANYPREVOUTANYSCRIPT
67+
if (!hashType.anyPrevOutAnyScript) {
68+
details.prevOuts.first.write(writer);
69+
}
70+
71+
// Always include sequence
72+
writer.writeUInt32(thisInput.sequence);
73+
5874
}
5975

6076
// Data specific to matched output
@@ -66,8 +82,11 @@ final class TaprootSignatureHasher extends SignatureHasher with Writable {
6682

6783
// Data specific to the script
6884
if (leafHash != null) {
69-
writer.writeSlice(leafHash);
70-
writer.writeUInt8(0); // Key version = 0
85+
if (!hashType.anyPrevOutAnyScript) {
86+
writer.writeSlice(leafHash);
87+
}
88+
final keyVersion = hashType.requiresApo ? 1 : 0;
89+
writer.writeUInt8(keyVersion);
7190
writer.writeUInt32(details.codeSeperatorPos);
7291
}
7392

coinlib/lib/src/tx/sign_details.dart

+38-4
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ base class TaprootSignDetails extends SignDetails {
149149
/// empty for ANYPREVOUTANYSCRIPT.
150150
final List<Output> prevOuts;
151151

152-
/// The leafhash to sign, or null for key-spends
152+
/// The leafhash to sign, null for key-spends or empty when using
153+
/// ANYPREVOUTANYSCRIPT.
153154
final Uint8List? leafHash;
154155
/// The last executed CODESEPARATOR position in the script
155156
final int codeSeperatorPos;
@@ -202,7 +203,11 @@ final class TaprootKeySignDetails extends TaprootSignDetails {
202203
required super.inputN,
203204
required super.prevOuts,
204205
super.hashType,
205-
}) : super();
206+
}) : super() {
207+
if (hashType.requiresApo) {
208+
throw CannotSignInput("Cannot use APO for key-spend");
209+
}
210+
}
206211

207212
Program? get program => switch (hashType.inputs) {
208213
InputSigHashOption.all => prevOuts[inputN].program,
@@ -213,19 +218,48 @@ final class TaprootKeySignDetails extends TaprootSignDetails {
213218

214219
}
215220

216-
/// Details for a Taproot script-spend, containing the leaf hash
221+
/// Details for a Taproot script-spend
217222
final class TaprootScriptSignDetails extends TaprootSignDetails {
223+
224+
/// See [TaprootSignDetails()].
225+
///
226+
/// [codeSeperatorPos] can be provided with the position of the last executed
227+
/// CODESEPARATOR unless none have been executed in the script.
228+
TaprootScriptSignDetails({
229+
required super.tx,
230+
required super.inputN,
231+
required super.prevOuts,
232+
super.codeSeperatorPos,
233+
super.hashType,
234+
}) : super();
235+
236+
TaprootScriptSignDetailsWithLeafHash addLeafHash(Uint8List leafHash)
237+
=> TaprootScriptSignDetailsWithLeafHash(
238+
tx: tx,
239+
inputN: inputN,
240+
prevOuts: prevOuts,
241+
leafHash: leafHash,
242+
codeSeperatorPos: codeSeperatorPos,
243+
hashType: hashType,
244+
);
245+
246+
}
247+
248+
/// Details for a Taproot script-spend, containing the leaf hash
249+
final class TaprootScriptSignDetailsWithLeafHash extends TaprootSignDetails {
250+
218251
/// See [TaprootSignDetails()].
219252
///
220253
/// The [leafHash] must be provided. [codeSeperatorPos] can be provided with
221254
/// the position of the last executed CODESEPARATOR unless none have been
222255
/// executed in the script.
223-
TaprootScriptSignDetails({
256+
TaprootScriptSignDetailsWithLeafHash({
224257
required super.tx,
225258
required super.inputN,
226259
required super.prevOuts,
227260
required Uint8List leafHash,
228261
super.codeSeperatorPos,
229262
super.hashType,
230263
}) : super(leafHash: leafHash);
264+
231265
}

coinlib/test/tx/sighash/taproot_signature_hasher_test.dart

+28-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:typed_data';
12
import 'package:coinlib/coinlib.dart';
23
import 'package:test/test.dart';
34

@@ -44,16 +45,25 @@ final prevOuts = [
4445
];
4546

4647
class TaprootSignatureVector {
48+
4749
final int inputN;
4850
final SigHashType hashType;
49-
final String? leafHashHex;
51+
final bool useLeafHash;
5052
final String sigHashHex;
53+
5154
TaprootSignatureVector({
5255
required this.inputN,
5356
required this.hashType,
54-
this.leafHashHex,
57+
this.useLeafHash = false,
5558
required this.sigHashHex,
5659
});
60+
61+
Uint8List? get leafHash => useLeafHash
62+
? hexToBytes(
63+
"2bfe58ab6d9fd575bdc3a624e4825dd2b375d64ac033fbc46ea79dbab4f69a3e",
64+
)
65+
: null;
66+
5767
}
5868

5969
final taprootSigVectors = [
@@ -96,7 +106,19 @@ final taprootSigVectors = [
96106
inputN: 0,
97107
hashType: SigHashType.single(),
98108
sigHashHex: "20834f382e040a8b6d03600667c2c593b4ffa955f15476ba3b70b72c2538320c",
99-
leafHashHex: "2bfe58ab6d9fd575bdc3a624e4825dd2b375d64ac033fbc46ea79dbab4f69a3e",
109+
useLeafHash: true,
110+
),
111+
TaprootSignatureVector(
112+
inputN: 0,
113+
hashType: SigHashType.all(inputs: InputSigHashOption.anyPrevOut),
114+
sigHashHex: "d36ed3bfe384ab0308b3ee90d1f11d1ad9624072f3bfa47580ff2b9a07c25d16",
115+
useLeafHash: true,
116+
),
117+
TaprootSignatureVector(
118+
inputN: 0,
119+
hashType: SigHashType.all(inputs: InputSigHashOption.anyPrevOutAnyScript),
120+
sigHashHex: "16c8b2007c8c66d708f536a8b676fc7d392de8e0bfb009da9343f9c9e9be3bf9",
121+
useLeafHash: true,
100122
),
101123
];
102124

@@ -117,12 +139,10 @@ void main() {
117139
TaprootSignDetails(
118140
tx: tx,
119141
inputN: vec.inputN,
120-
prevOuts: vec.hashType.anyOneCanPay
142+
prevOuts: (vec.hashType.anyOneCanPay || vec.hashType.anyPrevOut)
121143
? [prevOuts[vec.inputN]]
122-
: prevOuts,
123-
leafHash: vec.leafHashHex == null
124-
? null
125-
: hexToBytes(vec.leafHashHex!),
144+
: (vec.hashType.anyPrevOutAnyScript ? [] : prevOuts),
145+
leafHash: vec.leafHash,
126146
hashType: vec.hashType,
127147
),
128148
).hash,

coinlib/test/tx/transaction_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ void main() {
590590

591591
final solvedInput = inputToSign.updateStack([
592592
inputToSign.createScriptSignature(
593-
details: TaprootKeySignDetails(
593+
details: TaprootScriptSignDetails(
594594
tx: tx,
595595
inputN: 0,
596596
prevOuts: [prevOut],

0 commit comments

Comments
 (0)