TARDÍGRADOS

Ciencia en español -ʟᴀ ʀᴀᴢóɴ ᴇsᴛá ᴀʜí ғᴜᴇʀᴀ-

Cómo “robar” bitcoins con una modesta tarjeta gráfica NVIDIA

Posted by Albert Zotkin on July 15, 2021

Hola amigos de Tardígrados. El propósito principal del siguiente documento informático, no es enseñar a robar Bitcoins, eso estaría muy feo sino demostrar la robustez de la protección criptográfica de Bitcoin, y mostrar de paso, con un caso práctico, la casi inexpugnable muralla que ofrece esa criptografía contra posibles ataques, que los hay. ¿Es tan segura esa protección criptográfica?. Sí. Estamos hablando de cadenas de 256 bits. Esas contraseñas serán las semillas para generar las llaves privadas y las direcciones públicas de Bitcoin. O sea, estamos hablando de un número monstruosamente grande de combinaciones. Ese número entero es:

\displaystyle N =2^{2^{2^{3}}}=1157920892373161954235709850086879078532699846656405640394575840079131\ 29639936

que, como vemos, tiene 78 cifras decimales. Para intentar hacernos una idea de lo que ese número representa, supongamos que tenemos una lista de 135 direcciones de Bitcoins, que queremos atacar. Atacar, aquí quiere decir descubrir la clave privada desde la dirección publica. Entonces la probabilidad de dar con una de esas 135 claves sería de:

\displaystyle P =\frac{135}{ 2^{2^{2^{3}}} } = 1.1658827549377500244271575014780539421006600491989 \text{ x } 10^{-75}

Es decir, aun suponiendo que nuestra computadora fuera capaz de procesar 1 millón de direcciones por segundo, tendríamos por delante aún unos

\displaystyle t =\frac{ 2^{2^{2^{3}}} }{135 \times 1000000} = 8.5771917953567552165608137043472524335755544196771 \text{ x } 10^{68} \text{ segundos}

para encontrar la primera de esas 135 direcciones de la lista que queremos atacar. Creo que ni Matusalén conseguiría vivir tanto para ver como se consigue ese primer hallazgo 🙂

Mi tarjeta gráfica es una NVIDIA GeForce GT 430. Escribiremos un pequeño programa para Node.JS , en el que aceleraremos la computación mediante tarjeta gráfica, es decir, usaremos la GPU de la tarjeta gráfica, en lugar de la CPU de la computadora. Para la computación con tarjeta gráfica usaremos el módulo gpu.js, el cual posee un javascript de aceleración muy interesante. Para trabajar con bitcoins, yo uso el módulo bitcore-lib, donde además de las herramientas para bitcoin también trae otras interesantes utilidades. Empecemos abriendo un editor de texto cualquiera, y escribamos las primeras instrucciones de nuestro programilla, que como sabemos deberá ser escrito en lenguaje javascript, ya que es para Node.JS .

  1. const {GPU} = require(‘./gpu.js’);
  2. const bitcore = require(‘bitcore-lib’);
  3. const crypto = require(‘crypto’);
  4. const fs = require(‘fs’);
  5. const gpu = new GPU();
  6. var argv = require(‘yargs’)
  7. .usage(‘Usage: $0 –range [num] –samples [num]’)
  8. /*.demand([‘range’,’samples’])*/
  9. .argv;
  10. const jsonutil = require(‘jsonutil’);
  11. const JSONStream = require(‘JSONStream’);
Los módulos gpu.js, yargs, jsonutil y bitcore-lib, los puedes decargar desde npm. Los módulos crypto y fs, no, porque ya vienen incluidos en el propio Node.js.
Nuestro programilla, que trabajará desde una consola DOS de windows mediante comandos, tiene el módulo yargs, con el que definimos dos argumentos para pasar en la linea de comandos al ser ejecutado. Nuestro primer argumento será –range [num], y en él especificaremos el número de coincidencias que deseamos (obviamente, desearíamos un número de coincidencias pleno, pero como esta es sólo una herramienta demostrativa, tenemos que dar juego a las coincidencias. Las direcciones públicas de bitcoin poseen 34 caracteres, porque están codificadas en Base58, por lo tanto el juego podrá ir desde 1 coincidencias hasta el pleno de 34. ¿Por qué 34?. Porque son números hexadecimales de 50 dígitos, que están codificados en Base58. Después usaremos en nuestra linea de comando el argumento –samples [num], que será el número de direcciones que queremos en nuestra lista de resultados, Obviamente si nuestra lista de direcciones objetivo es de 135, el número num que sigue a –samples no podrá ser nunca mayor a esos 135.
Sigamos escribiendo nuestro código en nuestro editor de textos favorito:

  1. const range = argv.range?parseInt(argv.range):4;
  2. var max_limit = argv.samples?parseInt(argv.samples):10;
  3. const file = “keys-“+range+“.json”;
  4. var results_file = “keys-“+range+“.json”;
  5. var jsonwriter = JSONStream.stringify();
  6. var jsonwriter_file = fs.createWriteStream(results_file);
  7. jsonwriter.pipe(jsonwriter_file);
  8. const objetive_addrs =[// list of bitcoin public addreses, can’t be empty
  9. //…TODO
  10. ];
  11. const objetive_size = 100;
  12. const m = Fill(objetive_size);
  13. const render = gpu.createKernel(function(a,m) {
  14. var h = a[this.thread.y][this.thread.x];
  15. var c = m[this.thread.y][this.thread.x];
  16. if(h == c) return h;
  17. return 1;
  18. }).setOutput([42, objetive_size]);
  19. var n=0;
  20. var time0 = Date.now();
  21. var total=0;
  22. var stop = 8;
  23. var keys = {
  24. phrase:null,
  25. private_key:null,
  26. wif:null,
  27. public_address:null,
  28. objetive_addrs:null
  29. };
En las lineas 14 y 15 pasamos los argumentos –range y –samples a sus respectivas variables range y max_limit. En las lineas 16 y 17 nombramos 2 archivos con el mismo nombre.. En las lineas 18 a 20 creamos los streams para escribir el archivo results_file.

El resto del programilla es como sigue:

  1. /***********************************************************************************************************/
  2. console.log(\x1b[32m***************************************************\x1b[0m”);
  3. console.log(\x1b[32m*******************\x1b[0m\x1b[92m THE IMPOSSIBLE\x1b[0m\x1b[32m ****************\x1b[0m”);
  4. console.log(\x1b[32m*******\x1b[0m\x1b[92mIt’s kind of fun to do the impossible\x1b[0m\x1b[32m*******\x1b[0m”);
  5. console.log(\x1b[32m—————RANGE \x1b[1m%s\x1b[0m \x1b[35mwith samples\x1b[0m \x1b[1m%s\x1b[0m \x1b[35mSTARTED—————\x1b[0m”,range,max_limit);
  6. console.log({miners_size:objetive_adrs.length});
  7. while(n < max_limit){
  8. var ar = FillArray(miners_size);
  9. var a = ar[0];
  10. var b = ar[1];
  11. var p = ar[2];
  12. var w = ar[3];
  13. var r = render(a,m);
  14. var t = filterArray(r, a, b, p, w, m, range);
  15. if(t.length){
  16. for(var i=0;i<t.length;i++){
  17. keys.phrase = t[i][0];
  18. keys.private_key = t[i][1];
  19. keys.wif = t[i][2];
  20. keys.public_address = t[i][3];
  21. keys.miner__address = t[i][4];
  22. jsonwriter.write(keys);
  23. var pair = highlight(t[i]);
  24. keys.phrase = pair[0];
  25. keys.private_key = pair[1];
  26. keys.wif = pair[2];
  27. keys.public_address = pair[3];
  28. keys.miner__address = pair[4];
  29. consola(keys);
  30. var time = Date.now()time0;
  31. total += time;
  32. time0 = Date.now();
  33. console.log(\x1b[33mNew keys has been appended to file \x1b[1m%s\x1b[0m. \x1b[33mDuration of this sample: \x1b[31m\x1b[1m%s\x1b[0m \x1b[33mseconds\x1b[0m”,results_file, time/1000);
  34. }
  35. n++;
  36. if(n==max_limit) {
  37. jsonwriter.end();
  38. console.log(“max-limit of %s samples reached. Job terminated.”,max_limit);
  39. console.log(\x1b[33m – Total \x1b[1mduration\x1b[0m \x1b[33mof job: \x1b[31m\x1b[1m%s\x1b[0m \x1b[33mseconds – \x1b[0m”,total/1000);
  40. break;
  41. }
  42. }
  43. }
  44. /******************************************************/
  45. function FillArray(size){
  46. var a = [];
  47. var b = [];
  48. var p = [];
  49. var w = [];
  50. for(var j = 0;j < size;j++){
  51. var t = [];
  52. var hash = crypto.randomBytes(32);
  53. //var hash = bitcore.crypto.Hash.sha256(crypto.randomBytes(32));
  54. var bn = bitcore.crypto.BN.fromBuffer(hash);
  55. var privateKey = new bitcore.PrivateKey(bn);
  56. var address = privateKey.toAddress().toString(‘hex’);
  57. b.push(hash.toString(‘hex’));
  58. p.push(privateKey.toString(‘hex’));
  59. w.push(privateKey.toWIF().toString(‘hex’));
  60. for(var i = 0;i < address.length;i++){
  61. t.push(address.charCodeAt(i));
  62. }
  63. a.push(t);
  64. }
  65. return [a,b,p,w];
  66. }
  67. function FillMiners(size){
  68. var a = [];
  69. var k = 0;
  70. for(var j = 0;j < size;j++){
  71. if(k == objetive_adrs.length)k=0;
  72. var address = objetive_adrs[k];
  73. k++;
  74. var t = [];
  75. for(var i = 0;i < 42;i++){
  76. if(i<address.length)
  77. t.push(address.charCodeAt(i));
  78. else
  79. t.push(0);
  80. }
  81. a.push(t);
  82. }
  83. return a;
  84. }
  85. function FillMiners2(size){
  86. var a = [];
  87. var k = 0;
  88. for(var j = 0;j < size;j++){
  89. if(k == objetive_addrs.length)k=0;
  90. var address = objetive_addrs[k];
  91. k++;
  92. var t = [];
  93. for(var i = 0;i < 42;i++){
  94. if(i<address.length)
  95. t.push(address.charCodeAt(i));
  96. else
  97. t.push(0);
  98. }
  99. a.push(t);
  100. }
  101. return a;
  102. }
  103. function check(a) {
  104. return a != 1;
  105. }
  106. function filterArray(r, a, b, p, w, m, range){
  107. var f = [];
  108. for(i in r){
  109. var s = r[i].filter(check);
  110. if(s.length >= range){
  111. var a1 = new Buffer.from(a[i]).toString();
  112. var b1 = b[i];
  113. var p1 = p[i];
  114. var w1 = w[i];
  115. var m1 = new Buffer.from(objetive_adrs[i]).toString();
  116. f.push([b1,p1,w1,a1,m1]);
  117. }
  118. }
  119. return f;
  120. }
  121. // funciones para imprimir resultados en la consols DOS de Winsdows
  122. function highlight(keys){
  123. var pa = keys[3].split();
  124. var ma = keys[4].split();
  125. const h =\x1b[1m’;
  126. const t =\x1b[0m\x1b[32m’;
  127. const y =\x1b[33m’;
  128. const g =\x1b[32m’;
  129. for(id in pa){
  130. if(pa[id] == ma[id]){
  131. pa[id] = ma[id] = h+ma[id]+t;
  132. }
  133. }
  134. var b = y+keys[0]+\x1b[0m’;
  135. var p = y+keys[1]+\x1b[0m’;
  136. var w = y+keys[2]+\x1b[0m’;
  137. var pa2 = g+pa.join()+\x1b[0m’;
  138. var ma2 = g+ma.join()+\x1b[0m’;
  139. return [b,p,w,pa2,ma2];
  140. }
  141. function consola(json){
  142. var a = JSON.stringify(json,null,8);
  143. a = a.replace(/”/g,);
  144. a = a.replace(/\\u001b/g,\x1b’);
  145. process.stdout.write(a);
  146. }
  147. /******************************************************/

Saludos

Leave a comment