Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer

Implementing the "Icarus" crypto mining protocol for some old hardware

by cavac (Priest)
on Feb 05, 2022 at 20:50 UTC ( #11141158=perlquestion: print w/replies, xml ) Need Help??

cavac has asked for the wisdom of the Perl Monks concerning the following question:

I found an old USB "ASIC Miner Block Erupter" thing in my box of old hardware that waits for the day i find a new purpose for it. This crypto dongle does about 330 Megahashes per second. Not planning to use it to mine bitcoin, but it could be a nice proof-of-concept hardware for a blockchain thing i'm playing around with. No commercial purpose, just me playing around on how a blockchain could be integrated with a project that uses a PostgreSQL database. Basically, just me looking into how crypto blockchains work, but hating the idea of using a big-ass file when you could use SQL ;-)

I normally wouldn't even play with mining hardware devices because they waste tons of power and they are bad for the environment. But this little toy only uses about 2.5 Watts, so i figured i could experiment with it without boiling the oceans.

Before we can use that device for anything, we'd better find out how to communicate with it. There isn't much technical info on the internet about the hardware. Most articles are all about "yadayada blockchains are the future yadayada get rich quick yadayada bitcoin not a pyramid scheme yadayada". I found some very old discussions that mention it uses the Icarus protocol, whatever that is.

Another few minutes of googling found this old article. It describes the "Communication protocol V3". It's not very detailed, but it says what data to send and what data comes back. Except it only mentions "last 12 bytes of block header", whatever that entails.

Eventually, Google found me this post, which helped me in finding out what those mysterious 12 bytes are supposed to be. Timestamp and Nonce are easy, these are just basically 8 bytes i can play around with. The Difficulty target supposedly changes on how difficult it is for the hardware to find a matching block.

To be honest, i have absolutely no idea how that "difficulty" is encoded and what it does on my specific hardware. Changing the value doesn't seem to change on how fast i get the result on average, it just changes the results. I'm probably doing something wrong or i'm missing a step or something. So, if you have any ideas/bugfixes/patches or just want to shout at me for doing something incredibly stupid, feel free to write a comment :-)

Ok, here is the code i have. It's not pretty, it's full of debug stuff. And i'm doing quite a bit of data manipulation the hard way, just to make it easier for me to play around with the bits. But, as a proof of concept, it should be a decent start for you to play around with.

#!/usr/bin/env perl use strict; use warnings; use Crypt::Digest::SHA256 qw(sha256); use Device::SerialPort qw( :PARAM :STAT 0.07 ); use English; use Carp; use Time::HiRes qw(time); use Data::Dumper; my $miner = Device::SerialPort->new('/dev/ttyUSB0') or croak("Modem er +ror $ERRNO"); $miner->baudrate(115_200); $miner->parity('none'); $miner->databits(8); $miner->stopbits(1); # One cycle takes about 12 seconds and there seems no way to # reset the thing. Hardware was designed by crypto-currency people, # so naturally, it is decently fast, very inefficient and has a # very clunky, amateurish interface. # # So, just wait the length of a cycle and empty # the result bytes. initHardware(); my @times; for(my $i = 0; $i < 5; $i++) { my $starttime = time; my $hash; my $loopcount = 0; while(1) { $loopcount++; my $rawdata = getRandomData(); if(1) { # use random data and proper timestamps $hash = generateHash($rawdata, $starttime, $loopcount); } else { # Use fixed data for validation between multiple runs $hash = generateHash('BLA', $i, $loopcount); } if(defined($hash)) { last; } } my $endtime = time; my $elapsed = $endtime - $starttime; my $decodedhash = unpack("H*", $hash); print "Calculated result $i: $decodedhash in $elapsed seconds\n"; push @times, $elapsed; } print "\n"; my $total; foreach my $val (@times) { $total += $val; } $total /= scalar @times; print "Took on average $total seconds to calculate a result.\n"; exit(0); sub initHardware { # This "cleans out" the hardware by running one hash without any n +ew hashing request. # This has the effect of making sure any active loop has time to f +inish and we empty # out all result bytes from the buffer my $targettime = time + 13.5; print "Hardware initialization...\n"; # Remove previous results from queue (is any) while(time < $targettime) { my ($count, $data) = $miner->read(1); if(!$count) { next; } print "! ", ord($data), "\n"; } print "Ready for work!\n"; return; } sub generateHash { my ($data, $timestamp, $nonce) = @_; my $request = buildRequest($data, $timestamp, $nonce); $miner->write($request); my @resultbytes; my $targettime = time + 13.5; # Remove previous results from queue (is any) while(1) { my ($count, $data) = $miner->read(1); if(!$count) { last; } print "! ", ord($data), "\n"; } while(time < $targettime) { my ($count, $data) = $miner->read(1); if(!$count) { next; } my $bytecount = 0; #print "$bytecount > ", ord($data), "\n"; $bytecount++; push @resultbytes, $data; } if(scalar @resultbytes < 4) { # No result; return; } my $result = ''; for(1..4) { $result .= shift @resultbytes; } return $result; } sub buildRequest { my ($data, $timestamp, $nonce) = @_; my $source = sha256($data); # Add fill my $fill = "0" x 160; $fill = pack("B*", $fill); $source .= $fill; $source .= toBinary($timestamp); # timestamp # Difficulty target in "bits" encoding. No idea how that really wo +rks, though. # If i didn't do something stupid, i just copied the one used to m +ine the first bitcoin. $source .= toBitsBinary("1d00ffff"); $source .= toBinary($nonce); # Nonce return $source; } sub toBinary { my ($val) = @_; if(1) { return pack('N', $val); } else { # "shuffle bytes by hand" version for easier experimentation # compared to fiddling with the fast but confusing pack/unpack + function my @bytes; for(1..4) { unshift @bytes, chr($val & 0xff); $val >>= 8; } return join('', @bytes); } } sub toBitsBinary { my ($val) = @_; my @parts = unpack"(A2)*", $val; my @bytes; foreach my $part (@parts) { push @bytes, chr(hex($part)); } return join('', @bytes); } sub getRandomData { my @validchars = split//, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; my $token = 'XPD'; for(1..500) { $token .= $validchars[rand @validchars]; } $token .= 'VT'; return $token; }

So, if you have even a basic understanding about the internals of Bitcoin, i appreciate if you can show me on how to properly calculate the difficulty bits and verify that i have got a valid result. And if you see any error in my protocol implementation, i'd also be glad for any help you can give me.

perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'

Replies are listed 'Best First'.
Re: Implementing the "Icarus" crypto mining protocol for some old hardware
by bliako (Monsignor) on Feb 07, 2022 at 21:59 UTC

    I have near-zero bitcoin knowledge so your post was a good incentive to search it a bit more. Albeit nothing came out, or rather nothing came into my head... Anyway, from the little I understood "Difficulty" (D) is the crux of the bitcoin madness (idiocy?): they (who?) need a solved block every 10 minutes. And so in order to keep this fixed, D is adjusted every couple of weeks in order to keep the number of solved blocks constant and not depleting the unmined bitcoins too early (oh come on, get over and done with it, miners!). So, in my understanding you can retrieve current D from some website which keeps track of it (google difficulty charts). Or calculate it yourself by retrieving the number of mined blocks. I guess.

    I *think* D relates to the length of the hash you need to solve. For some D, some parts of the hash can be skipped. It's like solving a problem with specified accuracy, i.e. number of decimal digits. More digits means more difficulty and a different solution (to the decimal digits). That's probably why you get results back from USB anyway. But they are different.

    If I understood correctly, even in general terms, "crux of the madness" is a good term for all these. For example, consider this implication: someone invests in hardware to solve blocks faster. That investment will only yield results for 2 weeks max. Then D will increase in order to compensate for this panter's activity and keep the number of solved blocks per 2 weeks fixed. So it's a constant antagonism in order to get a percentage from the same fixed award-pot, to split the same pie so-to-speak. And it was K.M. who said that investment profit only comes from human labour, not technology (because sooner or later all investors adapt adopt the same technologies, even "secret" ones).

    In general, it looks to me to be like the donkey running to reach the carrot. In which case carrot's speed is adjusted automatically (by the System!) so that donkey never eats it. Clever algorithms would enable the donkey eating a bit of the carrot from time to time to keep content.

    bw, bliako

    ps. hehehe

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11141158]
Approved by LanX
Front-paged by Discipulus
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (4)
As of 2022-12-02 03:22 GMT
Find Nodes?
    Voting Booth?