Skip to content

Commit

Permalink
Refactor signing details into new classes
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLM committed Feb 11, 2025
1 parent d3d7680 commit 6b85b00
Show file tree
Hide file tree
Showing 25 changed files with 577 additions and 479 deletions.
4 changes: 2 additions & 2 deletions coinlib/example/coinlib_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ void main() async {

// Sign the input with the private key. The signed transaction is returned as
// a new object as most objects in the library are immutable.
final signedTx = tx.sign(inputN: 0, key: key1.privateKey);
final signedTx = tx.signLegacy(inputN: 0, key: key1.privateKey);

if (signedTx.complete) {
print("Signed transaction is complete");
Expand Down Expand Up @@ -106,7 +106,7 @@ void main() async {
final trTx = Transaction(
inputs: [TaprootKeyInput(prevOut: OutPoint(prevHash, 1))],
outputs: [trOutput],
).sign(
).signTaproot(
inputN: 0,
// Private keys must be tweaked by the Taproot object
key: taproot.tweakPrivateKey(key1.privateKey),
Expand Down
1 change: 1 addition & 0 deletions coinlib/lib/src/coinlib_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export 'package:coinlib/src/scripts/programs/p2wsh.dart';

export 'package:coinlib/src/tx/coin_selection.dart';
export 'package:coinlib/src/tx/transaction.dart';
export 'package:coinlib/src/tx/sign_details.dart';
export 'package:coinlib/src/tx/outpoint.dart';
export 'package:coinlib/src/tx/output.dart';

Expand Down
28 changes: 6 additions & 22 deletions coinlib/lib/src/tx/inputs/legacy_input.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import 'package:coinlib/src/crypto/ec_private_key.dart';
import 'package:coinlib/src/crypto/ecdsa_signature.dart';
import 'package:coinlib/src/scripts/script.dart';
import 'package:coinlib/src/tx/sighash/legacy_signature_hasher.dart';
import 'package:coinlib/src/tx/sighash/sighash_type.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'input.dart';
import 'input_signature.dart';
import 'p2pkh_input.dart';
Expand All @@ -19,35 +17,21 @@ abstract class LegacyInput extends RawInput {
super.sequence = Input.sequenceFinal,
});

/// Signs the input given the [tx], input number ([inputN]) and a private
/// [key] using the specifified [hashType].
/// Signs the input given the sign [details] and [key].
/// Implemented by specific subclasses.
LegacyInput sign({
required Transaction tx,
required int inputN,
required LegacySignDetails details,
required ECPrivateKey key,
SigHashType hashType = const SigHashType.all(),
});

/// Creates a signature for the input. Used by subclasses to implement
/// signing.
ECDSAInputSignature createInputSignature({
required Transaction tx,
required int inputN,
required LegacySignDetailsWithScript details,
required ECPrivateKey key,
required Script scriptCode,
SigHashType hashType = const SigHashType.all(),
}) => ECDSAInputSignature(
ECDSASignature.sign(
key,
LegacySignatureHasher(
tx: tx,
inputN: inputN,
scriptCode: scriptCode,
hashType: RawInput.checkHashTypeNotSchnorr(hashType),
).hash,
),
hashType,
ECDSASignature.sign(key, LegacySignatureHasher(details).hash),
details.hashType,
);

}
31 changes: 6 additions & 25 deletions coinlib/lib/src/tx/inputs/legacy_witness_input.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import 'package:coinlib/src/crypto/ec_private_key.dart';
import 'package:coinlib/src/crypto/ecdsa_signature.dart';
import 'package:coinlib/src/scripts/script.dart';
import 'package:coinlib/src/tx/inputs/raw_input.dart';
import 'package:coinlib/src/tx/sighash/sighash_type.dart';
import 'package:coinlib/src/tx/sighash/witness_signature_hasher.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'input.dart';
import 'input_signature.dart';
Expand All @@ -18,39 +16,22 @@ abstract class LegacyWitnessInput extends WitnessInput {
super.sequence = Input.sequenceFinal,
});

/// Signs the input given the [tx], input number ([inputN]), private
/// [key] and input [value] using the specifified [hashType]. Should throw
/// Signs the input given the [details] and [key]. Should throw
/// [CannotSignInput] if the key cannot sign the input.
/// Implemented by specific subclasses.
LegacyWitnessInput sign({
required Transaction tx,
required int inputN,
required LegacyWitnessSignDetails details,
required ECPrivateKey key,
required BigInt value,
SigHashType hashType = const SigHashType.all(),
});

/// Creates a signature for the input. Used by subclasses to implement
/// signing.
ECDSAInputSignature createInputSignature({
required Transaction tx,
required int inputN,
required LegacyWitnessSignDetailsWithScript details,
required ECPrivateKey key,
required Script scriptCode,
required BigInt value,
SigHashType hashType = const SigHashType.all(),
}) => ECDSAInputSignature(
ECDSASignature.sign(
key,
WitnessSignatureHasher(
tx: tx,
inputN: inputN,
scriptCode: scriptCode,
value: value,
hashType: RawInput.checkHashTypeNotSchnorr(hashType),
).hash,
),
hashType,
ECDSASignature.sign(key, WitnessSignatureHasher(details).hash),
details.hashType,
);

}
12 changes: 3 additions & 9 deletions coinlib/lib/src/tx/inputs/p2pkh_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import 'package:coinlib/src/crypto/ec_public_key.dart';
import 'package:coinlib/src/scripts/operations.dart';
import 'package:coinlib/src/scripts/programs/p2pkh.dart';
import 'package:coinlib/src/scripts/script.dart';
import 'package:coinlib/src/tx/sighash/sighash_type.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'input.dart';
import 'input_signature.dart';
import 'legacy_input.dart';
Expand Down Expand Up @@ -64,17 +63,12 @@ class P2PKHInput extends LegacyInput with PKHInput {

@override
P2PKHInput sign({
required Transaction tx,
required int inputN,
required LegacySignDetails details,
required ECPrivateKey key,
hashType = const SigHashType.all(),
}) => addSignature(
createInputSignature(
tx: tx,
inputN: inputN,
key: checkKey(key),
scriptCode: scriptCode,
hashType: hashType,
details: details.addScript(scriptCode),
),
);

Expand Down
22 changes: 11 additions & 11 deletions coinlib/lib/src/tx/inputs/p2sh_multisig_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:coinlib/src/scripts/programs/p2sh.dart';
import 'package:coinlib/src/scripts/script.dart';
import 'package:coinlib/src/tx/sighash/legacy_signature_hasher.dart';
import 'package:coinlib/src/tx/sighash/sighash_type.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'input.dart';
import 'input_signature.dart';
Expand Down Expand Up @@ -95,10 +96,8 @@ class P2SHMultisigInput extends LegacyInput {

@override
LegacyInput sign({
required Transaction tx,
required int inputN,
required LegacySignDetails details,
required ECPrivateKey key,
hashType = const SigHashType.all(),
}) {

if (!program.pubkeys.contains(key.pubkey)) {
Expand All @@ -107,18 +106,17 @@ class P2SHMultisigInput extends LegacyInput {

return insertSignature(
createInputSignature(
tx: tx,
inputN: inputN,
key: key,
scriptCode: program.script,
hashType: hashType,
details: details.addScript(program.script),
),
key.pubkey,
(hashType) => LegacySignatureHasher(
tx: tx,
inputN: inputN,
scriptCode: program.script,
hashType: hashType,
LegacySignDetailsWithScript(
tx: details.tx,
inputN: details.inputN,
scriptCode: program.script,
hashType: hashType,
),
).hash,
);

Expand All @@ -128,8 +126,10 @@ class P2SHMultisigInput extends LegacyInput {
/// The [pubkey] should be the public key for the signature to ensure that it
/// matches. [getSigHash] obtains the signature hash for a given type so that
/// existing signatures can be checked.
///
/// If existing signatures are not in-order then they may not be fully matched
/// and included in the resulting input.
///
/// If there are more signatures than the required threshold, the last
/// signature will be removed.
P2SHMultisigInput insertSignature(
Expand Down
14 changes: 3 additions & 11 deletions coinlib/lib/src/tx/inputs/p2wpkh_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import 'dart:typed_data';
import 'package:coinlib/src/crypto/ec_private_key.dart';
import 'package:coinlib/src/crypto/ec_public_key.dart';
import 'package:coinlib/src/scripts/programs/p2wpkh.dart';
import 'package:coinlib/src/tx/sighash/sighash_type.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'input.dart';
import 'input_signature.dart';
import 'pkh_input.dart';
Expand Down Expand Up @@ -69,19 +68,12 @@ class P2WPKHInput extends LegacyWitnessInput with PKHInput {

@override
LegacyWitnessInput sign({
required Transaction tx,
required int inputN,
required LegacyWitnessSignDetails details,
required ECPrivateKey key,
required BigInt value,
hashType = const SigHashType.all(),
}) => addSignature(
createInputSignature(
tx: tx,
inputN: inputN,
key: checkKey(key),
scriptCode: scriptCode,
value: value,
hashType: hashType,
details: details.addScript(scriptCode),
),
);

Expand Down
38 changes: 4 additions & 34 deletions coinlib/lib/src/tx/inputs/taproot_input.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import 'dart:typed_data';
import 'package:coinlib/src/crypto/ec_private_key.dart';
import 'package:coinlib/src/crypto/schnorr_signature.dart';
import 'package:coinlib/src/tx/output.dart';
import 'package:coinlib/src/tx/sighash/sighash_type.dart';
import 'package:coinlib/src/tx/sighash/taproot_signature_hasher.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'input.dart';
import 'input_signature.dart';
import 'witness_input.dart';
Expand All @@ -18,41 +15,14 @@ abstract class TaprootInput extends WitnessInput {
super.sequence = Input.sequenceFinal,
});

/// Signs the input given the [tx], input number ([inputN]), private [key] and
/// [prevOuts] using the specifified [hashType]. Should throw
/// [CannotSignInput] if the key cannot sign the input. Implemented by
/// specific subclasses.
TaprootInput sign({
required Transaction tx,
required int inputN,
required ECPrivateKey key,
required List<Output> prevOuts,
SigHashType hashType = const SigHashType.schnorrDefault(),
}) => throw CannotSignInput("Unimplemented sign() for {this.runtimeType}");

/// Creates a signature for the input. Used by subclasses to implement
/// signing.
SchnorrInputSignature createInputSignature({
required Transaction tx,
required int inputN,
required ECPrivateKey key,
required List<Output> prevOuts,
SigHashType hashType = const SigHashType.schnorrDefault(),
Uint8List? leafHash,
int codeSeperatorPos = 0xFFFFFFFF,
required TaprootSignDetails details,
}) => SchnorrInputSignature(
SchnorrSignature.sign(
key,
TaprootSignatureHasher(
tx: tx,
inputN: inputN,
prevOuts: prevOuts,
hashType: hashType,
leafHash: leafHash,
codeSeperatorPos: codeSeperatorPos,
).hash,
),
hashType,
SchnorrSignature.sign(key, TaprootSignatureHasher(details).hash),
details.hashType,
);

}
27 changes: 6 additions & 21 deletions coinlib/lib/src/tx/inputs/taproot_key_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import 'package:coinlib/src/crypto/ec_private_key.dart';
import 'package:coinlib/src/scripts/programs/p2tr.dart';
import 'package:coinlib/src/taproot.dart';
import 'package:coinlib/src/tx/inputs/taproot_input.dart';
import 'package:coinlib/src/tx/output.dart';
import 'package:coinlib/src/tx/sighash/sighash_type.dart';
import 'package:coinlib/src/tx/sign_details.dart';
import 'package:coinlib/src/tx/transaction.dart';
import 'input.dart';
import 'input_signature.dart';
Expand Down Expand Up @@ -45,40 +44,26 @@ class TaprootKeyInput extends TaprootInput {

}

@override
/// Return a signed Taproot input using tweaked private key for the key-path
/// spend. The [key] should be tweaked by [Taproot.tweakScalar].
TaprootKeyInput sign({
required Transaction tx,
required int inputN,
required TaprootKeySignDetails details,
required ECPrivateKey key,
required List<Output> prevOuts,
SigHashType hashType = const SigHashType.schnorrDefault(),
}) {

if (inputN >= prevOuts.length) {
throw CannotSignInput(
"Input is out of range of the previous outputs provided",
);
if (details.hashType.requiresApo) {
throw CannotSignInput("A Taproot key-spend doesn't support APO");
}

// Check key corresponds to matching prevOut
final program = prevOuts[inputN].program;
final program = details.program;
if (program is! P2TR || key.pubkey.xonly != program.tweakedKey) {
throw CannotSignInput(
"Key cannot sign for Taproot input's tweaked key",
);
}

return addSignature(
createInputSignature(
tx: tx,
inputN: inputN,
key: key,
prevOuts: prevOuts,
hashType: hashType,
),
);
return addSignature(createInputSignature(key: key, details: details));

}

Expand Down
Loading

0 comments on commit 6b85b00

Please sign in to comment.