File Coverage

blib/lib/Crypt/HashCash/Mint.pm
Criterion Covered Total %
statement 9 11 81.8
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 13 15 86.6


line stmt bran cond sub pod time code
1             # -*-cperl-*-
2             #
3             # Crypt::HashCash::Mint - Mint for HashCash Digital Cash
4             # Copyright (c) 2001-2017 Ashish Gulhati
5             #
6             # $Id: lib/Crypt/HashCash/Mint.pm v1.126 Sat Jun 24 02:15:18 PDT 2017 $
7              
8             package Crypt::HashCash::Mint;
9              
10 1     1   15569 use 5.008001;
  1         6  
11 1     1   9 use warnings;
  1         4  
  1         52  
12 1     1   8 use strict;
  1         2  
  1         34  
13              
14 1     1   1203 use Crypt::RSA::Blind;
  0            
  0            
15             use Crypt::ECDSA::Blind;
16             use Compress::Zlib;
17             use Persistence::Object::Simple;
18             use vars qw( $VERSION $AUTOLOAD );
19             use DBI;
20              
21             our ( $VERSION ) = '$Revision: 1.126 $' =~ /\s+([\d\.]+)/;
22              
23             sub new {
24             my $class = shift;
25             my %arg = @_;
26             my $self = bless { VERSION => "Crypt::HashCash::Mint v$VERSION",
27             RSAB => new Crypt::RSA::Blind,
28             ECDSAB => new Crypt::ECDSA::Blind (Create => 1),
29             SIGSCHEME => 'ECDSA',
30             COMMENT => '',
31             DEBUG => $arg{Debug} || 0,
32             KEYSIZE => 1024,
33             KEYDB => $arg{KeyDB} || '/tmp/vault.key',
34             DENOMS => [qw(100 200 500 1000 2000 5000 10000 20000 50000 100000 200000
35             500000 1000000 2000000 5000000 10000000 20000000 50000000
36             100000000 200000000 500000000 1000000000)],
37             DB => $arg{DB}
38             }, $class;
39             return unless my $keydb = new Persistence::Object::Simple ('__Fn' => $self->keydb); $self->keydb($keydb);
40             my $db = $self->db;
41             unless ($db) {
42             unlink $arg{SpentDB} if defined $arg{SpentDB} and $arg{SpentDB} ne ':memory:' and $arg{Clobber};
43             return unless $db = DBI->connect("dbi:SQLite:dbname=$arg{SpentDB}", undef, undef, {AutoCommit => 1});
44             $self->{DB} = $db;
45             }
46             my @tables = $db->tables('%','%','spent','TABLE');
47             unless ($tables[0]) {
48             if ($arg{Create}) {
49             return undef unless $db->do('CREATE TABLE spent (id text NOT NULL,
50             denom int NOT NULL,
51             spent int NOT NULL
52             );');
53             return undef unless $db->do('CREATE INDEX idx_spent_id ON spent(id);');
54             }
55             else {
56             return undef;
57             }
58             }
59             return $self;
60             }
61              
62             sub keygen {
63             my $self = shift;
64             $self->_diag("MINT: keygen\n");
65             my (%skey, %pkey);
66             for (@{$self->denoms}) {
67             $self->_diag("MINT: keygen for denom $_\n");
68             my ($pk, $sk) = $self->signer->keygen (
69             Identity => "HashCash $_",
70             Size => $self->keysize,
71             Verbosity => $self->debug,
72             ) or die "Error creating key for denomination $_";
73             $skey{$_} = $sk; $pkey{$_} = $pk;
74             $self->keydb->{sec}->{$_} = $sk->as_hex; $self->keydb->{pub}->{$_} = $pk->as_hex;
75             }
76             $self->keydb->commit;
77             $self->skeys(\%skey); $self->pkeys(\%pkey);
78             }
79              
80             sub loadkeys {
81             my $self = shift;
82             $self->_diag("MINT: loadkeys\n");
83             my (%skey, %pkey);
84             my $sigmod = 'Crypt::' . $self->sigscheme . '::Blind';
85             no strict 'refs';
86             for (@{$self->denoms}) {
87             $skey{$_} = &{$sigmod.'::SecKey::from_hex'}($self->keydb->{sec}->{$_});
88             $pkey{$_} = &{$sigmod.'::PubKey::from_hex'}($self->keydb->{pub}->{$_});
89             }
90             $self->skeys(\%skey); $self->pkeys(\%pkey);
91             }
92              
93             sub init {
94             my $self = shift;
95             $self->_diag("MINT: init\n");
96             $self->signer->init;
97             }
98              
99             sub mint_coin {
100             my ($self,$req) = @_;
101             return unless $req; return unless defined $self->skeys->{$req->{D}};
102             $self->_diag ("MINT: mint_coin\nD: $req->{D}\n");
103             return unless my $coin = $self->signer->sign(Key => $self->skeys->{$req->{D}}, Message => $req->{R}, Init => $req->{Init});
104             $self->_diag ("req: $req->{R}\ncoin: $coin\n");
105             return ( bless { C => "$coin", D => $req->{D}, Init => $req->{Init} }, 'Crypt::HashCash::Coin::Blinded' );
106             }
107              
108             sub verify_coin {
109             my ($self, $coin) = @_;
110             return unless ref $coin eq 'Crypt::HashCash::Coin' and defined $self->pkeys->{$coin->{D}};
111             $self->_diag ("MINT: verify_coin\ncoin: $coin->{Z}\nX: $coin->{X}\nD: $coin->{D}\n");
112             # Check if coin already spent, and if signature is valid
113             return 0 if $self->db->selectcol_arrayref("SELECT spent from spent WHERE id='$coin->{X}' and denom='$coin->{D}';")->[0];
114             return 0 unless $self->signer->verify(Key => $self->pkeys->{$coin->{D}}, Signature => $coin->{Z}, Message => $coin->{X});
115             # Valid, unspent coin
116             return 1;
117             }
118              
119             sub spend_coin {
120             my ($self, $coin) = @_;
121             return unless ref $coin eq 'Crypt::HashCash::Coin' and $coin->is_valid and defined $self->pkeys->{$coin->{D}};
122             $self->_diag ("MINT: spend_coin\ncoin: $coin->{Z}\nX: $coin->{X}\nD: $coin->{D}\n");
123             my $timestamp = time;
124             $self->db->begin_work;
125             # First check if coin already spent, so we don't waste time verifying if double-spend
126             $self->db->rollback, return 0 if $self->db->selectcol_arrayref("SELECT spent from spent WHERE id='$coin->{X}' and denom='$coin->{D}';")->[0];
127             # Unspent coin, add to DB
128             $self->db->do("INSERT INTO spent values ('$coin->{X}', '$coin->{D}', '$timestamp');");
129             # Verify coin
130             $self->db->rollback, return 0 unless $self->signer->verify(Key => $self->pkeys->{$coin->{D}}, Signature => $coin->{Z}, Message => $coin->{X});
131             $self->db->commit;
132             return 1;
133             }
134              
135             sub unspend_coin {
136             my ($self, $coin) = @_;
137             return unless ref $coin eq 'Crypt::HashCash::Coin' and $coin->is_valid and defined $self->pkeys->{$coin->{D}};
138             $self->_diag ("MINT: unspend_coin\ncoin: $coin->{Z}\nX: $coin->{X}\nD: $coin->{D}\n");
139             $self->db->do("DELETE from spent WHERE id='$coin->{X}' and denom='$coin->{D}';");
140             }
141              
142             sub _diag {
143             my $self = shift;
144             print STDERR @_ if $self->debug;
145             }
146              
147             sub AUTOLOAD {
148             my $self = shift; (my $auto = $AUTOLOAD) =~ s/.*:://;
149             return if $auto eq 'DESTROY';
150             if ($auto =~ /^((s|p)keys|rsab|ecdsab|keysize|debug|version|comment|spentdb|keydb|units|sigscheme)$/x) {
151             $self->{"\U$auto"} = shift if (defined $_[0]);
152             }
153             if ($auto =~ /^((s|p)keys|rsab|ecdsab|keysize|debug|version|comment|spentdb|keydb|units|denoms|db|sigscheme)$/x) {
154             return $self->{"\U$auto"};
155             }
156             if ($auto eq 'signer') {
157             $self->sigscheme eq 'RSA' ? $self->rsab : $self->ecdsab;
158             }
159             else {
160             die "Could not AUTOLOAD method $auto.";
161             }
162             }
163              
164             sub Crypt::RSA::Key::Private::as_hex {
165             unpack('H*', compress(shift->serialize));
166             }
167              
168             sub Crypt::RSA::Key::Public::as_hex {
169             unpack('H*', compress(shift->serialize));
170             }
171              
172             1;
173              
174             __END__