1package Crypt::Perl::Ed25519::PrivateKey;
2
3use strict;
4use warnings;
5
6=encoding utf-8
7
8=head1 NAME
9
10Crypt::Perl::Ed25519::PrivateKey
11
12=head1 SYNOPSIS
13
14    my $new_key = Crypt::Perl::Ed25519::PrivateKey->new();
15
16    # The passed-in string should contain ONLY the private pieces.
17    my $import_key = Crypt::Perl::Ed25519::PrivateKey->new( $priv_str );
18
19    # … or do this if you’ve got the public component:
20    $import_key = Crypt::Perl::Ed25519::PrivateKey->new( $priv_str, $pub_str );
21
22    # Returns an octet string
23    my $signature = $key->sign( $message );
24
25    $key->verify( $message, $signature ) or die "Invalid sig for msg!";
26
27    #----------------------------------------------------------------------
28
29    # These return an octet string.
30    my $pub_str = $key->get_public();
31    my $priv_str = $key->get_private();
32
33    # Returns an object
34    my $pub_obj = $key->get_public_key();
35
36    # These return a hash reference, NOT a JSON string.
37    my $priv_hr = $key->get_struct_for_private_jwk();
38    my $pub_hr  = $key->get_struct_for_public_jwk();
39
40=head1 DESCRIPTION
41
42This class implements Ed25519 signing and verification.
43
44=cut
45
46use parent qw(
47  Crypt::Perl::Ed25519::KeyBase
48);
49
50use Digest::SHA ();
51
52use Crypt::Perl::Ed25519::Math;
53
54use constant _ASN1 => q<
55    FG_Key ::= SEQUENCE {
56        version             INTEGER,
57        algorithmIdentifier AlgorithmIdentifier,
58        privateKey          PrivateKey
59    }
60
61    PrivateKey ::= OCTET STRING
62>;
63
64use constant _PEM_HEADER => 'PRIVATE KEY';
65
66sub new {
67    my ($class, $priv, $pub) = @_;
68
69    if (defined($priv) && length($priv)) {
70        $class->_verify_binary_key_part($priv);
71    }
72    else {
73        $priv = do {
74            require Crypt::Perl::RNG;
75            Crypt::Perl::RNG::bytes(32);
76        };
77    }
78
79    my ($pub_ar);
80
81    if (defined($pub) && length($pub)) {
82        $class->_verify_binary_key_part($pub);
83
84        $pub_ar = unpack 'C*', $pub;
85    }
86    else {
87        $pub_ar = _deduce_public_from_private($priv);
88        $pub = pack 'C*', @$pub_ar;
89    }
90
91    return bless {
92        _public => $pub,
93        _public_ar => $pub_ar,
94        _private => $priv,
95        _private_ar => [ unpack 'C*', $priv ],
96    }, $class;
97}
98
99sub get_struct_for_private_jwk {
100    my ($self) = @_;
101
102    my $struct = $self->get_struct_for_public_jwk();
103
104    require MIME::Base64;
105
106    $struct->{'d'} = MIME::Base64::encode_base64url($self->{'_private'});
107
108    return $struct;
109}
110
111sub get_private {
112    my ($self) = @_;
113
114    return $self->{'_private'};
115}
116
117sub get_public_key {
118    my ($self) = @_;
119
120    require Crypt::Perl::Ed25519::PublicKey;
121
122    return Crypt::Perl::Ed25519::PublicKey->new( $self->{'_public'} );
123}
124
125sub sign {
126    my ($self, $msg) = @_;
127
128    my @x = (0) x 64;
129
130    my @p = map { [ Crypt::Perl::Ed25519::Math::gf0() ] } 1 .. 4;
131
132    my $digest_ar = _digest32( $self->{'_private'} );
133
134    my @sm = (0) x 32;
135    push @sm, @{$digest_ar}[32 .. 63];
136    push @sm, unpack( 'C*', $msg );
137
138    my @r = unpack 'C*', Digest::SHA::sha512( pack 'C*', @sm[32 .. $#sm] );
139    Crypt::Perl::Ed25519::Math::reduce(\@r);
140    Crypt::Perl::Ed25519::Math::scalarbase( \@p, \@r );
141    @sm[ 0 .. 31 ] = @{ Crypt::Perl::Ed25519::Math::pack(\@p) };
142
143    @sm[32 .. 63] = @{$self->{'_public_ar'}};
144
145    my @h = unpack 'C*', Digest::SHA::sha512( pack 'C*', @sm );
146    Crypt::Perl::Ed25519::Math::reduce( \@h );
147
148    @x[0 .. 31] = @r[0 .. 31];
149
150    for my $i ( 0 .. 31) {
151        for my $j ( 0 .. 31 ) {
152            $x[ $i + $j ] += $h[$i] * $digest_ar->[$j];
153        }
154    }
155
156    my @latter_sm = @sm[32 .. $#sm];
157
158    Crypt::Perl::Ed25519::Math::modL( \@latter_sm, \@x );
159
160    @sm[32 .. $#sm] = @latter_sm;
161
162    return pack 'C*', @sm[ 0 .. ($self->SIGN_BYTE_LENGTH - 1) ];
163}
164
165#----------------------------------------------------------------------
166
167sub _to_der_args {
168    my ($self) = @_;
169
170    return (
171
172        # The leading bytes are the encoding of the inner CurvePrivateKey
173        # (i.e., OCTET STRING).
174        privateKey => "\x04\x20" . $self->{'_private'},
175    );
176}
177
178sub _deduce_public_from_private {
179    my ($private) = @_;
180
181    my $digest_ar = _digest32($private);
182
183    my $p = [ map { [ Crypt::Perl::Ed25519::Math::gf0() ] } 0 .. 3 ];
184
185    # private key is 32 bytes for private part
186    # plus 32 bytes for the public part
187
188    Crypt::Perl::Ed25519::Math::scalarbase($p, $digest_ar);
189    my $pk = Crypt::Perl::Ed25519::Math::pack($p);
190
191    return \@$pk;
192}
193
194sub _digest32 {
195    my ($seed) = @_;
196
197    my @digest = unpack 'C*', Digest::SHA::sha512($seed);
198    $digest[0]  &= 0xf8;    #248
199    $digest[31] &= 0x7f;    #127
200    $digest[31] |= 0x40;    # 64
201
202    return \@digest;
203}
204
2051;
206