-
Notifications
You must be signed in to change notification settings - Fork 319
/
ch-2.pl
executable file
·128 lines (99 loc) · 3.13 KB
/
ch-2.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env perl
use v5.22;
use strict;
use warnings;
# Turn on method signatures
use feature 'signatures';
no warnings 'experimental::signatures';
use autodie;
use bigint;
use Digest::SHA qw(sha256);
die("Please provide bitcoint address as argument") unless scalar(@ARGV) eq 1;
if ($ARGV[0] eq '--test') {
test();
} else {
say validate($ARGV[0]) ? 'True' : 'False';
}
sub test() {
my @tests = (
['1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2' => 1 ],
['1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN211' => undef ], # Too long
['1BvBMSEYstWetqTFn5Au4m4GFg0xJaNVN2' => undef ], # Invalid char
['1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVM2' => undef ], # Bad checksum
['3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy' => 1 ],
['1111111111111111111114oLvT2' => 1 ],
['111111111111111111114oLvT2' => undef ], # Too short
['11111111111111111111114oLvT2' => undef ], # Too long
);
use Test2::V0;
for my $test (@tests) {
is validate($test->[0]), $test->[1], $test->[0] . ' ' . ($test->[1] ? 'True' : 'False');
}
done_testing;
}
sub validate($addr) {
# Is it valid base-58?
my $val = base58_decode($addr);
return if !defined($val);
# Format we expect:
# <version> <20 bytes> <4 byte checksum>
return if $val >= (2**200);
# Is the first byte a 1 or a 5? Those are the only types we support.
my $ver = $val / (2**(24*8)) & 0xff;
return if ($ver != 0 and $ver != 5);
# Is it in cannonical format - I.E. the standard format it would be
# encoded as?
return unless $addr eq base58_encode($val, 25);
# Does the checksum match?
my (@buf) = int_to_buf($val, 25)->@*;
my $sha = sha256(sha256(join '', @buf[0..20]));
return unless substr($sha, 0, 4) eq join('',@buf[21..24]);
return 1;
}
sub base58_decode($txt) {
state $chars;
if (!defined($chars)) {
$chars = {};
my (@txt) = split //, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
for (my $i=0; $i<58; $i++) {
$chars->{$txt[$i]} = $i;
}
}
my $val = 0;
for my $c (split //, $txt) {
return unless exists $chars->{$c};
$val = $val * 58 + $chars->{$c};
}
return $val;
}
sub base58_encode($int, $bytes) {
state $vals;
if (!defined($vals)) {
$vals = {};
my (@txt) = split //, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
for (my $i=0; $i<58; $i++) {
$vals->{$i} = $txt[$i];
}
}
# Handle leading zero bytes
my $zeros = '';
my $buf = int_to_buf($int, $bytes);
for my $byte (@$buf) {
if (ord($byte) == 0) { $zeros .= '1'; } # Leading zero comparison
if (ord($byte) != 0) { last; }
}
my $encoded = '';
while ($int > 0) {
$encoded = $vals->{$int % 58} . $encoded;
$int = $int / 58;
}
return "$zeros$encoded";
}
sub int_to_buf($int, $bytes) {
my $buf = [];
for (my $i=0; $i<$bytes; $i++) {
my $byte = ( $int / (2**(8 * (($bytes-1) - $i)) )) % 256;
push @$buf, chr($byte);
}
return $buf;
}