File Coverage

blib/lib/Bitcoin/Crypto/Role/SignVerify.pm
Criterion Covered Total %
statement 60 61 98.3
branch 5 8 62.5
condition 1 3 33.3
subroutine 12 12 100.0
pod n/a
total 78 84 92.8


line stmt bran cond sub pod time code
1             package Bitcoin::Crypto::Role::SignVerify;
2             $Bitcoin::Crypto::Role::SignVerify::VERSION = '2.000_01'; # TRIAL
3             $Bitcoin::Crypto::Role::SignVerify::VERSION = '2.00001';
4 16     16   10522 use v5.10;
  16         83  
5 16     16   95 use strict;
  16         47  
  16         377  
6 16     16   104 use warnings;
  16         51  
  16         524  
7 16     16   105 use Mooish::AttributeBuilder -standard;
  16         47  
  16         114  
8 16     16   2003 use Type::Params -sigs;
  16         1689  
  16         113  
9              
10 16     16   7885 use Bitcoin::Crypto::Types qw(Object Str ByteStr InstanceOf HashRef);
  16         48  
  16         103  
11 16     16   63573 use Bitcoin::Crypto::Helpers qw(carp_once); # load Math::BigInt
  16         41  
  16         881  
12 16     16   144 use Crypt::Digest::SHA256 qw(sha256);
  16         33  
  16         774  
13 16     16   7688 use Bitcoin::Crypto::Transaction::Sign;
  16         83  
  16         581  
14 16     16   150 use Moo::Role;
  16         51  
  16         135  
15              
16 16   33 16   12535 use constant HAS_DETERMINISTIC_SIGNATURES => eval { require Crypt::Perl } && Crypt::Perl->VERSION gt '0.33';
  16         50  
  16         41  
17              
18             requires qw(
19             key_instance
20             _is_private
21             );
22              
23             has field '_crypt_perl_prv' => (
24             isa => InstanceOf ['Crypt::Perl::ECDSA::PrivateKey'],
25             lazy => sub {
26             require Crypt::Perl::ECDSA::Parse;
27             return Crypt::Perl::ECDSA::Parse::private($_[0]->key_instance->export_key_der('private'));
28             }
29             );
30              
31             sub _fix_der_signature
32             {
33 21     21   65 my ($self, $signature) = @_;
34              
35 21 50       68 return undef unless defined $signature;
36              
37             # https://bitcoin.stackexchange.com/questions/92680/what-are-the-der-signature-and-sec-format
38 21         48 my $pos = 0;
39              
40 21         63 my $compound = substr $signature, $pos, 1;
41 21         42 $pos += 1;
42              
43 21         75 my $total_len = unpack 'C', substr $signature, $pos, 1;
44 21         40 $pos += 1;
45              
46 21         53 my $int1 = substr $signature, $pos, 1;
47 21         41 $pos += 1;
48              
49 21         46 my $r_len = unpack 'C', substr $signature, $pos, 1;
50 21         32 $pos += 1;
51              
52 21         46 my $r = substr $signature, $pos, $r_len;
53 21         37 $pos += $r_len;
54              
55 21         40 my $int2 = substr $signature, $pos, 1;
56 21         39 $pos += 1;
57              
58 21         66 my $s_len = unpack 'C', substr $signature, $pos, 1;
59 21         34 $pos += 1;
60              
61 21         155 my $s = Math::BigInt->from_bytes(substr $signature, $pos, $s_len);
62 21         2689 $pos += $s_len;
63              
64 21 50       77 die 'invalid signature'
65             unless $pos == length $signature;
66              
67             # fixup $s - must be below order / 2 (BIP62)
68 21         101 my $order = $self->curve_order;
69 21 100       6264 if ($s > $order->copy->btdiv(2)) {
70 8         2894 $s = $order - $s;
71             }
72              
73 21         5546 $s = $s->as_bytes;
74 21 50       697 if (unpack('C', $s) & 0x80) {
75              
76             # top bit is 1, so prepend with zero to avoid being interpreted as
77             # negative
78 0         0 $s = "\x00$s";
79             }
80              
81 21         64 $total_len = $total_len - $s_len + length $s;
82 21         50 $s_len = length $s;
83              
84 21         208 return join '',
85             $compound,
86             pack('C', $total_len),
87             $int1,
88             pack('C', $r_len),
89             $r,
90             $int2,
91             pack('C', $s_len),
92             $s,
93             ;
94             }
95              
96             signature_for sign_message => (
97             method => Object,
98             positional => [ByteStr],
99             );
100              
101             sub sign_message
102             {
103             my ($self, $message) = @_;
104              
105             Bitcoin::Crypto::Exception::Sign->raise(
106             'cannot sign a message with a public key'
107             ) unless $self->_is_private;
108              
109             $message = sha256($message);
110              
111             return Bitcoin::Crypto::Exception::Sign->trap_into(
112             sub {
113             my $signature;
114             if (HAS_DETERMINISTIC_SIGNATURES) {
115             $signature = $self->_crypt_perl_prv->sign_sha256($message);
116             }
117             else {
118             carp_once
119             'Current implementation of CryptX signature generation does not produce deterministic results. For better security, install the Crypt::Perl module.';
120             $signature = $self->key_instance->sign_message($message, 'sha256');
121             }
122              
123             return $self->_fix_der_signature($signature);
124             }
125             );
126             }
127              
128             signature_for sign_transaction => (
129             method => Object,
130             positional => [
131             InstanceOf ['Bitcoin::Crypto::Transaction'],
132             HashRef, {slurpy => !!1}
133             ],
134             );
135              
136             sub sign_transaction
137             {
138             my ($self, $transaction, $args) = @_;
139              
140             $args->{transaction} = $transaction;
141             $args->{key} = $self;
142             my $signer = Bitcoin::Crypto::Transaction::Sign->new($args);
143             $signer->sign;
144              
145             return;
146             }
147              
148             signature_for verify_message => (
149             method => Object,
150             positional => [ByteStr, ByteStr],
151             );
152              
153             sub verify_message
154             {
155             my ($self, $message, $signature, $algorithm) = @_;
156             $message = sha256($message);
157              
158             return Bitcoin::Crypto::Exception::Verify->trap_into(
159             sub {
160             $self->key_instance->verify_message($signature, $message, 'sha256');
161             }
162             );
163             }
164              
165             1;
166