File Coverage

blib/lib/Crypt/Dining.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Crypt::Dining;
2              
3 2     2   25165 use strict;
  2         4  
  2         70  
4 2     2   9 use warnings;
  2         4  
  2         64  
5 2     2   11 use vars qw($VERSION $PORT $PACKETSZ);
  2         7  
  2         143  
6             # use Data::Dumper;
7 2     2   1724 use IO::Socket::INET;
  2         49738  
  2         16  
8 2     2   2122 use Crypt::Random qw(makerandom_octet);
  0            
  0            
9             use Net::Address::IPv4::Local;
10              
11             $VERSION = '1.01';
12             $PORT = 17355;
13             $PACKETSZ = 1024;
14              
15             sub debug {
16             my ($self, @msg) = @_;
17             if ($self->{Debug}) {
18             print "[$$] ", @msg, "\n";
19             }
20             }
21              
22             sub new {
23             my $class = shift;
24             my $self = ($#_ == 0) ? { %{ (shift) } } : { @_ };
25              
26             unless ($self->{LocalPort}) {
27             $self->{LocalPort} = $PORT;
28             }
29              
30             unless ($self->{LocalAddr}) {
31             $self->{LocalAddr} = Net::Address::IPv4::Local->public;
32             }
33              
34             my $this = $self->{LocalAddr} . ":" . $self->{LocalPort};
35              
36             my @peers = @{ $self->{Peers} };
37             unless (@peers) {
38             die "No peers given - protocol will be a no-op";
39             }
40              
41             foreach (0..$#peers) {
42             $peers[$_] .= ":$PORT" unless $peers[$_] =~ /:/;
43             }
44             @peers = sort { $a cmp $b } @peers;
45              
46             my $next = $peers[0];
47             my $prev = $peers[-1];
48             PEER: foreach (@peers) {
49             if ($this lt $_) {
50             $next = $_;
51             last PEER;
52             }
53             $prev = $_;
54             }
55              
56             # print "Peers are " . Dumper(\@peers);
57             # print "Prev is $prev\n";
58             # print "This is $this\n";
59             # print "Next is $next\n";
60              
61             $self->{Peers} = \@peers;
62              
63             $prev =~ m/(.*):(.*)/
64             or die "No address:port in $prev";
65             $self->{PrevAddr} = $1;
66             $self->{PrevPort} = $2;
67              
68             $next =~ m/(.*):(.*)/
69             or die "No address:port in $next";
70             $self->{NextAddr} = $1;
71             $self->{NextPort} = $2;
72              
73              
74             return bless $self, $class;
75             }
76              
77             sub socket_udp {
78             my ($self) = @_;
79             unless ($self->{SocketUdp}) {
80             $self->debug(
81             "Creating socket $self->{LocalAddr}:$self->{LocalPort}"
82             );
83             $self->{SocketUdp} = new IO::Socket::INET(
84             Proto => "udp",
85             LocalAddr => $self->{LocalAddr},
86             LocalPort => $self->{LocalPort},
87             ReuseAddr => 1,
88             # Listen => 5,
89             )
90             or die "socket: $self->{LocalAddr}:$self->{LocalPort}: $!";
91             }
92             return $self->{SocketUdp};
93             }
94              
95             sub listen_prev {
96             my ($self) = @_;
97             $self->socket_udp();
98             }
99              
100             sub send_next {
101             my ($self, $data) = @_;
102             my $socket = $self->socket_udp();
103             $self->debug("Send coin to $self->{NextAddr}:$self->{NextPort}");
104             my $addr = pack_sockaddr_in($self->{NextPort}, inet_aton($self->{NextAddr}));
105             return $socket->send($data, 0, $addr);
106             }
107              
108             sub send_all {
109             my ($self, $data) = @_;
110             my $socket = $self->socket_udp();
111             foreach (@{ $self->{Peers} }) {
112             $self->debug("Send hand to $_");
113             m/(.*):(.*)/ or die "Invalid peer: $_: No host:port";
114             my ($host, $port) = ($1, $2);
115             my $addr = pack_sockaddr_in($port, inet_aton($host));
116             $socket->send($data, 0, $addr);
117             }
118             }
119              
120             sub recv_prev {
121             my ($self) = @_;
122             my $socket = $self->socket_udp();
123              
124             my $data;
125             my $addr = $socket->recv($data, $PACKETSZ + 4);
126             $self->debug("Got " . length($data) . " bytes from prev");
127             my ($port, $iaddr) = unpack_sockaddr_in($addr);
128             my $aaddr = inet_ntoa($iaddr);
129             die "Unexpected packet from $aaddr:$port"
130             unless $aaddr eq $self->{PrevAddr};
131             return $data;
132             }
133              
134             sub recv_all {
135             my ($self) = @_;
136             my $socket = $self->socket_udp();
137              
138             my %data;
139             foreach (@{ $self->{Peers} }) {
140             my $data;
141             my $addr = $socket->recv($data, $PACKETSZ + 4);
142             $data{$_} = $data;
143             }
144              
145             return %data;
146             }
147              
148             sub round {
149             my ($self, $message) = @_;
150              
151             my $random = makerandom_octet(
152             Length => $PACKETSZ,
153             Strength => 0,
154             );
155             # print "Random is $random\n";
156              
157             $self->listen_prev();
158             sleep 1;
159              
160             $self->send_next("coin" . $random);
161              
162             my $packet = $self->recv_prev();
163             unless (substr($packet, 0, 4) eq 'coin') {
164             die "Didn't get a coin packet: got " . substr($packet, 0, 4);
165             }
166             die "Bad length for received coin data"
167             unless length $packet eq $PACKETSZ + 4;
168             my $store = substr($packet, 4) ^ $random;
169             $store ^= $message if $message;
170              
171             $self->debug("====");
172             # sleep 1;
173              
174             # return;
175              
176             $self->send_all("hand" . $store);
177              
178             my %answers = $self->recv_all();
179             my $answer = $store;
180             foreach (keys %answers) {
181             $packet = $answers{$_};
182             unless (substr($packet, 0, 4) eq 'hand') {
183             die "Didn't get a hand packet from $_: got " .
184             substr($packet, 0, 4);
185             }
186             die "Bad length for received hand data"
187             unless length $packet eq $PACKETSZ + 4;
188             $answer ^= substr($packet, 4);
189             }
190              
191             return $answer;
192             }
193              
194             =head1 NAME
195              
196             Crypt::Dining - The Dining Cryptographers' Protocol
197              
198             =head1 SYNOPSIS
199              
200             my $dc = new Crypt::Dining(
201             LocalAddr => '123.45.6.7',
202             Peers => [ '123.45.6.8', ... ],
203             );
204             my $answer = $dc->round;
205             my $answer = $dc->round("hello");
206              
207             =head1 DESCRIPTION
208              
209             The dining cryptographers' protocol is documented in Bruce
210             Schneier's book as a kind of "cryptographic ouija board". It works
211             as follows:
212              
213             A number of cryptographers are dining at a circular table. At the end
214             of the meal, the waiter is summoned and asked for the bill. He replies,
215             "Thank you, sir. The bill has been paid." The cryptographers now have
216             the problem of working out whether someone at the table paid the bill,
217             or whether the NSA has paid it as some sort of veiled threat. The
218             protocol proceeds.
219              
220             Each cryptographer flips a coin, and shows the result ONLY to the
221             participant on his RIGHT. Each cryptographer then compares his coin
222             with that on his LEFT, and raises his hand if they show different
223             faces. If any participant paid the bill, he "cheats" and does the
224             opposite, that is, he raises his hand if the coins show the same
225             face. Now, the hands are counted. An odd number means that someone
226             at the table paid the bill. An even number means that the NSA paid.
227              
228             =head1 ASSUMPTIONS AND IMPLEMENTATION
229              
230             At most one person "cheats" at any time, otherwise the message is
231             scrambled. Detecting scrambling is only possible with multi-bit
232             messages containing a checksum.
233              
234             The comparison operator described above is the XOR operator on
235             single-bit values. If the protocol is performed with multi-bit
236             messages, then the XOR is still used.
237              
238             =head1 WIKIPEDIA DESCRIPTION
239              
240             The following description is copied from
241             L and
242             is redistributed under the GNU Free Documentation License. It is
243             a very slightly different protocol to that implemented here, but the
244             result is the same.
245              
246             The dining cryptographers protocol is a method of anonymous
247             communication. It offers untraceability of both the sender and the
248             recipient.
249              
250             The method is as follows: two or more cryptographers arrange
251             themselves around a circular dinner table, with menus hiding the
252             interaction of each pair of adjacent cryptographers from the rest.
253             Each adjacent pair picks a random number in private. Then each
254             cryptographer announces publicly the difference between the number
255             on his right and the number on his left, adding a message if he
256             wants to transmit one. All cryptographers then add up the publicly
257             announced numbers. If the sum is 0, no one sent a message. If the
258             sum is a valid message, one cryptographer transmitted a message. If
259             the sum is invalid, more than one cryptographer tried to transmit a
260             message; they wait a random time and try again.
261              
262             =head1 BUGS
263              
264             If the send_*() and recv_*() methods are overridden to use TCP sockets
265             with very large messages, deadlock may occur around the ring unless
266             something intelligent is done with select().
267              
268             =head1 SEE ALSO
269              
270             L,
271             L - another cryptographic curiosity.
272              
273             =head1 COPYRIGHT
274              
275             Copyright (c) 2005 Shevek. All rights reserved.
276              
277             This program is free software; you can redistribute it and/or modify
278             it under the same terms as Perl itself.
279              
280             =cut
281              
282             1;