Skip to content

Commit

Permalink
Specify legacy input signatures as ECDSAInputSignatures
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLM committed Oct 12, 2023
1 parent 42003bc commit ca018b5
Show file tree
Hide file tree
Showing 15 changed files with 74 additions and 58 deletions.
14 changes: 7 additions & 7 deletions coinlib/lib/src/scripts/operations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ abstract class ScriptOp {
String get asm;
/// Returns an integer if the operation pushes a number, or null
int? get number;
/// If this is a pushdata of an input signature then it shall be returned, or
/// null
InputSignature? get insig;
/// If this is a pushdata of an ECDSA input signature then it shall be
/// returned, or null
ECDSAInputSignature? get ecdsaSig;
/// If this is a pushdata of a public key then it shall be returned, or null
ECPublicKey? get publicKey;

Expand Down Expand Up @@ -191,7 +191,7 @@ class ScriptOpCode implements ScriptOp {
bool match(ScriptOp other) => other is ScriptOpCode && code == other.code;

@override
InputSignature? get insig => null;
ECDSAInputSignature? get ecdsaSig => null;

@override
ECPublicKey? get publicKey => null;
Expand Down Expand Up @@ -275,9 +275,9 @@ class ScriptPushData implements ScriptOp {
}

@override
InputSignature? get insig {
ECDSAInputSignature? get ecdsaSig {
try {
return InputSignature.fromBytes(data);
return ECDSAInputSignature.fromBytes(data);
} on InvalidInputSignature {
return null;
}
Expand Down Expand Up @@ -328,7 +328,7 @@ class ScriptPushDataMatcher implements ScriptOp {
int? get number => null;

@override
InputSignature? get insig => null;
ECDSAInputSignature? get ecdsaSig => null;

@override
ECPublicKey? get publicKey => null;
Expand Down
16 changes: 12 additions & 4 deletions coinlib/lib/src/tx/inputs/input_signature.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@ import 'package:coinlib/src/tx/sighash/sighash_type.dart';

class InvalidInputSignature implements Exception {}

/// The base for input signatures that carry a [hashType].
abstract interface class InputSignature {
SigHashType get hashType;
Uint8List get bytes;
}

/// Encapsulates an ECDSA [signature] and [hashType] for inclusion in an
/// [Input].
class InputSignature {
class ECDSAInputSignature implements InputSignature {

final ECDSASignature signature;
@override
final SigHashType hashType;

InputSignature(this.signature, [this.hashType = const SigHashType.all()]);
ECDSAInputSignature(this.signature, [this.hashType = const SigHashType.all()]);

factory InputSignature.fromBytes(Uint8List bytes) {
factory ECDSAInputSignature.fromBytes(Uint8List bytes) {

if (bytes.isEmpty) throw InvalidInputSignature();

Expand All @@ -27,10 +34,11 @@ class InputSignature {
final hashType = bytes.last;
if (!SigHashType.validValue(hashType)) throw InvalidInputSignature();

return InputSignature(sig, SigHashType.fromValue(hashType));
return ECDSAInputSignature(sig, SigHashType.fromValue(hashType));

}

@override
Uint8List get bytes => Uint8List.fromList([...signature.der, hashType.value]);

}
4 changes: 2 additions & 2 deletions coinlib/lib/src/tx/inputs/legacy_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ abstract class LegacyInput extends RawInput {

/// Creates a signature for the input. Used by subclasses to implement
/// signing.
InputSignature createInputSignature({
ECDSAInputSignature createInputSignature({
required Transaction tx,
required int inputN,
required ECPrivateKey key,
required Script scriptCode,
hashType = const SigHashType.all(),
}) => InputSignature(
}) => ECDSAInputSignature(
ECDSASignature.sign(
key,
LegacySignatureHasher(
Expand Down
4 changes: 2 additions & 2 deletions coinlib/lib/src/tx/inputs/legacy_witness_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ abstract class LegacyWitnessInput extends WitnessInput {

/// Creates a signature for the input. Used by subclasses to implement
/// signing.
InputSignature createInputSignature({
ECDSAInputSignature createInputSignature({
required Transaction tx,
required int inputN,
required ECPrivateKey key,
required Script scriptCode,
required BigInt value,
hashType = const SigHashType.all(),
}) => InputSignature(
}) => ECDSAInputSignature(
ECDSASignature.sign(
key,
WitnessSignatureHasher(
Expand Down
10 changes: 5 additions & 5 deletions coinlib/lib/src/tx/inputs/p2pkh_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class P2PKHInput extends LegacyInput with PKHInput {
@override
final ECPublicKey publicKey;
@override
final InputSignature? insig;
final ECDSAInputSignature? insig;

P2PKHInput({
required OutPoint prevOut,
Expand All @@ -48,7 +48,7 @@ class P2PKHInput extends LegacyInput with PKHInput {
final ops = script.ops;
if (ops.isEmpty || ops.length > 2) return null;

final insig = ops.length == 2 ? ops[0].insig : null;
final insig = ops.length == 2 ? ops[0].ecdsaSig : null;
if (insig == null && ops.length == 2) return null;

final publicKey = ops.last.publicKey;
Expand Down Expand Up @@ -80,9 +80,9 @@ class P2PKHInput extends LegacyInput with PKHInput {
);

@override
/// Returns a new [P2PKHInput] with the [InputSignature] added. Any existing
/// signature is replaced.
P2PKHInput addSignature(InputSignature insig) => P2PKHInput(
/// Returns a new [P2PKHInput] with the [ECDSAInputSignature] added. Any
/// existing signature is replaced.
P2PKHInput addSignature(ECDSAInputSignature insig) => P2PKHInput(
prevOut: prevOut,
publicKey: publicKey,
insig: insig,
Expand Down
26 changes: 17 additions & 9 deletions coinlib/lib/src/tx/inputs/p2sh_multisig_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import 'raw_input.dart';
class P2SHMultisigInput extends LegacyInput {

final MultisigProgram program;
final List<InputSignature> sigs;
final List<ECDSAInputSignature> sigs;

P2SHMultisigInput({
required OutPoint prevOut,
required this.program,
Iterable<InputSignature> sigs = const [],
Iterable<ECDSAInputSignature> sigs = const [],
int sequence = Input.sequenceFinal,
}) : sigs = List.unmodifiable(sigs), super(
prevOut: prevOut,
Expand Down Expand Up @@ -77,16 +77,19 @@ class P2SHMultisigInput extends LegacyInput {
// Can only have upto threshold sigs plus OP_0 and redeemScript
if (ops.length > 2 + multisig.threshold) return null;

// Convert signature data into InputSignatures
final sigs = ops.getRange(1, ops.length-1).map((op) => op.insig).toList();
// Convert signature data into ECDSAInputSignatures
final sigs
= ops.getRange(1, ops.length-1)
.map((op) => op.ecdsaSig).toList();

// Fail if any signature is null
if (sigs.any((sig) => sig == null)) return null;

return P2SHMultisigInput(
prevOut: raw.prevOut,
program: multisig,
// Cast necessary to ensure non-null, despite checking for null above
sigs: sigs.whereType<InputSignature>().toList(),
sigs: sigs.whereType<ECDSAInputSignature>().toList(),
sequence: raw.sequence,
);

Expand Down Expand Up @@ -132,26 +135,29 @@ class P2SHMultisigInput extends LegacyInput {
/// If there are more signatures than the required threshold, the last
/// signature will be removed.
P2SHMultisigInput insertSignature(
InputSignature insig,
ECDSAInputSignature insig,
ECPublicKey pubkey,
Uint8List Function(SigHashType hashType) getSigHash,
) {

final pubkeys = program.pubkeys;

// Create list that will match signatures to the public keys in order
List<InputSignature?> positionedSigs = List.filled(pubkeys.length, null);
List<ECDSAInputSignature?> positionedSigs
= List.filled(pubkeys.length, null);

// Iterate both public key positions and signatures sequentially as they
// should already be in order
for (int pos = 0, sigI = 0; pos < pubkeys.length; pos++) {

final numAdded = positionedSigs.whereType<ECDSAInputSignature>().length;

// Check existing first to ensure they get matched
if (
// Check all signatures have not already been matched
sigI != sigs.length
// Do not add any more when threshold is reached
&& positionedSigs.whereType<InputSignature>().length < program.threshold
&& numAdded < program.threshold
// Check signature against candidate public key and message hash
&& sigs[sigI].signature.verify(
pubkeys[pos], getSigHash(sigs[sigI].hashType),
Expand All @@ -171,7 +177,9 @@ class P2SHMultisigInput extends LegacyInput {
program: program,
// Remove nulls leaving actual signatures and trim down to threshold if
// needed
sigs: positionedSigs.whereType<InputSignature>().take(program.threshold),
sigs: positionedSigs
.whereType<ECDSAInputSignature>()
.take(program.threshold),
sequence: sequence,
);

Expand Down
10 changes: 5 additions & 5 deletions coinlib/lib/src/tx/inputs/p2wpkh_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class P2WPKHInput extends LegacyWitnessInput with PKHInput {
@override
final ECPublicKey publicKey;
@override
final InputSignature? insig;
final ECDSAInputSignature? insig;

P2WPKHInput({
required OutPoint prevOut,
Expand All @@ -48,7 +48,7 @@ class P2WPKHInput extends LegacyWitnessInput with PKHInput {
try {

final insig = witness.length == 2
? InputSignature.fromBytes(witness[0])
? ECDSAInputSignature.fromBytes(witness[0])
: null;
final publicKey = ECPublicKey(witness.last);

Expand Down Expand Up @@ -86,9 +86,9 @@ class P2WPKHInput extends LegacyWitnessInput with PKHInput {
);

@override
/// Returns a new [P2WPKHInput] with the [InputSignature] added. Any existing
/// signature is replaced.
P2WPKHInput addSignature(InputSignature insig) => P2WPKHInput(
/// Returns a new [P2WPKHInput] with the [ECDSAInputSignature] added. Any
/// existing signature is replaced.
P2WPKHInput addSignature(ECDSAInputSignature insig) => P2WPKHInput(
prevOut: prevOut,
publicKey: publicKey,
insig: insig,
Expand Down
6 changes: 3 additions & 3 deletions coinlib/lib/src/tx/inputs/pkh_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import 'package:coinlib/src/tx/transaction.dart';
import 'input_signature.dart';

/// A mixin for Public Key Hash input types, providing the [ECPublicKey] and
/// [InputSignature] required in these inputs.
/// [ECDSAInputSignature] required in these inputs.
abstract mixin class PKHInput {

ECPublicKey get publicKey;
InputSignature? get insig;
PKHInput addSignature(InputSignature insig);
ECDSAInputSignature? get insig;
PKHInput addSignature(ECDSAInputSignature insig);
bool get complete => insig != null;
Script get scriptCode => P2PKH.fromPublicKey(publicKey).script;

Expand Down
2 changes: 1 addition & 1 deletion coinlib/test/scripts/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ expectScriptOp(ScriptOp op, String asm, String hex, int? number, bool isPush) {
expect(bytesToHex(op.compiled), hex);
expect(op.number, number);
expect(op, isPush ? isA<ScriptPushData>() : isA<ScriptOpCode>());
expect(op.insig, null);
expect(op.ecdsaSig, null);
expect(op.publicKey, null);
}
2 changes: 1 addition & 1 deletion coinlib/test/scripts/operations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ void main() {
test("provides insig", () {
final der = hexToBytes(validDerSigs[0]);
final bytes = Uint8List.fromList([ ...der, SigHashType.allValue]);
final insig = ScriptPushData(bytes).insig;
final insig = ScriptPushData(bytes).ecdsaSig;
expect(insig, isNotNull);
expect(insig!.signature.der, der);
expect(insig.hashType.all, true);
Expand Down
10 changes: 5 additions & 5 deletions coinlib/test/tx/inputs/input_signature_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import '../../vectors/signatures.dart';

void main() {

group("InputSignature", () {
group("ECDSAInputSignature", () {

setUpAll(loadCoinlib);

Expand All @@ -16,14 +16,14 @@ void main() {

final bytes = Uint8List.fromList([...der, 0x81]);

expectSig(InputSignature sig) {
expectSig(ECDSAInputSignature sig) {
expect(sig.bytes, bytes);
expect(sig.hashType, hashType);
expect(sig.signature.der, der);
}

expectSig(InputSignature(ECDSASignature.fromDer(der), hashType));
expectSig(InputSignature.fromBytes(bytes));
expectSig(ECDSAInputSignature(ECDSASignature.fromDer(der), hashType));
expectSig(ECDSAInputSignature.fromBytes(bytes));

});

Expand All @@ -35,7 +35,7 @@ void main() {
[...hexToBytes(invalidDerSigs[0]), 1],
]) {
expect(
() => InputSignature.fromBytes(Uint8List.fromList(list)),
() => ECDSAInputSignature.fromBytes(Uint8List.fromList(list)),
throwsA(isA<InvalidInputSignature>()),
);
}
Expand Down
4 changes: 2 additions & 2 deletions coinlib/test/tx/inputs/p2pkh_input_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ void main() {

final der = validDerSigs[0];
late ECPublicKey pk;
late InputSignature insig;
late ECDSAInputSignature insig;
setUpAll(() async {
await loadCoinlib();
pk = ECPublicKey.fromHex(pubkeyVec);
insig = InputSignature(
insig = ECDSAInputSignature(
ECDSASignature.fromDerHex(der),
SigHashType.single(),
);
Expand Down
6 changes: 3 additions & 3 deletions coinlib/test/tx/inputs/p2sh_multisig_input_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ void main() {
group("P2SHMultisigInput", () {

late List<ECPublicKey> pks;
late List<InputSignature> insigs;
late List<ECDSAInputSignature> insigs;
late MultisigProgram multisig;

setUpAll(() async {
Expand All @@ -24,7 +24,7 @@ void main() {

insigs = validDerSigs
.getRange(0, 4)
.map((der) => InputSignature(ECDSASignature.fromDerHex(der)))
.map((der) => ECDSAInputSignature(ECDSASignature.fromDerHex(der)))
.toList();

multisig = MultisigProgram(3, pks);
Expand Down Expand Up @@ -175,7 +175,7 @@ void main() {
4,
(i) {
final hashType = SigHashType.fromValue(i % 3 + 1);
return InputSignature(
return ECDSAInputSignature(
ECDSASignature.sign(keys[i], getSigHash(hashType)),
hashType,
);
Expand Down
4 changes: 2 additions & 2 deletions coinlib/test/tx/inputs/p2wpkh_input_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ void main() {
final der = validDerSigs[0];
final pkBytes = hexToBytes(pubkeyVec);
late ECPublicKey pk;
late InputSignature insig;
late ECDSAInputSignature insig;

setUpAll(() async {
await loadCoinlib();
pk = ECPublicKey(pkBytes);
insig = InputSignature(
insig = ECDSAInputSignature(
ECDSASignature.fromDerHex(der),
SigHashType.none(),
);
Expand Down
Loading

0 comments on commit ca018b5

Please sign in to comment.