Skip to content

Commit

Permalink
Add APO hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLM committed Feb 12, 2025
1 parent 6b85b00 commit b5cd99e
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 36 deletions.
4 changes: 0 additions & 4 deletions coinlib/lib/src/tx/inputs/taproot_key_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ class TaprootKeyInput extends TaprootInput {
required ECPrivateKey key,
}) {

if (details.hashType.requiresApo) {
throw CannotSignInput("A Taproot key-spend doesn't support APO");
}

// Check key corresponds to matching prevOut
final program = details.program;
if (program is! P2TR || key.pubkey.xonly != program.tweakedKey) {
Expand Down
14 changes: 3 additions & 11 deletions coinlib/lib/src/tx/inputs/taproot_script_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,17 @@ class TaprootScriptInput extends TaprootInput {
);

/// Creates a [SchnorrInputSignature] to be used for the input's script data.
/// Uses the [details] plus an optional [codeSeperatorPos] for the last
/// executed position of the script.
/// The leaf hash of the [tapscript] is added to the details.
///
/// [InputSigHashOption.anyPrevOut] or
/// [InputSigHashOption.anyPrevOutAnyScript] can be used, but it must be
/// assured that the tapscript has a signature operation for a BIP118 APO key
/// as this is not checked by this method.
SchnorrInputSignature createScriptSignature({
required TaprootKeySignDetails details,
required TaprootScriptSignDetails details,
required ECPrivateKey key,
int codeSeperatorPos = 0xFFFFFFFF,
}) => createInputSignature(
details: TaprootScriptSignDetails(
tx: details.tx,
inputN: details.inputN,
prevOuts: details.prevOuts,
leafHash: TapLeaf(tapscript).hash,
codeSeperatorPos: codeSeperatorPos,
),
details: details.addLeafHash(TapLeaf(tapscript).hash),
key: key,
);

Expand Down
35 changes: 27 additions & 8 deletions coinlib/lib/src/tx/sighash/taproot_signature_hasher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'package:coinlib/src/common/serial.dart';
import 'package:coinlib/src/crypto/hash.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'precomputed_signature_hashes.dart';
import 'signature_hasher.dart';

Expand All @@ -20,7 +21,11 @@ final class TaprootSignatureHasher extends SignatureHasher with Writable {
: txHashes = TransactionSignatureHashes(details.tx),
prevOutHashes = details.hashType.allInputs
? PrevOutSignatureHashes(details.prevOuts)
: null;
: null {
if (details is TaprootScriptSignDetails) {
throw CannotSignInput("Missing leaf hash for tapscript sign details");
}
}

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

if (hashType.anyOneCanPay) {
thisInput.prevOut.write(writer);
details.prevOuts.first.write(writer);
writer.writeUInt32(thisInput.sequence);
} else {
if (hashType.allInputs) {
writer.writeUInt32(inputN);
} else {

// ANYONECANPAY commits to the prevout point
if (hashType.anyOneCanPay) {
thisInput.prevOut.write(writer);
}

// Commit to the output value and script unless ANYPREVOUTANYSCRIPT
if (!hashType.anyPrevOutAnyScript) {
details.prevOuts.first.write(writer);
}

// Always include sequence
writer.writeUInt32(thisInput.sequence);

}

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

// Data specific to the script
if (leafHash != null) {
writer.writeSlice(leafHash);
writer.writeUInt8(0); // Key version = 0
if (!hashType.anyPrevOutAnyScript) {
writer.writeSlice(leafHash);
}
final keyVersion = hashType.requiresApo ? 1 : 0;
writer.writeUInt8(keyVersion);
writer.writeUInt32(details.codeSeperatorPos);
}

Expand Down
42 changes: 38 additions & 4 deletions coinlib/lib/src/tx/sign_details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ base class TaprootSignDetails extends SignDetails {
/// empty for ANYPREVOUTANYSCRIPT.
final List<Output> prevOuts;

/// The leafhash to sign, or null for key-spends
/// The leafhash to sign, null for key-spends or empty when using
/// ANYPREVOUTANYSCRIPT.
final Uint8List? leafHash;
/// The last executed CODESEPARATOR position in the script
final int codeSeperatorPos;
Expand Down Expand Up @@ -202,7 +203,11 @@ final class TaprootKeySignDetails extends TaprootSignDetails {
required super.inputN,
required super.prevOuts,
super.hashType,
}) : super();
}) : super() {
if (hashType.requiresApo) {
throw CannotSignInput("Cannot use APO for key-spend");
}
}

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

}

/// Details for a Taproot script-spend, containing the leaf hash
/// Details for a Taproot script-spend
final class TaprootScriptSignDetails extends TaprootSignDetails {

/// See [TaprootSignDetails()].
///
/// [codeSeperatorPos] can be provided with the position of the last executed
/// CODESEPARATOR unless none have been executed in the script.
TaprootScriptSignDetails({
required super.tx,
required super.inputN,
required super.prevOuts,
super.codeSeperatorPos,
super.hashType,
}) : super();

TaprootScriptSignDetailsWithLeafHash addLeafHash(Uint8List leafHash)
=> TaprootScriptSignDetailsWithLeafHash(
tx: tx,
inputN: inputN,
prevOuts: prevOuts,
leafHash: leafHash,
codeSeperatorPos: codeSeperatorPos,
hashType: hashType,
);

}

/// Details for a Taproot script-spend, containing the leaf hash
final class TaprootScriptSignDetailsWithLeafHash extends TaprootSignDetails {

/// See [TaprootSignDetails()].
///
/// The [leafHash] must be provided. [codeSeperatorPos] can be provided with
/// the position of the last executed CODESEPARATOR unless none have been
/// executed in the script.
TaprootScriptSignDetails({
TaprootScriptSignDetailsWithLeafHash({
required super.tx,
required super.inputN,
required super.prevOuts,
required Uint8List leafHash,
super.codeSeperatorPos,
super.hashType,
}) : super(leafHash: leafHash);

}
36 changes: 28 additions & 8 deletions coinlib/test/tx/sighash/taproot_signature_hasher_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:typed_data';
import 'package:coinlib/coinlib.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -44,16 +45,25 @@ final prevOuts = [
];

class TaprootSignatureVector {

final int inputN;
final SigHashType hashType;
final String? leafHashHex;
final bool useLeafHash;
final String sigHashHex;

TaprootSignatureVector({
required this.inputN,
required this.hashType,
this.leafHashHex,
this.useLeafHash = false,
required this.sigHashHex,
});

Uint8List? get leafHash => useLeafHash
? hexToBytes(
"2bfe58ab6d9fd575bdc3a624e4825dd2b375d64ac033fbc46ea79dbab4f69a3e",
)
: null;

}

final taprootSigVectors = [
Expand Down Expand Up @@ -96,7 +106,19 @@ final taprootSigVectors = [
inputN: 0,
hashType: SigHashType.single(),
sigHashHex: "20834f382e040a8b6d03600667c2c593b4ffa955f15476ba3b70b72c2538320c",
leafHashHex: "2bfe58ab6d9fd575bdc3a624e4825dd2b375d64ac033fbc46ea79dbab4f69a3e",
useLeafHash: true,
),
TaprootSignatureVector(
inputN: 0,
hashType: SigHashType.all(inputs: InputSigHashOption.anyPrevOut),
sigHashHex: "d36ed3bfe384ab0308b3ee90d1f11d1ad9624072f3bfa47580ff2b9a07c25d16",
useLeafHash: true,
),
TaprootSignatureVector(
inputN: 0,
hashType: SigHashType.all(inputs: InputSigHashOption.anyPrevOutAnyScript),
sigHashHex: "16c8b2007c8c66d708f536a8b676fc7d392de8e0bfb009da9343f9c9e9be3bf9",
useLeafHash: true,
),
];

Expand All @@ -117,12 +139,10 @@ void main() {
TaprootSignDetails(
tx: tx,
inputN: vec.inputN,
prevOuts: vec.hashType.anyOneCanPay
prevOuts: (vec.hashType.anyOneCanPay || vec.hashType.anyPrevOut)
? [prevOuts[vec.inputN]]
: prevOuts,
leafHash: vec.leafHashHex == null
? null
: hexToBytes(vec.leafHashHex!),
: (vec.hashType.anyPrevOutAnyScript ? [] : prevOuts),
leafHash: vec.leafHash,
hashType: vec.hashType,
),
).hash,
Expand Down
2 changes: 1 addition & 1 deletion coinlib/test/tx/transaction_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ void main() {

final solvedInput = inputToSign.updateStack([
inputToSign.createScriptSignature(
details: TaprootKeySignDetails(
details: TaprootScriptSignDetails(
tx: tx,
inputN: 0,
prevOuts: [prevOut],
Expand Down

0 comments on commit b5cd99e

Please sign in to comment.