diff --git a/coinlib/lib/src/tx/inputs/input.dart b/coinlib/lib/src/tx/inputs/input.dart index 882d776..e5178d1 100644 --- a/coinlib/lib/src/tx/inputs/input.dart +++ b/coinlib/lib/src/tx/inputs/input.dart @@ -21,6 +21,12 @@ abstract class Input with Writable { /// True when the input is fully signed and ready for broadcast bool get complete; + /// The maximum total size when fully signed via the default hash type + /// including any witness data of the input. If this is unknown, this is + /// null. The actual signed size may be lower according to the data being + /// encoded. + int? get signedSize => null; + Input(); /// Given a [RawInput] and witness data, the specific [Input] subclass is diff --git a/coinlib/lib/src/tx/inputs/p2pkh_input.dart b/coinlib/lib/src/tx/inputs/p2pkh_input.dart index 2b15836..d9bbcf0 100644 --- a/coinlib/lib/src/tx/inputs/p2pkh_input.dart +++ b/coinlib/lib/src/tx/inputs/p2pkh_input.dart @@ -23,6 +23,8 @@ class P2PKHInput extends LegacyInput with PKHInput { final ECPublicKey publicKey; @override final ECDSAInputSignature? insig; + @override + final int? signedSize = 147; P2PKHInput({ required OutPoint prevOut, diff --git a/coinlib/lib/src/tx/inputs/p2sh_multisig_input.dart b/coinlib/lib/src/tx/inputs/p2sh_multisig_input.dart index 2bcccb6..91df1e1 100644 --- a/coinlib/lib/src/tx/inputs/p2sh_multisig_input.dart +++ b/coinlib/lib/src/tx/inputs/p2sh_multisig_input.dart @@ -200,4 +200,17 @@ class P2SHMultisigInput extends LegacyInput { @override Script get script => super.script!; + int get _signedScriptSize + => 1 // Extra 0 + + program.threshold*73 // Add 73 bytes per signature + // Determine the length of the program pushdata by actually compiling it. + // Not the most efficient but the simplest solution. + + ScriptPushData(program.script.compiled).compiled.length; + + @override + int? get signedSize + => 40 // Outpoint plus sequence + + _signedScriptSize + + (_signedScriptSize < 0xfd ? 1 : 3); // Varint size + } diff --git a/coinlib/lib/src/tx/inputs/p2wpkh_input.dart b/coinlib/lib/src/tx/inputs/p2wpkh_input.dart index c4998d2..c00d64c 100644 --- a/coinlib/lib/src/tx/inputs/p2wpkh_input.dart +++ b/coinlib/lib/src/tx/inputs/p2wpkh_input.dart @@ -22,6 +22,8 @@ class P2WPKHInput extends LegacyWitnessInput with PKHInput { final ECPublicKey publicKey; @override final ECDSAInputSignature? insig; + @override + final int? signedSize = 147; P2WPKHInput({ required OutPoint prevOut, diff --git a/coinlib/lib/src/tx/inputs/taproot_key_input.dart b/coinlib/lib/src/tx/inputs/taproot_key_input.dart index 0cb919f..4408d00 100644 --- a/coinlib/lib/src/tx/inputs/taproot_key_input.dart +++ b/coinlib/lib/src/tx/inputs/taproot_key_input.dart @@ -16,6 +16,10 @@ class TaprootKeyInput extends TaprootInput { final SchnorrInputSignature? insig; + @override + // 64-bit sig plus varint with default sighash type + final int? signedSize = 41 + 65; + TaprootKeyInput({ required OutPoint prevOut, this.insig, diff --git a/coinlib/test/tx/transaction_test.dart b/coinlib/test/tx/transaction_test.dart index 86cb1ac..1d59935 100644 --- a/coinlib/test/tx/transaction_test.dart +++ b/coinlib/test/tx/transaction_test.dart @@ -40,6 +40,10 @@ void main() { expect(mapToHex(tx.outputs), mapToHex(vec.obj.outputs)); } + expectInputSignedSize(Input input) => expect( + input.size, lessThanOrEqualTo(input.signedSize!), + ); + test("valid txs", () { for (final vec in validTxVecs) { @@ -331,6 +335,7 @@ void main() { key: keyVec.privateObj, hashType: hashType, ); + expectInputSignedSize(signed.inputs[i]); } expect(tx.complete, false); @@ -426,6 +431,7 @@ void main() { inputN: 1, key: keyVec.privateObj, value: BigInt.from(3000000), ); expect(signed.complete, true); + expectInputSignedSize(signed.inputs[1]); expect( signed.toHex(), @@ -479,6 +485,9 @@ void main() { ); expect(signed.complete, true); + for (final input in signed.inputs) { + expectInputSignedSize(input); + } expect( signed.toHex(), @@ -602,6 +611,8 @@ void main() { outputs: [exampleOutput, exampleOutput], ); + final signedSizeFromUnsigned = tx.inputs[1].signedSize; + // Sign first P2PKH input var signed = tx.sign(inputN: 0, key: privkeys[0]); expect(signed.complete, false); @@ -638,6 +649,8 @@ void main() { // Check final tx expect(signed.complete, true); + expectInputSignedSize(signed.inputs[1]); + expect(signed.inputs[1].signedSize, signedSizeFromUnsigned); expectMultisigSigs( signed, [ SigHashType.single(),