Skip to content

Commit 233432b

Browse files
committed
Add UTXO loading from the PSBT
1 parent fc69a78 commit 233432b

3 files changed

Lines changed: 91 additions & 4 deletions

File tree

lib/Bitcoin/Crypto/PSBT.pm

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use Types::Common -sigs;
88
use List::Util qw(any);
99
use Scalar::Util qw(blessed);
1010

11-
use Bitcoin::Crypto qw(btc_transaction);
11+
use Bitcoin::Crypto qw(btc_transaction btc_utxo);
1212
use Bitcoin::Crypto::PSBT::Map;
1313
use Bitcoin::Crypto::PSBT::Field;
1414
use Bitcoin::Crypto::PSBT::FieldType;
@@ -354,6 +354,41 @@ sub get_locktime
354354
}
355355
}
356356

357+
sub _get_utxo
358+
{
359+
my ($self, $input_index, $txid, $output_index) = @_;
360+
361+
my $non_witness_utxo = $self->get_all_fields('PSBT_IN_NON_WITNESS_UTXO', $input_index);
362+
my $witness_utxo = $self->get_all_fields('PSBT_IN_WITNESS_UTXO', $input_index);
363+
364+
return unless $non_witness_utxo || $witness_utxo;
365+
366+
my $output;
367+
368+
if ($non_witness_utxo) {
369+
my $utxo_tx = $non_witness_utxo->value;
370+
371+
Bitcoin::Crypto::Exception::PSBT->raise(
372+
'invalid PSBT_IN_NON_WITNESS_UTXO - txid mismatch'
373+
) unless $utxo_tx->txid eq $txid;
374+
375+
Bitcoin::Crypto::Exception::PSBT->raise(
376+
"invalid PSBT_IN_NON_WITNESS_UTXO - no output $output_index"
377+
) unless defined $utxo_tx->outputs->[$output_index];
378+
379+
$output = $utxo_tx->outputs->[$output_index];
380+
}
381+
elsif ($witness_utxo) {
382+
$output = $witness_utxo->value;
383+
}
384+
385+
return btc_utxo->new(
386+
txid => $txid,
387+
output_index => $output_index,
388+
output => $output,
389+
);
390+
}
391+
357392
sub get_transaction
358393
{
359394
my ($self) = @_;
@@ -377,7 +412,10 @@ sub get_transaction
377412
my $sequence_field = $self->get_all_fields('PSBT_IN_SEQUENCE', $input_index);
378413

379414
$tx->add_input(
380-
utxo => [$utxo_txid, $utxo_output],
415+
416+
# try to get utxo - if not present in the PSBT, fallback to just
417+
# basic UTXO information. UTXO will not be registered.
418+
utxo => $self->_get_utxo($input_index, $utxo_txid, $utxo_output) // [$utxo_txid, $utxo_output],
381419
(defined $sequence_field ? (sequence_no => $sequence_field->value) : ()),
382420
);
383421
}
@@ -595,6 +633,10 @@ L<Bitcoin::Crypto::Constants/LOCKTIME_HEIGHT_THRESHOLD>.
595633
596634
Builds a new L<Bitcoin::Crypto::Transaction> object based on the contents of the PSBT.
597635
636+
If UTXO fields in the PSBT are populated, then the resulting transaction inputs
637+
will have proper UTXOs set. However, those UTXOs will not be registered
638+
globally, and will only be available to this transaction only.
639+
598640
=head3 to_serialized
599641
600642
$serialized = $object->to_serialized()

lib/Bitcoin/Crypto/Transaction/UTXO.pm

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ has param 'txid' => (
1919
coerce => ByteStrLen [32],
2020
);
2121

22-
# NOTE: ideally, utxo should point to a transaction, and transaction should
23-
# point to a block
2422
has option 'block' => (
2523
isa => InstanceOf ['Bitcoin::Crypto::Block'],
2624
);

t/PSBT/integration.t

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,53 @@ subtest 'should return valid height-based locktime from version 2 based on fallb
148148
is $psbt->get_locktime, 555, 'locktime ok';
149149
};
150150

151+
subtest 'should build transaction with UTXOs' => sub {
152+
my $psbt = build_psbt(@minimal_v2);
153+
ok !$psbt->get_transaction->inputs->[0]->utxo_registered, 'no utxo for base transaction ok';
154+
155+
$psbt = build_psbt(
156+
@minimal_v2,
157+
{
158+
type => 'PSBT_IN_NON_WITNESS_UTXO',
159+
index => 0,
160+
raw_value => [
161+
hex =>
162+
'02000000000101511b7dac52f391e294e799b1b4097c1a60e578d5183e0d294de78638650b58500200000000ffffffff021a4f03000000000022512078b8bc45652bd8b48059d4d7cfa15415054dee17888ff42bd1dd97b778d0462443420100000000001600147e7467a4cde82b8681c927c30bc116f4916db16f02483045022100959c4a3a29b25df59c997764fff11aadc8e6889f3a58e3b87cab3807b92f00b5022045c7601bd902a70323e15ec4ba4dff57c1fc6e30475abc9f8cee6664190cf59d012102f6f1ff1fe9a1c43020f43b91d91d24090f0f803b54f8a32f17db2795a58949eb00000000'
163+
],
164+
}
165+
);
166+
167+
is $psbt->get_transaction->inputs->[0]->utxo->output->locking_script->get_address,
168+
'bc1p0zutc3t990vtfqze6ntulg25z5z5mmsh3z8lg273mktmw7xsgcjqrc42h3', 'non-witness utxo ok';
169+
170+
$psbt = build_psbt(
171+
@minimal_v2,
172+
{
173+
type => 'PSBT_IN_WITNESS_UTXO',
174+
index => 0,
175+
raw_value =>
176+
[hex => '1a4f03000000000022512078b8bc45652bd8b48059d4d7cfa15415054dee17888ff42bd1dd97b778d04624'],
177+
}
178+
);
179+
180+
is $psbt->get_transaction->inputs->[0]->utxo->output->locking_script->get_address,
181+
'bc1p0zutc3t990vtfqze6ntulg25z5z5mmsh3z8lg273mktmw7xsgcjqrc42h3', 'witness utxo ok';
182+
};
183+
184+
subtest 'should not build transaction with invalid non-witness UTXO' => sub {
185+
my $psbt = build_psbt(
186+
@minimal_v2,
187+
{
188+
type => 'PSBT_IN_NON_WITNESS_UTXO',
189+
index => 0,
190+
raw_value => [hex => $minimal_hex],
191+
}
192+
);
193+
194+
# feed tx to its own as its UTXO, which should fail (wrong transaction id)
195+
isa_ok dies { $psbt->get_transaction }, 'Bitcoin::Crypto::Exception::PSBT';
196+
};
197+
151198
done_testing;
152199

153200
sub build_psbt

0 commit comments

Comments
 (0)