-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
244 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import 'dart:typed_data'; | ||
import 'package:coinlib/src/common/serial.dart'; | ||
import 'package:coinlib/src/scripts/operations.dart'; | ||
import 'package:coinlib/src/scripts/script.dart'; | ||
import 'package:coinlib/src/taproot.dart'; | ||
import 'package:coinlib/src/tx/inputs/taproot_input.dart'; | ||
import 'package:coinlib/src/tx/outpoint.dart'; | ||
import 'input.dart'; | ||
import 'raw_input.dart'; | ||
|
||
/// A [TaprootInput] which spends using the script-path for 0xc0 version | ||
/// Tapscripts. There is no signing logic and sign() is not implemented. | ||
/// Subclasses should handle signing. [createInputSignature] can be used to | ||
/// create signatures for insertion as necessary. | ||
class TaprootScriptInput extends TaprootInput { | ||
|
||
/// The tapscript embedded in the witness data, not to be confused with the | ||
/// empty [script]. | ||
final Script tapscript; | ||
|
||
TaprootScriptInput({ | ||
required OutPoint prevOut, | ||
required Uint8List controlBlock, | ||
required this.tapscript, | ||
List<Uint8List>? stack, | ||
int sequence = Input.sequenceFinal, | ||
}) : super( | ||
prevOut: prevOut, | ||
sequence: sequence, | ||
witness: [if (stack != null) ...stack, tapscript.compiled, controlBlock], | ||
); | ||
|
||
TaprootScriptInput.fromTaprootLeaf({ | ||
required OutPoint prevOut, | ||
required Taproot taproot, | ||
required TapLeaf leaf, | ||
List<Uint8List>? stack, | ||
int sequence = Input.sequenceFinal, | ||
}) : this( | ||
prevOut: prevOut, | ||
controlBlock: taproot.controlBlockForLeaf(leaf), | ||
tapscript: leaf.script, | ||
stack: stack, | ||
sequence: sequence, | ||
); | ||
|
||
/// Checks if the [raw] input and [witness] data match the expected format for | ||
/// a [TaprootScriptInput] with the control block and script. If it matches | ||
/// this returns a [TaprootScriptInput] for the input or else it returns null. | ||
/// The script must be valid with minimal push data. The control block must be | ||
/// the correct size and contain the correct 0xc0 tapscript version but the | ||
/// internal key and parity bit is not validated. | ||
static TaprootScriptInput? match(RawInput raw, List<Uint8List> witness) { | ||
|
||
if (raw.scriptSig.isNotEmpty) return null; | ||
if (witness.length < 2) return null; | ||
|
||
final controlBlock = witness.last; | ||
final lengthAfterKey = controlBlock.length - 33; | ||
|
||
if ( | ||
controlBlock.length < 33 | ||
|| lengthAfterKey % 32 != 0 | ||
|| lengthAfterKey / 32 > 128 | ||
|| controlBlock[0] & 0xfe != TapLeaf.tapscriptVersion | ||
) { | ||
return null; | ||
} | ||
|
||
try { | ||
|
||
return TaprootScriptInput( | ||
prevOut: raw.prevOut, | ||
controlBlock: controlBlock, | ||
tapscript: Script.decompile(witness[witness.length-2]), | ||
stack: witness.sublist(0, witness.length-2), | ||
sequence: raw.sequence, | ||
); | ||
|
||
} on OutOfData { | ||
return null; | ||
} on PushDataNotMinimal { | ||
return null; | ||
} | ||
|
||
} | ||
|
||
Uint8List get controlBlock => witness.last; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import 'dart:typed_data'; | ||
import 'package:coinlib/coinlib.dart'; | ||
import 'package:test/test.dart'; | ||
import '../../vectors/inputs.dart'; | ||
import '../../vectors/taproot.dart'; | ||
|
||
// Placed in global for lazy initialisation after loadCoinlib | ||
final taprootVec = taprootVectors[3]; | ||
final controlBlockHex = taprootVec.controlBlocks[0]; | ||
final controlBlock = hexToBytes(controlBlockHex); | ||
final script = taprootVec.object.leaves[0].script; | ||
final witness = [script.compiled, controlBlock]; | ||
final stack = [hexToBytes("0102030405"), hexToBytes("01020304")]; | ||
|
||
void main() { | ||
|
||
group("TaprootScriptInput", () { | ||
|
||
setUpAll(loadCoinlib); | ||
|
||
test("valid script-path taproot inputs", () { | ||
|
||
expectTaprootScriptInput(TaprootScriptInput input, bool withStack) { | ||
|
||
expectInput(input); | ||
|
||
expect(input.complete, true); | ||
expect(input.scriptSig.isEmpty, true); | ||
expect(input.script!.length, 0); | ||
|
||
expect(input.tapscript.asm, script.asm); | ||
expect(bytesToHex(input.controlBlock), controlBlockHex); | ||
|
||
expect(input.witness, [if (withStack) ...stack, ...witness]); | ||
expect(input.size, rawWitnessInputBytes.length); | ||
expect(input.toBytes(), rawWitnessInputBytes); | ||
|
||
} | ||
|
||
for (final withStack in [false, true]) { | ||
expectTaprootScriptInput( | ||
TaprootScriptInput( | ||
prevOut: prevOut, | ||
controlBlock: controlBlock, | ||
tapscript: script, | ||
sequence: sequence, | ||
stack: withStack ? stack : null, | ||
), | ||
withStack, | ||
); | ||
expectTaprootScriptInput( | ||
TaprootScriptInput.fromTaprootLeaf( | ||
prevOut: prevOut, | ||
taproot: taprootVec.object, | ||
leaf: taprootVec.object.leaves[0], | ||
sequence: sequence, | ||
stack: withStack ? stack : null, | ||
), | ||
withStack, | ||
); | ||
expectTaprootScriptInput( | ||
Input.match( | ||
RawInput.fromReader(BytesReader(rawWitnessInputBytes)), | ||
[if (withStack) ...stack, ...witness], | ||
) as TaprootScriptInput, | ||
withStack, | ||
); | ||
} | ||
|
||
}); | ||
|
||
test("control blocks up-to 128 hashes accepted", () { | ||
expect( | ||
TaprootScriptInput( | ||
prevOut: prevOut, | ||
controlBlock: Uint8List.fromList( | ||
[...controlBlock.take(33), ...Uint8List(32*128)], | ||
), | ||
tapscript: script, | ||
), | ||
isA<TaprootScriptInput>(), | ||
); | ||
}); | ||
|
||
test("doesn't match non script-spend inputs", () { | ||
|
||
expectNoMatch(String asm, List<Uint8List> witness) => expect( | ||
TaprootScriptInput.match( | ||
RawInput( | ||
prevOut: prevOut, | ||
scriptSig: Script.fromAsm(asm).compiled, | ||
sequence: 0, | ||
), | ||
witness, | ||
), | ||
null, | ||
); | ||
|
||
expectNoMatch("0", witness); | ||
// Requires script | ||
expectNoMatch("", witness.skip(1).toList()); | ||
// Not allowing annex | ||
expectNoMatch("", [...witness, hexToBytes("5001020304")]); | ||
// Control block must be correct size | ||
expectNoMatch("", [script.compiled, hexToBytes("c1")]); | ||
expectNoMatch("", [script.compiled, Uint8List(0)]); | ||
expectNoMatch( | ||
"", | ||
[script.compiled, controlBlock.sublist(0, controlBlock.length-1)], | ||
); | ||
expectNoMatch( | ||
"", | ||
[ | ||
script.compiled, | ||
Uint8List.fromList([...controlBlock.take(33), ...Uint8List(32*129)]), | ||
], | ||
); | ||
// Control block must have valid tapscript version. | ||
expectNoMatch( | ||
"", | ||
[ | ||
script.compiled, | ||
Uint8List.fromList([0xc2, ...controlBlock.sublist(1)]), | ||
], | ||
); | ||
// Script must be valid and minimal | ||
expectNoMatch("", [hexToBytes("0201"), controlBlock]); | ||
expectNoMatch("", [hexToBytes("0101"), controlBlock]); | ||
}); | ||
|
||
}); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters