@@ -8,11 +8,13 @@ use Types::Common -sigs;
88use List::Util qw( any) ;
99use Scalar::Util qw( blessed) ;
1010
11+ use Bitcoin::Crypto qw( btc_transaction) ;
1112use Bitcoin::Crypto::PSBT::Map;
1213use Bitcoin::Crypto::PSBT::Field;
1314use Bitcoin::Crypto::PSBT::FieldType;
1415use Bitcoin::Crypto::Types -types;
1516use Bitcoin::Crypto::Constants qw( :psbt) ;
17+ use Bitcoin::Crypto::Util qw( to_format) ;
1618use Bitcoin::Crypto::Exception;
1719
1820has field ' maps' => (
@@ -87,9 +89,17 @@ sub get_field
8789 my ($self , $type , $index , $key ) = @_ ;
8890
8991 my @values = $self -> get_all_fields($type , $index , $key );
90- Bitcoin::Crypto::Exception::PSBT-> raise(
91- ' Could not get value for field ' . $type -> name . ' : found ' . @values . ' values in PSBT'
92- ) unless @values == 1;
92+ if (@values != 1) {
93+ my $id = $type -> name;
94+ $id .= " , index $index " if defined $index ;
95+ $id .= ' , key ' . to_format [hex => $key ] if defined $key ;
96+
97+ my $count = @values ;
98+
99+ Bitcoin::Crypto::Exception::PSBT-> raise(
100+ " Could not get value for field $id : found $count values in PSBT"
101+ );
102+ }
93103
94104 return $values [0];
95105}
@@ -291,6 +301,112 @@ sub check
291301 return $self ;
292302}
293303
304+ sub get_locktime
305+ {
306+ my ($self ) = @_ ;
307+ my $version = $self -> version;
308+
309+ if ($version == 0) {
310+ return $self -> get_transaction-> locktime;
311+ }
312+ elsif ($version == 2) {
313+
314+ # NOTE: this logic is based on:
315+ # https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time
316+
317+ my $time_locktimes = 0;
318+ my $height_locktimes = 0;
319+ my $max_time_locktime = 0;
320+ my $max_height_locktime = 0;
321+
322+ foreach my $input_index (0 .. $self -> input_count - 1) {
323+ my $time = $self -> get_all_fields(' PSBT_IN_REQUIRED_TIME_LOCKTIME' , $input_index );
324+ my $height = $self -> get_all_fields(' PSBT_IN_REQUIRED_HEIGHT_LOCKTIME' , $input_index );
325+
326+ $time_locktimes += defined $time ;
327+ $height_locktimes += defined $height ;
328+
329+ $max_time_locktime = $time -> value
330+ if $time && $time -> value > $max_time_locktime ;
331+ $max_height_locktime = $height -> value
332+ if $height && $height -> value > $max_height_locktime ;
333+ }
334+
335+ if ($time_locktimes > 0 && $height_locktimes > 0) {
336+ if ($time_locktimes == $self -> input_count && $time_locktimes > $height_locktimes ) {
337+ return $max_time_locktime ;
338+ }
339+ else {
340+ return $max_height_locktime ;
341+ }
342+ }
343+ elsif ($time_locktimes > $height_locktimes ) {
344+ return $max_time_locktime ;
345+ }
346+ elsif ($height_locktimes > $time_locktimes ) {
347+ return $max_height_locktime ;
348+ }
349+ else {
350+ my $fallback = $self -> get_all_fields(' PSBT_GLOBAL_FALLBACK_LOCKTIME' );
351+
352+ return $fallback ? $fallback -> value : 0;
353+ }
354+ }
355+ }
356+
357+ sub get_transaction
358+ {
359+ my ($self ) = @_ ;
360+
361+ $self -> check;
362+ my $version = $self -> version;
363+
364+ my $tx ;
365+ if ($version == 0) {
366+ $tx = $self -> get_field(' PSBT_GLOBAL_UNSIGNED_TX' )-> value;
367+ }
368+ elsif ($version == 2) {
369+ $tx = btc_transaction-> new(
370+ version => $self -> get_field(' PSBT_GLOBAL_TX_VERSION' )-> value,
371+ locktime => $self -> get_locktime,
372+ );
373+
374+ foreach my $input_index (0 .. $self -> input_count - 1) {
375+ my $utxo_txid = $self -> get_field(' PSBT_IN_PREVIOUS_TXID' , $input_index )-> value;
376+ my $utxo_output = $self -> get_field(' PSBT_IN_OUTPUT_INDEX' , $input_index )-> value;
377+ my $sequence_field = $self -> get_all_fields(' PSBT_IN_SEQUENCE' , $input_index );
378+
379+ $tx -> add_input(
380+ utxo => [$utxo_txid , $utxo_output ],
381+ (defined $sequence_field ? (sequence_no => $sequence_field -> value) : ()),
382+ );
383+ }
384+
385+ foreach my $output_index (0 .. $self -> output_count - 1) {
386+ my $value = $self -> get_field(' PSBT_OUT_AMOUNT' , $output_index )-> value;
387+ my $script = $self -> get_field(' PSBT_OUT_SCRIPT' , $output_index )-> value;
388+
389+ $tx -> add_output(
390+ locking_script => $script ,
391+ value => $value ,
392+ );
393+ }
394+ }
395+
396+ foreach my $input_index (0 .. $self -> input_count - 1) {
397+ my $signature_field = $self -> get_all_fields(' PSBT_IN_FINAL_SCRIPTSIG' , $input_index );
398+ my $witness_field = $self -> get_all_fields(' PSBT_IN_FINAL_SCRIPTWITNESS' , $input_index );
399+
400+ $tx -> inputs-> [$input_index ]-> set_signature_script($signature_field -> value)
401+ if $signature_field ;
402+
403+ $tx -> inputs-> [$input_index ]-> set_serialized_witness($witness_field -> value)
404+ if $witness_field ;
405+ }
406+
407+ return $tx ;
408+ }
409+
294410sub dump
295411{
296412 my ($self ) = @_ ;
@@ -461,6 +577,24 @@ may use this data to loop through PSBT fields.
461577Checks the internal state of PSBT fields and throws an exception if it is
462578invalid. Returns the object itself.
463579
580+ =head3 get_locktime
581+
582+ $int = $object->get_locktime()
583+
584+ Returns the locktime encoded in the PSBT as an integer. For version 0 PSBTs,
585+ this simply returns the locktime in the unsigned transaction field. For version
586+ 2, it follows L<procedure described in
587+ BIP370|https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time>.
588+
589+ Time or height-based threshold can be determined by comparing the result with
590+ L<Bitcoin::Crypto::Constants/LOCKTIME_HEIGHT_THRESHOLD> .
591+
592+ =head3 get_transaction
593+
594+ $transaction = $object->get_transaction()
595+
596+ Builds a new L<Bitcoin::Crypto::Transaction> object based on the contents of the PSBT.
597+
464598=head3 to_serialized
465599
466600 $serialized = $object->to_serialized()
0 commit comments