@ -0,0 +1,2 @@ |
|||||
|
data-dir* |
||||
|
config.yaml |
@ -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 |
||||
|
``` |
@ -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) |
||||
|
} |
||||
|
} |
@ -0,0 +1,2 @@ |
|||||
|
privK: "da7079f082a1ced80c5dee3bf00752fd67f75321a637e5d5073ce1489af062d8" |
||||
|
gethurl: "https://ropsten.infura.io/TFnR8BWJlqZOKxHHZNcs" |
@ -0,0 +1,2 @@ |
|||||
|
privK: "here the private key" |
||||
|
gethurl: "here the Geth URL" |
@ -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 |
||||
|
} |
@ -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) |
||||
|
} |
@ -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") |
||||
|
} |
@ -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) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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> |
@ -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); |
||||
|
}); |
||||
|
} |