arnaucube
arnaucube/tor-eth-online-shopPublic Self-hosted repository
first commit
arnaucode arnaucode@gmail.com
Fri, 27 Jul 2018 00:23:16 +0200
commit

7c773b4c3ea7e23f83c0f9e9099e57108d7b7e38

parent

4fec2ab36bb04582f33ab366f4a3985b89aed5fe

A .gitignore

@@ -0,0 +1,2 @@

+data-dir* +config.yaml
A README.md

@@ -0,0 +1,11 @@

+# tor-eth-online-shop + +Online shop through Tor hidden service, that allows payments in Ethereum. + + +## Run +First edit configEXAMPLE.yaml to config.yaml, and edit the content. +Then, run: +``` +go run *.go +```
A config.go

@@ -0,0 +1,23 @@

+package main + +import "github.com/spf13/viper" + +type Config struct { + GethURL string + PrivK string +} + +var config Config + +func ReadConfig(path, filename string) { + viper.SetConfigName(filename) + viper.AddConfigPath(path) + viper.SetConfigType("yaml") + if err := viper.ReadInConfig(); err != nil { + panic(err) + } + err := viper.Unmarshal(&config) + if err != nil { + panic(err) + } +}
A config.yaml

@@ -0,0 +1,2 @@

+privK: "da7079f082a1ced80c5dee3bf00752fd67f75321a637e5d5073ce1489af062d8" +gethurl: "https://ropsten.infura.io/TFnR8BWJlqZOKxHHZNcs"
A configEXAMPLE.yaml

@@ -0,0 +1,2 @@

+privK: "here the private key" +gethurl: "here the Geth URL"
A eth.go

@@ -0,0 +1,32 @@

+package main + +import ( + "crypto/ecdsa" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +var ( + client *ethclient.Client + key *ecdsa.PrivateKey + address common.Address +) + +func Web3Open() error { + // geth set up + var err error + + client, err = ethclient.Dial(config.GethURL) + if err != nil { + return err + } + key, err = crypto.HexToECDSA(config.PrivK) + if err != nil { + return err + } + address = crypto.PubkeyToAddress(key.PublicKey) + + return nil +}
A main.go

@@ -0,0 +1,51 @@

+package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "time" + + "github.com/cretz/bine/tor" + "github.com/ipsn/go-libtor" +) + +func main() { + ReadConfig(".", "config") + + // Start tor with some defaults + elevated verbosity + fmt.Println("Starting and registering onion service, please wait a bit...") + t, err := tor.Start(nil, &tor.StartConf{ProcessCreator: libtor.Creator, DebugWriter: os.Stderr}) + if err != nil { + log.Panicf("Failed to start tor: %v", err) + } + defer t.Close() + + // Wait at most a few minutes to publish the service + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + // Create an onion service to listen on any port but show as 80 + onion, err := t.Listen(ctx, &tor.ListenConf{RemotePorts: []int{80}}) + if err != nil { + log.Panicf("Failed to create onion service: %v", err) + } + defer onion.Close() + + fmt.Printf("Please open a Tor capable browser and navigate to http://%v.onion\n", onion.ID) + + // connect to eth + // Ethereum + err = Web3Open() + if err != nil { + log.Fatal(err) + } + + // Run a Hello-World HTTP service until terminated + http.Handle("/", http.FileServer(http.Dir("./www"))) + http.HandleFunc("/api/purchase", handlePurchase) + http.HandleFunc("/api/txid", handleTxId) + http.Serve(onion, nil) +}
A rest.go

@@ -0,0 +1,19 @@

+package main + +import ( + "fmt" + "net/http" +) + +func handlePurchase(w http.ResponseWriter, r *http.Request) { + challenge, err := generateChallenge(20) + if err != nil { + panic(err) + return + } + fmt.Fprintf(w, challenge) +} +func handleTxId(w http.ResponseWriter, r *http.Request) { + + fmt.Fprintf(w, "ack") +}
A utils.go

@@ -0,0 +1,23 @@

+package main + +import ( + "crypto/rand" + "math/big" +) + +func generateChallenge(length int) (string, error) { + result := "" + for { + if len(result) >= length { + return result, nil + } + num, err := rand.Int(rand.Reader, big.NewInt(int64(127))) + if err != nil { + return "", err + } + n := num.Int64() + if n > 32 && n < 127 { + result += string(n) + } + } +}
A www/index.html

@@ -0,0 +1,71 @@

+<!doctype html> +<html lang="en"> + <head> + <!-- Required meta tags --> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <!-- Bootstrap CSS --> + <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> + <title>tor-eth-online-shop</title> + </head> + <body> + <div class="container"> + <h1>tor-eth-online-shop</h1> + <div class="row" id="mainView"> + <div class="col-md-9"> + <div class="row"> + <div class="col-md-4"> + <div class="card"> + <img class="card-img-top" src="https://vignette.wikia.nocookie.net/joke-battles/images/f/f7/711MkPTJ4OL._SL1500_.jpg" alt="Card image cap"> + <div class="card-body"> + <h5 class="card-title">Product 1</h5> + <p class="card-text">Description.</p> + <a onclick="addProduct('id1')" href="#" class="btn btn-outline-success float-right">Add to card</a> + </div> + </div> + </div> + <div class="col-md-4"> + <div class="card"> + <img class="card-img-top" src="https://5.imimg.com/data5/YX/SD/MY-2492706/slotted-carton-box-500x500.jpg" alt="Card image cap"> + <div class="card-body"> + <h5 class="card-title">Product 2</h5> + <p class="card-text">Description.</p> + <a onclick="addProduct('id2')" href="#" class="btn btn-outline-success float-right">Add to card</a> + </div> + </div> + </div> + <div class="col-md-4"> + <div class="card"> + <img class="card-img-top" src="https://image.shutterstock.com/image-photo/closed-cardboard-box-taped-isolated-260nw-168691364.jpg" alt="Card image cap"> + <div class="card-body"> + <h5 class="card-title">Product 3</h5> + <p class="card-text">Description.</p> + <a onclick="addProduct('id3')" href="#" class="btn btn-outline-success float-right">Add to card</a> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-3"> + <div class="card"> + <div class="card-body"> + <h3>Selected products</h3> + <p id="list" class="card-text"></p> + <a onclick="purchase()" href="#" class="btn btn-outline-success float-right">Purchase</a> + </div> + </div> + </div> + </div> + </div> + + <!-- Optional JavaScript --> + <!-- jQuery first, then Popper.js, then Bootstrap JS --> + <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> + <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> + + <script src="https://unpkg.com/axios/dist/axios.min.js"></script> + + <script src="index.js"></script> + </body> + </html>
A www/index.js

@@ -0,0 +1,77 @@

+var products = [ + { + id: 'id1', + title: 'beer1', + price: '0.1' + }, + { + id: 'id2', + title: 'beer2', + price: '0.1' + }, + { + id: 'id3', + title: 'beer3', + price: '0.1' + } +]; +var list = {}; +function getProduct(id) { + for(var i=0; i<products.length; i++) { + if(products[i].id == id) { + return(products[i]); + } + } + return("not found"); +} +function addProduct(id) { + if (!list[id]) { + list[id] = 1; + } else { + list[id]++; + } + // update html + var total = 0; + var html = ""; + html += "<ul class='list-group'>"; + for (var property in list) { + if (list.hasOwnProperty(property)) { + html += "<li class='list-group-item'>"; + html += property + " x" + list[property]; + html += "<div class='float-right'>"; + html += (getProduct(property).price * list[property]).toFixed(4) + " eth"; + html += "</div>"; + html += "</li>"; + total = (+(total) + +(getProduct(property).price * list[property])).toFixed(4); + } + } + html += "</ul>"; + html += "<br>"; + html += "<div class='float-right'>"; + html += "<b>Total: " + total + "</b>"; + html += "</div>"; + html += "<br>"; + document.getElementById("list").innerHTML = html; +} + +function purchase() { + var total = 0; + for (var property in list) { + if (list.hasOwnProperty(property)) { + total = (+(total) + +(getProduct(property).price * list[property])).toFixed(4); + } + } + var answ = confirm("total to pay: " + total + " eth"); + if (!answ) { + return; + } + axios.post('/api/purchase', { + list: list + }) + .then(function (response) { + console.log(response); + }) + .catch(function (error) { + console.log(error); + }); +}