I've been doing the
Perl Weekly Challenges. This one
was to do a fiddly numerical calculation, and to check Bitcoin addresses.
The first part was to split a resource between 100 people: person
number 1 gets 1%, number 2 gets 2% of what's left, and so on. Who gets
most? Quick inspection shows that this is a function with a peak, but
the numbers will get quite small, so let's use an unsung module:
use Math::BigFloat;
Set up the resource:
my $n=$ARGV[0] || 100;
my $remaining=Math::BigFloat->new(1);
my $max=0;
my $maxp=0;
Then simply do the calculation, and check whether it's a larger
portion than any other:
foreach my $participant (1..$n) {
my $portion=$remaining*$participant/$n;
if ($portion > $max) {
$max=$portion;
$maxp=$participant;
}
$remaining-=$portion;
}
print "$maxp: $max\n";
Not very challenging, really. You could probably do it with standard
floats.
The other one… well, like everything to do with bitcoin, it's
bizarrely overcomplicated. There's a Base58 encoding using a
non-standard alphabet, and the checksum is a SHA256 of a SHA256 but
then you throw away all but the first four bytes… yech. I really
didn't fancy writing my own decoder, so I ended up moving things in
and out of GMP representation.
use Encode::Base58::GMP;
use Math::GMPz qw(:mpz);
use Digest::SHA qw(sha256 sha256_hex);
So first we do a basic validation…
foreach my $address (@ARGV) {
unless ($address =~ /^[1-9A-HJ-NP-Za-km-z]*$/) {
warn "$address\ninvalid characters\n";
next;
}
Decode it into a number, then convert that number to a hex string,
because we'll be pulling individual bytes off it.
my $a=decode_base58($address,'bitcoin');
my $ah=Rmpz_get_str($a,16);
if (length($ah)%2==1) {
$ah='0'.$ah;
}
my @e=($ah =~ /(..)/g);
while (scalar @e < 25) {
unshift @e,'00';
}
Pull out the last four bytes as the checksum.
my $cksum=join('',splice(@e,-4));
What's left gets packed back into raw bytes for the SHA256 hashing.
my $raw=pack('C*',map {hex($_)} @e);
my $digest=substr(sha256_hex(sha256($raw)),0,8);
if ($digest eq $cksum) {
print join('',@e),"\n";
} else {
warn "$address\nCalculated $digest does not match stored $cksum\n";
}
}
I hate bitcoin. There is of course no formal specification of any of
this, just code, and if I read someone else's code I wouldn't be
writing my own.
Comments on this post are now closed. If you have particular grounds for adding a late comment, comment on a more recent post quoting the URL of this one.