| @ -0,0 +1,3 @@ | |||
| topisto - www | |||
| Il s'agit du contenu WEB présneté par le serveur https://www.topisto.net | |||
| @ -0,0 +1,14 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12"> | |||
| <br> | |||
| <h4>This QR Code is my PGP public key</h4> | |||
| <p>Scan or click to get it</p> | |||
| <center> | |||
| <a href="files/albert_seandhils.asc"> | |||
| <img src="articles/00000000/public_key_qrcode.png" style="max-width: 435px; width: 100%; height: auto"></img> | |||
| </a> | |||
| </center> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,112 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-8"> | |||
| <h2>A digital currency</h2> | |||
| <p> | |||
| <small><i>From Wikipedia, the free encyclopedia</i></small><br> | |||
| Bitcoin is a cryptocurrency and a digital payment system invented by an unknown programmer, or a group of programmers, under the name Satoshi Nakamoto.It was released as open-source software in 2009.<br> | |||
| The system is peer-to-peer, and transactions take place between users directly, without an intermediary.These transactions are verified by network nodes and recorded in a public distributed ledger called a blockchain. Since the system works without a central repository or single administrator, bitcoin is called the first decentralized digital currency.<br> | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-4 align-middle"> | |||
| <img src="articles/ARTICLE/images/bitcoin.jpg" width="100%; height: auto"></img> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4 align-middle"> | |||
| <img src="articles/ARTICLE/images/wallet.png" height="300px"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>WALLET</h2> | |||
| <p> | |||
| You can install a software on your computer or your phone to manage your bitcoins.<br> | |||
| As Bitcoin is open source, there is a lot of such software avalaible.<br> | |||
| It's called a wallet.<br> | |||
| Those softwares are all talking with the same protocol. They are connected to the same network.<br> | |||
| But, in fact, your true wallet is your 2 crypt keys : the public and the private one.<br> | |||
| Everybody who knows those 2 values can spend your bitcoins.<br> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-8"> | |||
| <h2>Transaction</h2> | |||
| <p> | |||
| When Bob is sending 42 <span class="glyphicon glyphicon-bitcoin"></span> to Alice, the bitcoin protocol is creating a transaction.<br> | |||
| A transaction is the balance between his Inputs and his Outputs. | |||
| <ul> | |||
| <li>First the network needs to be sure that Bob has got enough <span class="glyphicon glyphicon-bitcoin"></span> in his wallet.<br>So Bob will include a list of transactions that he has received before.<br>This is the inputs of the transactions.<br>For example, previous transaction's amount are 10, 25 and another 10, so total inputs are 45 <span class="glyphicon glyphicon-bitcoin"></span></li> | |||
| <li>As the input's sum is superior to the amount of the transaction, an ouput is added.<br>This output is the change to Bob.<br>In the example, this 3 <span class="glyphicon glyphicon-bitcoin"></span></li> | |||
| <li>Fees are took by the network to process the transaction.<br>Let say it will take 0.5<span class="glyphicon glyphicon-bitcoin"></span>.<br>This is another ouput</li> | |||
| <li>So Alice will get only a part of the transaction's amount.<br>this the last output, 41.5<span class="glyphicon glyphicon-bitcoin"></span></li> | |||
| </ul> | |||
| So the transaction is a made with : | |||
| <ul> | |||
| <li>Inputs : a list of previous transactions that bob has received</li> | |||
| <li>Outputs : a list of amounts to send to Alice, Bob, and fees</li> | |||
| <li>A timestamp</li> | |||
| </ul> | |||
| Then the wallet will broadcast the transaction on the network.<br> | |||
| It will be sent to the area called "mempool".<br> | |||
| Nodes of the network will validate the transaction by verfying the Inputs and the Ouputs.<br> | |||
| A hash of the transaction is computed.<br> | |||
| So the transaction is sealed. | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-4 align-middle"> | |||
| <img src="articles/ARTICLE/images/bob_to_alice.gif" width="100%; height: auto"></img> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="articles/ARTICLE/images/tx2block.gif" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Block</h2> | |||
| <p> | |||
| Then the bitcoin protocol is grouping validated transactions in blocks.<br> | |||
| So a block is an array of transactions.<br> | |||
| A block is made with : | |||
| <ul> | |||
| <li>An array of transactions.<br> | |||
| That's what my script is drawing.<br> | |||
| Running the transactions array, it is computing the sum of each outputs of each transaction.<br> | |||
| Then, it draws a rectangle for each sum.<br> | |||
| </li> | |||
| <li>A "proof of work".<br>The miner is computing a special hash that depends on the array of transaction and that will answer to a constraint.<br>This hash is very difficult to compute. In fact it is not really computed, the miner must test every value until he find the solution.<br> | |||
| That why the network will give him a reward.<br>This reward is a special transaction that has no inputs.<br> | |||
| As this reward is in bitcoin, this is also a mean to control the money stock.<br> | |||
| The more you use the money, the more there is blocks, the more there is money.<br> | |||
| </li> | |||
| </ul> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-8"> | |||
| <h2>Blockchain</h2> | |||
| <p> | |||
| Each block has a hash and a reference to the ihash of the previous block.<br> | |||
| So blocks are chained together.<br> | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-4"> | |||
| <img src="articles/ARTICLE/images/blockchain.gif" width="100%; height: auto"></img> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,17 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="articles/ARTICLE/images/btcfond.jpg" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Bitcoin and blockchain</h2> | |||
| <h4>This quickly explain what is Bitcoin and how it works</h4> | |||
| <p> | |||
| Everything is on <a href="https://bitcoin.org">bitcoin.org</a>, but here you can find a quick intro to Bitcoin and the Blockchain.<br> | |||
| The project here is to make datavisualization about the Blockchain. So I must explain what is the Blockchain. I'm not interesting in trading. The Bitcoin is only see as technical object. | |||
| </p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,17 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="articles/ARTICLE/images/btc_node.jpg" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Install a full node</h2> | |||
| <h4>Want to be part of it !</h4> | |||
| <p> | |||
| But I don't have enought disk space ...<br> | |||
| So it will be a pruned one. | |||
| </p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,14 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="articles/ARTICLE/images/topisto_rouge.png" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>R. Topisto (Twitterbot)</h2> | |||
| <h4>This robot is listening to the bitcoin blockchain. Every new block it draw an image using one of the above methods.</h4> | |||
| <p>This is not technic ... this is computationnal art. It uses PHP, blockchain.info API, abraham TwitterOAuth.</p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,65 @@ | |||
| <style> | |||
| .fond { | |||
| margin:auto; | |||
| top:0; | |||
| } | |||
| .texte { | |||
| color:black; | |||
| margin:auto; | |||
| position:absolute; | |||
| z-index:2; | |||
| top:0; | |||
| width:100%; | |||
| } | |||
| </style> | |||
| <div class="container-fluid" id="BlocksBrowser"></div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| Drawing the treemap | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| Drawing the block hashes | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="js/browse_blocks.js"></script> | |||
| <script> | |||
| function afficherBlock(block, index) | |||
| { | |||
| var methode = $("#selectMethod").val(); | |||
| html = '<img src="twitterbot/image.php?methode='+methode+'&block_hash='+block.hash+'" style="opacity: 0.5;image-orientation: 90deg" width="100%; height: auto"></img>'; | |||
| $('#block0'+index+'_img').html(html); | |||
| html = ''; | |||
| html += '<table width="100%">' | |||
| html += '<tr><td width="10%"> </td><td width="30%">Height</td><td width="50%" align="right"><strong>HEIGHT</strong></td><td width="10%"> </td></tr>'; | |||
| html += '<tr><td width="10%"> </td><td width="30%">Outputs</td><td width="50%" align="right"><strong>OUTPUT</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| //html += '<tr><td width="10%"> </td><td width="30%">Inputs</td><td width="50%" align="right"><strong>INPUT</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| html += '<tr><td width="10%"> </td><td width="30%">Reward</td><td width="50%" align="right"><strong>REWARD</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| //html += '<tr><td width="10%"> </td><td width="30%">Fees</td><td width="50%" align="right"><strong>FEE</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| html += '</table>' | |||
| html = html.replace(new RegExp('INDEX', 'g'),block.block_index); | |||
| html = html.replace(new RegExp('HEIGHT', 'g'),block.height); | |||
| html = html.replace(new RegExp('OUTPUT', 'g'),block.topisto_outputs/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('INPUT', 'g'),block.topisto_inputs/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('REWARD', 'g'),(block.topisto_outputs-block.topisto_inputs)/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('FEE', 'g'),block.topisto_fees/Math.pow(10,8)); | |||
| $('#block0'+index+'_txt').html(html); | |||
| return true; | |||
| } | |||
| </script> | |||
| @ -0,0 +1,37 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="images/block_image.php?methode=treemap" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Blockchain as a Treemap</h2> | |||
| <h4 class="text-justify">Draw a Treemap where each rectangle is a transaction's inputs sum. Then add block's hashes on top and previous block's invert hash on bottom. This way we can draw the blockchain by putting images last's one above the others.</h4> | |||
| <p>That way, we can "see" the Blockchain...</p> | |||
| <p>You can choose an alternative drawing method. | |||
| <select id="selectMethod_ARTICLE" onchange="javascript:DrawLastBlock_ARTICLE()"> | |||
| <option value=4>Random method</option> | |||
| <option value=4>Gradient 2 random colors</option> | |||
| <option value=1>Grey</option> | |||
| <option value=2>Mondrian</option> | |||
| <option value=3>Pink</option> | |||
| <option value=0>Random colors</option> | |||
| </select> | |||
| </p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script> | |||
| function DrawLastBlock_ARTICLE() | |||
| { | |||
| var methode = $("#selectMethod_ARTICLE").val(); | |||
| var downloadingImage = new Image(); | |||
| downloadingImage.onload = function(){ | |||
| $('#logo_ARTICLE').attr('src', this.src); | |||
| }; | |||
| $('#logo_ARTICLE').attr('src','images/loading.gif'); | |||
| downloadingImage.src='images/block_image.php?strict=1&methode=treemap&mode='+methode; | |||
| return true; | |||
| } | |||
| </script> | |||
| @ -0,0 +1,207 @@ | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div id="dessin" class="col-sm-9"> | |||
| <svg width="800" height="800"> | |||
| </div> | |||
| <div class="col-sm-3 bg-grey" style="height:800px;overflow-y:scroll"> | |||
| <div id="controle_dessin" class="col-sm-12"> | |||
| <input type="hidden" id="node_charge" value="-2"> | |||
| <input type="hidden" id="link_charge" value="1"> | |||
| <input type="hidden" id="link_distance" value="25"> | |||
| <input type="submit" style="width:100%" onclick="javascript:afficherGraphe(le_filtre);" value="Refresh"><br> | |||
| <input type="submit" style="width:100%" onclick="javascript:simulation.alphaTarget(0.3).restart();" value="Start"><br> | |||
| <input type="submit" style="width:100%" onclick="javascript:simulation.stop();" value="Stop"><br> | |||
| <input type="submit" style="width:100%" onclick="javascript:tibo_zoom(80/100);" value="Zoom Out"><br> | |||
| <input type="submit" style="width:100%" onclick="javascript:tibo_zoom(100/80);" value="Zoom In"><br> | |||
| </div> | |||
| <div id="laliste" style="font-family: Klingon"></div> | |||
| <div id="utilisateurs" style="font-family: Klingon"></div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </body> | |||
| </html> | |||
| <script> | |||
| $(document).ready(function(){ | |||
| // Add smooth scrolling to all links in navbar + footer link | |||
| $(".navbar a, footer a[href='#myPage']").on('click', function(event) { | |||
| // Make sure this.hash has a value before overriding default behavior | |||
| if (this.hash !== "") { | |||
| // Prevent default anchor click behavior | |||
| event.preventDefault(); | |||
| // Store hash | |||
| var hash = this.hash; | |||
| // Using jQuery's animate() method to add smooth page scroll | |||
| // The optional number (900) specifies the number of milliseconds it takes to scroll to the specified area | |||
| $('html, body').animate({ | |||
| scrollTop: $(hash).offset().top | |||
| }, 900, function(){ | |||
| // Add hash (#) to URL when done scrolling (default click behavior) | |||
| window.location.hash = hash; | |||
| }); | |||
| } // End if | |||
| }); | |||
| $(window).scroll(function() { | |||
| // Smooth scrolling | |||
| $(".slideanim").each(function(){ | |||
| var pos = $(this).offset().top; | |||
| var winTop = $(window).scrollTop(); | |||
| if (pos < winTop + 600) { | |||
| $(this).addClass("slide"); | |||
| } | |||
| }); | |||
| }); | |||
| }) | |||
| </script> | |||
| <script src="https://d3js.org/d3.v4.min.js"></script> | |||
| <script> | |||
| var le_filtre = null; | |||
| var svg = d3.select("svg"), | |||
| width = +svg.attr("width"), | |||
| height = +svg.attr("height"); | |||
| var color = d3.scaleOrdinal(d3.schemeCategory20); | |||
| var simulation = d3.forceSimulation() | |||
| .force("link", d3.forceLink().id(function(d) { return d.id; })) | |||
| .force("charge", d3.forceManyBody().strength(node_charge)) | |||
| .force("center", d3.forceCenter(width / 2, height / 2)); | |||
| function afficherGraphe(un_filtre) | |||
| { | |||
| var les_donnees = "d3/data.php"; | |||
| if (un_filtre != null) | |||
| { | |||
| les_donnees += '?filtre='+un_filtre; | |||
| $("#laliste").html('<a href="javascript:;" onclick="javascript:afficherGraphe(null);">ALL DATA</a><br>'); | |||
| } else $("#laliste").html('Tout<br>'); | |||
| le_filtre = un_filtre; | |||
| $("#dessin").html(''); | |||
| $("#dessin").html('<svg width="800" height="800">'); | |||
| $("#utilisateurs").html(''); | |||
| svg = d3.select("svg"); | |||
| d3.json(les_donnees, function(error, graph) { | |||
| if (error) throw error; | |||
| d3.selectAll(graph.nodes) | |||
| .each(function(d, i) { | |||
| {graph.nodes[i].x = width/2; graph.nodes[i].y = height/2;}; | |||
| //if (graph.nodes[i].group == 5976) | |||
| if ((graph.nodes[i].group < 10000) && (graph.nodes[i].group != 5237)) | |||
| { | |||
| if (un_filtre != graph.nodes[i].id) | |||
| $("#laliste").append('<a href="javascript:;" onclick="javascript:afficherGraphe('+graph.nodes[i].id+');">'+graph.nodes[i].label+'</a><br>'); | |||
| else | |||
| $("#laliste").append(graph.nodes[i].label+'<br>'); | |||
| } | |||
| if (graph.nodes[i].group > 10000) | |||
| { | |||
| // $("#utilisateurs").append(graph.nodes[i].label+'<br>'); | |||
| if (un_filtre != graph.nodes[i].id) | |||
| $("#utilisateurs").append('<a href="javascript:;" onclick="javascript:afficherGraphe('+graph.nodes[i].id+');">'+graph.nodes[i].label+'</a><br>'); | |||
| else | |||
| $("#utilisateurs").append(graph.nodes[i].label+'<br>'); | |||
| } | |||
| }); | |||
| var link = svg.append("g") | |||
| .attr("class", "links") | |||
| .selectAll("line") | |||
| .data(graph.links) | |||
| .enter().append("line") | |||
| .attr("stroke-width", function(d) { return Math.sqrt(d.value); }); | |||
| var node = svg.append("g") | |||
| .attr("class", "nodes") | |||
| .selectAll("circle") | |||
| .data(graph.nodes) | |||
| .enter().append("circle") | |||
| .attr("r", 5) | |||
| .attr("fill", function(d) { return color(d.group); }) | |||
| .call(d3.drag() | |||
| .on("start", dragstarted) | |||
| .on("drag", dragged) | |||
| .on("end", dragended)); | |||
| node.append("title") | |||
| .text(function(d) { return d.label; }); | |||
| simulation | |||
| .nodes(graph.nodes) | |||
| .on("tick", ticked); | |||
| simulation.force("link") | |||
| .links(graph.links); | |||
| function ticked() { | |||
| link | |||
| .attr("x1", function(d) { return d.source.x; }) | |||
| .attr("y1", function(d) { return d.source.y; }) | |||
| .attr("x2", function(d) { return d.target.x; }) | |||
| .attr("y2", function(d) { return d.target.y; }); | |||
| node | |||
| .attr("cx", function(d) { return d.x; }) | |||
| .attr("cy", function(d) { return d.y; }); | |||
| } | |||
| }); | |||
| } | |||
| function node_charge(d) | |||
| { | |||
| return $("#node_charge").val(); | |||
| } | |||
| var zoom_scale = 1; | |||
| function tibo_zoom(valeur) | |||
| { | |||
| var N1 = 0; | |||
| var X1 = 0; | |||
| var Y1 = 0; | |||
| d3.selectAll('circle').each(function(d,i){ | |||
| var elt = d3.select(this); | |||
| N1 += 1; | |||
| tmp = elt.attr("cx"); | |||
| X1 += parseFloat(elt.attr("cx")); | |||
| Y1 += parseFloat(elt.attr("cy")); | |||
| }); | |||
| zoom_scale *= valeur; | |||
| tx = X1 / N1; | |||
| ty = Y1 / N1; | |||
| svg.transition().duration(500).attr('transform','translate('+tx+' '+ty+') scale('+zoom_scale+')'); | |||
| } | |||
| function dragstarted(d) { | |||
| if (!d3.event.active) simulation.alphaTarget(0.3).restart(); | |||
| d.fx = d.x; | |||
| d.fy = d.y; | |||
| } | |||
| function dragged(d) { | |||
| d.fx = d3.event.x; | |||
| d.fy = d3.event.y; | |||
| } | |||
| function dragended(d) { | |||
| if (!d3.event.active) simulation.alphaTarget(0); | |||
| d.fx = null; | |||
| d.fy = null; | |||
| } | |||
| afficherGraphe(10001); | |||
| </script> | |||
| @ -0,0 +1,17 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img src="articles/ARTICLE/images/D3-force-driven-graph.png" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Exploring D3 force driven graphs</h2> | |||
| <h4>This the Klingon starfleet organisation</h4> | |||
| <p>Not really finished ...</p> | |||
| <p class="text-justify" style="font-family: Klingon"> | |||
| Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | |||
| </p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,12 @@ | |||
| <link href="ARTICLE/js/vis-4.18.1/dist/vis-network.min.css" rel="stylesheet" type="text/css"/> | |||
| <style> | |||
| .links line { | |||
| stroke: #999; | |||
| stroke-opacity: 0.6; | |||
| } | |||
| .nodes circle { | |||
| stroke: #fff; | |||
| stroke-width: 1.5px; | |||
| } | |||
| </style> | |||
| @ -0,0 +1,99 @@ | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| Let explain how I do this<br> | |||
| I will take the "Pizza Block" to illustrate.<br> | |||
| On 2010, may the 22nd, a bitcoin developper (Laszlo Hanyecz) bought a pizza with 10 000 BTC.<br> | |||
| This is widely recognised as the first real-world transaction with bitcoin.<br> | |||
| As the Blockchain is public, we can find the block that contains this transaction.<br> | |||
| I call it the "Pizza block", it is at height 57035.<br> | |||
| It contains 2 transactions, the pizza and the reward for the mining.<br> | |||
| You can explore the block <a href="https://blockchain.info/block-index/71885/00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00" target="_blank">here.</a><br> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img height="150px" src="images/block_image.php?strict=1&methode=spline_2&hash=00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00&mode=0" /> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| Fisrt, I draw a line for each transaction.<br> | |||
| The heigth of a line depends of the amount of the transaction.<br> | |||
| So, in the Pizza block, we have a line at 50 and a line at 10 000. | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img height="150px" src="images/block_image.php?strict=1&methode=spline_2&hash=00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00&mode=1" /> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| It will be more beautiful with a gradient of 2 colors, from right to left.<br> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img height="150px" src="images/block_image.php?strict=1&methode=spline_2&hash=00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00&mode=2" /> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| Each transaction have a hash. In our case, those hashes are : | |||
| <ul> | |||
| <li>dc79b6d28309783a0aa2b47be2037626fbd19d93ad1338d187c27df0a1d5e1a4</li> | |||
| <li>49d2adb6e476fa46d8357babf78b1b501fd39e177ac7833124b3f67b17c40c2a</li> | |||
| </ul> | |||
| A hash is a 64 characters string. So i will cut each line in 64 parts.<br> | |||
| It's also an hexadecimal value, so each character is a value between 0 and f.<br> | |||
| Each character will be a point. | |||
| <ul> | |||
| <li>above the line if the value is above 8</li> | |||
| <li>under the line if the value is under 8</li> | |||
| </ul> | |||
| Rather than draw a line, i will draw a spline which link each point.<br> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img height="150px" src="images/block_image.php?strict=1&methode=spline_2&hash=00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00&mode=3" /> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| It will be more beautiful with a growing factor from left to right.<br> | |||
| And it is also a mean to see the "true" height of the transaction on the left side.<br> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img height="150px" src="images/block_image.php?strict=1&methode=spline_2&hash=00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00&mode=4" /> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| Then i will add iterations and hasard.<br> | |||
| I will iterate on each spline.<br> | |||
| At each point, i will substract or add a random value.<br> | |||
| So the lines becomes ribbons. | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img height="150px" src="images/block_image.php?strict=1&methode=spline_2&hash=00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00&mode=5" /> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| Then I will use transaprency.<br> | |||
| And that's it ! | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,14 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="images/block_image.php?methode=spline" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Blockchain and Splines</h2> | |||
| <h4>Another block drawing method.</h4> | |||
| <p>Each line is a transaction value. The line is then converted to spline using transaction's hash value. I'm following Inconvergent's blog to achieve this work.</p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,55 @@ | |||
| <style> | |||
| .fond { | |||
| margin:auto; | |||
| top:0; | |||
| } | |||
| .texte { | |||
| color:black; | |||
| margin:auto; | |||
| position:absolute; | |||
| z-index:2; | |||
| top:0; | |||
| width:100%; | |||
| } | |||
| </style> | |||
| <div class="container-fluid" id="BlocksBrowser"></div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| Drawing the splines | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="js/browse_blocks.js"></script> | |||
| <script> | |||
| function afficherBlock(block, index) | |||
| { | |||
| html = '<img src="splines/image.php?block_hash='+block.hash+'" style="opacity: 0.5;image-orientation: 90deg" width="100%; height: auto"></img>'; | |||
| $('#block0'+index+'_img').html(html); | |||
| html = ''; | |||
| html += '<table width="100%">' | |||
| html += '<tr><td width="10%"> </td><td width="30%">Height</td><td width="50%" align="right"><strong>HEIGHT</strong></td><td width="10%"> </td></tr>'; | |||
| html += '<tr><td width="10%"> </td><td width="30%">Outputs</td><td width="50%" align="right"><strong>OUTPUT</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| //html += '<tr><td width="10%"> </td><td width="30%">Inputs</td><td width="50%" align="right"><strong>INPUT</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| html += '<tr><td width="10%"> </td><td width="30%">Reward</td><td width="50%" align="right"><strong>REWARD</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| //html += '<tr><td width="10%"> </td><td width="30%">Fees</td><td width="50%" align="right"><strong>FEE</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| html += '</table>' | |||
| html = html.replace(new RegExp('INDEX', 'g'),block.block_index); | |||
| html = html.replace(new RegExp('HEIGHT', 'g'),block.height); | |||
| html = html.replace(new RegExp('OUTPUT', 'g'),block.topisto_outputs/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('INPUT', 'g'),block.topisto_inputs/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('REWARD', 'g'),(block.topisto_outputs-block.topisto_inputs)/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('FEE', 'g'),block.topisto_fees/Math.pow(10,8)); | |||
| $('#block0'+index+'_txt').html(html); | |||
| return true; | |||
| } | |||
| </script> | |||
| @ -0,0 +1,14 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="images/block_image.php?methode=treemap_fuzzy" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>fuzzy Treemap</h2> | |||
| <h4>Another Treemap drawing method.</h4> | |||
| <p>Drawing with 2 colors, fuzzy is using to draw the rectangle ...</p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,55 @@ | |||
| <style> | |||
| .fond { | |||
| margin:auto; | |||
| top:0; | |||
| } | |||
| .texte { | |||
| color:black; | |||
| margin:auto; | |||
| position:absolute; | |||
| z-index:2; | |||
| top:0; | |||
| width:100%; | |||
| } | |||
| </style> | |||
| <div class="container-fluid" id="BlocksBrowser"></div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| Drawing the splines | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="js/browse_blocks.js"></script> | |||
| <script> | |||
| function afficherBlock(block, index) | |||
| { | |||
| html = '<img src="splines/image.php?block_hash='+block.hash+'" style="opacity: 0.5;image-orientation: 90deg" width="100%; height: auto"></img>'; | |||
| $('#block0'+index+'_img').html(html); | |||
| html = ''; | |||
| html += '<table width="100%">' | |||
| html += '<tr><td width="10%"> </td><td width="30%">Height</td><td width="50%" align="right"><strong>HEIGHT</strong></td><td width="10%"> </td></tr>'; | |||
| html += '<tr><td width="10%"> </td><td width="30%">Outputs</td><td width="50%" align="right"><strong>OUTPUT</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| //html += '<tr><td width="10%"> </td><td width="30%">Inputs</td><td width="50%" align="right"><strong>INPUT</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| html += '<tr><td width="10%"> </td><td width="30%">Reward</td><td width="50%" align="right"><strong>REWARD</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| //html += '<tr><td width="10%"> </td><td width="30%">Fees</td><td width="50%" align="right"><strong>FEE</strong> <span class="glyphicon glyphicon-bitcoin"></span></td><td width="10%"> </td></tr>'; | |||
| html += '</table>' | |||
| html = html.replace(new RegExp('INDEX', 'g'),block.block_index); | |||
| html = html.replace(new RegExp('HEIGHT', 'g'),block.height); | |||
| html = html.replace(new RegExp('OUTPUT', 'g'),block.topisto_outputs/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('INPUT', 'g'),block.topisto_inputs/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('REWARD', 'g'),(block.topisto_outputs-block.topisto_inputs)/Math.pow(10,8)); | |||
| html = html.replace(new RegExp('FEE', 'g'),block.topisto_fees/Math.pow(10,8)); | |||
| $('#block0'+index+'_txt').html(html); | |||
| return true; | |||
| } | |||
| </script> | |||
| @ -0,0 +1,14 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="images/block_image.php?methode=treemap_fuzzy" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>fuzzy Treemap</h2> | |||
| <h4>Another Treemap drawing method.</h4> | |||
| <p>Drawing with 2 colors, fuzzy is using to draw the rectangle ...</p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,24 @@ | |||
| <style> | |||
| .fond { | |||
| margin:auto; | |||
| top:0; | |||
| } | |||
| .texte { | |||
| color:black; | |||
| margin:auto; | |||
| position:absolute; | |||
| z-index:2; | |||
| top:0; | |||
| width:100%; | |||
| } | |||
| </style> | |||
| <div class="container-fluid" id="BlocksBrowser"></div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| Drawing the splines | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,34 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="images/full_block_image.php?methode=full_treemap" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Draw the "Full" Block</h2> | |||
| <h4>Use the drawing methods from below, but drawing each part of the block : inputs, outputs, fees and reward</h4> | |||
| <p>You can choose an alternative drawing method. | |||
| <select id="selectMethod_ARTICLE" onchange="javascript:DrawLastFullBlock_ARTICLE()"> | |||
| <option value="full_treemap">Treemap</option> | |||
| <option value="full_spline">Spline</option> | |||
| <option value="full_treemap_fuzzy">Fuzzy</option> | |||
| </select> | |||
| </p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script> | |||
| function DrawLastFullBlock_ARTICLE() | |||
| { | |||
| var methode = $("#selectMethod_ARTICLE").val(); | |||
| var downloadingImage = new Image(); | |||
| downloadingImage.onload = function(){ | |||
| $('#logo_ARTICLE').attr('src', this.src); | |||
| }; | |||
| $('#logo_ARTICLE').attr('src','images/loading.gif'); | |||
| downloadingImage.src = 'images/full_block_image.php?methode='+methode; | |||
| return true; | |||
| } | |||
| </script> | |||
| @ -0,0 +1,24 @@ | |||
| <style> | |||
| .fond { | |||
| margin:auto; | |||
| top:0; | |||
| } | |||
| .texte { | |||
| color:black; | |||
| margin:auto; | |||
| position:absolute; | |||
| z-index:2; | |||
| top:0; | |||
| width:100%; | |||
| } | |||
| </style> | |||
| <div class="container-fluid" id="BlocksBrowser"></div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| Drawing the splines | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,584 @@ | |||
| // shim layer with setTimeout fallback (Paul Irish) | |||
| window.requestAnimFrame = (function(){ | |||
| return window.requestAnimationFrame || | |||
| window.webkitRequestAnimationFrame || | |||
| window.mozRequestAnimationFrame || | |||
| window.oRequestAnimationFrame || | |||
| window.msRequestAnimationFrame || | |||
| function( callback ){ | |||
| window.setTimeout(callback, 1000 / 60); | |||
| }; | |||
| })(); | |||
| // Based on http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf | |||
| var NavierStokes = function(settings){ | |||
| this.init(settings); | |||
| }; | |||
| NavierStokes.prototype = { | |||
| init : function(settings){ | |||
| var defaultSettings = { | |||
| resolution : 64, | |||
| iterations : 10, | |||
| fract : 1/4, | |||
| diffusion : 1, | |||
| gridmodify : 0, | |||
| dt : 0.1, | |||
| callbackUser : function(D, U, V, size){}, | |||
| callbackDisplay : function(D, U, V, size){} | |||
| }; | |||
| if (this.settings === undefined){ | |||
| this.settings = defaultSettings; | |||
| } | |||
| this._mergeRecursive(this.settings, settings); | |||
| this.rows = this.settings.resolution + 2; | |||
| this.arraySize = (this.settings.resolution+2)*(this.settings.resolution+2); | |||
| this.U = new Float32Array(this.arraySize); | |||
| this.V = new Float32Array(this.arraySize); | |||
| this.D = new Float32Array(this.arraySize); | |||
| this.U_prev = new Float32Array(this.arraySize); | |||
| this.V_prev = new Float32Array(this.arraySize); | |||
| this.D_prev = new Float32Array(this.arraySize); | |||
| this.NullArray = new Float32Array(this.arraySize); | |||
| // Precalculate lookup table for 2D > 1D array. | |||
| this.IX = new Array(this.rows); | |||
| for (var i = 0; i < this.rows; i++){ | |||
| this.IX[i] = new Array(this.rows); | |||
| for (var b = 0; b < this.rows; b++){ | |||
| this.IX[i][b] = i+b*this.rows; | |||
| } | |||
| } | |||
| // Init all Arrays. | |||
| for (i = 0; i < this.arraySize; i++){ | |||
| this.D_prev[i] = this.U_prev[i] = this.V_prev[i] = this.D[i] = this.U[i] = this.V[i] = this.NullArray[i] = 0.0; | |||
| } | |||
| //Init some vars based on the Resolution settings: | |||
| this.calculateSettings(); | |||
| }, | |||
| clear : function(){ | |||
| this.D.set(this.NullArray); | |||
| this.U.set(this.NullArray); | |||
| this.V.set(this.NullArray); | |||
| }, | |||
| // Getter Setter | |||
| getResolution : function(){ | |||
| return this.settings.resolution; | |||
| }, | |||
| getSettings : function(){ | |||
| return this.settings; | |||
| }, | |||
| update : function(){ | |||
| // Add user Action | |||
| this.userAction(); | |||
| // Cals velosity & density | |||
| this.vel_step (this.U, this.V, this.U_prev, this.V_prev, this.settings.dt ); | |||
| this.dens_step (this.D, this.D_prev, this.U, this.V, this.settings.dt ); | |||
| this.settings.callbackDisplay(this.D, this.U, this.V, this.settings.resolution); | |||
| }, | |||
| calculateSettings : function(){ | |||
| this.centerPos = (-0.5/this.settings.resolution) * (1 + this.settings.gridmodify); | |||
| this.scale = this.settings.resolution * 0.5; | |||
| this.dt0 = this.settings.dt * this.settings.resolution; | |||
| this.p5 = this.settings.resolution + 0.5; | |||
| }, | |||
| userAction : function (){ | |||
| this.D_prev.set(this.NullArray); | |||
| this.U_prev.set(this.NullArray); | |||
| this.V_prev.set(this.NullArray); | |||
| this.settings.callbackUser(this.D_prev, this.U_prev, this.V_prev, this.settings.resolution); | |||
| }, | |||
| vel_step : function (u, v, u0, v0, dt ){ | |||
| var tmp; | |||
| this.add_source(u, u0, dt ); | |||
| this.add_source(v, v0, dt ); | |||
| tmp = u0; | |||
| u0 = u; | |||
| u = tmp; | |||
| this.diffuse ( 1, u, u0, dt ); | |||
| tmp = v0; | |||
| v0 = v; | |||
| v = tmp; | |||
| this.diffuse ( 2, v, v0, dt ); | |||
| this.project(u, v, u0, v0); | |||
| tmp = u0; | |||
| u0 = u; | |||
| u = tmp; | |||
| tmp = v0; | |||
| v0 = v; | |||
| v = tmp; | |||
| this.advect(1, u, u0, u0, v0, dt); | |||
| this.advect(2, v, v0, u0, v0, dt); | |||
| this.project(u, v, u0, v0 ); | |||
| }, | |||
| dens_step : function (x, x0, u, v, dt){ | |||
| var tmp; | |||
| this.add_source (x, x0, dt); | |||
| //SWAP ( x0, x ); | |||
| this.diffuse (0, x0, x, dt ); | |||
| //SWAP ( x0, x ); | |||
| this.advect ( 0, x, x0, u, v, dt ); | |||
| }, | |||
| add_source : function (x, s, dt){ | |||
| for (var i=0; i<this.arraySize ; i++ ){ | |||
| x[i] += dt * s[i]; | |||
| } | |||
| }, | |||
| diffuse : function (b, x, x0, dt){ | |||
| for (var i=1; i < this.arraySize;i++){ | |||
| x[i] = x0[i]*this.settings.diffusion; | |||
| } | |||
| this.set_bnd(b, x); | |||
| }, | |||
| project : function ( u, v, p, div ) { | |||
| var i, k,prevRow, thisRow, nextValue, nextRow, to, lastRow; | |||
| for (i = 1 ; i <= this.settings.resolution; i++ ){ | |||
| prevRow = this.IX[0][i-1]; | |||
| thisRow = this.IX[0][i]; | |||
| nextRow = this.IX[0][i+1]; | |||
| valueBefore = thisRow - 1; | |||
| valueNext = thisRow + 1; | |||
| to = this.settings.resolution + valueNext; | |||
| for (k = valueNext; k < to; k++ ) { | |||
| p[k] = 0; | |||
| div[k] = (u[++valueNext] - u[++valueBefore] + v[++nextRow] - v[++prevRow]) * this.centerPos; | |||
| } | |||
| } | |||
| this.set_bnd(0, div); | |||
| this.set_bnd(0, p); | |||
| for (k=0 ; k<this.settings.iterations; k++) { | |||
| for (j=1 ; j<=this.settings.resolution; j++) { | |||
| lastRow = this.IX[0][j-1]; | |||
| thisRow = this.IX[0][j]; | |||
| nextRow = this.IX[0][j+1]; | |||
| prevX = p[thisRow]; | |||
| thisRow++; | |||
| for (i=1; i<=this.settings.resolution; i++){ | |||
| p[thisRow] = prevX = (div[thisRow] + p[++lastRow] + p[++thisRow] + p[++nextRow] + prevX ) * this.settings.fract; | |||
| } | |||
| } | |||
| this.set_bnd(0, p); | |||
| } | |||
| for (j = 1; j <= this.settings.resolution; j++ ) { | |||
| lastRow = this.IX[0][j-1]; | |||
| thisRow = this.IX[0][j]; | |||
| nextRow = this.IX[0][j+1]; | |||
| valueBefore = thisRow - 1; | |||
| valueNext = thisRow + 1; | |||
| for (i = 1; i <= this.settings.resolution; i++) { | |||
| u[++thisRow] -= this.scale * (p[++valueNext] - p[++valueBefore]); | |||
| v[thisRow] -= this.scale * (p[++nextRow] - p[++lastRow]); | |||
| } | |||
| } | |||
| this.set_bnd(1, u); | |||
| this.set_bnd(2, v); | |||
| }, | |||
| advect : function (b, d, d0, u, v, dt ){ | |||
| var to; | |||
| for (var j = 1; j<= this.settings.resolution; j++) { | |||
| var pos = j * this.rows; | |||
| for (var k = 1; k <= this.settings.resolution; k++) { | |||
| var x = k - this.dt0 * u[++pos]; | |||
| var y = j - this.dt0 * v[pos]; | |||
| if (x < 0.5) x = 0.5 | |||
| if (x > this.p5) x = this.p5; | |||
| var i0 = x | 0; | |||
| var i1 = i0 + 1; | |||
| if (y < 0.5) y = 0.5 | |||
| if (y > this.p5) y = this.p5; | |||
| var j0 = y | 0; | |||
| var j1 = j0 + 1; | |||
| var s1 = x - i0; | |||
| var s0 = 1 - s1; | |||
| var t1 = y - j0; | |||
| var t0 = 1 - t1; | |||
| var toR1 = this.IX[0][j0]; | |||
| var toR2 = this.IX[0][j1]; | |||
| d[pos] = s0 * (t0 * d0[i0 + toR1] + t1 * d0[i0 + toR2]) + s1 * (t0 * d0[i1 + toR1] + t1 * d0[i1 + toR2]); | |||
| } | |||
| } | |||
| this.set_bnd(b, d); | |||
| }, | |||
| // Calculate Boundary's | |||
| set_bnd : function (b, x ){ | |||
| var i, j; | |||
| switch (b){ | |||
| case 1 : | |||
| for (i = 1; i <= this.settings.resolution; i++) { | |||
| x[i] = x[i + this.rows]; | |||
| x[this.IX[i][(this.settings.resolution+1)]] = x[this.IX[i][(this.settings.resolution)]]; | |||
| x[this.IX[0][i]] = -x[this.IX[1][i]]; | |||
| x[this.IX[(this.settings.resolution+1)][i]] = -x[this.IX[(this.settings.resolution)][i]]; | |||
| } | |||
| break; | |||
| case 2 : | |||
| for ( i = 1; i <= this.settings.resolution; i++) { | |||
| x[i] = -x[i + this.rows]; | |||
| x[this.IX[i][(this.settings.resolution+1)]] = -x[this.IX[i][(this.settings.resolution)]]; | |||
| x[this.IX[0][i]] = x[this.IX[1][i]]; | |||
| x[this.IX[(this.settings.resolution+1)][i]] = x[this.IX[(this.settings.resolution)][i]]; | |||
| } | |||
| break; | |||
| default : | |||
| for ( i = 1; i <= this.settings.resolution; i++) { | |||
| x[i] = x[i + this.rows]; | |||
| x[this.IX[i][(this.settings.resolution+1)]] = x[this.IX[i][(this.settings.resolution)]]; | |||
| x[this.IX[0][i]] = x[this.IX[1][i]]; | |||
| x[this.IX[(this.settings.resolution+1)][i]] = x[this.IX[(this.settings.resolution)][i]]; | |||
| } | |||
| } | |||
| // Boundes of the Canvas | |||
| var topPos = this.IX[0][this.settings.resolution+1]; | |||
| x[0] = (x[1] + x[this.rows]) / 2; | |||
| x[topPos] = (x[1 + topPos] + x[this.IX[this.settings.resolution][0]]) / 2; | |||
| x[(this.settings.resolution+1)] = (x[this.settings.resolution] + x[(this.settings.resolution + 1) + this.rows]) / 2; | |||
| x[(this.settings.resolution+1)+topPos] = (x[this.settings.resolution + topPos] + x[this.IX[this.settings.resolution+1][this.settings.resolution]]); | |||
| }, | |||
| // Merge Settings. | |||
| _mergeRecursive : function(obj1, obj2) { | |||
| for (var p in obj2) { | |||
| if ( obj2[p].constructor==Object ) { | |||
| obj1[p] = this._mergeRecursive(obj1[p], obj2[p]); | |||
| } else { | |||
| obj1[p] = obj2[p]; | |||
| } | |||
| } | |||
| return obj1; | |||
| } | |||
| }; | |||
| var Display = function(canvas){ | |||
| this.colorFunctions = { | |||
| "BW" : this.calcColorBW , | |||
| "Color": this.calcColor, | |||
| "User" : this.calcUserColor | |||
| }; | |||
| this.currColorFunc = "BW"; | |||
| this.canvas = canvas; | |||
| this.context = canvas.getContext("2d", {alpha : false}); | |||
| this.supportImageData = !!this.context.getImageData; | |||
| this.colorUser = { | |||
| R : 0, | |||
| G : 255, | |||
| B : 0 | |||
| } | |||
| }; | |||
| Display.prototype = { | |||
| init : function (res){ | |||
| this.calcResolution = res; | |||
| this.imageData = null; | |||
| this.showColors = false; | |||
| this.line = null; | |||
| }, | |||
| density : function(D, U, V){ | |||
| var r,g,b, x, y, d; | |||
| if (this.supportImageData){ | |||
| // Get Image Data | |||
| if (this.imageData === null){ | |||
| this.imageData = this.context.getImageData(0, 0, this.calcResolution, this.calcResolution); | |||
| } | |||
| for (x = 0; x < this.calcResolution; x++) { | |||
| for (y = 0; y < this.calcResolution; y++){ | |||
| var posC = (x + y * this.calcResolution) * 4; | |||
| var pos = fluid.IX[x][y]; | |||
| var cArray = this.colorFunctions[this.currColorFunc].call(this, D, U, V, pos); | |||
| this.imageData.data[posC] = cArray[0]; // R | |||
| this.imageData.data[posC + 1] = cArray[1]; // G | |||
| this.imageData.data[posC + 2] = cArray[2]; // B | |||
| this.imageData.data[posC + 3] = 255; //A | |||
| } | |||
| } | |||
| this.context.putImageData(this.imageData, 0, 0); | |||
| }else{ | |||
| // Slow fallback for oldie | |||
| for (x = 0; x < this.calcResolution; x++) { | |||
| for (y = 0; y < this.calcResolution; y++) { | |||
| var pos = fluid.IX[x][y]; | |||
| var c =(D[pos] / 2); | |||
| this.context.setFillColor(c , c, c , 1); | |||
| this.context.fillRect(x, y, 1, 1); | |||
| } | |||
| } | |||
| } | |||
| // Draw the line for creating an Emitter | |||
| if (this.line != null){ | |||
| this.context.beginPath(); | |||
| this.context.lineWidth = 1; | |||
| this.context.strokeStyle = "rgb(255,255,255)"; | |||
| this.context.moveTo(this.line[0][0],this.line[0][1]); | |||
| this.context.lineTo(this.line[1][0],this.line[1][1]); | |||
| this.context.stroke(); | |||
| } | |||
| }, | |||
| setColorFunction: function(colorFuncName){ | |||
| this.currColorFunc = "BW"; | |||
| }, | |||
| calcColorBW : function(D, U, V, pos){ | |||
| var bw = (D[pos] * 255 / 6) | 0; | |||
| return [bw, bw, bw]; | |||
| }, | |||
| calcColor : function(D, U, V, pos){ | |||
| var r = Math.abs((U[pos] * 1300 ) | 0); | |||
| var b = Math.abs((V[pos] * 1300 ) | 0); | |||
| var g = (D[pos] * 255 / 6) | 0; | |||
| return [r, g, b]; | |||
| }, | |||
| calcUserColor : function(D, U, V, pos){ | |||
| var r = Math.abs((U[pos] *500* this.colorUser.R) | 0); | |||
| var g = (D[pos] * this.colorUser.G) | 0; | |||
| var b = Math.abs((V[pos] *500* this.colorUser.B ) | 0); | |||
| return [r, g, b]; | |||
| }, | |||
| drawLine : function (a0, a1, scale){ | |||
| var l0 = [a0[0]*scale, a0[1]*scale]; | |||
| var l1 = [a1[0]*scale, a1[1]*scale]; | |||
| this.line = [l0, l1]; | |||
| }, | |||
| removeLine : function(){this.line = null}, | |||
| }; | |||
| var user = { | |||
| displaySize : 500, | |||
| canvas : null, | |||
| canvasOffset : null, | |||
| scale : 0, | |||
| mouseStart: [], | |||
| mouseEnd : [], | |||
| mouseLeftDown : false, | |||
| mousePath : [], | |||
| mouseRightDown : false, | |||
| mouseRightStart : [], | |||
| forceEmitters : [], | |||
| insertedDensity : 50, | |||
| init : function(canvas){ | |||
| this.canvas = $(canvas); | |||
| this.canvasOffset = this.canvas.offset(); | |||
| var that = this; | |||
| window.ontouchend = window.onmouseup = function(e){that.handleInputEnd(e);}; | |||
| canvas.ontouchstart = canvas.onmousedown = function(e){that.handleInput(e);}; | |||
| canvas.ontouchmove = canvas.onmousemove = function(e){that.handleInputMove(e);}; | |||
| canvas.oncontextmenu = function(e){e.preventDefault();}; | |||
| }, | |||
| interact : function(D, U, V, size){ | |||
| var x, y, pos, i ; | |||
| if (this.mouseLeftDown){ | |||
| var dx = this.mouseStart[0] - this.mouseEnd[0]; | |||
| var dy = this.mouseStart[1] - this.mouseEnd[1]; | |||
| var mousePathLength = Math.sqrt(dx * dx + dy * dy) | 0; | |||
| mousePathLength = (mousePathLength < 1) ? 1 : mousePathLength; | |||
| for ( i = 0; i < mousePathLength; i++) { | |||
| x = (((this.mouseStart[0] - (i / mousePathLength) * dx)) * this.scale) | 0; | |||
| y = (((this.mouseStart[1] - (i / mousePathLength) * dy)) * this.scale) | 0; | |||
| pos = fluid.IX[x][y]; | |||
| U[pos] = -dx / 6; | |||
| V[pos] = -dy / 6; | |||
| D[pos] = this.insertedDensity; | |||
| } | |||
| this.mouseStart[0] = this.mouseEnd[0]; | |||
| this.mouseStart[1] = this.mouseEnd[1]; | |||
| } | |||
| for (i = 0;i<this.forceEmitters.length;i++){ | |||
| var posDir = this.forceEmitters[i]; | |||
| var pos = fluid.IX[posDir[0]*this.scale | 0][posDir[1]*this.scale | 0]; | |||
| U[pos] = posDir[2]; | |||
| V[pos] = posDir[3]; | |||
| } | |||
| }, | |||
| initFluidWithResolution : function(){ | |||
| fluid.init(); | |||
| this.setCanvasSize(fluid.settings.resolution); | |||
| display.init(fluid.settings.resolution); | |||
| }, | |||
| clearDisplay : function(){ | |||
| fluid.clear(); | |||
| this.forceEmitters = []; | |||
| }, | |||
| setDisplay : function(e){ | |||
| var displaySize = parseInt(e); | |||
| if (displaySize === 0){ // 1:1 | |||
| this.setDisplaySize(fluid.getSettings().resolution); | |||
| }else{ | |||
| this.setDisplaySize(displaySize); | |||
| } | |||
| }, | |||
| setCanvasSize : function(size){ | |||
| this.canvas.attr("width", size); | |||
| this.canvas.attr("height", size); | |||
| this.calculateScale(); | |||
| }, | |||
| setDisplaySize : function(size){ | |||
| $('#wrapper').width(size); | |||
| this.canvas.width(size); | |||
| this.canvas.height(size); | |||
| this.calculateScale(); | |||
| }, | |||
| calculateScale : function(){ | |||
| this.scale = fluid.getResolution() / this.canvas.width(); | |||
| this.canvasOffset = this.canvas.offset(); | |||
| }, | |||
| handleInput : function(e){ | |||
| if (e.type === "touchstart"){ | |||
| this.mouseLeftDown = true; | |||
| this.mouseEnd = this.mouseStart = [e.pageX - this.canvasOffset.left, e.pageY - this.canvasOffset.top]; | |||
| }else if (e.button !== undefined){ | |||
| var mPos = this.getMousePositon(e); | |||
| if (e.button == 0){ | |||
| this.mouseLeftDown = true; | |||
| this.mouseEnd = this.mouseStart = mPos; | |||
| }else if (e.button == 2){ | |||
| this.mouseRightDown = true; | |||
| this.mouseRightStart = mPos; | |||
| } | |||
| } | |||
| e.preventDefault(); | |||
| }, | |||
| handleInputMove : function(e){ | |||
| var mPos = this.getMousePositon(e); | |||
| if (this.mouseLeftDown){ | |||
| if (e.type === "touchmove"){ | |||
| this.mouseEnd = [e.pageX - this.canvasOffset.left, e.pageY - this.canvasOffset.top]; | |||
| }else{ | |||
| this.mouseEnd = mPos; | |||
| } | |||
| } | |||
| if (this.mouseRightDown){ | |||
| display.drawLine(this.mouseRightStart, mPos, this.scale); | |||
| } | |||
| }, | |||
| handleInputEnd : function(e){ | |||
| this.mouseLeftDown = false; | |||
| if (this.mouseRightDown){ | |||
| this.mouseRightDown = false; | |||
| var endPos = this.getMousePositon(e); | |||
| var x = this.mouseRightStart[0]; | |||
| var y = this.mouseRightStart[1]; | |||
| var dx = -(x - endPos[0]) / 10; | |||
| var dy = -(y - endPos[1]) / 10; | |||
| this.forceEmitters.push([x, y, dx, dy]); | |||
| display.removeLine(); | |||
| } | |||
| }, | |||
| getMousePositon : function (e){ | |||
| var mouseX, mouseY; | |||
| if(e.offsetX) { | |||
| mouseX = e.offsetX; | |||
| mouseY = e.offsetY; | |||
| } | |||
| else if(e.layerX) { | |||
| mouseX = e.layerX; | |||
| mouseY = e.layerY; | |||
| } | |||
| return [mouseX, mouseY]; | |||
| } | |||
| }; | |||
| var canvas = document.getElementById("d"); | |||
| var display = new Display(canvas); | |||
| var fluid = new NavierStokes({ | |||
| callbackDisplay : function(D, U, V, size){ | |||
| display.density(D, U, V, size); | |||
| }, | |||
| callbackUser : function(D, U, V, size){ | |||
| user.interact(D, U, V, size); | |||
| }}); | |||
| user.init(canvas, fluid, display); | |||
| user.initFluidWithResolution(); | |||
| //Start Simulation | |||
| function simulation(){ | |||
| window.requestAnimFrame(simulation); | |||
| //var thisFrameTime = (thisLoop=new Date) - lastLoop; | |||
| //frameTime+= (thisFrameTime - frameTime) / filterStrength; | |||
| //lastLoop = thisLoop; | |||
| fluid.update(); | |||
| }; | |||
| // Run Simulation | |||
| simulation(); | |||
| @ -0,0 +1,19 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br> | |||
| <canvas id="d" width="64" height="64" style="width:100%;height:250px;display:block;position:relative;"></canvas> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Test Fluid animation</h2> | |||
| <h4>Try a new kind of visualisation</h4> | |||
| <p> | |||
| Inspired from <a href="https://codepen.io/FWeinb/pen/JhzvI" target="_blank">Fabrice Weinberg's code</a> | |||
| </p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="articles/20180224/fluid.js"></script> | |||
| @ -0,0 +1,26 @@ | |||
| <style> | |||
| .fond { | |||
| margin:auto; | |||
| top:0; | |||
| } | |||
| .texte { | |||
| color:black; | |||
| margin:auto; | |||
| position:absolute; | |||
| z-index:2; | |||
| top:0; | |||
| width:100%; | |||
| } | |||
| </style> | |||
| <div class="container-fluid" id="BlocksBrowser"></div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12 text-center"> | |||
| OK ... Here i don't really understand anything.<br> | |||
| That's a copy/passte of Jeff's Thomas code.<br> | |||
| And i just replace mouse move by a path computed from the block HASH. | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,780 @@ | |||
| /* | |||
| Comments were requested, here we go :) | |||
| Here's the rundown: | |||
| This script creates a grid of cells and a separate layer of particles that | |||
| float on top of the grid. Each cell of the grid holds X and Y velocity | |||
| (direction and magnitude) values and a pressure value. | |||
| Whenever the user holds down and moves their mouse over the canvas, the velocity | |||
| of the mouse is calculated and is used to influence the velocity and pressure in | |||
| each cell that was within the defined range of the mouse coordinates. Then, the | |||
| pressure change is communicated to all of the neighboring cells of those affected, | |||
| adjusting their velocity and pressure, and this is repeated over and over until | |||
| the change propogates to all of the cells in the path of the direction of movement. | |||
| The particles are randomly placed on the canvas and move according to the | |||
| velocity of the grid cells below, similar to grass seed floating on the surface | |||
| of water as it's moving. Whenever the particles move off the edge of the canvas, | |||
| they are "dropped" back on to the canvas in a random position. The velocity, | |||
| however, is "wrapped" around to the opposite edge of the canvas. The slowing | |||
| down of the movement is simulated viscosity, which is basically frictional drag | |||
| in the liquid. | |||
| Let's get started: | |||
| -------- | |||
| This is a self-invoking function. Basically, that means that it runs itself | |||
| automatically. The reason for wrapping the script in this is to isolate the | |||
| majority of the variables that I define inside from the global scope and | |||
| only reveal specific functions and values. It looks like this: | |||
| (function(argument) { | |||
| alert(argument); | |||
| })("Yo."); | |||
| and it does the same thing as this: | |||
| function thing(argument) { | |||
| alert(argument); | |||
| } | |||
| thing("Yo."); | |||
| */ | |||
| /* added by TOPISTO */ | |||
| function myGetRandom(max) | |||
| { | |||
| return Math.floor(Math.random() * max); | |||
| } | |||
| (function(w) { | |||
| var canvas, ctx; | |||
| /* | |||
| These are the variable definitions for the values that will be used | |||
| throughout the rest of the script. | |||
| */ | |||
| var canvas_width = 500; //Needs to be a multiple of the resolution value below. | |||
| var canvas_height = 250; //This too. | |||
| /* | |||
| This is an associative array to hold the status of the mouse cursor | |||
| Whenever the mouse is moved or pressed, there are event handlers that | |||
| update the values in this array. | |||
| */ | |||
| var mouse = { | |||
| x: Math.floor(canvas_width / 2), | |||
| y: Math.floor(canvas_height / 2), | |||
| px: Math.floor(canvas_width / 2), | |||
| py: Math.floor(canvas_height / 2), | |||
| down: false | |||
| }; | |||
| //var resolution = 10; //Width and height of each cell in the grid. | |||
| var resolution = 5; | |||
| var pen_size = 20; //Radius around the mouse cursor coordinates to reach when stirring | |||
| var num_cols = canvas_width / resolution; //This value is the number of columns in the grid. | |||
| var num_rows = canvas_height / resolution; //This is number of rows. | |||
| //var speck_count = 5000; //This determines how many particles will be made. | |||
| var speck_count = 5000; | |||
| var vec_cells = []; //The array that will contain the grid cells | |||
| var particles = []; //The array that will contain the particles | |||
| /* added by topisto */ | |||
| var current_block_hash = []; | |||
| var current_block_height = myGetRandom(canvas_height); | |||
| var current_block_hash_ndx = 0; | |||
| var current_block_hash_pos = 0; | |||
| var current_topisto_way = 0; // 0 : left 2 right, 1 : right 2 left, 2 : top 2 bottom, 3 : bottom 2 top | |||
| /* | |||
| This is the main function. It is triggered to start the process of constructing the | |||
| the grid and creating the particles, attaching event handlers, and starting the | |||
| animation loop. | |||
| */ | |||
| function init() { | |||
| vec_cells = []; | |||
| particles = []; | |||
| /* added by topisto */ | |||
| if (current_block_hash.length == 0) for(i=0;i<64;i++) current_block_hash[i] = myGetRandom(16); | |||
| //These lines get the canvas DOM element and canvas context, respectively. | |||
| canvas = document.getElementById("c"); | |||
| ctx = canvas.getContext("2d"); | |||
| //These two set the width and height of the canvas to the defined values. | |||
| canvas.width = canvas_width; | |||
| canvas.height = canvas_height; | |||
| /* | |||
| This loop begins at zero and counts up to the defined number of particles, | |||
| less one, because array elements are numbered beginning at zero. | |||
| */ | |||
| for (i = 0; i < speck_count; i++) { | |||
| /* | |||
| This calls the function particle() with random X and Y values. It then | |||
| takes the returned object and pushes it into the particles array at the | |||
| end. | |||
| */ | |||
| particles.push(new particle(Math.random() * canvas_width, Math.random() * canvas_height)); | |||
| } | |||
| //This loops through the count of columns. | |||
| for (col = 0; col < num_cols; col++) { | |||
| //This defines the array element as another array. | |||
| vec_cells[col] = []; | |||
| //This loops through the count of rows. | |||
| for (row = 0; row < num_rows; row++) { | |||
| /* | |||
| This line calls the cell() function, which creates an individual grid cell | |||
| and returns it as an object. The X and Y values are multiplied by the | |||
| resolution so that when the loops are referring to "column 2, row 2", the | |||
| width and height of "column 1, row 1" are counted in so that the top-left | |||
| corner of the new grid cell is at the bottom right of the other cell. | |||
| */ | |||
| var cell_data = new cell(col * resolution, row * resolution, resolution) | |||
| //This pushes the cell object into the grid array. | |||
| vec_cells[col][row] = cell_data; | |||
| /* | |||
| These two lines set the object's column and row values so the object knows | |||
| where in the grid it is positioned. | |||
| */ | |||
| vec_cells[col][row].col = col; | |||
| vec_cells[col][row].row = row; | |||
| } | |||
| } | |||
| /* | |||
| These loops move through the rows and columns of the grid array again and set variables | |||
| in each cell object that will hold the directional references to neighboring cells. | |||
| For example, let's say the loop is currently on this cell: | |||
| OOOOO | |||
| OOOXO | |||
| OOOOO | |||
| These variables will hold the references to neighboring cells so you only need to | |||
| use "up" to refer to the cell above the one you're currently on. | |||
| */ | |||
| for (col = 0; col < num_cols; col++) { | |||
| for (row = 0; row < num_rows; row++) { | |||
| /* | |||
| This variable holds the reference to the current cell in the grid. When you | |||
| refer to an element in an array, it doesn't copy that value into the new | |||
| variable; the variable stores a "link" or reference to that spot in the array. | |||
| If the value in the array is changed, the value of this variable would change | |||
| also, and vice-versa. | |||
| */ | |||
| var cell_data = vec_cells[col][row]; | |||
| /* | |||
| Each of these lines has a ternary expression. A ternary expression is similar | |||
| to an if/then clause and is represented as an expression (e.g. row - 1 >= 0) | |||
| which is evaluated to either true or false. If it's true, the first value after | |||
| the question mark is used, and if it's false, the second value is used instead. | |||
| If you're on the first row and you move to the row above, this wraps the row | |||
| around to the last row. This is done so that momentum that is pushed to the edge | |||
| of the canvas is "wrapped" to the opposite side. | |||
| */ | |||
| var row_up = (row - 1 >= 0) ? row - 1 : num_rows - 1; | |||
| var col_left = (col - 1 >= 0) ? col - 1 : num_cols - 1; | |||
| var col_right = (col + 1 < num_cols) ? col + 1 : 0; | |||
| //Get the reference to the cell on the row above. | |||
| var up = vec_cells[col][row_up]; | |||
| var left = vec_cells[col_left][row]; | |||
| var up_left = vec_cells[col_left][row_up]; | |||
| var up_right = vec_cells[col_right][row_up]; | |||
| /* | |||
| Set the current cell's "up", "left", "up_left" and "up_right" attributes to the | |||
| respective neighboring cells. | |||
| */ | |||
| cell_data.up = up; | |||
| cell_data.left = left; | |||
| cell_data.up_left = up_left; | |||
| cell_data.up_right = up_right; | |||
| /* | |||
| Set the neighboring cell's opposite attributes to point to the current cell. | |||
| */ | |||
| up.down = vec_cells[col][row]; | |||
| left.right = vec_cells[col][row]; | |||
| up_left.down_right = vec_cells[col][row]; | |||
| up_right.down_left = vec_cells[col][row]; | |||
| } | |||
| } | |||
| /* | |||
| These lines create triggers that fire when certain events happen. For | |||
| instance, when you move your mouse, the mouse_move_handler() function | |||
| will run and will be passed the event object reference into it's "e" | |||
| variable. Something to note, the mousemove event doesn't necessarily | |||
| fire for *every* mouse coordinate position; the mouse movement is | |||
| sampled at a certain rate, meaning that it's checked periodically, and | |||
| if the mouse has moved, the event is fired and the current coordinates | |||
| are sent. That's why you'll see large jumps from one pair of coordinates | |||
| to the next if you move your mouse very fast across the screen. That's | |||
| also how I measure the mouse's velocity. | |||
| */ | |||
| /* | |||
| w.addEventListener("mousedown", mouse_down_handler); | |||
| w.addEventListener("touchstart", mouse_down_handler); | |||
| w.addEventListener("mouseup", mouse_up_handler); | |||
| w.addEventListener("touchend", touch_end_handler); | |||
| canvas.addEventListener("mousemove", mouse_move_handler); | |||
| canvas.addEventListener("touchmove", touch_move_handler); | |||
| */ | |||
| //When the page is finished loading, run the draw() function. | |||
| w.onload = draw; | |||
| } | |||
| /* | |||
| This function updates the position of the particles according to the velocity | |||
| of the cells underneath, and also draws them to the canvas. | |||
| */ | |||
| function update_particle() { | |||
| //Loops through all of the particles in the array | |||
| for (i = 0; i < particles.length; i++) { | |||
| //Sets this variable to the current particle so we can refer to the particle easier. | |||
| var p = particles[i]; | |||
| //If the particle's X and Y coordinates are within the bounds of the canvas... | |||
| if (p.x >= 0 && p.x < canvas_width && p.y >= 0 && p.y < canvas_height) { | |||
| /* | |||
| These lines divide the X and Y values by the size of each cell. This number is | |||
| then parsed to a whole number to determine which grid cell the particle is above. | |||
| */ | |||
| var col = parseInt(p.x / resolution); | |||
| var row = parseInt(p.y / resolution); | |||
| //Same as above, store reference to cell | |||
| var cell_data = vec_cells[col][row]; | |||
| /* | |||
| These values are percentages. They represent the percentage of the distance across | |||
| the cell (for each axis) that the particle is positioned. To give an example, if | |||
| the particle is directly in the center of the cell, these values would both be "0.5" | |||
| The modulus operator (%) is used to get the remainder from dividing the particle's | |||
| coordinates by the resolution value. This number can only be smaller than the | |||
| resolution, so we divide it by the resolution to get the percentage. | |||
| */ | |||
| var ax = (p.x % resolution) / resolution; | |||
| var ay = (p.y % resolution) / resolution; | |||
| /* | |||
| These lines subtract the decimal from 1 to reverse it (e.g. 100% - 75% = 25%), multiply | |||
| that value by the cell's velocity, and then by 0.05 to greatly reduce the overall change in velocity | |||
| per frame (this slows down the movement). Then they add that value to the particle's velocity | |||
| in each axis. This is done so that the change in velocity is incrementally made as the | |||
| particle reaches the end of it's path across the cell. | |||
| */ | |||
| p.xv += (1 - ax) * cell_data.xv * 0.05; | |||
| p.yv += (1 - ay) * cell_data.yv * 0.05; | |||
| /* | |||
| These next four lines are are pretty much the same, except the neighboring cell's | |||
| velocities are being used to affect the particle's movement. If you were to comment | |||
| them out, the particles would begin grouping at the boundary between cells because | |||
| the neighboring cells wouldn't be able to pull the particle into their boundaries. | |||
| */ | |||
| p.xv += ax * cell_data.right.xv * 0.05; | |||
| p.yv += ax * cell_data.right.yv * 0.05; | |||
| p.xv += ay * cell_data.down.xv * 0.05; | |||
| p.yv += ay * cell_data.down.yv * 0.05; | |||
| //This adds the calculated velocity to the position coordinates of the particle. | |||
| p.x += p.xv; | |||
| p.y += p.yv; | |||
| ctx.strokeStyle = p.color; | |||
| //For each axis, this gets the distance between the old position of the particle and it's new position. | |||
| var dx = p.px - p.x; | |||
| var dy = p.py - p.y; | |||
| //Using the Pythagorean theorum (A^2 + B^2 = C^2), this determines the distance the particle travelled. | |||
| var dist = Math.sqrt(dx * dx + dy * dy); | |||
| //This line generates a random value between 0 and 0.5 | |||
| var limit = Math.random() * 0.5; | |||
| //If the distance the particle has travelled this frame is greater than the random value... | |||
| if (dist > limit) { | |||
| ctx.lineWidth = 1; | |||
| //ctx.lineWidth = 3; | |||
| ctx.beginPath(); //Begin a new path on the canvas | |||
| ctx.moveTo(p.x, p.y); //Move the drawing cursor to the starting point | |||
| ctx.lineTo(p.px, p.py); //Describe a line from the particle's old coordinates to the new ones | |||
| ctx.stroke(); //Draw the path to the canvas | |||
| }else{ | |||
| //If the particle hasn't moved further than the random limit... | |||
| ctx.beginPath(); | |||
| ctx.moveTo(p.x, p.y); | |||
| /* | |||
| Describe a line from the particle's current coordinates to those same coordinates | |||
| plus the random value. This is what creates the shimmering effect while the particles | |||
| aren't moving. | |||
| */ | |||
| ctx.lineTo(p.x + limit, p.y + limit); | |||
| ctx.stroke(); | |||
| } | |||
| //This updates the previous X and Y coordinates of the particle to the new ones for the next loop. | |||
| p.px = p.x; | |||
| p.py = p.y; | |||
| } | |||
| else { | |||
| //If the particle's X and Y coordinates are outside the bounds of the canvas... | |||
| //Place the particle at a random location on the canvas | |||
| p.x = p.px = Math.random() * canvas_width; | |||
| p.y = p.py = Math.random() * canvas_height; | |||
| //Set the particles velocity to zero. | |||
| p.xv = 0; | |||
| p.yv = 0; | |||
| } | |||
| //These lines divide the particle's velocity in half everytime it loops, slowing them over time. | |||
| p.xv *= 0.5; | |||
| p.yv *= 0.5; | |||
| } | |||
| } | |||
| /* | |||
| This is the main animation loop. It is run once from the init() function when the page is fully loaded and | |||
| uses RequestAnimationFrame to run itself again and again. | |||
| */ | |||
| function draw() { | |||
| if (current_block_hash_ndx == 64) | |||
| { | |||
| current_block_hash_ndx = 0; | |||
| current_block_height = myGetRandom(canvas_height); | |||
| current_topisto_way = myGetRandom(2); | |||
| } | |||
| //if ((!mouse.down)&&(30 < myGetRandom(100))) | |||
| if ((!mouse.down)&&(current_block_hash_ndx < 64)) | |||
| { | |||
| /* | |||
| v1 = 5 - myGetRandom(10); | |||
| v2 = 5 - myGetRandom(10); | |||
| mouse.x += v1; | |||
| mouse.y += v2; | |||
| */ | |||
| var pos = -1; | |||
| // if (current_block_hash_ndx == 64) current_block_hash_ndx = 0; | |||
| local_height = (12+(current_block_height-8)); | |||
| mouse.x = Math.floor(((3+current_block_hash_ndx) * canvas_width) / 70); | |||
| if (current_topisto_way == 1) mouse.x = canvas_width - mouse.x; | |||
| mouse.y = Math.floor(local_height + (1.2 * (8 - current_block_hash[current_block_hash_ndx++]))); | |||
| /* | |||
| if (current_block_hash_ndx == 0) | |||
| { | |||
| current_topisto_way = myGetRandom(4); | |||
| current_block_hash_pos = myGetRandom(100); | |||
| current_block_hash_pos = current_block_hash_pos / (canvas_height / 100); | |||
| mouse.y = current_block_hash_pos + (8 - current_block_hash[current_block_hash_ndx++]); | |||
| if (current_topisto_way > 10) // ! impossible | |||
| { | |||
| current_block_hash_pos = current_block_hash_pos / (canvas_width / 100); | |||
| mouse.x = current_block_hash_pos + (8 - current_block_hash[current_block_hash_ndx++]); | |||
| } | |||
| } | |||
| switch(current_topisto_way) | |||
| { | |||
| case 0 : | |||
| case 1 : | |||
| mouse.x = current_block_hash_ndx * ( canvas_width / 64); | |||
| if (current_topisto_way == 1) mouse.x = canvas_width - mouse.x; | |||
| break; | |||
| case 2 : | |||
| case 3 : | |||
| mouse.y = current_block_hash_ndx * ( canvas_height / 64); | |||
| if (current_topisto_way == 3) mouse.y = canvas_height - mouse.y; | |||
| break; | |||
| } | |||
| */ | |||
| if (mouse.x < 0) mouse.x = 0; | |||
| if (mouse.y < 0) mouse.y = 0; | |||
| if (mouse.x > canvas_width) mouse.x = canvas_width; | |||
| if (mouse.y > canvas_height) mouse.y = canvas_height; | |||
| } else { | |||
| mouse.x = mouse.xp; | |||
| mouse.y = mouse.yp; | |||
| } | |||
| /* | |||
| This calculates the velocity of the mouse by getting the distance between the last coordinates and | |||
| the new ones. The coordinates will be further apart depending on how fast the mouse is moving. | |||
| var mouse_xv = mouse.x - mouse.px; | |||
| var mouse_yv = mouse.y - mouse.py; | |||
| */ | |||
| var topisto_facteur_velocite = 3; // Entre 1 et 5 en focntion du montant du bloc ? | |||
| var mouse_xv = (mouse.x - mouse.px) / (topisto_facteur_velocite * 2); | |||
| var mouse_yv = (mouse.y - mouse.py) / (topisto_facteur_velocite * 2); | |||
| //Loops through all of the columns | |||
| for (i = 0; i < vec_cells.length; i++) { | |||
| var cell_datas = vec_cells[i]; | |||
| //Loops through all of the rows | |||
| for (j = 0; j < cell_datas.length; j++) { | |||
| //References the current cell | |||
| var cell_data = cell_datas[j]; | |||
| //If the mouse button is down, updates the cell velocity using the mouse velocity | |||
| //if (mouse.down) { | |||
| change_cell_velocity(cell_data, mouse_xv, mouse_yv, pen_size); | |||
| //} | |||
| //This updates the pressure values for the cell. | |||
| update_pressure(cell_data); | |||
| } | |||
| } | |||
| /* | |||
| This line clears the canvas. It needs to be cleared every time a new frame is drawn | |||
| so the particles move. Otherwise, the particles would just look like long curvy lines. | |||
| */ | |||
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |||
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |||
| //This sets the color to draw with. | |||
| ctx.strokeStyle = "#00FFFF"; | |||
| //ctx.strokeStyle = "#FFFFFF"; | |||
| // TOPISTO : draw the path of the HASH | |||
| /* | |||
| ctx.strokeStyle = "#999999"; | |||
| ctx.lineWidth = 2; | |||
| x1 = Math.floor((3*canvas_width) / 70); | |||
| y1 = Math.floor((canvas_height / 2) + (1.2 * (8 - current_block_hash[0]))); | |||
| ctx.beginPath(); //Begin a new path on the canvas | |||
| ctx.moveTo(x1, y1); //Move the drawing cursor to the starting point | |||
| for(i=1;i<current_block_hash_ndx; i++) | |||
| { | |||
| x2 = Math.floor(((i+3) * canvas_width) / 70); | |||
| y2 = Math.floor((canvas_height / 2) + (1.2 * (8 - current_block_hash[i]))); | |||
| ctx.lineTo(x2, y2); | |||
| } | |||
| ctx.stroke(); //Draw the path to the canvas | |||
| */ | |||
| // TOPISTO : draw the path of the HASH | |||
| //This calls the function to update the particle positions. | |||
| update_particle(); | |||
| /* | |||
| This calls the function to update the cell velocity for every cell by looping through | |||
| all of the rows and columns. | |||
| */ | |||
| for (i = 0; i < vec_cells.length; i++) { | |||
| var cell_datas = vec_cells[i]; | |||
| for (j = 0; j < cell_datas.length; j++) { | |||
| var cell_data = cell_datas[j]; | |||
| update_velocity(cell_data); | |||
| } | |||
| } | |||
| //This replaces the previous mouse coordinates values with the current ones for the next frame. | |||
| mouse.px = mouse.x; | |||
| mouse.py = mouse.y; | |||
| //This requests the next animation frame which runs the draw() function again. | |||
| if (flag_20180225_animation) return TOPISTO_draw(); | |||
| } | |||
| function TOPISTO_draw(){ | |||
| return requestAnimationFrame(draw); | |||
| } | |||
| /* | |||
| This function changes the cell velocity of an individual cell by first determining whether the cell is | |||
| close enough to the mouse cursor to be affected, and then if it is, by calculating the effect that mouse velocity | |||
| has on the cell's velocity. | |||
| */ | |||
| function change_cell_velocity(cell_data, mvelX, mvelY, pen_size) { | |||
| //This gets the distance between the cell and the mouse cursor. | |||
| var dx = cell_data.x - mouse.x; | |||
| var dy = cell_data.y - mouse.y; | |||
| var dist = Math.sqrt(dy * dy + dx * dx); | |||
| //If the distance is less than the radius... | |||
| if (dist < pen_size) { | |||
| //If the distance is very small, set it to the pen_size. | |||
| if (dist < 4) { | |||
| dist = pen_size; | |||
| } | |||
| //Calculate the magnitude of the mouse's effect (closer is stronger) | |||
| var power = pen_size / dist; | |||
| /* | |||
| Apply the velocity to the cell by multiplying the power by the mouse velocity and adding it to the cell velocity | |||
| */ | |||
| cell_data.xv += mvelX * power; | |||
| cell_data.yv += mvelY * power; | |||
| } | |||
| } | |||
| /* | |||
| This function updates the pressure value for an individual cell using the | |||
| pressures of neighboring cells. | |||
| */ | |||
| function update_pressure(cell_data) { | |||
| //This calculates the collective pressure on the X axis by summing the surrounding velocities | |||
| var pressure_x = ( | |||
| cell_data.up_left.xv * 0.5 //Divided in half because it's diagonal | |||
| + cell_data.left.xv | |||
| + cell_data.down_left.xv * 0.5 //Same | |||
| - cell_data.up_right.xv * 0.5 //Same | |||
| - cell_data.right.xv | |||
| - cell_data.down_right.xv * 0.5 //Same | |||
| ); | |||
| //This does the same for the Y axis. | |||
| var pressure_y = ( | |||
| cell_data.up_left.yv * 0.5 | |||
| + cell_data.up.yv | |||
| + cell_data.up_right.yv * 0.5 | |||
| - cell_data.down_left.yv * 0.5 | |||
| - cell_data.down.yv | |||
| - cell_data.down_right.yv * 0.5 | |||
| ); | |||
| //This sets the cell pressure to one-fourth the sum of both axis pressure. | |||
| cell_data.pressure = (pressure_x + pressure_y) * 0.25; | |||
| } | |||
| /* | |||
| This function updates the velocity value for an individual cell using the | |||
| velocities of neighboring cells. | |||
| */ | |||
| function update_velocity(cell_data) { | |||
| /* | |||
| This adds one-fourth of the collective pressure from surrounding cells to the | |||
| cell's X axis velocity. | |||
| */ | |||
| cell_data.xv += ( | |||
| cell_data.up_left.pressure * 0.5 | |||
| + cell_data.left.pressure | |||
| + cell_data.down_left.pressure * 0.5 | |||
| - cell_data.up_right.pressure * 0.5 | |||
| - cell_data.right.pressure | |||
| - cell_data.down_right.pressure * 0.5 | |||
| ) * 0.25; | |||
| //This does the same for the Y axis. | |||
| cell_data.yv += ( | |||
| cell_data.up_left.pressure * 0.5 | |||
| + cell_data.up.pressure | |||
| + cell_data.up_right.pressure * 0.5 | |||
| - cell_data.down_left.pressure * 0.5 | |||
| - cell_data.down.pressure | |||
| - cell_data.down_right.pressure * 0.5 | |||
| ) * 0.25; | |||
| /* | |||
| This slowly decreases the cell's velocity over time so that the fluid stops | |||
| if it's left alone. | |||
| */ | |||
| cell_data.xv *= 0.99; | |||
| cell_data.yv *= 0.99; | |||
| } | |||
| //This function is used to create a cell object. | |||
| function cell(x, y, res) { | |||
| //This stores the position to place the cell on the canvas | |||
| this.x = x; | |||
| this.y = y; | |||
| //This is the width and height of the cell | |||
| this.r = res; | |||
| //These are the attributes that will hold the row and column values | |||
| this.col = 0; | |||
| this.row = 0; | |||
| //This stores the cell's velocity | |||
| this.xv = 0; | |||
| this.yv = 0; | |||
| //This is the pressure attribute | |||
| this.pressure = 0; | |||
| } | |||
| //This function is used to create a particle object. | |||
| function particle(x, y) { | |||
| /* topisto : add color property */ | |||
| var colortable = [ | |||
| "#FF0000", | |||
| "#FF4000", | |||
| "#FF8000", | |||
| "#FFBF00", | |||
| "#FFFF00", | |||
| "#BFFF00", | |||
| "#80FF00", | |||
| "#40FF00", | |||
| "#00BF00", | |||
| "#00FF40", | |||
| "#00FF80", | |||
| "#00FFBF", | |||
| "#00FFFF", | |||
| "#00BFFF", | |||
| "#0080FF", | |||
| "#0040FF", | |||
| "#0000FF" | |||
| ]; | |||
| this.color = Math.floor((canvas_height - y) * (colortable.length / canvas_height)); | |||
| this.color = colortable[this.color]; | |||
| /* topisto : add color property */ | |||
| this.x = this.px = x; | |||
| this.y = this.py = y; | |||
| this.xv = this.yv = 0; | |||
| } | |||
| /* | |||
| This function is called whenever the mouse button is pressed. The event object is passed to | |||
| this function when it's called. | |||
| */ | |||
| function mouse_down_handler(e) { | |||
| e.preventDefault(); //Prevents the default action from happening (e.g. navigation) | |||
| mouse.down = true; //Sets the mouse object's "down" value to true | |||
| } | |||
| //This function is called whenever the mouse button is released. | |||
| function mouse_up_handler() { | |||
| mouse.down = false; | |||
| } | |||
| //This function is called whenever a touch point is removed from the screen. | |||
| function touch_end_handler(e) { | |||
| if (!e.touches) mouse.down = false; //If there are no more touches on the screen, sets "down" to false. | |||
| } | |||
| /* | |||
| This function is called whenever the mouse coordinates have changed. The coordinates are checked by the | |||
| browser at intervals. | |||
| */ | |||
| function mouse_move_handler(e) { | |||
| //Saves the previous coordinates | |||
| mouse.px = mouse.x; | |||
| mouse.py = mouse.y; | |||
| //Sets the new coordinates | |||
| mouse.x = e.offsetX || e.layerX; | |||
| mouse.y = e.offsetY || e.layerY; | |||
| } | |||
| /* | |||
| This function is called whenever one of the coordinates have changed. The coordinates are checked by the | |||
| browser at intervals. | |||
| */ | |||
| function touch_move_handler(e) { | |||
| mouse.px = mouse.x; | |||
| mouse.py = mouse.y; | |||
| //This line gets the coordinates for where the canvas is positioned on the screen. | |||
| var rect = canvas.getBoundingClientRect(); | |||
| /* | |||
| And this sets the mouse coordinates to where the first touch is. Since we're using pageX | |||
| and pageY, we need to subtract the top and left offset of the canvas so the values are correct. | |||
| */ | |||
| mouse.x = e.touches[0].pageX - rect.left; | |||
| mouse.y = e.touches[0].pageY - rect.top; | |||
| } | |||
| /* | |||
| And this line attaches an object called "Fluid" to the global scope. "window" was passed into | |||
| the self-invoking function as "w", so setting "w.Fluid" adds it to "window". | |||
| */ | |||
| w.Fluid = { | |||
| draw: TOPISTO_draw, | |||
| initialize: init | |||
| } | |||
| }(window)); //Passes "window" into the self-invoking function. | |||
| /* | |||
| Request animation frame polyfill. This enables you to use "requestAnimationFrame" | |||
| regardless of the browser the script is running in. | |||
| */ | |||
| window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; | |||
| //And this line calls the init() function defined above to start the script. | |||
| Fluid.initialize(); | |||
| @ -0,0 +1,26 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br> | |||
| <canvas id="c" width="64" height="64" style="width:100%;height:250px;display:block;position:relative;"></canvas> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Fluid animation 2</h2> | |||
| <h4>Last Block Hash is touching a Fluid surface </h4> | |||
| <p> | |||
| Inspired from <a href="https://codepen.io/aecend/pen/WbONyK" target="_blank">Jeff Thomas's code</a> | |||
| </p> | |||
| <a id="20180225_control_btn" href="javascript:void(0)" onclick="javascript:switch_20180225_animation()" class="btn btn-primary btn-lg" data-toggle="tooltip" title="PAUSE"> | |||
| <span id="20180225_control_icon" class="glyphicon glyphicon-pause"></span> | |||
| </a> | |||
| <a id="20180225_repeat_btn" href="javascript:void(0)" onclick="javascript:init_20180225_animation()" class="btn btn-primary btn-lg" data-toggle="tooltip" title="REINIT"> | |||
| <span class="glyphicon glyphicon-repeat"></span> | |||
| </a> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="articles/20180225/header.js" defer></script> | |||
| <script src="articles/20180225/fluid2.js" defer></script> | |||
| @ -0,0 +1,36 @@ | |||
| var flag_20180225_animation = true; | |||
| function switch_20180225_animation() | |||
| { | |||
| btn = document.getElementById('20180225_control_btn'); | |||
| ctrl = document.getElementById('20180225_control_icon'); | |||
| if(ctrl.className == "glyphicon glyphicon-pause") | |||
| { | |||
| ctrl.title = "PLAY"; | |||
| ctrl.className = "glyphicon glyphicon-play"; | |||
| } else { | |||
| ctrl.title = "PAUSE"; | |||
| ctrl.className = "glyphicon glyphicon-pause"; | |||
| } | |||
| flag_20180225_animation = ! flag_20180225_animation; | |||
| if (flag_20180225_animation) Fluid.draw(); | |||
| } | |||
| function init_20180225_animation() | |||
| { | |||
| ctrl = document.getElementById('20180225_control_icon'); | |||
| ctrl.title = "PAUSE"; | |||
| ctrl.className = "glyphicon glyphicon-pause"; | |||
| flag_20180225_animation = true; | |||
| Fluid.initialize(); | |||
| setTimeout(switch_20180225_animation, 60000); | |||
| } | |||
| function init_2018025(leblock) | |||
| { | |||
| current_block_hash = leblock.hash; | |||
| init_20180225_animation(); | |||
| } | |||
| $(document).ready(function(){ | |||
| last_block_hooks.push(init_2018025); | |||
| }); | |||
| @ -0,0 +1,13 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="images/block_image.php?methode=treemapV2" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Refactoring my code</h2> | |||
| <p>Trying to rewrite some code, starting on new basis</p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,70 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="articles/ARTICLE/images/1522026225650.JPEG" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <p class="text-justify"> | |||
| <br><br><br> | |||
| Structure de Quadrilatères (Square Structures), 1985, ink on paper, 11.81 x 16.54 inches (30 x 42 cm) | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="articles/ARTICLE/images/quadrilateres1985.jpg" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <p class="text-justify"> | |||
| <br><br><br> | |||
| Structure de Quadrilatères (Square Structures), 1985. | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="articles/ARTICLE/images/Molnar_1.jpeg" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <p class="text-justify"> | |||
| <br><br><br> | |||
| Structure de Quadrilatères (Square Structures), 1987. | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="articles/ARTICLE/images/veramolnar2.jpg" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <p class="text-justify"> | |||
| <br><br><br> | |||
| Structure de Quadrilatères (Square Structures), 1988. | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="articles/ARTICLE/images/VeraMolnar.jpg" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <p class="text-justify"> | |||
| <br><br><br> | |||
| Affiche promo exposition.<br> | |||
| <i>"1 % de désordre, ou la vulnérabilité de l'angle droit"</i> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,14 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="images/block_image.php?methode=veraMolnar" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Tribute to Vera Molnar</h2> | |||
| <h4>In fact, my "fuzzy treemap" is a true <span style="color:blue">Vera Molnar</span> legacy.</h4> | |||
| <p>As usual, see <a href="https://en.wikipedia.org/wiki/Vera_Moln%C3%A1r" target="_blank">wikipedia</a> for more information</p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,667 @@ | |||
| <style> | |||
| blockquote { | |||
| font-size: 14px !important; | |||
| font-style: italic !important; | |||
| } | |||
| h5 { | |||
| font-size: 17px !important; | |||
| text-decoration: underline !important; | |||
| padding-top: 15px; | |||
| } | |||
| </style> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-12"> | |||
| <p class="text-justify"> | |||
| Bitcoin est vraiment cool. Bien sûr il y a des remarques à faire à son sujet, qu'il s'agisse de savoir si c'est une technologie interessante, si nous sommes dans une bulle des cryptomonnaies ou bien si le problème de gouvernance auquel nous sommes confrontés va trouver une solution. Mais d'un point de vue purement technique, le mystique Satoshi Nakamoto a créé une technologie impressionnante. | |||
| <br><br | |||
| >Malheureusement, bien qu'il y ait de nombreuses ressources résumant comment Bitcoin fonctionne à un haut niveau (je recommanderai par exemple la fantastique vidéo de Anders Brownworth, <a href="https://anders.com/blockchain/" target="_blank">Blockchain visual 101</a>), il n'y a pas beaucoup d'informations sur ce qui se passe dans les couches basses et, à mon avis, on ne peut pas toujours se contenter de la vue à 10 000 pieds d'altitude. | |||
| <br><br> | |||
| En tant que néophyte, j'ai eu beaucoup de mal à comprendre la mécanique du foncitonnement de Bitcoin. Heureusement, parce que bitcoin est décentralisé et pair à pair par nature, tout à chacun est capable de développer un client respectant le protocole. Afin de mieux comprendre la façon dont bitcoin foncionne, j'ai donc décidé d'écrire mon propre petit client Bitcoin, capable de publier une transaction sur la Blockchain Bitcoin. | |||
| <br><br> | |||
| Cet article décrit le processus de création d'un client minimal, capable de créer une transaction, de la soumettre au réseau pair à pair Bitcoin afin qu'il soit inscrit dans sa Blockchain. Si vous préférez juste lire le code brut, vous pouvez sans problème vous rendre sur mon <a href="https://github.com/samvrlewis/simple-bitcoin" target="_blank">repo GITHUB</a>. | |||
| </p> | |||
| <h4>Génération d'une adresse</h4> | |||
| <p class="text-justify"> | |||
| Pour participer au réseau bitcoin, il faut disposer d'une adresse à partir de laquelle il est possible d'envoyer et de recevoir des fonds. Bitcoin utilise la cryptographie à clé publique et une adresse est simplement un hash de la clé publique qui, elle-même, dérive de la clé privée. | |||
| Etonnamment, et contrairement à la plupart des autres systèmes de cryptographie à clé publique, la clé publique est égalment gardée secrète jusqu'à ce que les fonds soient dépenses depuis cette adresse - mais nous décrirons cela plus tard. | |||
| <blockquote> | |||
| En aparté, un point de terminologie : Le terme de "porte-monnaie (wallet)" est utilisé par les clients Bitcoin pour désigner une liste d'adresses. Ce concept de porte-monnaie n'existe pas dans le protocole technique qui ne connait que des adresses. | |||
| </blockquote> | |||
| Bitcoin utilise la cryptographie à courbe elliptique pour définir ses adresses. Au niveau le plus fin, la cryptographie à courbe elliptique sert à générer une clé publique à partir d'une clé privée, un peu comme le ferait RSA, mais avec une empreinte plus légère. Si vous souhaitez appronfondir l'aspect mathématique de ce fonctionnement, <a href="https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/" target="_blank">Cloudflare's primer</a> est une ressource fantastique. | |||
| <br><br> | |||
| Le schéma ci-dessous montre le processus de génération d'une adresse Bitcoin à partir d'une clé privée de 256 bits. | |||
| <br> | |||
| <div style="text-align:center"><img src="articles/ARTICLE/images/bitcoin_address_generation.png"></div> | |||
| <br> | |||
| Sous Python, j'utilise la <a href="https://github.com/warner/python-ecdsa" target="_blank">bibliothèque ecsda</a> pour faire le gros du travail de cryptographie à courbe elliptique. | |||
| Le bout de code suivant crée une clé publique à partir de la très mémorable (et très peu sûre) clé privée <i>0xFEEDB0BDEADBEEF</i> (En ajoutant assez de zéros à l'avant pour faire 64 caractères hexadécimaux, soit 256 bis). | |||
| Utilisez une méthode de création de clé privée plus sûre si vous souhaitez stocker de vrai montants sur une adresse ! | |||
| <blockquote> | |||
| Aparté amusante. | |||
| J'avais initialemment créé une adresse en utilisant la clé privé <i>OxFACEBEFF</i> en y stockant 0.0005 BTC. Après seulement un mois <b>quelqu'un avait volé mes 0.0005 BTC</b> ! | |||
| J'imagine que certains doivent de temps en temps aller à la pêche sur les adresses issues des clés privées les plus simples/usuelles. | |||
| Vous devez vraiment utiliser une technique fiable pour définir vos clés privées ! | |||
| </blockquote> | |||
| </p> | |||
| <pre> | |||
| <span class="kn">from</span> <span class="nn">ecdsa</span> <span class="kn">import</span> <span class="n">SECP256k1</span><span class="p">,</span> <span class="n">SigningKey</span> | |||
| <span class="k">def</span> <span class="nf">get_private_key</span><span class="p">(</span><span class="n">hex_string</span><span class="p">):</span> | |||
| <span class="k">return</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">hex_string</span><span class="o">.</span><span class="n">zfill</span><span class="p">(</span><span class="mi">64</span><span class="p">))</span> <span class="c1"># pad the hex string to the required 64 characters</span> | |||
| <span class="k">def</span> <span class="nf">get_public_key</span><span class="p">(</span><span class="n">private_key</span><span class="p">):</span> | |||
| <span class="c1"># this returns the concatenated x and y coordinates for the supplied private address</span> | |||
| <span class="c1"># the prepended 04 is used to signify that it's uncompressed</span> | |||
| <span class="k">return</span> <span class="p">(</span><span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s2">"04"</span><span class="p">)</span> <span class="o">+</span> <span class="n">SigningKey</span><span class="o">.</span><span class="n">from_string</span><span class="p">(</span><span class="n">private_key</span><span class="p">,</span> <span class="n">curve</span><span class="o">=</span><span class="n">SECP256k1</span><span class="p">)</span><span class="o">.</span><span class="n">verifying_key</span><span class="o">.</span><span class="n">to_string</span><span class="p">())</span> | |||
| <span class="n">private_key</span> <span class="o">=</span> <span class="n">get_private_key</span><span class="p">(</span><span class="s2">"FEEDB0BDEADBEEF"</span><span class="p">)</span> | |||
| <span class="n">public_key</span> <span class="o">=</span> <span class="n">get_public_key</span><span class="p">(</span><span class="n">private_key</span><span class="p">)</span></pre> | |||
| <p class="text-justify">Ce code produit la clé privée suivante (en hexadécimal) :</p> | |||
| <pre>0000000000000000000000000000000000000000000000000feedb0bdeadbeef</pre> | |||
| <p class="text-justify">Et la clé publique (toujours en hexadécimal) :</p> | |||
| <pre>04d077e18fd45c031e0d256d75dfa8c3c21c589a861c4c33b99e64cf613113fcff9fc9d90a9d81346bcac64d3c01e6e0ef0828543edad73c0e257b845812cc8d28</pre> | |||
| <p class="text-justify"> | |||
| Le <code>0x04</code> au début de la clé publique indique qu'il s'agit d'une clé publique<em>non compressée</em>, cela signifie que les coordonnées <em>x</em> et le <em>y</em> sur la courbe ECSDA sont simplement concaténés. | |||
| De part la structure de la courbe ECSDA, si vous connaissez la valeur de l'abcisse <em>x</em>, l'ordonnée <em>y</em> ne peut prendre que deux valeurs, uen paire et une impaire. En utilisant cette caractéristique, il est possible d'exprimer une clé publique avec seulement son abcisse et la polarité de son ordonnée. | |||
| Cela réduit la taille de la clé de 65 à 33 bits, la clé (et par conséquent l'adresse qui en découle) est dite <em>compressée</em>. | |||
| Pour les clés publiques compressées, la valeur de début est <code>0x02</code> ou <code>0x03</code> en fonction de la polarité de l'abcisse. Bitcoin utilise principalement les clés non compressées, c'est donc ce que nous ferons également par la suite. | |||
| <br><br> | |||
| A partir de là, afin de générer l'adresse Bitcoin depuis la clé publique, cette dernière est "hashée" par sha256 puis par "ripemd160". | |||
| Cette double empreinte produit une sur-couche de sécurité et le hash ripemd160 réduit la longueur de l'adresse, qui passe de 256 bits à 160 bits. | |||
| Une conséquence remarquable est qu'il alors possible que deux clés publiques produisent la même adresse ! | |||
| Toutefois, avec 2^160 adresses possibles, ce n'est pas susceptible de se produire de si tôt. | |||
| </p> | |||
| <pre> | |||
| <span class="kn">import</span> <span class="nn">hashlib</span> | |||
| <span class="k">def</span> <span class="nf">get_public_address</span><span class="p">(</span><span class="n">public_key</span><span class="p">):</span> | |||
| <span class="n">address</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">public_key</span><span class="p">)</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span> | |||
| <span class="n">h</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s1">'ripemd160'</span><span class="p">)</span> | |||
| <span class="n">h</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">address</span><span class="p">)</span> | |||
| <span class="n">address</span> <span class="o">=</span> <span class="n">h</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span> | |||
| <span class="k">return</span> <span class="n">address</span> | |||
| <span class="n">public_address</span> <span class="o">=</span> <span class="n">get_public_address</span><span class="p">(</span><span class="n">public_key</span><span class="p">)</span></pre> | |||
| <p class="text-justify"> | |||
| Le code ci-dessus génère l'adresse <em>c8db639c24f6dc026378225e40459ba8a9e54d1a</em>. | |||
| Ceci est parfois appelé <em>l'adresse hash 160</em>. | |||
| <br><br> | |||
| Comme évoqué plus tôt, un point intéressant est que les conversions de la clé privée en clé publique puis de la clé publique vers l'adresse ne sont pas réversibles. | |||
| Si vous diposez d'une adresse la seule possibilité de remonter à la clé publique est de résoudre le hash sha256. | |||
| C'est un peu différent de la plupart des cryptographies à clés publiques dans lesquelles votre clé publique est connue et votre clé privée cachée. | |||
| Dans ce cas, les deux clés sont cachées et seule l'adresse (qui est une empreinte de la clé publique) est publiée. | |||
| </p> | |||
| <blockquote> | |||
| Les clés publiques sont cachées pour uen bonne raison. Bien qu'il soit normalement impossible de remonter d'une clé publique vers sa clé privée, si la méthode de génération de clé privée est corrompue, alors l'accès aux clés publiques permet un peu plus facilement de déduire la clé privée. | |||
| En 2013, <a href="https://bitcoin.org/en/alert/2013-08-11-android" target="_blank">c'est malheureusement arrivé dans les porte-monnaies Bitcoin pour Androïd</a>. | |||
| Androïd avait une faiblesse critique dans sa génération de nombres aléatoires, créant ainsi un vecteur d'attaque pour trouver les clés privées depuis les clés publiques. | |||
| C'est également la raison pour laquelle la réutilisation d'adresses publiques n'est pas encouragée. | |||
| La signature d'une transaction oblige à produire sa clé publique. | |||
| si vous ne réutilisez pas une clé après l'envoi d'une transaction depuis son adresse, vous n'avez pas à vous inquiéter d'avoir exposé cette clé publique. | |||
| </blockquote> | |||
| <p class="text-justify"> | |||
| La façon standard d'exrpimer une adresse Bitcoin est de l'encoder via <a href="https://en.bitcoin.it/wiki/Base58Check_encoding" target="_blank">Base58Check</a>. | |||
| Cet encodage n'est qu'une représentation de l'adresse (il peut être décodé). | |||
| Base58Check génère des adresses de la forme <em>1661HxZpSy5jhcJ2k6av2dxuspa8aafDac</em>. | |||
| L'encodage Base58Check produit des adresses plus courtes et embarque une somme de contrôle, cela permet de détecter les adresses malformées. | |||
| Dans à peu près tous les clients Bitcoin les adresses encodées en Base58Check sont les seules que vous verrez. | |||
| Base58Check comporte également un numéro de version, que je positionne à <code>0</code> dans le code suivant pour indiquer qu'il s'agit du hash d'une clé publique. | |||
| </p> | |||
| <pre> | |||
| <span class="c1"># 58 character alphabet used</span> | |||
| <span class="n">BASE58_ALPHABET</span> <span class="o">=</span> <span class="s1">'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'</span> | |||
| <span class="k">def</span> <span class="nf">base58_encode</span><span class="p">(</span><span class="n">version</span><span class="p">,</span> <span class="n">public_address</span><span class="p">):</span> | |||
| <span class="sd">"""</span> | |||
| <span class="sd"> Gets a Base58Check string</span> | |||
| <span class="sd"> See https://en.bitcoin.it/wiki/base58Check_encoding</span> | |||
| <span class="sd"> """</span> | |||
| <span class="n">version</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">version</span><span class="p">)</span> | |||
| <span class="n">checksum</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">version</span> <span class="o">+</span> <span class="n">public_address</span><span class="p">)</span><span class="o">.</span><span class="n">digest</span><span class="p">())</span><span class="o">.</span><span class="n">digest</span><span class="p">()[:</span><span class="mi">4</span><span class="p">]</span> | |||
| <span class="n">payload</span> <span class="o">=</span> <span class="n">version</span> <span class="o">+</span> <span class="n">public_address</span> <span class="o">+</span> <span class="n">checksum</span> | |||
| <span class="n">result</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">byteorder</span><span class="o">=</span><span class="s2">"big"</span><span class="p">)</span> | |||
| <span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> | |||
| <span class="c1"># count the leading 0s</span> | |||
| <span class="n">padding</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="o">.</span><span class="n">lstrip</span><span class="p">(</span><span class="sa">b</span><span class="s1">'</span><span class="se">\0</span><span class="s1">'</span><span class="p">))</span> | |||
| <span class="n">encoded</span> <span class="o">=</span> <span class="p">[]</span> | |||
| <span class="k">while</span> <span class="n">result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span> | |||
| <span class="n">result</span><span class="p">,</span> <span class="n">remainder</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">58</span><span class="p">)</span> | |||
| <span class="n">encoded</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">BASE58_ALPHABET</span><span class="p">[</span><span class="n">remainder</span><span class="p">])</span> | |||
| <span class="k">return</span> <span class="n">padding</span><span class="o">*</span><span class="s2">"1"</span> <span class="o">+</span> <span class="s2">""</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">encoded</span><span class="p">)[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> | |||
| <span class="n">bitcoin_address</span> <span class="o">=</span> <span class="n">base58_encode</span><span class="p">(</span><span class="s2">"00"</span><span class="p">,</span> <span class="n">public_address</span><span class="p">)</span></pre> | |||
| <p class="text-justify"> | |||
| Au final, à partir de ma clé privée <em>feedb0bdeadbeef</em> (préfixée par des zéros), je suis arrivé à l'adresse Bitcoin <em>1KK2xni6gmTtdnSGRiuAf94jciFgRjDj7W</em> ! | |||
| <br><br> | |||
| Muni d'une adresse, il est maintenant possible d'obtenir queqlues bitcoins ! | |||
| Pour ce faire, je me suis acheté sur 0.0045 BTC (à peu près 11 USD au moment où j'écris) depuis <a href="https://www.btcmarkets.com" target="">btcmarkets</a> en utilisant des dollars australiens. | |||
| Depuis le portail de trading de btcmarkets, je les ai transférés sur l'adresse ci-dessus, perdant 0.0005 BTC de frais durant la procédure. Vous pouvez voir ce mouvement sur la blockchain à la transaction <a href="https://www.blockchain.com/btc/tx/95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7" target="_blank">95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7</a> | |||
| </p> | |||
| <h4>Se connecter au réseau P2P</h4> | |||
| <p class="text-justify"> | |||
| Maintenant que j'ai une adresse avec quelques bitcoinc dessus, les choses deviennent plus intéressantes. | |||
| Pour envoyer ces bitcoins ailleurs, il est nécessaire de se connecter au réseau P2P de Bitcoin. | |||
| </p> | |||
| <h5>Bootstrapping</h5> | |||
| <p class="text-justify"> | |||
| Une des difficultés que j'ai eu à la première approche de Bitcoin, étant donné la nature décentralisée du réseau, fût de déterminer comment les pairs du réseau trouvaient les autres pairs. | |||
| Sans autorité centrale, comment un client peut-il démarrer et commencer le dialogue avec le reste du réseau ? | |||
| <br><br> | |||
| Il s'est avéré que l'idéalism se soumet au réalisme et qu'il s'avère subsister un reliquat de centralisation dans le processus de découverte initiale des autres pairs.<!DOCTYPE html> | |||
| La principale manière pour un nouveau client de trouver des pairs est d'utiliser une requête DNS vers un certain nombre de <em>serveurs DNS graines</em> maintenus par des membres de la communauté Bitcoin. | |||
| <br><br> | |||
| Il s'avère que le DNS est bien équipé pour ce type d'initialisation, ce protocole, qui utilise UDP et est trés léger, est difficilement sujet aux attauqes par Déni de Service. | |||
| Précédemment, c'est IRC qui était utilisé pour cette phase, mais cela a cessé justement pour sa faiblesse aux attaques DDOS. | |||
| <br><br> | |||
| Les graines sont codées en dutr dans le source du noyau Bitcoin mais peuvent être changés par le noyau de développeurs. | |||
| <br><br> | |||
| Le code Python ci-dessous se connecte à une graine DNS et choisit arbitrairement le premier des pairs pour se connecter. | |||
| Il utilise la bibliothèque <em>socket</em>, et réalise un <em>nslookup</em> pour retourner l'adresse IPV4 du premier résultat sur la requête vers la graine <em>seed.bitcoin.sipa.be</em>. | |||
| </p> | |||
| <pre> | |||
| <span class="kn">import</span> <span class="nn">socket</span> | |||
| <span class="c1"># use a dns request to a seed bitcoin DNS server to find a node</span> | |||
| <span class="n">nodes</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">getaddrinfo</span><span class="p">(</span><span class="s2">"seed.bitcoin.sipa.be"</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span> | |||
| <span class="c1"># arbitrarily choose the first node</span> | |||
| <span class="n">node</span> <span class="o">=</span> <span class="n">nodes</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">4</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span></pre> | |||
| <p class="text-justify"> | |||
| Après avoir lancé ceci, l'adresse retournée fût <em>208.67.251.126</em> qui semble un pair amical auquel je peux me connecter ! | |||
| </p> | |||
| <h5>Dire coucou à mon nouvel ami</h5> | |||
| <p class="text-justify"> | |||
| Les connexions bitcoin entre pairs se font via TCP. | |||
| Afin de se connecter à pair, la poignée de main initiale du protocole Bitcoin est un message de type <em>Version</em>. | |||
| Tant que les pairs n'ont pas échangé un message de type Version, aucun autre message ne sera accepté. | |||
| <br><br> | |||
| Les messages Bitcoin sont bien documentés dans <a href="https://bitcoin.org/en/developer-reference" target="_blank">La référence du développeur Bitcoin</a> | |||
| En utilisant cette référence comme un guide, le message de type <a href="https://bitcoin.org/en/developer-reference#version" target="_blank">version</a> peut être construit en Python comme le bout de code suivant le montre. | |||
| La plupart des données sont des données administratives peu intéressantes qui sont utilisées pour établir la connexion. | |||
| si vous souhaitez plus de détails que ceux présents dans les commentaires, consultez la référence du développeur. | |||
| </p> | |||
| <pre> | |||
| <span class="n">version</span> <span class="o">=</span> <span class="mi">70014</span> | |||
| <span class="n">services</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1"># not a full node, cant provide any data</span> | |||
| <span class="n">timestamp</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">())</span> | |||
| <span class="n">addr_recvservices</span> <span class="o">=</span> <span class="mi">1</span> | |||
| <span class="n">addr_recvipaddress</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">inet_pton</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET6</span><span class="p">,</span> <span class="s2">"::ffff:127.0.0.1"</span><span class="p">)</span> <span class="c1">#ip address of receiving node in big endian</span> | |||
| <span class="n">addr_recvport</span> <span class="o">=</span> <span class="mi">8333</span> | |||
| <span class="n">addr_transservices</span> <span class="o">=</span> <span class="mi">1</span> | |||
| <span class="n">addr_transipaddress</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">inet_pton</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET6</span><span class="p">,</span> <span class="s2">"::ffff:127.0.0.1"</span><span class="p">)</span> | |||
| <span class="n">addr_transport</span> <span class="o">=</span> <span class="mi">8333</span> | |||
| <span class="n">nonce</span> <span class="o">=</span> <span class="mi">0</span> | |||
| <span class="n">user_agentbytes</span> <span class="o">=</span> <span class="mi">0</span> | |||
| <span class="n">start_height</span> <span class="o">=</span> <span class="mi">329167</span> | |||
| <span class="n">relay</span> <span class="o">=</span> <span class="mi">0</span></pre> | |||
| <p class="text-justify"> | |||
| En utilisant <a href="https://docs.python.org/3/library/struct.html" target="_blank">la bibliothèque struct de Python</a> les données sont empaquetées dans le bon format, en prenant une attention particulière sur la taille en octets des données et à leur sens de lecture (endianness). | |||
| Le bon empaquetage des données est important, dans le cas contraire, l'interlocuteur ne sera pas capable de comprendre les octets bruts qu'il reçoit. | |||
| </p> | |||
| <pre> | |||
| <span class="n">payload</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<I"</span><span class="p">,</span> <span class="n">version</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<Q"</span><span class="p">,</span> <span class="n">services</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<Q"</span><span class="p">,</span> <span class="n">timestamp</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<Q"</span><span class="p">,</span> <span class="n">addr_recvservices</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"16s"</span><span class="p">,</span> <span class="n">addr_recvipaddress</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">">H"</span><span class="p">,</span> <span class="n">addr_recvport</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<Q"</span><span class="p">,</span> <span class="n">addr_transservices</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"16s"</span><span class="p">,</span> <span class="n">addr_transipaddress</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">">H"</span><span class="p">,</span> <span class="n">addr_transport</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<Q"</span><span class="p">,</span> <span class="n">nonce</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<H"</span><span class="p">,</span> <span class="n">user_agentbytes</span><span class="p">)</span> | |||
| <span class="n">payload</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<I"</span><span class="p">,</span> <span class="n">start_height</span><span class="p">)</span></pre> | |||
| <p class="text-justify"> | |||
| A nouveau, on trouvera dans le guide du développeur la description de la façon dont les données doivent être empaquetées. | |||
| Enfin, chaque donnée transmise sur le réseau doit être préfixée par une entête, qui contient la longueur des données, une somme de contrôle et le type de message dont il s'agit. | |||
| L'entête contient également la constante magique <em>0xF9BEB4D9</em> qui doit être positionnée pour tous les messages du réseau principal de Bitcoin. | |||
| La fonction suivante retourne un message Bitcoin contenant les données attachées à leur entête. | |||
| </p> | |||
| <pre> | |||
| <span class="k">def</span> <span class="nf">get_bitcoin_message</span><span class="p">(</span><span class="n">message_type</span><span class="p">,</span> <span class="n">payload</span><span class="p">):</span> | |||
| <span class="n">header</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">">L"</span><span class="p">,</span> <span class="mh">0xF9BEB4D9</span><span class="p">)</span> | |||
| <span class="n">header</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"12s"</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">(</span><span class="n">message_type</span><span class="p">,</span> <span class="s1">'utf-8'</span><span class="p">))</span> | |||
| <span class="n">header</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<L"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span> | |||
| <span class="n">header</span> <span class="o">+=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span><span class="o">.</span><span class="n">digest</span><span class="p">())</span><span class="o">.</span><span class="n">digest</span><span class="p">()[:</span><span class="mi">4</span><span class="p">]</span> | |||
| <span class="k">return</span> <span class="n">header</span> <span class="o">+</span> <span class="n">payload</span></pre> | |||
| <p class="text-justify"> | |||
| Avec des données empaquetées dans le bon format et le header attaché, on peut les envoyer à notre pair ! | |||
| </p> | |||
| <pre> | |||
| <span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> | |||
| <span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">node</span><span class="p">,</span> <span class="mi">8333</span><span class="p">))</span> | |||
| <span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">get_bitcoin_message</span><span class="p">(</span><span class="s2">"version"</span><span class="p">,</span> <span class="n">payload</span><span class="p">))</span> | |||
| <span class="k">print</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">))</span></pre> | |||
| <p class="text-justify"> | |||
| Le protocole Bitcoin prévoit qu'en réponse à un message de type version, unpair doit répondre avec un message de type <em>Verack</em>. | |||
| Comme je développe un petit client pour le fun, et parce que cela ne me sera pas préjudiciable si je ne le fait pas, j'ignorerai les messages de type Version et je ne leur renverrai pas d'acquittement. | |||
| Se connecter avec un message de type Version suffit pour m'autoriser à envoyer d'autres messages par la suite. | |||
| <br><br> | |||
| Après exécution, le code précédent affiche ce qui suit. Cela est sûrement prometteur. "Satoshi" et "Verack" sont de bons mots à retrouver dans le dump de sortie ! | |||
| Si mon message de type Version avait été malformé, le pair ne m'aurait pas répondu du tout. | |||
| </p> | |||
| <pre> | |||
| <span class="sa">b</span><span class="s1">'</span><span class="se">\xf9\xbe\xb4\xd9</span><span class="s1">version</span><span class="se">\x00\x00\x00\x00\x00</span><span class="s1">f</span><span class="se">\x00\x00\x00\xf8\xdd\x9a</span><span class="s1">L</span><span class="se">\x7f\x11\x01\x00 | |||
| \r\x00\x00\x00\x00\x00\x00\x00\xdd</span><span class="s1">R1Y</span><span class="se">\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |||
| \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xcb\xce\x1d\xfc\xe9</span><span class="s1">j</span><span class="se">\r\x00\x00\x00 | |||
| \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |||
| \x00\x06\xb8</span><span class="s1">>*</span><span class="se">\x88</span><span class="s1">@I</span><span class="se">\x8e\x10</span><span class="s1">/Satoshi:0.14.0/t)</span><span class="se">\x07\x00\x01\xf9\xbe\xb4\xd9</span><span class="s1">verack</span><span class="se">\x00 | |||
| \x00\x00\x00\x00\x00\x00\x00\x00\x00</span><span class="s1">]</span><span class="se">\xf6\xe0\xe2</span><span class="s1">'</span></pre> | |||
| <p class="text-justify"> | |||
| <br> | |||
| </p> | |||
| <h4>Les transactions Bitcoin</h4> | |||
| <p class="text-justify"> | |||
| Pour transférer des bitcoins, il faut diffuser une transaction sur le réseau Bitcoin. | |||
| <br><br> | |||
| D'une manière critique, l'idée la plus importante à comprendre est que la balance d'une adresse Bitcoin est seulement constituée par le nombre de sorties non dépensées (UTXO, "Unspent Transaction Outputs") que cette adresse possède. | |||
| Lorsque Bob envoie des bitcoins à Alice, il ne fait en fait que créer une UTXO qu'Alice (et seulement Alice) pour utiliser plus tard pour créer une autre UTXO et envoyer des bitcoins dessus. | |||
| La balance d'une adresse est donc le nombre de bitcoins qu'elle peut transférer à une autre adresse plutôt que le nombre de bitcoins qu'elle possède. | |||
| <br><br> | |||
| J'insite encore, quand quelqqu'un dit qu'il possède X bitcoins, il dit en réalité que la somme de ses UTXOs fait X bitcoins. | |||
| La différence est subtile mais importante, la balance d'une adresse Bitcoin n'est enregistrée nulle part directement mais elle doit être trouvée en faisant la somme des UTXOs qu'elle peut encore dépenser. | |||
| Quand j'ai réalisé cela, ce fut un moment de "Oh, c'est comme ça que ça marche !". | |||
| <br><br> | |||
| Un effet de bord est qu'une sortie ne peut être que entièrement dépensée ou non dépensée. | |||
| Il n'est pas possible de ne dépenser qu'une moitié d'une sortie que l'on vous a envoyé, puis de dépenser le reste plus tard. | |||
| Si vous souhaitez dépenser une partie d'une sortie que vous avez reçu, vous devez envoyer la partie que vous souhaitez dépenser tout en vous renvoyant le reste. | |||
| Une version simplifiée de ceci est schématisée ci-dessous. | |||
| <br> | |||
| <div style="text-align:center"><img src="articles/ARTICLE/images/BobAlice.png"></div> | |||
| <br> | |||
| Lorsqu'une sortie de transaction est créée, elle comporte une condition verrou qui autorisera quelqu'un à la dépenser dans le futur, à travers ce que l'on appelle un script de transaction. | |||
| La plupart du temps, ce verrou est du type : "pour dépenser cette sortie, vous devez prouver que vous possédez la clé privée correspondant à une adresse particulière". | |||
| C'est ce que l'on appelle un script "Pay-to-Public-Key-Hash". | |||
| Toutefois rappelez vous que d'autres types de scripts Bitcoin sont possibles. | |||
| Par exemple, une sortie peut être créée qui pourrait être dépensée par toute personne pouvant résoudre une certaine empreinte, ou une transaction peut être créée que que tout le monde pourrait dépenser. | |||
| <br><br> | |||
| A travers le langage <b>Script</b>, il est possible de créer des transactions basées sur des contrats simples. | |||
| <b>Script</b> est un langage à pile rudimentaire avec un certain nombre d'opérations centrées sur le contrôle d'égalité d'empreinte ou de vérification de signatures. | |||
| <b>Script</b> n'est pas un langage "Turing complet" et il ne propose pas la possibilité de faire des boucles. | |||
| La cryptomonnaie concurrente Ethereum a été conçue pour être capable de traiter des "contrats intelligents", et possède un langage "Turing complet". | |||
| On peut débattre longuement de l'utilité, la nécessité et la sécurité de disposer d'un langage "Turing complet" dans les cryptomonnaies, mais je laisse ce débat à d'autres ! | |||
| <br><br> | |||
| Dans la terminologie standard, une transaction Bitcoin est faite d'entrées et de sorties. | |||
| Une entrée est une UTXO (une sortie maintenant dépensée) et une sortie est une nouvelle UTXO. | |||
| Il peut y avoir plusieurs sorties pour une entrée mais une sortie doit être complètement dépensée dans uen trasaction. | |||
| Toute partie non dépensée d'une entrée est considérée comme un pourboire de minage par les mineurs. | |||
| <br><br> | |||
| Pour mon petit client, je souhaite être capable d'envoyer les bitcoins précédemment transférés depuis une place de marché vers mon adresse FEEDB0BDEADBEEF. | |||
| En utilisant la même procédure que précédemment, j'ai généré une autre adresse à partir de la clé privée BADCAFEFABC0FFEE. | |||
| Cette adresse est 1QGNXLzGXhWTKF3HTSjuBMpQyUYFkWfgVC. | |||
| <br> | |||
| </p> | |||
| <h5>Création d'une transaction brute</h5> | |||
| <p class="text-justify"> | |||
| La création d'une transaction est une affaire d'empaquetage d'une "transaction brute", puis de signature de cette transaction brute. | |||
| Encore une fois, la référence du développeur contient une description de ce que doit contenir uen transaction. | |||
| Ce qui constitue une transaction est montré ci-dessous, mais tout d'abord : | |||
| <ul> | |||
| <li> | |||
| Le vocabulaire Bitcoin utilise les termes de "signature script" et "pubkey script" que je trouve un peu confus. | |||
| Le script de signature est utilisé pour définir les conditions de l'UTXO que l'on souhaite utiliser dans la transaction, et le script de clé publique est utilisé pour donner les conditions nécessaires pour dépenser l'UTXO que nous sommes en train de créer. | |||
| Il serait préférable d'appeler le script de signature le script de déverrouillage et d'appeler le le script de clé publique le script de verrouillage. | |||
| </li> | |||
| <li> | |||
| Le montant d'une transaction Bitcoin est donnée en Satoshis. | |||
| Le Satoshi représente la plus petite partie non divisible d'un bitcoin, soit le cent millionnième d'un bitcoin. | |||
| </li> | |||
| </ul> | |||
| <br><br> | |||
| Afin de rester simple, ce qui est indiqué ci-dessous correspond à une transaction comportant une entrée et une sortie. | |||
| Des transactions plus complexes, avec plusieurs entrées et plusieurs sorties, sont créées de la même façon. | |||
| <br><br> | |||
| <table> | |||
| <thead> | |||
| <tr><th>Champ</th><td>Description</th></tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr> | |||
| <td>Version</td> | |||
| <td>Transaction version (actuellement toujours 1)</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Number of inputs</td> | |||
| <td>Nombre d'entrées à dépenser</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Transaction ID</td> | |||
| <td>ID de la transaction à dépenser</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Output number</td> | |||
| <td>Numéro de la sortie à dépenser</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Signature script length</td> | |||
| <td>Longueur en octets du script de signature</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Signature script</td> | |||
| <td>Script de signature dans le langage Script</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Sequence number</td> | |||
| <td>Toujours 0xffffffff sauf si vous souhaitez positionner un Lock Time</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Number of outputs</td> | |||
| <td>Nombre de sorties à créer</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Value</td> | |||
| <td>Montant à dépenser en Satoshis</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Pubkey script length</td> | |||
| <td>Longueur en octets du script de clé publique</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Pubkey script</td> | |||
| <td>Script de clé publique en langage Script</td> | |||
| </tr> | |||
| <tr> | |||
| <td>Lock time</td> | |||
| <td>Prochaine date ou numéro de bloc où il sera possible d'inclure la transaction dans la blockchain</td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| <br><br> | |||
| Exceptés les script de signatur et de clé publique, il est assez facile de voir ce qui doit aller dans les autres champs de la trasaction brute. | |||
| Pour envoyer mes fonds de mon adresse FEEDB0BDEADBEEF à mon adresse BADCAFEFABC0FFEE, j'ai recherché la transaction qui avait été crée par la place de marché. | |||
| Cela m'a donné :<br><br> | |||
| <ul> | |||
| <li>L'ID de la transaction est <b>95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7</b>.</li> | |||
| <li>La sortie qui était envoyée à mon adresse était la deuxième sortie, soit la sortie <b>1</b> (le tableau des sorties commence à 0)</li> | |||
| <li>Le nombre de sortie est <b>1</b>, car je souhaite tout transférer de FEEDB0BDEADBEEF à BADCAFEFABC0FFEE</li> | |||
| <li> | |||
| Le montant doit être au maximum de <b>400 000</b> Satoshis.<br> | |||
| Cela doit être moins que cela pour accorder un pourboire.<br> | |||
| J'accorde <b>20 000</b> Satoshis à être pris en tant que pourboire, le montant sera donc fixé à <b>380 000</b> Satoshis. | |||
| </li> | |||
| <li>Le locktime sera mis à 0, autorisant la transaction a être prise en compte n'importe quand.</li> | |||
| </ul> | |||
| <br><br> | |||
| Le script de clé publique sera un script Pay to Pubkey hash (ou p2pk). | |||
| Ce script garantit que la seule personne capable de dépenser la sortie qui sera créée sera celle qui connait la clé publique dont l'empreinte coïncide avec l'adresse Bitcoin donnée et qui détient également la clé privée correspondant à cette la clé publique. | |||
| <br><br> | |||
| Pour débloquer une transaction verrouillée par un script P2PK, l'utilisateur doit fournir sa clé publique ainsi qu'une signature de l'empreinte de la transaction brute. | |||
| L'empreinte de la clé publique est comparée à l'adresse avec laquelle le script a été créé et la signature est vérifiée avec la clé publique fournie. | |||
| Si l'empreinte coïncide et que la signature est vérifiée, la sortie peut être dépensée. | |||
| <br><br> | |||
| Avec les opérateurs de script Bitcoin, le script P2PK ressemble à ce qui suit. | |||
| <br><br> | |||
| OP_DUP<br> | |||
| OP_HASH160<br> | |||
| -- Longueur en octets de l'adresse --<br> | |||
| -- adresse Bitcoin --<br> | |||
| OP_EQUALVERIFY<br> | |||
| OP_CHECKSIG | |||
| <br><br> | |||
| Après conversion des opérateurs dans leur valeurs (fournies par le WIKI) et en rajoutant l'adresse publique (avant encodage en Base58Check), cela donne le script hexadecimal suivant. | |||
| <br><br> | |||
| 0x76<br> | |||
| 0xA9<br> | |||
| 0x14<br> | |||
| 0xFF33195EC053D6E58D5FD3CC67747D3E1C71B280<br> | |||
| 0x88<br> | |||
| 0xAC | |||
| <br><br> | |||
| L'adresse a été trouvée en utilisant le bout de code montré précédemment pour dérivé une adresse depuis une clé privée, en l'appliquant à la clé privée de destination, <b>0xBADCAFEFABC0FFEE</b>. | |||
| <br> | |||
| </p> | |||
| <h5>Signer la transaction</h5> | |||
| <p class="text-justify"> | |||
| Il y a 2 usages séparés, mais quelque part reliés, du script de signature dans une transaction P2PK. | |||
| <br> | |||
| <ul> | |||
| <li>Le script vérifie (déverrouille) l'UTXO que l'on essaie de dépenser en fournissant notre clé publique qui créée l'empreinte de l'adresse à laquelle l'UTXO a été envoyée.</li> | |||
| <li>Le sicript effectue également une signature de ce nous transmettons au réseau, de façon à ce personne ne puisse modifier la transaction sans corrompre la signature.</li> | |||
| </ul> | |||
| <br> | |||
| Ainsi, la transaction brute contient le script de signature qui contient uen signature de la transaction brute ! | |||
| Ce problème de l'oeuf et la poule est résolu en plaçant le script de clé publique de notre UTXO dans l'emplacement du script de signature avant de signer la transaction brute. | |||
| Pour autant, il ne semble pas de bonne raison pour utiliser l'emplacement du script de clé publique, cela pourrait être arbitraimenent n'importe quelle autre donnée. | |||
| <br><br> | |||
| Avant de calculer l'empreinte de la transaction brute, on doit d'abord ajouter la valeur du <a href="https://en.bitcoin.it/wiki/OP_CHECKSIG" target="_blank">type d'empreinte (Hashtype)</a>. | |||
| Le type d'empreinte le plus commun est SIGHASH_ALL, qui signe toute la structure pour qu'aucune entrée ni aucune sortie ne puisse être modifiée. | |||
| Le lien vers le WIKI liste les autres types d'empreinte, qui peuvent autoriser la combinaison de la modification des entrées et des sorties après que la transaction ait été signée. | |||
| <br><br> | |||
| Les fonctions Python ci-dessous placent ensemble le répertoire des valeurs de transaction brute | |||
| </p> | |||
| <pre><span></span><span class="k">def</span> <span class="nf">get_p2pkh_script</span><span class="p">(</span><span class="n">pub_key</span><span class="p">):</span> | |||
| <span class="sd">"""</span> | |||
| <span class="sd"> This is the standard 'pay to pubkey hash' script</span> | |||
| <span class="sd"> """</span> | |||
| <span class="c1"># OP_DUP then OP_HASH160 then 20 bytes (pub address length)</span> | |||
| <span class="n">script</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s2">"76a914"</span><span class="p">)</span> | |||
| <span class="c1"># The address to pay to</span> | |||
| <span class="n">script</span> <span class="o">+=</span> <span class="n">pub_key</span> | |||
| <span class="c1"># OP_EQUALVERIFY then OP_CHECKSIG</span> | |||
| <span class="n">script</span> <span class="o">+=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s2">"88ac"</span><span class="p">)</span> | |||
| <span class="k">return</span> <span class="n">script</span> | |||
| <span class="k">def</span> <span class="nf">get_raw_transaction</span><span class="p">(</span><span class="n">from_addr</span><span class="p">,</span> <span class="n">to_addr</span><span class="p">,</span> <span class="n">transaction_hash</span><span class="p">,</span> <span class="n">output_index</span><span class="p">,</span> <span class="n">satoshis_spend</span><span class="p">):</span> | |||
| <span class="sd">"""</span> | |||
| <span class="sd"> Gets a raw transaction for a one input to one output transaction</span> | |||
| <span class="sd"> """</span> | |||
| <span class="n">transaction</span> <span class="o">=</span> <span class="p">{}</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"version"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"num_inputs"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span> | |||
| <span class="c1"># transaction byte order should be reversed:</span> | |||
| <span class="c1"># https://bitcoin.org/en/developer-reference#hash-byte-order</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"transaction_hash"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">transaction_hash</span><span class="p">)[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"output_index"</span><span class="p">]</span> <span class="o">=</span> <span class="n">output_index</span> | |||
| <span class="c1"># temporarily make the signature script the old pubkey script</span> | |||
| <span class="c1"># this will later be replaced. I'm assuming here that the previous</span> | |||
| <span class="c1"># pubkey script was a p2pkh script here</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"sig_script_length"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"sig_script"</span><span class="p">]</span> <span class="o">=</span> <span class="n">get_p2pkh_script</span><span class="p">(</span><span class="n">from_addr</span><span class="p">)</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"sequence"</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0xffffffff</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"num_outputs"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"satoshis"</span><span class="p">]</span> <span class="o">=</span> <span class="n">satoshis_spend</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"pubkey_length"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"pubkey_script"</span><span class="p">]</span> <span class="o">=</span> <span class="n">get_p2pkh_script</span><span class="p">(</span><span class="n">to_addr</span><span class="p">)</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"lock_time"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span> | |||
| <span class="n">transaction</span><span class="p">[</span><span class="s2">"hash_code_type"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span> | |||
| <span class="k">return</span> <span class="n">transaction</span></pre> | |||
| <p class="text-justify"> | |||
| Appeler ce code avec les valeurs ci-dessous crée la transaction brute que je me suis fixé. | |||
| </p> | |||
| <pre> | |||
| <span class="n">private_key</span> <span class="o">=</span> <span class="n">address_utils</span><span class="o">.</span><span class="n">get_private_key</span><span class="p">(</span><span class="s2">"FEEDB0BDEADBEEF"</span><span class="p">)</span> | |||
| <span class="n">public_key</span> <span class="o">=</span> <span class="n">address_utils</span><span class="o">.</span><span class="n">get_public_key</span><span class="p">(</span><span class="n">private_key</span><span class="p">)</span> | |||
| <span class="n">from_address</span> <span class="o">=</span> <span class="n">address_utils</span><span class="o">.</span><span class="n">get_public_address</span><span class="p">(</span><span class="n">public_key</span><span class="p">)</span> | |||
| <span class="n">to_address</span> <span class="o">=</span> <span class="n">address_utils</span><span class="o">.</span><span class="n">get_public_address</span><span class="p">(</span><span class="n">address_utils</span><span class="o">.</span><span class="n">get_public_key</span><span class="p">(</span><span class="n">address_utils</span><span class="o">.</span><span class="n">get_private_key</span><span class="p">(</span><span class="s2">"BADCAFEFABC0FFEE"</span><span class="p">)))</span> | |||
| <span class="n">transaction_id</span> <span class="o">=</span> <span class="s2">"95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7"</span> | |||
| <span class="n">satoshis</span> <span class="o">=</span> <span class="mi">380000</span> | |||
| <span class="n">output_index</span> <span class="o">=</span> <span class="mi">1</span> | |||
| <span class="n">raw</span> <span class="o">=</span> <span class="n">get_raw_transaction</span><span class="p">(</span><span class="n">from_address</span><span class="p">,</span> <span class="n">to_address</span><span class="p">,</span> <span class="n">transaction_id</span><span class="p">,</span> <span class="n">output_index</span><span class="p">,</span> <span class="n">satoshis</span><span class="p">)</span> </pre> | |||
| <p class="text-justify"> | |||
| Il peut paraître perturbant d'utliser la clé privée pour générer la valeur <em>to_address</em>. | |||
| C'est fait uniquement par commodité et pour montrer comment ce champ est trouvé. | |||
| Lorsque vous faites une transaction avec un tiers, vous devez lui demander son adresse et y effectuer le transfert, sans en connaître la clé privée. | |||
| <br><br> | |||
| Pour être capable de signer, et éventuellement de transmettre cette transaction au réseau, la trasaction brute doit être empaquetée correctement. | |||
| Ceci est implémenté dans la focntion <em>get_packed_transaction</em> que je ne vais pas copier ici, puisqu'il s'agit essentiellement d'un nouvel usage de la bibliothèque <b>struct</b>. | |||
| Si cela vous intéresse, voyus pouvez la trouver dans le fichier <a href="https://github.com/samvrlewis/simple-bitcoin/blob/master/bitcoin_transaction_utils.py" target="_blank">Python bitcoin_transaction_utils.py</a> de mon dépôt GITHUB. | |||
| <br><br> | |||
| Cela me permet de définir la fonction qui va produire le script de signature. | |||
| Une fois le script de signature généré, il va remplacer le contenu du champs signature script. | |||
| </p> | |||
| <pre> | |||
| <span class="k">def</span> <span class="nf">get_transaction_signature</span><span class="p">(</span><span class="n">transaction</span><span class="p">,</span> <span class="n">private_key</span><span class="p">):</span> | |||
| <span class="sd">"""</span> | |||
| <span class="sd"> Gets the sigscript of a raw transaction</span> | |||
| <span class="sd"> private_key should be in bytes form</span> | |||
| <span class="sd"> """</span> | |||
| <span class="n">packed_raw_transaction</span> <span class="o">=</span> <span class="n">get_packed_transaction</span><span class="p">(</span><span class="n">transaction</span><span class="p">)</span> | |||
| <span class="nb">hash</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">packed_raw_transaction</span><span class="p">)</span><span class="o">.</span><span class="n">digest</span><span class="p">())</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span> | |||
| <span class="n">public_key</span> <span class="o">=</span> <span class="n">address_utils</span><span class="o">.</span><span class="n">get_public_key</span><span class="p">(</span><span class="n">private_key</span><span class="p">)</span> | |||
| <span class="n">key</span> <span class="o">=</span> <span class="n">SigningKey</span><span class="o">.</span><span class="n">from_string</span><span class="p">(</span><span class="n">private_key</span><span class="p">,</span> <span class="n">curve</span><span class="o">=</span><span class="n">SECP256k1</span><span class="p">)</span> | |||
| <span class="n">signature</span> <span class="o">=</span> <span class="n">key</span><span class="o">.</span><span class="n">sign_digest</span><span class="p">(</span><span class="nb">hash</span><span class="p">,</span> <span class="n">sigencode</span><span class="o">=</span><span class="n">util</span><span class="o">.</span><span class="n">sigencode_der</span><span class="p">)</span> | |||
| <span class="n">signature</span> <span class="o">+=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s2">"01"</span><span class="p">)</span> <span class="c1">#hash code type</span> | |||
| <span class="n">sigscript</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<B"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">signature</span><span class="p">))</span> | |||
| <span class="n">sigscript</span> <span class="o">+=</span> <span class="n">signature</span> | |||
| <span class="n">sigscript</span> <span class="o">+=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">"<B"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">public_key</span><span class="p">))</span> | |||
| <span class="n">sigscript</span> <span class="o">+=</span> <span class="n">public_key</span> | |||
| <span class="k">return</span> <span class="n">sigscript</span></pre> | |||
| <p class="text-justify"> | |||
| Pour l'essentiel, le script de signature est fourni comme une entrée du script de clé publique de la transaction précédente que je tente d'utiliser, de cetet façon je peux prouver que je suis autorisé à en dépenser la sortie que j'utilise maintenant comme entrée. | |||
| La mécanique de focnitonnement est montrée ci-dessous, extrait du <a href="https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash" target="_blank">WIKI Bitcoin</a>. | |||
| Cela se lit de haut en bas, chaque ligne est une itération du script. | |||
| Cela décrit le script P2PK qui comme je l'ai déjà indiqué, est le plus utilisé. | |||
| C'est aussi celui que j'utilise pour ma transaction ainsi que pour la transaction dont j'utilise la sortie. | |||
| <table> | |||
| <thead> | |||
| <tr style="background-color: lightsteelblue"> | |||
| <th>Stack</th> | |||
| <th>Script </th> | |||
| <th>Description</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr> | |||
| <td>Empty</td> | |||
| <td><em>signature</em> <br/> <em>publicKey</em> <br/> OP_DUP <br/> OP_HASH160 <br/> <em>pubKeyHash</em> <br/> OP_EQUALVERIFY <br/> OP_CHECKSIG</td> | |||
| <td>La <em>signature</em> et la <em>clé publique</em> du script de signature sont combinées avec le script de clé publique.</td> | |||
| </tr> | |||
| <tr style="background-color: lightsteelblue"> | |||
| <td><em>publicKey</em> <br/> <em>signature</em></td> | |||
| <td>OP_DUP <br/> OP_HASH160 <br/> <em>pubKeyHash</em> <br/> OP_EQUALVERIFY <br/> OP_CHECKSIG</td> | |||
| <td>La <em>signature</em> et la <em>clé publique</em> sont ajouées sur la pile.</td> | |||
| </tr> | |||
| <tr> | |||
| <td><em>publicKey</em><br/><em>publicKey</em> <br/> <em>signature</em></td> | |||
| <td>OP_HASH160 <br/> <em>pubKeyHash</em> <br/> OP_EQUALVERIFY <br/> OP_CHECKSIG</td> | |||
| <td>L'item du haut de la pile est dupliqué par <code>OP_DUP</code></td> | |||
| </tr> | |||
| <tr style="background-color: lightsteelblue"> | |||
| <td><em>pubHashA</em><br/><em>publicKey</em> <br/> <em>signature</em></td> | |||
| <td><em>pubKeyHash</em> <br/> OP_EQUALVERIFY <br/> OP_CHECKSIG</td> | |||
| <td>On calcule l'empreinte de l'item du haut de la pile (<em>publicKey</em>) via <code>OP_HASH160</code>, le résultat <em>pubHashA</em> remplace le haut de la pile.</td> | |||
| </tr> | |||
| <tr> | |||
| <td><em>pubHash</em><br/><em>pubHashA</em><br/><em>publicKey</em> <br/> <em>signature</em></td> | |||
| <td>OP_EQUALVERIFY <br/> OP_CHECKSIG</td> | |||
| <td><em>pubKeyHash</em> est ajouté au haut de la pile.</td> | |||
| </tr> | |||
| <tr style="background-color: lightsteelblue"> | |||
| <td><em>publicKey</em> <br/> <em>signature</em></td> | |||
| <td>OP_CHECKSIG</td> | |||
| <td>Contrôle de l'égalité entre <em>pubHashA</em> et <em>pubKeyHash</em>. Le script s'arrête si ce n'est pas égal.</td> | |||
| </tr> | |||
| <tr> | |||
| <td>True</td> | |||
| <td></td> | |||
| <td>La <em>publicKey</em> est utiliséee pour signer la transaction, le résultat est comparé à la <em>signature</em> fournie par le script de signature.</td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| <br><br> | |||
| Ce script échouera si la clé fournie n'a pas une empreinte équivalente à celle fournie dans le script ou si la signature donnée ne correspond pas à la clé publique. | |||
| Cela garantit que seule la personne détenant la clé privée pour l'adresse donnée est capable de dépenser la sortie. | |||
| <br><br> | |||
| Vous pouvez voir que c'est ici la première fois que j'ai à produire ma clé publique. | |||
| Jusqu'ici, seule mon adresse avait été publiée. | |||
| Ici, il est nécessaire de montrer sa clé publique pour permettre de vérifier la signature utilisée dans la transaction. | |||
| <br><br> | |||
| En utilisant la fonction get_transaction_signature, nous pouvons maintenant signer et empaqueter notre transaction pour la transmettre ! | |||
| Cela implique de remplacer l'emplacement du script de signature par le vraie script de signature et d'enlever le hash_code_type de la transaction comme montré ci-dessous. | |||
| </p> | |||
| <pre> | |||
| <span class="n">signature</span> <span class="o">=</span> <span class="n">get_transaction_signature</span><span class="p">(</span><span class="n">raw</span><span class="p">,</span> <span class="n">private_key</span> <span class="p">)</span> | |||
| <span class="n">raw</span><span class="p">[</span><span class="s2">"sig_script_length"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">signature</span><span class="p">)</span> | |||
| <span class="n">raw</span><span class="p">[</span><span class="s2">"sig_script"</span><span class="p">]</span> <span class="o">=</span> <span class="n">signature</span> | |||
| <span class="k">del</span> <span class="n">raw</span><span class="p">[</span><span class="s2">"hash_code_type"</span><span class="p">]</span> | |||
| <span class="n">transaction</span> <span class="o">=</span> <span class="n">get_packed_transaction</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span></pre> | |||
| <h5>Publier la transaction</h5> | |||
| <p class="text-justify"> | |||
| Avec une transaction empaquetée et signée, il s'agit de dire au réseau qu'elle existe. | |||
| Utilisant les quelques fonctions définies dans cet article, placés dans <a href="https://github.com/samvrlewis/simple-bitcoin/blob/master/bitcoin_p2p_message_utils.py" target="_blank">bitcoin_p2p_message_utils.py</a>, le bout de code suivant place l'entête Bitcoin sur la transmission et l'envoie à un pair. | |||
| Comme indiqué précédemment, il est nécessaire d'envoyer un message de version au préalable à ce pair afin qu'il accepte les messages suivants. | |||
| </p> | |||
| <pre><span></span><span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> | |||
| <span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">get_bitcoin_peer</span><span class="p">(),</span> <span class="mi">8333</span><span class="p">))</span> | |||
| <span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">get_bitcoin_message</span><span class="p">(</span><span class="s2">"version"</span><span class="p">,</span> <span class="n">get_version_payload</span><span class="p">())</span> | |||
| <span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">get_bitcoin_message</span><span class="p">(</span><span class="s2">"tx"</span><span class="p">,</span> <span class="n">transaction</span><span class="p">)</span></pre> | |||
| <p class="text-justify"> | |||
| Envoyer la transaction fût la partie la plus ennuyeuse de ce travail. | |||
| Lorsque je soumettais une transaction qui était mal formée ou mal signée, souvent le pair se contentait de couper la connexion, ou dans le meilleur des cas, renvoyait un message d'erreur déroutant. | |||
| L'un de ces (vraiment incompréhensible) messages était "La valeur S est inutilement haute" qui était dûe à la signature de l'empreinte de la transaction par un encodage suivant la methode ECSDA nommée <em>sigencode_der</em>. | |||
| Bien que la signature soit valide, apparemment les mineurs Bitcoin <a href="https://blog.blockcypher.com/enforcing-low-s-values-to-eliminate-a-bitcoin-network-attack-3582fc0ae948" target="_blank">n'aiment pas les sigantures ECSDA formatées de cette façon car elles autorisent le spam sur le réseau</a>. | |||
| La solution fût d'utiliser la function sigencode_der_canonize qui prend soin de formter la signature d'une autre façon. | |||
| Un problème simple mais très difficile à débugguer ! | |||
| <br><br> | |||
| Quoiqu'il en soit, j'ai fini par tout faire fonctionner et je fût trés exité lorsque je vis que <a href="https://www.blockchain.com/btc/tx/ac812978fb87232ed5700fc64e8733546d70eaa4d9aa80cf1d20a3f71bd8d133" target="_blank">ma transaction faisait son chemin dans la blockchain</a>. | |||
| Ce fût un grand sentiment d'accomplissement de savoir que ma ravissante petite transaction faite à la main ferait partie du registre Bitcoin à tout jamais. | |||
| <br> | |||
| <div style="text-align:center"><img src="articles/ARTICLE/images/final_transaction.png"></div> | |||
| <br> | |||
| Lorsque ma transaction fût soumise, le montant du pourboire était un peu faible par rapport à la moyenne (j'ai utilisé <a href="https://bitcoinfees.earn.com/" target="_blank">bitcoin fees</a> pour vérifier) et ainsi il fallu 5 heures pour qu'un mineur se décide à l'inclure dans un bloc. | |||
| J'ai contrôlé cela en regardant le nombre de confirmations que la transaction avait - c'est une mesure du nombre de blocs suivants celui qui contient la transaction. | |||
| Au moment où j'écris ces lignes, il y a 190 confirmations, cela signifie que 190 blocs ont été produits depuis celui qui contient ma transaction. | |||
| Cela peut être considéré comme relativement sûrement confirmée, puisqu'il faudrait une attaque impressionnante sur le réseau pour réécrire 190 blocs afin d'effacer ma transaction. | |||
| </p> | |||
| <h4>Conclusion</h4> | |||
| <p class="text-justify"> | |||
| J'espère que vous avez gagné une petite idée de comment fonctionne Bitcoin en lisant cet article, pour ma part je suis certain de l'avoir fait pendant les mois que j'ai passé à mettre tout ceci ensemble ! | |||
| Bien que la plupart des informations présentées ici ne sont pas applicables de façon pragmatiques - normalement vous utilisez un client qui fait tout cela pour vous - je pense que comprendre un peu mieux comment les choses fonctionnent sous la couverture ont fait de vous un utilisateur plus confiant dans la technologie. | |||
| <br><br> | |||
| Si vous souhaitez utiliser le code, ou bien vous amuser plus en avant avec le petit jouet, consultez mon <a href="https://github.com/samvrlewis/simple-bitcoin" target="_blank">dépôt GITHUB</a>. | |||
| Il y a de nombreuses pièces à découvrir dans le monde Bitcoin, je n'en ai exploré que les plus communes. | |||
| Certaines pièces permettent probablement de faire des choses plus cool que le simple transfert de bitcoins entre deux adresses ! | |||
| Je n'ai même pas effleuré le minnage, le processus pour ajouter des blocks dans la blockchain. | |||
| Du travail et un nouveau trou de lapin en perspective ... | |||
| <br><br> | |||
| Si vous avez tout lu jusqu'ici, vous avez sans doute réalisé que les 380 000 Satoshis que j'ai transféré sur l'adresse <em>1QGNXLzGXhWTKF3HTSjuBMpQyUYFkWfgVC</em> peuvent, si on est malin, être récupérés par tout à chacun ... puisque la clé privée de cette adresse est indiquée dans l'article. | |||
| Je suis vraiment curieux de savoir combien de temps cela pour être transféré ailleurs et j'espère que celui qui le fera aura la décence de le faire en utilisant les techniques que j'ai décrit ici ! | |||
| Je serai vraiment dommage que la clé privée soit juste chargée dans un porte-monnaie pour les prendre, mais je ne pense vous en empêcher ! | |||
| Au moment où j'écris ces lignes, cela représente environ 10 USD, mais si Bitcoin "s'envole vers la lune", qui sait combien cela pourrait être plus ! | |||
| (Edit : Et c'est déjà retiré ! Pris par <a href="https://blockchain.info/tx/2685ff794de17cebdf94eb0f111e8b8c03529a9ae628909cef4090663b54e565" target="_blank">1KgoPFVDNcx7H2VY9bB2dxxP9yNM2Nar1N</a> quelques heures après la publication de ces lignes. Bien joué !) | |||
| <br><br> | |||
| Au cas où vous chercheriez une adresse pour envoyer des bitcoins lorsque vous jouerez avec ce travail, ou bien si vous pensez que cet article était suffisemment interessant pour le récompenser, mon adresse <em>18uKa5c9S84tkN1ktuG568CR23vmeU7F5H</em> sera heureuse de prendre de petits dons ! | |||
| Par ailleurs, si vous souhaitez me crier dessus pour signaler des erreurs, j'en serai très heureux. | |||
| </p> | |||
| <h4>D'autres ressources</h4> | |||
| <p class="text-justify"> | |||
| Si vous avez trouvé cet article intéressant, voici quelques ressources pour aller plus loin :<br> | |||
| <ul> | |||
| <li>Le livre <a href="https://www.amazon.com/Mastering-Bitcoin-Unlocking-Digital-Cryptocurrencies/dp/1449374042" target="_blank">Mastering Bitcoin</a> explique les détails techniques de Bitcoin. Je ne l'ai pas lu complètement mais il a l'air d'être riche en bonnes informations</li> | |||
| <li><a href="http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html" target="_blank">L'artcile du blog de Ken Sheriff</a> à l'air de couvrir le même sujet que mon article. Je ne l'ai malheureusement découvert que lorsque mon travail était déjà très avancé.Si vous ne comprenez pas quelque chose ici, consultez son article.</li> | |||
| <li>Déjà mentionnée, <a href="https://anders.com/blockchain/" target="_blank">la fantastique vidéo d'Anders Brownworth</a> est une excellente première approche de comment fonctionne les technologies Blockchain</li> | |||
| <li>A moins d'être complètement masochiste, je ne vous recommande pas de faire les choses à partir de rien, sauf dans un but pédagogique. La bibliothèque Python <a href="https://github.com/richardkiss/pycoin" target="_blank">Pycoin</a> vous économisera bien des maux de têtes</li> | |||
| <li>Pour économiser votre peine, il est sans doute préférable d'utiliser le réseau de test plutôt que le résau principal comme je l'ai fait. Ceci dit, c'est plus fun quand un code erroné vous fait perdre de l'argent réel !</li> | |||
| <li>Enfin je répète à nouveau que le code qui accompagne cet article est disponible sur mon <a href="https://github.com/samvrlewis/simple-bitcoin" target="_blank">dépôt GITHUB</a></li> | |||
| </ul> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <a id="lafin"></a> | |||
| @ -0,0 +1,15 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><img id="logo_ARTICLE" src="articles/ARTICLE/images/illustration.png" width="100%; height: auto"></img> | |||
| <br><p>ARTICLE</p> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Bitcoin : un coup d'oeil coup le capot</h2> | |||
| <h4>Un post pour traduire un article de 2017 sur le fonctionnement des transactions Bitcoin</h4> | |||
| <p>Pour une fois, un article en français. comme on ne peut pas toujours se contenter de la surface, cette fois-ci, on plonge plus en profondeur... L'article original a été écrit par <b>Sam Lewis</b> et se trouve <a href="http://www.samlewis.me/2017/06/a-peek-under-bitcoins-hood/ | |||
| " target="_blank">ici</a>.</p> | |||
| ####BUTTON#### | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,159 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="fr"> | |||
| <head> | |||
| <title>TOPISTO</title> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet" type="text/css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" type="text/css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Bangers" rel="stylesheet" type="text/css"> | |||
| <link href="css/topisto.css" rel="stylesheet" type="text/css"> | |||
| <link href="css/fonts.css" rel="stylesheet" type="text/css"> | |||
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> | |||
| <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> | |||
| </head> | |||
| <body id="myPage" data-spy="scroll" data-target=".navbar" data-offset="60"> | |||
| <nav class="navbar navbar-default navbar-fixed-top"> | |||
| <div class="container"> | |||
| <div class="navbar-header"> | |||
| <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar"> | |||
| <span class="icon-bar"></span> | |||
| <span class="icon-bar"></span> | |||
| <span class="icon-bar"></span> | |||
| </button> | |||
| <a class="navbar-brand" href=".."> | |||
| <img id="logo_topisto" src="images/topisto_vert.png" style="border-radius:6px;display:inline-block;height:72px;;box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"> | |||
| <span style="vertical-align:text-bottom;display:inline-block;color:black;font-family: Bangers, sans-serif;font-size: 70px;">TOPISTO</span> | |||
| </a> | |||
| </div> | |||
| <div class="collapse navbar-collapse" id="myNavbar"> | |||
| <ul class="nav navbar-nav navbar-right"> | |||
| <li><a href="#about">About</a></li> | |||
| <li><a href="#contact">Contact</a></li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </nav> | |||
| <div id="about" class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-12"> | |||
| <br><br> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <?php | |||
| $odd_even = 0; | |||
| $liste = ''; | |||
| foreach (glob("articles/*/header.html") as $filename) { | |||
| $article = basename(dirname($filename)); | |||
| $odd_even = 1 - $odd_even; | |||
| $header = file_get_contents($filename); | |||
| if ($odd_even == 0) $header = str_replace('bg-grey','bg-grey-odd',$header); | |||
| else $header = str_replace('bg-grey','bg-grey-even',$header); | |||
| $header = str_replace('ARTICLE',$article,$header); | |||
| if (!file_exists('articles/'.$article.'/content.html')) | |||
| { | |||
| $header = str_replace('####BUTTON####','',$header); | |||
| } else { | |||
| echo '<script>'; | |||
| echo '$(document).ready(function(){'; | |||
| echo ' $("#'.$article.'_button").on("click", function(event) {'; | |||
| echo ' window.location="page.php?id='.$article.'"'; | |||
| echo ' })'; | |||
| echo '})'; | |||
| echo '</script>'; | |||
| $header = str_replace('####BUTTON####','<button id="'.$article.'_button" class="btn btn-default btn-lg float-left">See more</button>',$header); | |||
| } | |||
| $liste = $header.PHP_EOL.$liste; | |||
| } | |||
| echo $liste; | |||
| ?> | |||
| <div id="contact" class="container-fluid bg-grey"> | |||
| <h4 class="text-center">CONTACT</h4> | |||
| <div class="row slideanim"> | |||
| <div class="col-sm-5"> | |||
| <p>Contact me</p> | |||
| <p><span class="glyphicon glyphicon-map-marker"></span> Shambala</p> | |||
| <p><span class="glyphicon glyphicon-globe"></span> Employer : Mutiny</p> | |||
| <p><span class="glyphicon glyphicon-phone"></span> +00 666 666 666</p> | |||
| <p> | |||
| <span class="glyphicon glyphicon-envelope"></span> | |||
| <!--Place the code below where you want the link to be displayed--> | |||
| <span id="obf"><script>document.getElementById("obf").innerHTML="<n uers=\"znvygb:nyoreg.frnaquvyf@gbcvfgb.arg?fhowrpg=pbagnpg\" gnetrg=\"_oynax\">nyoreg.frnaquvyf@gbcvfgb.arg</n>".replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});</script> | |||
| <noscript><span style="unicode-bidi:bidi-override;direction:rtl;">ten.otsipot@slihdnaes.trebla</span></noscript></span> | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-5"> | |||
| bookmarks : <br> | |||
| <a href="http://inconvergent.net/" target="_blank">Inconvergent</a><br> | |||
| <a href="http://www.datasketch.es/" target="_blank">Data Sketches</a><br> | |||
| <a href="https://bit101.github.io/lab/dailies/170310.html" target="_blank">bit101-github</a><br> | |||
| <a href="http://www.beingcaptainzero.com/" target="_blank">being captain zero</a><br> | |||
| </div> | |||
| <div class="col-sm-2"> | |||
| PGP : <br> | |||
| <a href="page.php?id=00000000"><img src="articles/00000000/public_key_qrcode.png" width="100%; height: auto"></img></a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <footer class="container-fluid bg-grey text-center"> | |||
| <a href="#myPage" title="To Top"> | |||
| <span class="glyphicon glyphicon-chevron-up"></span> | |||
| </a> | |||
| <p>Bootstrap Theme Made By <a href="https://www.w3schools.com" title="Visit w3schools">www.w3schools.com</a></p> | |||
| </footer> | |||
| <script> | |||
| $(document).ready(function(){ | |||
| // Add smooth scrolling to all links in navbar + footer link | |||
| $(".navbar a, footer a[href='#myPage']").on('click', function(event) { | |||
| // Make sure this.hash has a value before overriding default behavior | |||
| if (this.hash !== "") { | |||
| // Prevent default anchor click behavior | |||
| event.preventDefault(); | |||
| // Store hash | |||
| var hash = this.hash; | |||
| // Using jQuery's animate() method to add smooth page scroll | |||
| // The optional number (900) specifies the number of milliseconds it takes to scroll to the specified area | |||
| $('html, body').animate({ | |||
| scrollTop: $(hash).offset().top | |||
| }, 900, function(){ | |||
| // Add hash (#) to URL when done scrolling (default click behavior) | |||
| window.location.hash = hash; | |||
| }); | |||
| } // End if | |||
| }); | |||
| $(window).scroll(function() { | |||
| $(".slideanim").each(function(){ | |||
| var pos = $(this).offset().top; | |||
| var winTop = $(window).scrollTop(); | |||
| if (pos < winTop + 600) { | |||
| $(this).addClass("slide"); | |||
| } | |||
| // if (winTop < 400) $('#logo_topisto').css('visibility', 'hidden'); | |||
| // else $('#logo_topisto').css('visibility', 'visible'); | |||
| }); | |||
| }); | |||
| }) | |||
| </script> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,8 @@ | |||
| @font-face { | |||
| font-family: Klingon; | |||
| src: url('/fonts/klingon.ttf'); | |||
| } | |||
| @font-face { | |||
| font-family: Twengar; | |||
| src: url('/fonts/twengar.ttf'); | |||
| } | |||
| @ -0,0 +1,200 @@ | |||
| body { | |||
| font: 400 15px Lato, sans-serif; | |||
| line-height: 1.8; | |||
| color: #818181; | |||
| } | |||
| h2 { | |||
| font-size: 24px; | |||
| text-transform: uppercase; | |||
| color: #303030; | |||
| font-weight: 600; | |||
| margin-bottom: 30px; | |||
| } | |||
| h4 { | |||
| font-size: 19px; | |||
| line-height: 1.375em; | |||
| color: #303030; | |||
| font-weight: 400; | |||
| margin-bottom: 30px; | |||
| } | |||
| .btn-secondary { | |||
| background-color: #74716c; | |||
| color:#fff; | |||
| } | |||
| .jumbotron { | |||
| background-color: #74716c; | |||
| color: #fff; | |||
| padding: 100px 25px; | |||
| font-family: Bangers, sans-serif; | |||
| } | |||
| .container-fluid { | |||
| padding: 60px 50px; | |||
| } | |||
| .bg-grey { | |||
| background-color: #f6f6f6; | |||
| } | |||
| .bg-grey-odd { | |||
| background-color: #fff; | |||
| margin : 0px; | |||
| padding-top: 5px; | |||
| padding-bottom: 5px; | |||
| box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); | |||
| } | |||
| .bg-grey-even { | |||
| background-color: #fff; | |||
| margin : 0px; | |||
| padding-top: 5px; | |||
| padding-bottom: 5px; | |||
| box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); | |||
| } | |||
| .logo-small { | |||
| color: #f4511e; | |||
| font-size: 50px; | |||
| } | |||
| .logo { | |||
| color: #f4511e; | |||
| font-size: 200px; | |||
| } | |||
| .thumbnail { | |||
| padding: 0 0 15px 0; | |||
| border: none; | |||
| border-radius: 0; | |||
| } | |||
| .thumbnail img { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin-bottom: 10px; | |||
| } | |||
| .carousel-control.right, .carousel-control.left { | |||
| background-image: none; | |||
| color: #f4511e; | |||
| } | |||
| .carousel-indicators li { | |||
| border-color: #f4511e; | |||
| } | |||
| .carousel-indicators li.active { | |||
| background-color: #f4511e; | |||
| } | |||
| .item h4 { | |||
| font-size: 19px; | |||
| line-height: 1.375em; | |||
| font-weight: 400; | |||
| font-style: italic; | |||
| margin: 70px 0; | |||
| } | |||
| .item span { | |||
| font-style: normal; | |||
| } | |||
| .panel { | |||
| border: 1px solid #f4511e; | |||
| border-radius:0 !important; | |||
| transition: box-shadow 0.5s; | |||
| } | |||
| .panel:hover { | |||
| box-shadow: 5px 0px 40px rgba(0,0,0, .2); | |||
| } | |||
| .panel-footer .btn:hover { | |||
| border: 1px solid #f4511e; | |||
| background-color: #fff !important; | |||
| color: #f4511e; | |||
| } | |||
| .panel-heading { | |||
| color: #fff !important; | |||
| background-color: #f4511e !important; | |||
| padding: 25px; | |||
| border-bottom: 1px solid transparent; | |||
| border-top-left-radius: 0px; | |||
| border-top-right-radius: 0px; | |||
| border-bottom-left-radius: 0px; | |||
| border-bottom-right-radius: 0px; | |||
| } | |||
| .panel-footer { | |||
| background-color: white !important; | |||
| } | |||
| .panel-footer h3 { | |||
| font-size: 32px; | |||
| } | |||
| .panel-footer h4 { | |||
| color: #aaa; | |||
| font-size: 14px; | |||
| } | |||
| .panel-footer .btn { | |||
| margin: 15px 0; | |||
| background-color: #f4511e; | |||
| color: #fff; | |||
| } | |||
| .navbar { | |||
| margin-bottom: 0; | |||
| background-color: rgb(93, 152, 167); | |||
| z-index: 9999; | |||
| border: 0; | |||
| font-size: 12px !important; | |||
| line-height: 1.42857143 !important; | |||
| letter-spacing: 4px; | |||
| border-radius: 0; | |||
| font-family: Montserrat, sans-serif; | |||
| box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); | |||
| } | |||
| .navbar li a, .navbar .navbar-brand { | |||
| color: #fff !important; | |||
| font-family: Bangers, sans-serif; | |||
| } | |||
| .navbar-nav li a:hover { | |||
| color: #666 !important; | |||
| background-color: rgb(93, 152, 167)!important; | |||
| } | |||
| .navbar-nav li.active a { | |||
| color: #000 !important; | |||
| background-color: #fff !important; | |||
| } | |||
| .navbar-default .navbar-toggle { | |||
| border-color: transparent; | |||
| color: #fff !important; | |||
| } | |||
| footer .glyphicon { | |||
| font-size: 20px; | |||
| margin-bottom: 20px; | |||
| color: #f4511e; | |||
| } | |||
| .slideanim {visibility:hidden;} | |||
| .slide { | |||
| animation-name: slide; | |||
| -webkit-animation-name: slide; | |||
| animation-duration: 1s; | |||
| -webkit-animation-duration: 1s; | |||
| visibility: visible; | |||
| } | |||
| @keyframes slide { | |||
| 0% { | |||
| opacity: 0; | |||
| transform: translateY(70%); | |||
| } | |||
| 100% { | |||
| opacity: 1; | |||
| transform: translateY(0%); | |||
| } | |||
| } | |||
| @-webkit-keyframes slide { | |||
| 0% { | |||
| opacity: 0; | |||
| -webkit-transform: translateY(70%); | |||
| } | |||
| 100% { | |||
| opacity: 1; | |||
| -webkit-transform: translateY(0%); | |||
| } | |||
| } | |||
| @media screen and (max-width: 768px) { | |||
| .col-sm-4 { | |||
| text-align: center; | |||
| } | |||
| .btn-lg { | |||
| width: 100%; | |||
| } | |||
| } | |||
| @media screen and (max-width: 480px) { | |||
| .logo { | |||
| font-size: 150px; | |||
| } | |||
| } | |||
| @ -0,0 +1,55 @@ | |||
| <?php | |||
| // --- | |||
| // --- La config globale | |||
| // --- | |||
| chdir('/opt/TOPISTO/apps'); | |||
| require_once '/opt/TOPISTO/apps/global/inc/config.php'; | |||
| // --- | |||
| // --- External dependances | |||
| // --- | |||
| require TOPISTO_PATH.'/ressources/vendor/autoload.php'; | |||
| // --- | |||
| // --- Internal dependances | |||
| // --- | |||
| require_once APP_PATH.'/blockchain/inc/block.php'; | |||
| // --- | |||
| // --- Par défaut on cherche le dernier block en cache | |||
| // --- | |||
| $block_hash = blockchain::getLastCacheBlockHash(); | |||
| // --- | |||
| // --- Le cas échéant, on cherche block passé en argument | |||
| // --- | |||
| if (isset($_REQUEST['block_hash'])) $block_hash = $_REQUEST['block_hash']; | |||
| $the_block = blockchain::getBlockWithHash($block_hash); | |||
| if ($the_block === FALSE) die(); | |||
| $the_name = blockchain::hash2SpecialName($the_block->hash); | |||
| if ($the_name == $the_block->hash) $the_name =''; | |||
| // --- | |||
| // --- Si on a passé un hash, le navigateur peut le mettre en cache | |||
| // --- | |||
| if (isset($_REQUEST['block_hash'])) | |||
| { | |||
| $seconds_to_cache = 180; // 3 minutes de cache HTTP | |||
| $ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT"; | |||
| header("Expires: $ts"); | |||
| header("Pragma: cache"); | |||
| header("Cache-Control: max-age=$seconds_to_cache"); | |||
| header('Content-Type: application/json'); | |||
| } | |||
| $message = '{"hash":"'.$the_block->hash.'", "block_index":"'.$the_block->block_index.'", "time":"'.date('Y/m/d H:i:s', $the_block->time).'", "height":"'.$the_block->height.'", "topisto_outputs":"'.$the_block->topisto_outputs.'", "prev":"'.$the_block->prev_block.'", "topisto_inputs":"'.$the_block->topisto_inputs.'", "topisto_fees":"'.$the_block->topisto_fees.'", "topisto_reward":"'.$the_block->topisto_reward.'", "nonce":"'.$the_block->nonce.'", "n_tx":"'.$the_block->n_tx.'"}'; | |||
| if ($_REQUEST['FULL'] == 'OK') $message = json_encode($the_block); | |||
| header('Content-Type: application/json'); | |||
| die($message); | |||
| ?> | |||
| @ -0,0 +1,427 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <title>TOPISTO</title> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet" type="text/css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" type="text/css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Bangers" rel="stylesheet" type="text/css"> | |||
| <link href="css/topisto.css" rel="stylesheet" type="text/css"> | |||
| <link href="css/fonts.css" rel="stylesheet" type="text/css"> | |||
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> | |||
| <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> | |||
| <script> | |||
| // Init array | |||
| var liste_blocks = { | |||
| 'SEGWIT' : '000000000000000000cbeff0b533f8e1189cf09dfbebf57a8ebe349362811b80', | |||
| 'SEGWIT_LOCK' : '0000000000000000012e6060980c6475a9a8e62a1bf44b76c5d51f707d54522c', | |||
| 'BCC' : '00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148', | |||
| 'BIP_91_LOCK' : '0000000000000000015411ca4b35f7b48ecab015b14de5627b647e262ba0ec40', | |||
| 'HALVING_2' : '000000000000000002cce816c0ab2c5c269cb081896b7dcb34b8422d6b74ffa1', | |||
| 'HALVING_1' : '000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e', | |||
| 'PIZZA' : '00000000006de085dadb3ec413ef074022fe781121b467e98960280dd246bb00', | |||
| 'TOPISTO' : '000000000a73e64735a2b75c97ea674950a9018da1420d01328a918c9ff9852c', | |||
| 'LEET' : '000000008bf44a528a09d203203a6a97c165cf53a92ecc27aed0b49b86a19564', | |||
| 'LUCIFER' : '00000000fc5b3c76f27f810ee775e480ae7fd604fd196b2d8da4257fcd39f4f9', | |||
| 'THE_ANSWER' : '00000000314e90489514c787d615cea50003af2023796ccdd085b6bcc1fa28f5', | |||
| 'GENESIS' : '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', | |||
| }; | |||
| var flag_nav = true; | |||
| var classes = ['bg-grey-even','bg-grey-odd']; | |||
| var cur_class = 0; | |||
| var cur_height = []; | |||
| function precisionRound(number) { | |||
| var precision = 4; | |||
| var factor = Math.pow(10, precision); | |||
| return Math.round((number/100000000) * factor) / factor; | |||
| } | |||
| function addInfoForBlock(block) | |||
| { | |||
| var height = '300px'; | |||
| var contenu = ''; | |||
| var downloadingImage = new Image(); | |||
| cur_height.push(block.height); | |||
| cur_class = 1 - cur_class; | |||
| contenu += ' <h2> <span style="font-size:12px">block</span> '+block.height+'</h2>'; | |||
| contenu += ' <table width="100%">'; | |||
| //contenu += ' <tr><td>hash</td><td align="right"><b>'+block.hash+'</b></td></tr>'; | |||
| //contenu += ' <tr><td>index</td><td align="right"><b>'+block.block_index+'</b></td></tr>'; | |||
| contenu += ' <tr><td>timestamp</td><td align="right"><b>'+block.time+'</b></td></tr>'; | |||
| contenu += ' <tr><td>nonce</td><td align="right"><b>'+block.nonce+'</b></td></tr>'; | |||
| contenu += ' <tr><td>nb tx</td><td align="right"><b>'+block.n_tx+'</b></td></tr>'; | |||
| contenu += ' <tr><td>outputs</td><td align="right"><b>'+precisionRound(block.topisto_outputs).toFixed(4)+'</b></td></tr>'; | |||
| contenu += ' <tr><td>inputs</td><td align="right"><b>'+precisionRound(block.topisto_inputs).toFixed(4)+'</b></td></tr>'; | |||
| contenu += ' <tr><td>fees</td><td align="right"><b>'+precisionRound(block.topisto_fees).toFixed(4)+'</b></td></tr>'; | |||
| contenu += ' <tr><td>reward</td><td align="right"><b>'+precisionRound(block.topisto_reward).toFixed(4)+'</b></td></tr>'; | |||
| contenu += ' </table>'; | |||
| $('#info_'+block.height).html(contenu); | |||
| downloadingImage.onload = function(){ | |||
| $('#img_'+block.height).attr('src', this.src); | |||
| //$('#img_'+block.height).attr('height', height); | |||
| //$('#img_'+block.height).attr('width','auto'); | |||
| flag_nav = true; | |||
| }; | |||
| downloadingImage.src = 'images/block_image.php?methode=hasard&hash='+block.hash; | |||
| return true; | |||
| } | |||
| function addDivForBlock(block_height) | |||
| { | |||
| var contenu = ''; | |||
| contenu += '<div id="block_'+block_height+'" class="container-fluid '+classes[cur_class]+'">'; | |||
| contenu += ' <div class="row">'; | |||
| contenu += ' <div class="col-sm-8" id="info_'+block_height+'">'; | |||
| contenu += ' <h2>COMPUTING ...</h2>'; | |||
| contenu += ' </div>'; | |||
| contenu += ' <div class="col-sm-4 text-right">'; | |||
| contenu += ' <div width="100%" class="text-center">'; | |||
| contenu += ' <img id="img_'+block_height+'" src="images/loading.gif" class="img-responsive"></img>'; | |||
| contenu += ' </div>'; | |||
| contenu += ' </div>'; | |||
| contenu += ' </div>'; | |||
| contenu += '</div>'; | |||
| $('#blockchain').append(contenu); | |||
| return true; | |||
| } | |||
| function addDivForVoid() | |||
| { | |||
| var contenu = ''; | |||
| contenu += '<div id="the_void" class="container-fluid '+classes[cur_class]+'">'; | |||
| contenu += ' <div class="row">'; | |||
| contenu += ' <div class="col-sm-8">'; | |||
| contenu += ' <h2>the VOID ... </h2>'; | |||
| contenu += ' </div>'; | |||
| contenu += ' <div class="col-sm-4 text-right">'; | |||
| contenu += ' <div width="100%" class="text-center">'; | |||
| contenu += ' <img src="images/loading.gif" class="img-responsive"></img>'; | |||
| contenu += ' </div>'; | |||
| contenu += ' </div>'; | |||
| contenu += ' </div>'; | |||
| contenu += '</div>'; | |||
| $('#blockchain').append(contenu); | |||
| return true; | |||
| } | |||
| function toggleForwardBtn() | |||
| { | |||
| if (cur_height.length < 3) | |||
| { | |||
| $('#btn-forward').removeClass('btn-info'); | |||
| $('#btn-forward').addClass('btn-danger'); | |||
| } else { | |||
| $('#btn-forward').removeClass('btn-danger'); | |||
| $('#btn-forward').addClass('btn-info'); | |||
| } | |||
| return true; | |||
| } | |||
| function gotoBlock(block_name) | |||
| { | |||
| $(document).scrollTop( $("#navigation").offset().top ); | |||
| // Bloquer la navigation pendant le calcul | |||
| if (!flag_nav) | |||
| { | |||
| window.alert('A block image is currently computed, please wait ...'); | |||
| return false; | |||
| } | |||
| flag_nav = false; | |||
| if (block_name == 'NEXT') | |||
| { | |||
| flag_nav = true; | |||
| // Supprimer un block | |||
| if (cur_height.length < 3) | |||
| { | |||
| window.alert('No Next Block, you are at '+$('#blockSelector').val()+' block !'); | |||
| return false; | |||
| } | |||
| liste_blocks['PREVIOUS'] = liste_blocks['BLOCK_'+cur_height[cur_height.length-1]]; | |||
| cur_height.pop(); | |||
| toggleForwardBtn(); | |||
| $('#block_'+cur_height[cur_height.length-2]).slideDown(400, function() { | |||
| // Animation complete. | |||
| $('#blockchain').children('div:last').remove(); | |||
| cur_class = 1 - cur_class; | |||
| }); | |||
| } else { | |||
| // Ajouter un block | |||
| addDivForBlock(cur_height[cur_height.length-1] - 1); | |||
| // Décaler d'un block vers le haut | |||
| if (cur_height.length > 1) $('#block_'+cur_height[cur_height.length-2]).slideUp(); | |||
| block_hash = ''; | |||
| if (block_name != 'LAST') block_hash = '?block_hash='+liste_blocks[block_name]; | |||
| $.getJSON('data/getBlockInfo.php'+block_hash, function( data ) { | |||
| liste_blocks['PREVIOUS'] = data.prev; | |||
| liste_blocks['BLOCK_'+data.height] = data.hash; | |||
| addInfoForBlock(data); | |||
| toggleForwardBtn(); | |||
| }); | |||
| } | |||
| return true; | |||
| } | |||
| function initBlockchain(block_name) | |||
| { | |||
| $(document).scrollTop( $("#navigation").offset().top ); | |||
| $('#blockchain').html(''); | |||
| cur_height = []; | |||
| cur_class = 0; | |||
| flag_nav = true; | |||
| block_hash = ''; | |||
| if (block_name != 'LAST') block_hash = '?block_hash='+liste_blocks[block_name]; | |||
| $.getJSON('data/getBlockInfo.php'+block_hash, function( data ) { | |||
| addDivForBlock(data.height); | |||
| addInfoForBlock(data); | |||
| liste_blocks['PREVIOUS'] = data.prev; | |||
| if (data.prev != '0000000000000000000000000000000000000000000000000000000000000000') | |||
| gotoBlock('PREVIOUS'); | |||
| else | |||
| addDivForVoid(); | |||
| }); | |||
| return true; | |||
| } | |||
| function blockSelectorChange() | |||
| { | |||
| initBlockchain($('#blockSelector').val()); | |||
| $('#fast_forward_btn').attr('data-original-title', $('#blockSelector').val()); | |||
| } | |||
| $(document).ready(function(){ | |||
| // Init the selector | |||
| var select = $('#blockSelector'); | |||
| $.each(liste_blocks, function (key, text) { | |||
| select.append(new Option(key, key)); | |||
| }); | |||
| // tooltips activation | |||
| $('[data-toggle="tooltip"]').tooltip(); | |||
| // init de la Blockchain | |||
| initBlockchain('LAST'); | |||
| // Add smooth scrolling to all links in navbar + footer link | |||
| $(".navbar a, footer a[href='#myPage']").on('click', function(event) { | |||
| // Make sure this.hash has a value before overriding default behavior | |||
| if (this.hash !== "") { | |||
| // Prevent default anchor click behavior | |||
| event.preventDefault(); | |||
| // Store hash | |||
| var hash = this.hash; | |||
| // Using jQuery's animate() method to add smooth page scroll | |||
| // The optional number (900) specifies the number of milliseconds it takes to scroll to the specified area | |||
| $('html, body').animate({ | |||
| scrollTop: $(hash).offset().top | |||
| }, 900, function(){ | |||
| // Add hash (#) to URL when done scrolling (default click behavior) | |||
| window.location.hash = hash; | |||
| }); | |||
| } // End if | |||
| }); | |||
| $(window).scroll(function() { | |||
| $(".slideanim").each(function(){ | |||
| var pos = $(this).offset().top; | |||
| var winTop = $(window).scrollTop(); | |||
| if (pos < winTop + 600) { | |||
| $(this).addClass("slide"); | |||
| } | |||
| if (winTop < 400) $('#logo_topisto').css('visibility', 'hidden'); | |||
| else $('#logo_topisto').css('visibility', 'visible'); | |||
| }); | |||
| }); | |||
| }); | |||
| </script> | |||
| </head> | |||
| <body id="myPage" data-spy="scroll" data-target=".navbar" data-offset="60"> | |||
| <nav class="navbar navbar-default navbar-fixed-top"> | |||
| <div class="container"> | |||
| <div class="navbar-header"> | |||
| <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar"> | |||
| <span class="icon-bar"></span> | |||
| <span class="icon-bar"></span> | |||
| <span class="icon-bar"></span> | |||
| </button> | |||
| <a class="navbar-brand" href=".."> | |||
| <img id="logo_topisto" src="images/topisto_vert.png" style="border-radius:6px;display:inline-block;visibility:hidden;height:72px;;box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"> | |||
| <span style="vertical-align:text-bottom;display:inline-block;color:black;font-family: Bangers, sans-serif;font-size: 70px;">TOPISTO</span> | |||
| </a> | |||
| </div> | |||
| <div class="collapse navbar-collapse" id="myNavbar"> | |||
| <ul class="nav navbar-nav navbar-right"> | |||
| <li><a href="#about">About</a></li> | |||
| <li><a href="#blog">Blog</a></li> | |||
| <li><a href="#contact">Contact</a></li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </nav> | |||
| <div id="about" class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="images/topisto_vert.png" alt="avatar dragon gargoyle" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <br><br><br> | |||
| <h4>This site is a hobby.<br>It's also a testing place where i can do <a href="blog.php">things</a> that are impossible at work.</h4> | |||
| <p class="text-justify">I like <b>surfing</b> and sailing on the ocean. But during longs winter nights, computing is fun. I'm interesting into <b>cryptocurrencies</b>, computationnal art, datavisualisation. I know that <b>42</b> is the answer to the Life, Universe and Everything Else. As <b>Eto Demerzel</b> said to me, the <b>Seldon's Plan</b> will save the Galaxy. My favorites books are the <b>House of leaves</b>, the <b>Necronomicron</b> and the <b>DarkHold</b>. I also know that great power comes with great responsibility. I'm still in <b>the search of Captain Zero</b>, because <b>the truth is out there</b>.</p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div id="navigation" class="container-fluid bg-grey" style="padding-top:10px"> | |||
| <div class="row"> | |||
| <div class="col-sm-12"> </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey" style="padding-top:0px;padding-bottom:10px"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <br><br><xsmall>A kind of Blockchain Explorer</xsmall> | |||
| </div> | |||
| <div class="col-sm-2"> | |||
| <a href="#navigation" onclick="javascript:initBlockchain('GENESIS');" class="btn btn-success btn-lg" data-toggle="tooltip" title="GENESIS"> | |||
| <span class="glyphicon glyphicon-fast-backward"></span> | |||
| </a> | |||
| <a href="#navigation" onclick="javascript:gotoBlock('PREVIOUS');" class="btn btn-info btn-lg" data-toggle="tooltip" title="PREVIOUS"> | |||
| <span class="glyphicon glyphicon-backward"></span> | |||
| </a> | |||
| </div> | |||
| <div class="col-sm-4"> | |||
| <select id="blockSelector" style="width:100%" onchange="javascript:blockSelectorChange()" data-width="100%"> | |||
| <option value="LAST">LAST</option> | |||
| </select> | |||
| </div> | |||
| <div class="col-sm-2 text-right"> | |||
| <a id="btn-forward" href="#navigation" onclick="javascript:gotoBlock('NEXT');" class="btn btn-danger btn-lg" data-toggle="tooltip" title="NEXT"> | |||
| <span class="glyphicon glyphicon-forward"></span> | |||
| </a> | |||
| <a href="#navigation" id="fast_forward_btn" onclick="javascript:blockSelectorChange()" class="btn btn-success btn-lg" data-toggle="tooltip" title="LAST"> | |||
| <span class="glyphicon glyphicon-fast-forward"></span> | |||
| </a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div id="blockchain"></div> | |||
| <div id="blog" class="container-fluid bg-grey" style="padding-top:50px;align:right"> | |||
| <div class="row"> | |||
| <div class="col-sm-8"> </div> | |||
| <div class="col-sm-4"> | |||
| <span style="color:black;font-family: Bangers, sans-serif;font-size: 35px;">Blog</span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <?php | |||
| $odd_even = 0; | |||
| $liste = ''; | |||
| foreach (glob("articles/*/header.html") as $filename) { | |||
| $article = basename(dirname($filename)); | |||
| $odd_even = 1 - $odd_even; | |||
| $header = file_get_contents($filename); | |||
| if ($odd_even == 0) $header = str_replace('bg-grey','bg-grey-odd',$header); | |||
| else $header = str_replace('bg-grey','bg-grey-even',$header); | |||
| $header = str_replace('ARTICLE',$article,$header); | |||
| if (!file_exists('articles/'.$article.'/content.html')) | |||
| { | |||
| $header = str_replace('####BUTTON####','',$header); | |||
| } else { | |||
| echo '<script>'; | |||
| echo '$(document).ready(function(){'; | |||
| echo ' $("#'.$article.'_button").on("click", function(event) {'; | |||
| echo ' window.location="page.php?id='.$article.'"'; | |||
| echo ' })'; | |||
| echo '})'; | |||
| echo '</script>'; | |||
| $header = str_replace('####BUTTON####','<button id="'.$article.'_button" class="btn btn-default btn-lg float-left">See more</button>',$header); | |||
| } | |||
| $liste = $header.PHP_EOL.$liste; | |||
| } | |||
| echo $liste; | |||
| ?> | |||
| <div id="contact" class="container-fluid bg-grey"> | |||
| <h4 class="text-center">CONTACT</h4> | |||
| <div class="row slideanim"> | |||
| <div class="col-sm-5"> | |||
| <p>Contact me</p> | |||
| <p><span class="glyphicon glyphicon-map-marker"></span> Shambala</p> | |||
| <p><span class="glyphicon glyphicon-globe"></span> Employer : Mutiny</p> | |||
| <p><span class="glyphicon glyphicon-phone"></span> +00 666 666 666</p> | |||
| <p> | |||
| <span class="glyphicon glyphicon-envelope"></span> | |||
| <!--Place the code below where you want the link to be displayed--> | |||
| <span id="obf"><script>document.getElementById("obf").innerHTML="<n uers=\"znvygb:nyoreg.frnaquvyf@gbcvfgb.arg?fhowrpg=pbagnpg\" gnetrg=\"_oynax\">nyoreg.frnaquvyf@gbcvfgb.arg</n>".replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});</script> | |||
| <noscript><span style="unicode-bidi:bidi-override;direction:rtl;">ten.otsipot@slihdnaes.trebla</span></noscript></span> | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-5"> | |||
| bookmarks : <br> | |||
| <a href="http://inconvergent.net/" target="_blank">Inconvergent</a><br> | |||
| <a href="http://www.datasketch.es/" target="_blank">Data Sketches</a><br> | |||
| <a href="https://bit101.github.io/lab/dailies/170310.html" target="_blank">bit101-github</a><br> | |||
| <a href="http://www.imdb.com/title/tt1508021/" target="_blank">being captain zero</a><br> | |||
| </div> | |||
| <div class="col-sm-2"> | |||
| PGP : <br> | |||
| <a href="page.php?id=00000000"><img src="articles/00000000/public_key_qrcode.png" width="100%; height: auto"></img></a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <footer class="container-fluid bg-grey text-center"> | |||
| <a href="#myPage" title="To Top"> | |||
| <span class="glyphicon glyphicon-chevron-up"></span> | |||
| </a> | |||
| <p>Bootstrap Theme Made By <a href="https://www.w3schools.com" title="Visit w3schools">www.w3schools.com</a></p> | |||
| </footer> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,106 @@ | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-8"> | |||
| <h2>A digital currency</h2> | |||
| <p> | |||
| Use cryptography.<br> | |||
| No "central bank".<br> | |||
| P2P network.<br> | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-4 align-middle"> | |||
| <img src="images/bitcoin.jpg" width="100%; height: auto"></img> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4 align-middle"> | |||
| <img src="images/wallet.png" height="300px"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>WALLET</h2> | |||
| <p> | |||
| Use cryptography.<br> | |||
| No "central bank".<br> | |||
| P2P network.<br> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-8"> | |||
| <h2>Transaction</h2> | |||
| <p> | |||
| When Bob is sending 42 <span class="glyphicon glyphicon-bitcoin"></span> to Alice, the bitcoin protocol is creating a transaction.<br> | |||
| A transaction is the balance between his Inputs and his Outputs. | |||
| <ul> | |||
| <li>First the network needs to be sure that Bob has got enough <span class="glyphicon glyphicon-bitcoin"></span> in his wallet.<br>So Bob will include a list of transactions that he has received before.<br>This is the inputs of the transactions.<br>For example, previous transaction's amount are 10, 25 and another 10, so total inputs are 45 <span class="glyphicon glyphicon-bitcoin"></span></li> | |||
| <li>As the input's sum is superior to the amount of the transaction, an ouput is added.<br>This output is the change to Bob.<br>In the example, this 3 <span class="glyphicon glyphicon-bitcoin"></span></li> | |||
| <li>Fees are took by the network to process the transaction.<br>Let say it will take 0.5<span class="glyphicon glyphicon-bitcoin"></span>.<br>This is another ouput</li> | |||
| <li>So Alice will get only a part of the transaction's amount.<br>this the last output, 41.5<span class="glyphicon glyphicon-bitcoin"></span></li> | |||
| </ul> | |||
| So the transaction is a made with : | |||
| <ul> | |||
| <li>Inputs : a list of previous transactions that bob has received</li> | |||
| <li>Outputs : a list of amounts to send to Alice, Bob, and fees</li> | |||
| <li>A timestamp</li> | |||
| </ul> | |||
| Then the wallet will broadcast the transaction on the network.<br> | |||
| It will be sent to the area called "mempool".<br> | |||
| Nodes of the network will validate the transaction by verfying the Inputs and the Ouputs.<br> | |||
| A hash of the transaction is computed.<br> | |||
| So the transaction is sealed. | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-4 align-middle"> | |||
| <img src="images/bob_to_alice.gif" width="100%; height: auto"></img> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid"> | |||
| <div class="row"> | |||
| <div class="col-sm-4"> | |||
| <img src="images/tx2block.gif" width="100%; height: auto"></img> | |||
| </div> | |||
| <div class="col-sm-8"> | |||
| <h2>Block</h2> | |||
| <p> | |||
| Then the bitcoin protocol is grouping validated transactions in blocks.<br> | |||
| So a block is an array of transactions.<br> | |||
| A block is made with : | |||
| <ul> | |||
| <li>An array of transactions.<br> | |||
| That's what my script is drawing.<br> | |||
| Running the transactions array, it is computing the sum of each outputs of each transaction.<br> | |||
| Then, it draws a rectangle for each sum.<br> | |||
| </li> | |||
| <li>A "proof of work".<br>The miner is computing a special hash that depends on the array of transaction and that will answer to a constraint.<br>This hash is very difficult to compute. In fact it is not really computed, the miner must test every value until he find the solution.<br></li> | |||
| <li>That why the network will give him a reward.<br>This is a special transaction that has no inputs.</li> | |||
| </ul> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="container-fluid bg-grey"> | |||
| <div class="row"> | |||
| <div class="col-sm-8"> | |||
| <h2>Blockchain</h2> | |||
| <p> | |||
| Each block has a hash and a reference to the ihash of the previous block.<br> | |||
| So blocks are chained together.<br> | |||
| </p> | |||
| </div> | |||
| <div class="col-sm-4"> | |||
| <img src="/images/loading.gif" width="100%; height: auto"></img> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,21 @@ | |||
| <?php | |||
| $type='spline'; | |||
| if (isset($_REQUEST['type'])) $type=$_REQUEST['type']; | |||
| $files = glob('/opt/TOPISTO/data/'.$type.'/*.png'); | |||
| usort($files, function($a, $b) { | |||
| return filemtime($a) > filemtime($b); | |||
| }); | |||
| $block_image = @imagecreatefrompng($files[0]); | |||
| // --- | |||
| // --- envoyer l'image au navigateur | |||
| // --- | |||
| header("Content-Type: image/png"); | |||
| imagepng($block_image); | |||
| imagedestroy($block_image); | |||
| ?> | |||
| @ -0,0 +1,231 @@ | |||
| <?php | |||
| function tri_filemtime( $a, $b ) { return filemtime($a) - filemtime($b); } | |||
| // --- | |||
| // --- La config globale | |||
| // --- | |||
| chdir('/opt/TOPISTO/apps'); | |||
| require_once '/opt/TOPISTO/apps/global/inc/config.php'; | |||
| // --- | |||
| // --- External dependances | |||
| // --- | |||
| require TOPISTO_PATH.'/ressources/vendor/autoload.php'; | |||
| // --- | |||
| // --- Internal dependances | |||
| // --- | |||
| require_once APP_PATH.'/blockchain/inc/block.php'; | |||
| // --- | |||
| // --- Par défaut on cherche le dernier block | |||
| // --- | |||
| $block_hash = '*'; | |||
| $methode='hasard'; | |||
| $mode=9999; | |||
| // --- | |||
| // --- Le cas échéant, on cherche block passé en argument | |||
| // --- | |||
| if (isset($_REQUEST['hash'])) $block_hash = $_REQUEST['hash']; | |||
| if (isset($_REQUEST['methode'])) $methode = $_REQUEST['methode']; | |||
| if (isset($_REQUEST['mode'])) $mode = intval($_REQUEST['mode']); | |||
| $img = null; | |||
| // --- | |||
| // --- Le cas général : on trouve le fichier image demandé | |||
| // --- | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash; | |||
| if ($mode != 9999) $imagefilename .= '-'.$mode; | |||
| $imagefilename .= '.png'; | |||
| if (file_exists($imagefilename)) $img = imagecreatefrompng($imagefilename); | |||
| // --- | |||
| // --- "strict" est un flag qui indique que l'on veut précisément | |||
| // --- ce que les paramètres demandent | |||
| // --- | |||
| if (($img==null)&&(!isset($_REQUEST['strict']))) | |||
| { | |||
| // | |||
| // On n'a pas passé de HASH | |||
| // | |||
| if (($img==null)&&(!isset($_REQUEST['hash']))) | |||
| { | |||
| // | |||
| // On prend le dernier connu pour la méthode et le mode | |||
| // | |||
| $myarray = glob(DATA_PATH.'/'.$methode.'/*-'.$mode.'.png'); | |||
| if (isset($myarray[0])) | |||
| { | |||
| usort( $myarray, tri_filemtime ); | |||
| $img = imagecreatefrompng($myarray[0]); | |||
| } | |||
| // | |||
| // On prend le dernier connu pour la méthode tout mode confondu | |||
| // | |||
| if ($img==null) | |||
| { | |||
| $myarray = glob(DATA_PATH.'/'.$methode.'/*.png'); | |||
| if (isset($myarray[0])) | |||
| { | |||
| usort( $myarray, tri_filemtime ); | |||
| $img = imagecreatefrompng($myarray[0]); | |||
| } | |||
| } | |||
| } | |||
| // | |||
| // On a passé un HASH | |||
| // | |||
| if (($img==null)&&(isset($_REQUEST['hash']))) | |||
| { | |||
| // | |||
| // On cherche le bon HASH et la bonne méthode | |||
| // | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash.'.png'; | |||
| if (($img==null)&&(file_exists($imagefilename))) | |||
| { | |||
| $img = imagecreatefrompng($imagefilename); | |||
| } | |||
| // | |||
| // En fait on s'en fiche de la méthode tant qu'on a le bon HASH | |||
| // | |||
| $imagefilename = DATA_PATH.'/hasard/'.$block_hash.'.png'; | |||
| if (($img==null)&&(file_exists($imagefilename))) | |||
| { | |||
| $img = imagecreatefrompng($imagefilename); | |||
| } | |||
| } | |||
| } | |||
| // | |||
| // On n'a rien trouvé, donc on dessine ce qui est demandé | |||
| // | |||
| if ($img==null) | |||
| { | |||
| if ($mode == 9999) $mode = 0; | |||
| if ($block_hash == '*') $block_hash = blockchain::getLastCacheBlockHash(); | |||
| if ($methode == 'hasard') $methode = 'treemap'; | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash.'.png'; | |||
| $the_block = blockchain::getBlockWithHash($block_hash); | |||
| if ($the_block === FALSE) die(); | |||
| $the_name = blockchain::hash2SpecialName($the_block->hash); | |||
| if ($the_name == $the_block->hash) $the_name =''; | |||
| $bandeau = 50; | |||
| $marge = 25; | |||
| $text_border = 20; | |||
| $width = GRAPH_WIDTH; | |||
| $height = GRAPH_HEIGHT; | |||
| // Pour que l'image simple ait les proportions que l'image full | |||
| $width = $marge + ($width*2) + (2*$text_border); | |||
| $height = $marge + ($height*2); | |||
| $img_w = $width; | |||
| $img_h = $height+(2*$bandeau); | |||
| $type=2; | |||
| if (count($the_block->tx)==1) $type=4; | |||
| // création d'une image plus haute pour inclure bandeaux haut et bas | |||
| $img = imagecreatetruecolor($img_w, $img_h); | |||
| $param0 = blockchain::DrawBlockHeaderFooter($the_block, $img, $bandeau); | |||
| $parametres = []; | |||
| $parametres['x'] = 0; | |||
| $parametres['y'] = $bandeau; | |||
| $parametres['width'] = $width; | |||
| $parametres['height'] = $height; | |||
| $parametres['methode'] = $mode; | |||
| $parametres['type'] = $type; | |||
| $parametres['transparent_color'] = $param0[0]; | |||
| $parametres['background_color'] = $param0[1]; | |||
| $parametres['font_color'] = $param0[2]; | |||
| $parametres['fontname'] = $param0[3]; | |||
| $parametres['font_RGB'] = $param0[4]; | |||
| $parametres['background_RGB'] = $param0[5]; | |||
| switch($methode) | |||
| { | |||
| case 'veraMolnar': | |||
| require_once APP_PATH.'/methode/veraMolnar/inc/treemap.php'; | |||
| topisto_veraMolnar::DrawBlock($the_block, $img, $parametres); | |||
| break; | |||
| case 'treemapV2': | |||
| require_once APP_PATH.'/methode/treemapV2/inc/treemap.php'; | |||
| topisto_treemap::DrawBlock($the_block, $img, $parametres); | |||
| break; | |||
| case 'treemap_fuzzy': | |||
| require_once APP_PATH.'/methode/treemap_fuzzy/inc/treemap.php'; | |||
| topisto_treemap_fuzzy::DrawBlock($the_block, $img, 0, $bandeau, $width, $height, $mode, $type); | |||
| break; | |||
| case 'spline': | |||
| require_once APP_PATH.'/methode/spline/inc/splines.php'; | |||
| topisto_spline::DefaultDrawBlock($the_block, $img, 0, $bandeau, $width, $height, $mode, $type); | |||
| break; | |||
| case 'spline_2': | |||
| require_once APP_PATH.'/methode/spline/inc/splines.php'; | |||
| topisto_spline::DrawBlock($the_block, $img, 0, $bandeau, $width, $height, $mode, 200, $type); | |||
| break; | |||
| case 'treemap' : | |||
| default: | |||
| require_once APP_PATH.'/methode/treemap/inc/treemap.php'; | |||
| topisto_treemap::DrawBlock($the_block, $img, 0, $bandeau, $width, $height, $mode, $type); | |||
| break; | |||
| } | |||
| // Les textes | |||
| /* | |||
| * INUTILE depuis que blockchain::DrawBlockHeaderFooter ajoute elle-même les textes | |||
| * | |||
| putenv('GDFONTPATH='.RESS_PATH.'/fonts/'); | |||
| $font = 'DS-DIGIB.TTF'; | |||
| $fontColor = imagecolorallocate($img, 227,227,153); | |||
| $the_texte = "Height : ".$the_block->height; | |||
| imagettftext($img,18, 0, 5, $bandeau-5, $fontColor, $font, $the_texte); | |||
| $the_texte = "Inputs : ".$the_block->topisto_inputs; | |||
| if ($type == 4) $the_texte = "Reward : ".$the_block->topisto_reward; | |||
| imagettftext($img,15, 0, 5, $bandeau+$height+18, $fontColor, $font, $the_texte); | |||
| if ($the_name == '') $the_name = date('Ymd H:m:s', $the_block->time); | |||
| $bbox = imagettfbbox(14, 0, $font, $the_name); | |||
| imagettftext($img, 14, 0, ($img_w-3)-($bbox[2]-$bbox[0]), ($bandeau-5), $fontColor, $font, $the_name); | |||
| */ | |||
| // Sauvegarder l'image et ajouter | |||
| // - un lien sur le mode | |||
| // - un lien dans le dossier "hasard" | |||
| $imagefilename2 = str_replace(".png","-$mode.png", $imagefilename); | |||
| imagepng($img,$imagefilename2); | |||
| if (!file_exists($imagefilename)) link($imagefilename2, $imagefilename); | |||
| $imagefilename2 = str_replace("methode/$methode/","methode/hasard/", $imagefilename); | |||
| if (!file_exists($imagefilename2)) link($imagefilename, $imagefilename2); | |||
| } | |||
| $seconds_to_cache = 7200; | |||
| $ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT"; | |||
| header("Expires: $ts"); | |||
| header("Pragma: cache"); | |||
| header("Cache-Control: max-age=$seconds_to_cache"); | |||
| header('Content-Type: image/png'); | |||
| imagepng($img); | |||
| imagedestroy($img); | |||
| ?> | |||
| @ -0,0 +1,53 @@ | |||
| <?php | |||
| // --- | |||
| // --- La config globale | |||
| // --- | |||
| chdir('/opt/TOPISTO/apps'); | |||
| require_once '/opt/TOPISTO/apps/global/inc/config.php'; | |||
| // --- | |||
| // --- External dependances | |||
| // --- | |||
| require TOPISTO_PATH.'/ressources/vendor/autoload.php'; | |||
| // --- | |||
| // --- Internal dependances | |||
| // --- | |||
| require_once APP_PATH.'/blockchain/inc/block.php'; | |||
| // --- | |||
| // --- Par défaut on cherche le dernier block | |||
| // --- | |||
| $block_hash = '*'; | |||
| // --- | |||
| // --- Le cas échéant, on cherche block passé en argument | |||
| // --- | |||
| if (isset($_REQUEST['hash'])) $block_hash = $_REQUEST['hash']; | |||
| $img = null; | |||
| $myarray = glob(DATA_PATH.'/hasard/'.$block_hash.'.png'); | |||
| if (isset($myarray[0])) | |||
| { | |||
| usort( $myarray, function( $a, $b ) { return filemtime($a) - filemtime($b); } ); | |||
| $img = imagecreatefrompng($myarray[0]); | |||
| $seconds_to_cache = 7200; | |||
| $ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT"; | |||
| header("Expires: $ts"); | |||
| header("Pragma: cache"); | |||
| header("Cache-Control: max-age=$seconds_to_cache"); | |||
| header('Content-Type: image/png'); | |||
| imagepng($img); | |||
| imagedestroy($img); | |||
| exit(0); | |||
| } | |||
| require_once('block_image.php'); | |||
| ?> | |||
| @ -0,0 +1,109 @@ | |||
| <?php | |||
| // --- | |||
| // --- La config globale | |||
| // --- | |||
| chdir('/opt/TOPISTO/apps'); | |||
| require_once '/opt/TOPISTO/apps/global/inc/config.php'; | |||
| // --- | |||
| // --- External dependances | |||
| // --- | |||
| require TOPISTO_PATH.'/ressources/vendor/autoload.php'; | |||
| // --- | |||
| // --- Internal dependances | |||
| // --- | |||
| require_once APP_PATH.'/blockchain/inc/block.php'; | |||
| // --- | |||
| // --- Par défaut on cherche le dernier block | |||
| // --- | |||
| $block_hash = '*'; | |||
| $methode='hasard'; | |||
| $mode=9999; | |||
| // --- | |||
| // --- Le cas échéant, on cherche block passé en argument | |||
| // --- | |||
| if (isset($_REQUEST['hash'])) $block_hash = $_REQUEST['hash']; | |||
| if (isset($_REQUEST['methode'])) $methode = $_REQUEST['methode']; | |||
| if (isset($_REQUEST['mode'])) $mode = intval($_REQUEST['mode']); | |||
| //$methode = str_replace('full_', '', $methode); | |||
| $img = null; | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash.'-'.$mode.'.png'; | |||
| echo $imagefilename; | |||
| if (file_exists($imagefilename)) echo ' <font color="green">OK</font><br>'.PHP_EOL; | |||
| else echo ' <font color="red">KO</font><br>'.PHP_EOL; | |||
| // | |||
| // "strict" est un flag qui indique que l'on veut précisément | |||
| // ce que les paramètres demandent | |||
| // | |||
| if (($img==null)&&(!isset($_REQUEST['strict']))) | |||
| { | |||
| // | |||
| // On n'a pas passé de HASH | |||
| // | |||
| if (($img==null)&&(!isset($_REQUEST['hash']))) | |||
| { | |||
| // | |||
| // On prend le dernier connu pour la méthode et le mode | |||
| // | |||
| $myarray = glob(DATA_PATH.'/'.$methode.'/*-'.$mode.'.png'); | |||
| if (isset($myarray[0])) | |||
| { | |||
| usort( $myarray, function( $a, $b ) { return filemtime($a) - filemtime($b); } ); | |||
| echo $myarray[0].' <font color="green">OK</font><br>'.PHP_EOL; | |||
| $img=true; | |||
| } | |||
| // | |||
| // On prend le dernier connu pour la méthode tout mode confondu | |||
| // | |||
| if ($img==null) | |||
| { | |||
| $myarray = glob(DATA_PATH.'/'.$methode.'/*.png'); | |||
| if (isset($myarray[0])) | |||
| { | |||
| usort( $myarray, function( $a, $b ) { return filemtime($a) - filemtime($b); } ); | |||
| echo $myarray[0].' <font color="green">OK</font><br>'.PHP_EOL; | |||
| $img=true; | |||
| } | |||
| } | |||
| } | |||
| // | |||
| // On a passé un HASH | |||
| // | |||
| if (($img==null)&&(isset($_REQUEST['hash']))) | |||
| { | |||
| // | |||
| // On cherche le bon HASH et la bonne méthode | |||
| // | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash.'.png'; | |||
| if (($img==null)&&(file_exists($imagefilename))) | |||
| { | |||
| echo $imagefilename.' <font color="green">OK</font><br>'.PHP_EOL; | |||
| $img=true; | |||
| } | |||
| // | |||
| // En fait on s'en fiche de la méthode tant qu'on a le bon HASH | |||
| // | |||
| $imagefilename = DATA_PATH.'/hasard/'.$block_hash.'.png'; | |||
| if (($img==null)&&(file_exists($imagefilename))) | |||
| { | |||
| echo $imagefilename.' <font color="green">OK</font><br>'.PHP_EOL; | |||
| $img=true; | |||
| } | |||
| } | |||
| } | |||
| // | |||
| // On n'a rien trouvé, donc on dessine ce qui est demandé | |||
| // | |||
| if ($img==null) echo '<font color="red">KO</font><br>'; | |||
| ?> | |||
| @ -0,0 +1,237 @@ | |||
| <?php | |||
| // --- | |||
| // --- La config globale | |||
| // --- | |||
| chdir('/opt/TOPISTO/apps'); | |||
| require_once '/opt/TOPISTO/apps/global/inc/config.php'; | |||
| // --- | |||
| // --- External dependances | |||
| // --- | |||
| require TOPISTO_PATH.'/ressources/vendor/autoload.php'; | |||
| // --- | |||
| // --- Internal dependances | |||
| // --- | |||
| require_once APP_PATH.'/blockchain/inc/block.php'; | |||
| // --- | |||
| // --- Par défaut on cherche le dernier block | |||
| // --- | |||
| $block_hash = '*'; | |||
| $methode='hasard'; | |||
| $mode=9999; | |||
| // --- | |||
| // --- Le cas échéant, on cherche block passé en argument | |||
| // --- | |||
| if (isset($_REQUEST['hash'])) $block_hash = $_REQUEST['hash']; | |||
| if (isset($_REQUEST['methode'])) $methode = $_REQUEST['methode']; | |||
| if (isset($_REQUEST['mode'])) $mode = intval($_REQUEST['mode']); | |||
| //$methode = str_replace('full_', '', $methode); | |||
| $img = null; | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash.'-'.$mode.'.png'; | |||
| if (file_exists($imagefilename)) | |||
| { | |||
| $img = imagecreatefrompng($imagefilename); | |||
| } | |||
| // | |||
| // "strict" est un flag qui indique que l'on veut précisément | |||
| // ce que les paramètres demandent | |||
| // | |||
| if (($img==null)&&(!isset($_REQUEST['strict']))) | |||
| { | |||
| // | |||
| // On n'a pas passé de HASH | |||
| // | |||
| if (($img==null)&&(!isset($_REQUEST['hash']))) | |||
| { | |||
| // | |||
| // On prend le dernier connu pour la méthode et le mode | |||
| // | |||
| $myarray = glob(DATA_PATH.'/'.$methode.'/*-'.$mode.'.png'); | |||
| if (isset($myarray[0])) | |||
| { | |||
| usort( $myarray, function( $a, $b ) { return filemtime($a) - filemtime($b); } ); | |||
| $img = imagecreatefrompng($myarray[0]); | |||
| } | |||
| // | |||
| // On prend le dernier connu pour la méthode tout mode confondu | |||
| // | |||
| if ($img==null) | |||
| { | |||
| $myarray = glob(DATA_PATH.'/'.$methode.'/*.png'); | |||
| if (isset($myarray[0])) | |||
| { | |||
| usort( $myarray, function( $a, $b ) { return filemtime($a) - filemtime($b); } ); | |||
| $img = imagecreatefrompng($myarray[0]); | |||
| } | |||
| } | |||
| } | |||
| // | |||
| // On a passé un HASH | |||
| // | |||
| if (($img==null)&&(isset($_REQUEST['hash']))) | |||
| { | |||
| // | |||
| // On cherche le bon HASH et la bonne méthode | |||
| // | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash.'.png'; | |||
| if (($img==null)&&(file_exists($imagefilename))) | |||
| { | |||
| $img = imagecreatefrompng($imagefilename); | |||
| } | |||
| // | |||
| // En fait on s'en fiche de la méthode tant qu'on a le bon HASH | |||
| // | |||
| $imagefilename = DATA_PATH.'/hasard/'.$block_hash.'.png'; | |||
| if (($img==null)&&(file_exists($imagefilename))) | |||
| { | |||
| $img = imagecreatefrompng($imagefilename); | |||
| } | |||
| } | |||
| } | |||
| // | |||
| // On n'a rien trouvé, donc on dessine ce qui est demandé | |||
| // | |||
| if ($img==null) | |||
| { | |||
| if ($mode == 9999) $mode = 0; | |||
| if ($block_hash == '*') $block_hash = blockchain::getLastCacheBlockHash(); | |||
| $imagefilename = DATA_PATH.'/'.$methode.'/'.$block_hash.'.png'; | |||
| switch($methode) | |||
| { | |||
| case 'full_treemap_fuzzy': | |||
| require_once APP_PATH.'/methode/treemap_fuzzy/inc/treemap.php'; | |||
| $imagefilename = DATA_PATH.'/full_treemap_fuzzy/'.$block_hash.'.png'; | |||
| break; | |||
| case 'full_spline': | |||
| require_once APP_PATH.'/methode/spline/inc/splines.php'; | |||
| $imagefilename = DATA_PATH.'/full_spline/'.$block_hash.'.png'; | |||
| break; | |||
| case 'full_treemap' : | |||
| default: | |||
| require_once APP_PATH.'/methode/treemap/inc/treemap.php'; | |||
| $imagefilename = DATA_PATH.'/full_treemap/'.$block_hash.'-'.$mode.'.png'; | |||
| } | |||
| $the_block = blockchain::getBlockWithHash($block_hash); | |||
| if ($the_block === FALSE) die(); | |||
| $the_name = blockchain::hash2SpecialName($the_block->hash); | |||
| if ($the_name == $the_block->hash) $the_name =''; | |||
| $bandeau = 50; | |||
| $marge = 25; | |||
| $text_border = 20; | |||
| $width = GRAPH_WIDTH; | |||
| $height = GRAPH_HEIGHT; | |||
| $img_w = $marge + ($width*2) + (2*$text_border); | |||
| $img_h = $marge + ($height*2) + (2*$bandeau); | |||
| // création d'une image plus haute pour inclure bandeaux haut et bas | |||
| $img = imagecreatetruecolor( $img_w, $img_h); | |||
| // Les parties du block : inputs, outputs, fees, reward | |||
| switch($methode) | |||
| { | |||
| case 'full_treemap_fuzzy': | |||
| topisto_treemap_fuzzy::DrawBlock($the_block, $img, $text_border + 0, $bandeau, $width, $height, $mode, 2); | |||
| topisto_treemap_fuzzy::DrawBlock($the_block, $img, $text_border+$marge+$width, $bandeau, $width, $height, $mode, 1); | |||
| topisto_treemap_fuzzy::DrawBlock($the_block, $img, $text_border+0, $marge+$height + $bandeau, $width, $height, $mode, 3); | |||
| topisto_treemap_fuzzy::DrawBlock($the_block, $img, $text_border+$marge+$width, $marge+$height + $bandeau, $width, $height, $mode, 4); | |||
| break; | |||
| case 'full_spline': | |||
| $type=2; | |||
| $x0 = $text_border; $y0 = $bandeau; | |||
| topisto_spline::DefaultDrawBlock($the_block, $img, $x0, $y0, $width, $height, $type); | |||
| $type=1; | |||
| $x0 = $text_border+$marge+$width; $y0 = $bandeau; | |||
| topisto_spline::DefaultDrawBlock($the_block, $img, $x0, $y0, $width, $height, $type); | |||
| $type=3; | |||
| $x0 = $text_border; $y0 = $marge+$height+$bandeau; | |||
| topisto_spline::DefaultDrawBlock($the_block, $img, $x0, $y0, $width, $height, $type); | |||
| $type=4; | |||
| $x0 = $text_border+$marge+$width; $y0 = $marge+$height+$bandeau; | |||
| topisto_spline::DefaultDrawBlock($the_block, $img, $x0, $y0, $width, $height, $type); | |||
| break; | |||
| case 'full_treemap': | |||
| default: | |||
| topisto_treemap::DrawBlock($the_block, $img, $text_border + 0, $bandeau, $width, $height, $mode, 2); | |||
| topisto_treemap::DrawBlock($the_block, $img, $text_border+$marge+$width, $bandeau, $width, $height, $mode, 1); | |||
| topisto_treemap::DrawBlock($the_block, $img, $text_border+0, $marge+$height + $bandeau, $width, $height, $mode, 3); | |||
| topisto_treemap::DrawBlock($the_block, $img, $text_border+$marge+$width, $marge+$height + $bandeau, $width, $height, $mode, 4); | |||
| break; | |||
| } | |||
| blockchain::DrawBlockHeaderFooter($the_block, $img, $bandeau); | |||
| $fond = imagecolorallocate($img, 40, 40, 40); | |||
| imagefilledrectangle($img, 0, $bandeau, $text_border, $img_h-$bandeau, $fond); | |||
| imagefilledrectangle($img, $img_w-$text_border, $bandeau, $img_w, $img_h-$bandeau, $fond); | |||
| imagefilledrectangle($img, $text_border+$width, $bandeau, $text_border+$marge+$width, $img_h-$bandeau, $fond); | |||
| imagefilledrectangle($img, 0, $bandeau+$height, $img_w - $text_border, $bandeau+$marge+$height, $fond); | |||
| // Les textes | |||
| putenv('GDFONTPATH='.RESS_PATH.'/fonts/'); | |||
| $font = 'DS-DIGIB.TTF'; | |||
| $fontColor = imagecolorallocate($img, 158,227,253); | |||
| $fontColor = imagecolorallocate($img, 227,253,158); | |||
| $fontColor = imagecolorallocate($img, 227,227,153); | |||
| $the_texte = "Height : ".$the_block->height; | |||
| imagettftext($img,18, 0, 5, $bandeau-5, $fontColor, $font, $the_texte); | |||
| $the_texte = "Inputs : ".$the_block->topisto_inputs; | |||
| imagettftext($img,15, 90, $text_border-3, $bandeau+$height, $fontColor, $font, $the_texte); | |||
| $the_texte = "Outputs : ".$the_block->topisto_outputs; | |||
| imagettftext($img,15, 90, $text_border+$width+$marge-3, $bandeau+$height, $fontColor, $font, $the_texte); | |||
| $the_texte = "Fees : ".$the_block->topisto_fees; | |||
| imagettftext($img,15, 90, $text_border-3, $bandeau+(2*$height)+$marge-3, $fontColor, $font, $the_texte); | |||
| $the_texte = "Reward : ".$the_block->topisto_reward; | |||
| imagettftext($img,15, 90, $text_border+$width+$marge-3, $bandeau+(2*$height)+$marge-3, $fontColor, $font, $the_texte); | |||
| if ($the_name == '') $the_name = date('Ymd H:m:s', $the_block->time); | |||
| $bbox = imagettfbbox(14, 0, $font, $the_name); | |||
| imagettftext($img, 14, 0, ($img_w-3)-($bbox[2]-$bbox[0]), ($bandeau-5), $fontColor, $font, $the_name); | |||
| // Sauvegarder l'image et ajouter | |||
| // - un lien sur le mode | |||
| // - un lien dans le dossier "hasard" | |||
| $imagefilename2 = str_replace(".png","-$mode.png", $imagefilename); | |||
| imagepng($img,$imagefilename2); | |||
| if (!file_exists($imagefilename)) link($imagefilename2, $imagefilename); | |||
| $imagefilename2 = str_replace("methode/$methode/","methode/hasard/", $imagefilename); | |||
| if (!file_exists($imagefilename2)) link($imagefilename, $imagefilename2); | |||
| } | |||
| $seconds_to_cache = 7200; | |||
| $ts = gmdate("D, d M Y H:i:s", time() + $seconds_to_cache) . " GMT"; | |||
| header("Expires: $ts"); | |||
| header("Pragma: cache"); | |||
| header("Cache-Control: max-age=$seconds_to_cache"); | |||
| header('Content-Type: image/png'); | |||
| imagepng($img); | |||
| imagedestroy($img); | |||
| ?> | |||
| @ -0,0 +1,29 @@ | |||
| <?php | |||
| $alea=rand(0,100); | |||
| $logo='topisto_vert.png'; | |||
| if ($alea > 30) | |||
| { | |||
| $files = glob('logo/medium/*/topisto_vert.png'); | |||
| usort($files, function($a, $b) { | |||
| return filemtime($a) > filemtime($b); | |||
| }); | |||
| shuffle($files); | |||
| $logo = $files[0]; | |||
| } | |||
| $block_image = @imagecreatefrompng($logo); | |||
| // --- | |||
| // --- envoyer l'image au navigateur | |||
| // --- | |||
| header("Content-Type: image/png"); | |||
| imagepng($block_image); | |||
| imagedestroy($block_image); | |||
| ?> | |||
| @ -0,0 +1,4 @@ | |||
| for toto in rouge vert | |||
| do | |||
| gmic ../topisto_$toto.png $@ -output topisto_$toto.png | |||
| done | |||