diff --git a/README.md b/README.md
index ee2c02d7..c5ac0b2f 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Decentralized websites using Bitcoin crypto and BitTorrent network
- We believe in open, free and uncensored network and communication.
- No single point of failure: Site goes on until at least 1 peer serving it.
- No hosting costs: Site served by visitors.
- - Imposible to shut down: It's nowhere because it's everywhere.
+ - Impossible to shut down: It's nowhere because it's everywhere.
- Fast and works offline: You can access the site even if your internet is gone.
@@ -16,7 +16,7 @@ Decentralized websites using Bitcoin crypto and BitTorrent network
- When you visit a new zeronet site, it's trying to find peers using BitTorrent network and download the site files (html, css, js...) from them.
- Each visited sites become also served by You.
- Every site containing a `site.json` which holds all other files sha1 hash and a sign generated using site's private key.
- - If the site owner (who has the private key for the site address) modifies the site, then he/she signs the new `content.json` and publish it to the peers. After the peers verified the `content.json` integrity using the sign they download the modified files and publish the new content to other peers.
+ - If the site owner (who has the private key for the site address) modifies the site, then he/she signs the new `content.json` and publish it to the peers. After the peers have verified the `content.json` integrity (using the sign), they download the modified files and publish the new content to other peers.
## Screenshot
@@ -35,13 +35,26 @@ Windows:
Linux (Debian):
- `apt-get install python-pip`
- - `pip install pyzmq` (if drops compile error then `apt-get install python-dev` and try again)
+ - `pip install pyzmq` (if it drops a compile error then run `apt-get install python-dev` and try again)
- `pip install gevent`
- `pip install msgpack-python`
- start using `python zeronet.py`
+Linux (Without root access):
+ - `wget https://bootstrap.pypa.io/get-pip.py`
+ - `python get-pip.py --user pyzmq gevent msgpack-python`
+ - start using `python zeronet.py`
+
+
+## Current limitations
+ - No torrent-like, file splitting big file support
+ - Just as anonymous as the bittorrent
+ - File transactions not compressed or encrypted yet
+ - No private sites
+
## How can I create a ZeroNet site?
+Shut down zeronet.py if you are running it already
```
$ zeronet.py siteCreate
...
@@ -54,6 +67,9 @@ $ zeronet.py
```
Congratulations, you are done! Now anyone can access your site using http://localhost:43110/13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2
+Next steps: [ZeroNet Developer Documentation](https://github.com/HelloZeroNet/ZeroNet/wiki/ZeroNet-Developer-Documentation)
+
+
## How can I modify a ZeroNet site?
- Modify files located in data/13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2 directory. After you done:
```
@@ -76,4 +92,8 @@ Site:13DNDk..bhC2 Successfuly published to 3 peers
Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX
-#### Thank you!
\ No newline at end of file
+
+#### Thank you!
+
+- More info, help, changelog, zeronet sites: http://www.reddit.com/r/zeronet/
+- Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..2b9f1afd
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+gevent==1.0.1
+pyzmq==14.4.1
+msgpack-python==0.4.4
diff --git a/src/Config.py b/src/Config.py
index dc455d91..df512c92 100644
--- a/src/Config.py
+++ b/src/Config.py
@@ -3,7 +3,7 @@ import ConfigParser
class Config(object):
def __init__(self):
- self.version = "0.1"
+ self.version = "0.1.5"
self.parser = self.createArguments()
argv = sys.argv[:] # Copy command line arguments
argv = self.parseConfig(argv) # Add arguments from config file
@@ -43,22 +43,25 @@ class Config(object):
# SitePublish
action = subparsers.add_parser("sitePublish", help='Publish site to other peers: address')
action.add_argument('address', help='Site to publish')
+ action.add_argument('peer_ip', help='Peer ip to publish (default: random peers ip from tracker)', default=None, nargs='?')
+ action.add_argument('peer_port', help='Peer port to publish (default: random peer port from tracker)', default=15441, nargs='?')
# SiteVerify
- action = subparsers.add_parser("siteVerify", help='Verify site files using md5: address')
+ action = subparsers.add_parser("siteVerify", help='Verify site files using sha512: address')
action.add_argument('address', help='Site to verify')
# Config parameters
parser.add_argument('--debug', help='Debug mode', action='store_true')
+ parser.add_argument('--debug_socket', help='Debug socket connections', action='store_true')
- parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='host')
- parser.add_argument('--ui_port', help='Web interface bind port', default=43110, metavar='port')
+ parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip')
+ parser.add_argument('--ui_port', help='Web interface bind port', default=43110, type=int, metavar='port')
parser.add_argument('--ui_restrict', help='Restrict web access', default=False, metavar='ip')
parser.add_argument('--homepage', help='Web interface Homepage', default='1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr', metavar='address')
- parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='host')
- parser.add_argument('--fileserver_port',help='FileServer bind port', default=15441, metavar='port')
+ parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip')
+ parser.add_argument('--fileserver_port',help='FileServer bind port', default=15441, type=int, metavar='port')
parser.add_argument('--ip_external', help='External ip (tested on start if None)', metavar='ip')
parser.add_argument('--upnpc', help='MiniUPnP binary for open port on router', default=upnpc, metavar='executable_path')
diff --git a/src/Crypt/CryptBitcoin.py b/src/Crypt/CryptBitcoin.py
index f7ba209f..63199ffe 100644
--- a/src/Crypt/CryptBitcoin.py
+++ b/src/Crypt/CryptBitcoin.py
@@ -1,11 +1,12 @@
from src.lib.BitcoinECC import BitcoinECC
-import hashlib
-def newPrivatekey(): # Return new private key
- bitcoin = BitcoinECC.Bitcoin()
- bitcoin.GeneratePrivateKey()
- return bitcoin.PrivateEncoding()
+def newPrivatekey(uncompressed=True): # Return new private key
+ from src.lib.BitcoinECC import newBitcoinECC # Use new lib to generate WIF compatible addresses, but keep using the old yet for backward compatiblility issues
+ bitcoin = newBitcoinECC.Bitcoin()
+ d = bitcoin.GenerateD()
+ bitcoin.AddressFromD(d, uncompressed)
+ return bitcoin.PrivFromD(d, uncompressed)
def privatekeyToAddress(privatekey): # Return address from private key
diff --git a/src/Crypt/CryptHash.py b/src/Crypt/CryptHash.py
index 57932acc..d9de55f9 100644
--- a/src/Crypt/CryptHash.py
+++ b/src/Crypt/CryptHash.py
@@ -15,7 +15,7 @@ def sha512sum(file, blocksize=65536):
hash = hashlib.sha512()
for block in iter(lambda: file.read(blocksize), ""):
hash.update(block)
- return hash.hexdigest()
+ return hash.hexdigest()[0:64] # Truncate to 256bits is good enough
if __name__ == "__main__":
diff --git a/src/Debug/Debug.py b/src/Debug/Debug.py
new file mode 100644
index 00000000..3ab4c48b
--- /dev/null
+++ b/src/Debug/Debug.py
@@ -0,0 +1,39 @@
+import sys, os, traceback
+
+# Non fatal exception
+class Notify(Exception):
+ def __init__(self, message):
+ self.message = message
+
+ def __str__(self):
+ return self.message
+
+
+def formatException(err=None):
+ exc_type, exc_obj, exc_tb = sys.exc_info()
+ if not err: err = exc_obj.message
+ tb = []
+ for frame in traceback.extract_tb(exc_tb):
+ path, line, function, text = frame
+ file = os.path.split(path)[1]
+ tb.append("%s line %s" % (file, line))
+ return "%s: %s in %s" % (exc_type.__name__, err, " > ".join(tb))
+
+
+if __name__ == "__main__":
+ try:
+ print 1/0
+ except Exception, err:
+ print type(err).__name__
+ print "1/0 error: %s" % formatException(err)
+
+ def loadJson():
+ json.loads("Errr")
+
+ import json
+ try:
+ loadJson()
+ except Exception, err:
+ print err
+ print "Json load error: %s" % formatException(err)
+ loadJson()
diff --git a/src/Debug/DebugHook.py b/src/Debug/DebugHook.py
index d03e7fb9..60229f61 100644
--- a/src/Debug/DebugHook.py
+++ b/src/Debug/DebugHook.py
@@ -3,14 +3,14 @@ import gevent, sys
last_error = None
def handleError(*args):
global last_error
- if not args: # Get last error
+ if not args: # Called explicitly
args = sys.exc_info()
silent = True
else:
silent = False
print "Error catched", args
last_error = args
- if not silent: sys.__excepthook__(*args)
+ if not silent and args[0].__name__ != "Notify": sys.__excepthook__(*args)
OriginalGreenlet = gevent.Greenlet
class ErrorhookedGreenlet(OriginalGreenlet):
diff --git a/src/File/FileRequest.py b/src/File/FileRequest.py
index df655bda..f70671b8 100644
--- a/src/File/FileRequest.py
+++ b/src/File/FileRequest.py
@@ -1,6 +1,8 @@
import os, msgpack, shutil
from Site import SiteManager
from cStringIO import StringIO
+from Debug import Debug
+from Config import config
FILE_BUFF = 1024*512
@@ -43,6 +45,7 @@ class FileRequest:
buff = StringIO(params["body"])
valid = site.verifyFile(params["inner_path"], buff)
if valid == True: # Valid and changed
+ self.log.debug("Update for %s looks valid, saving..." % params["inner_path"])
buff.seek(0)
file = open(site.getPath(params["inner_path"]), "wb")
shutil.copyfileobj(buff, file) # Write buff to disk
@@ -60,12 +63,14 @@ class FileRequest:
elif valid == None: # Not changed
peer = site.addPeer(*params["peer"], return_peer = True) # Add or get peer
+ self.log.debug("Same version, adding new peer for locked files: %s, tasks: %s" % (peer.key, len(site.worker_manager.tasks)) )
for task in site.worker_manager.tasks: # New peer add to every ongoing task
- site.needFile(task["inner_path"], peer=peer, update=True, blocking=False) # Download file from peer
+ if task["peers"]: site.needFile(task["inner_path"], peer=peer, update=True, blocking=False) # Download file from this peer too if its peer locked
- self.send({"ok": "File file not changed"})
+ self.send({"ok": "File not changed"})
else: # Invalid sign or sha1 hash
+ self.log.debug("Update for %s is invalid" % params["inner_path"])
self.send({"error": "File invalid"})
@@ -76,15 +81,19 @@ class FileRequest:
self.send({"error": "Unknown site"})
return False
try:
- file = open(site.getPath(params["inner_path"]), "rb")
+ file_path = site.getPath(params["inner_path"])
+ if config.debug_socket: self.log.debug("Opening file: %s" % file_path)
+ file = open(file_path, "rb")
file.seek(params["location"])
back = {}
back["body"] = file.read(FILE_BUFF)
back["location"] = file.tell()
back["size"] = os.fstat(file.fileno()).st_size
+ if config.debug_socket: self.log.debug("Sending file %s from position %s to %s" % (file_path, params["location"], back["location"]))
self.send(back)
+ if config.debug_socket: self.log.debug("File %s sent" % file_path)
except Exception, err:
- self.send({"error": "File read error: %s" % err})
+ self.send({"error": "File read error: %s" % Debug.formatException(err)})
return False
diff --git a/src/File/FileServer.py b/src/File/FileServer.py
index dce3d8b7..4a5e4f86 100644
--- a/src/File/FileServer.py
+++ b/src/File/FileServer.py
@@ -4,6 +4,7 @@ import zmq.green as zmq
from Config import config
from FileRequest import FileRequest
from Site import SiteManager
+from Debug import Debug
class FileServer:
@@ -48,12 +49,16 @@ class FileServer:
self.log.info("Try to open port using upnpc...")
try:
exit = os.system("%s -e ZeroNet -r %s tcp" % (config.upnpc, self.port))
- if exit == 0:
+ if exit == 0: # Success
upnpc_success = True
- else:
- upnpc_success = False
+ else: # Failed
+ exit = os.system("%s -r %s tcp" % (config.upnpc, self.port)) # Try without -e option
+ if exit == 0:
+ upnpc_success = True
+ else:
+ upnpc_success = False
except Exception, err:
- self.log.error("Upnpc run error: %s" % err)
+ self.log.error("Upnpc run error: %s" % Debug.formatException(err))
upnpc_success = False
if upnpc_success and self.testOpenport(port)["result"] == True:
@@ -73,7 +78,7 @@ class FileServer:
message = re.match('.*
(.*?)
', data, re.DOTALL).group(1)
message = re.sub("<.*?>", "", message.replace("
", " ").replace(" ", " ")) # Strip http tags
except Exception, err:
- message = "Error: %s" % err
+ message = "Error: %s" % Debug.formatException(err)
if "Error" in message:
self.log.info("[BAD :(] Port closed: %s" % message)
if port == self.port:
@@ -121,13 +126,25 @@ class FileServer:
# Announce sites every 10 min
def announceSites(self):
while 1:
- time.sleep(10*60) # Announce sites every 10 min
+ time.sleep(20*60) # Announce sites every 20 min
for address, site in self.sites.items():
if site.settings["serving"]:
site.announce() # Announce site to tracker
time.sleep(2) # Prevent too quick request
+ # Detects if computer back from wakeup
+ def wakeupWatcher(self):
+ last_time = time.time()
+ while 1:
+ time.sleep(30)
+ if time.time()-last_time > 60: # If taken more than 60 second then the computer was in sleep mode
+ self.log.info("Wakeup detected: time wrap from %s to %s (%s sleep seconds), acting like startup..." % (last_time, time.time(), time.time()-last_time))
+ self.port_opened = None # Check if we still has the open port on router
+ self.checkSites()
+ last_time = time.time()
+
+
# Bind and start serving sites
def start(self, check_sites = True):
self.log = logging.getLogger(__name__)
@@ -149,8 +166,9 @@ class FileServer:
return
if check_sites: # Open port, Update sites, Check files integrity
gevent.spawn(self.checkSites)
-
+
gevent.spawn(self.announceSites)
+ gevent.spawn(self.wakeupWatcher)
while True:
try:
@@ -159,7 +177,7 @@ class FileServer:
self.handleRequest(req)
except Exception, err:
self.log.error(err)
- self.socket.send(msgpack.packb({"error": "%s" % err}, use_bin_type=True))
+ self.socket.send(msgpack.packb({"error": "%s" % Debug.formatException(err)}, use_bin_type=True))
if config.debug: # Raise exception
import sys
sys.excepthook(*sys.exc_info())
diff --git a/src/Peer/Peer.py b/src/Peer/Peer.py
index af530f88..7641e571 100644
--- a/src/Peer/Peer.py
+++ b/src/Peer/Peer.py
@@ -2,6 +2,7 @@ import os, logging, gevent, time, msgpack
import zmq.green as zmq
from cStringIO import StringIO
from Config import config
+from Debug import Debug
context = zmq.Context()
@@ -11,22 +12,33 @@ class Peer:
self.ip = ip
self.port = port
self.site = site
+ self.key = "%s:%s" % (ip, port)
+ self.log = None
+
self.socket = None
- self.last_found = None
+ self.last_found = None # Time of last found in the torrent tracker
+ self.last_response = None # Time of last successfull response from peer
+ self.last_ping = None # Last response time for ping
self.added = time.time()
- self.connection_error = 0
- self.hash_failed = 0
- self.download_bytes = 0
- self.download_time = 0
+ self.connection_error = 0 # Series of connection error
+ self.hash_failed = 0 # Number of bad files from peer
+ self.download_bytes = 0 # Bytes downloaded
+ self.download_time = 0 # Time spent to download
# Connect to host
def connect(self):
- self.log = logging.getLogger("Peer:%s:%s" % (self.ip, self.port))
+ if not self.log: self.log = logging.getLogger("Peer:%s:%s" % (self.ip, self.port))
+ if self.socket: self.socket.close()
+
self.socket = context.socket(zmq.REQ)
self.socket.setsockopt(zmq.SNDTIMEO, 5000) # Wait for data send
self.socket.setsockopt(zmq.LINGER, 500) # Wait for socket close
+ #self.socket.setsockopt(zmq.TCP_KEEPALIVE, 1) # Enable keepalive
+ #self.socket.setsockopt(zmq.TCP_KEEPALIVE_IDLE, 4*60) # Send after 4 minute idle
+ #self.socket.setsockopt(zmq.TCP_KEEPALIVE_INTVL, 15) # Wait 15 sec to response
+ #self.socket.setsockopt(zmq.TCP_KEEPALIVE_CNT, 4) # 4 Probes
self.socket.connect('tcp://%s:%s' % (self.ip, self.port))
@@ -38,24 +50,32 @@ class Peer:
# Send a command to peer
def sendCmd(self, cmd, params = {}):
if not self.socket: self.connect()
- try:
- self.socket.send(msgpack.packb({"cmd": cmd, "params": params}, use_bin_type=True))
- response = msgpack.unpackb(self.socket.recv())
- if "error" in response:
- self.log.debug("%s %s error: %s" % (cmd, params, response["error"]))
- else: # Successful request, reset connection error num
- self.connection_error = 0
- return response
- except Exception, err:
- self.onConnectionError()
- self.log.error("%s" % err)
- if config.debug:
- import traceback
- traceback.print_exc()
- self.socket.close()
- time.sleep(1)
- self.connect()
- return None
+ if cmd != "ping" and self.last_response and time.time() - self.last_response > 20*60: # If last response if older than 20 minute, ping first to see if still alive
+ if not self.ping(): return None
+
+ for retry in range(1,3): # Retry 3 times
+ if config.debug_socket: self.log.debug("sendCmd: %s" % cmd)
+ try:
+ self.socket.send(msgpack.packb({"cmd": cmd, "params": params}, use_bin_type=True))
+ if config.debug_socket: self.log.debug("Sent command: %s" % cmd)
+ response = msgpack.unpackb(self.socket.recv())
+ if config.debug_socket: self.log.debug("Got response to: %s" % cmd)
+ if "error" in response:
+ self.log.debug("%s error: %s" % (cmd, response["error"]))
+ self.onConnectionError()
+ else: # Successful request, reset connection error num
+ self.connection_error = 0
+ self.last_response = time.time()
+ return response
+ except Exception, err:
+ self.onConnectionError()
+ self.log.debug("%s (connection_error: %s, hash_failed: %s, retry: %s)" % (Debug.formatException(err), self.connection_error, self.hash_failed, retry))
+ time.sleep(1*retry)
+ self.connect()
+ if type(err).__name__ == "Notify" and err.message == "Worker stopped": # Greenlet kill by worker
+ self.log.debug("Peer worker got killed, aborting cmd: %s" % cmd)
+ break
+ return None # Failed after 4 retry
# Get a file content from peer
@@ -81,7 +101,24 @@ class Peer:
# Send a ping request
def ping(self):
- return self.sendCmd("ping")
+ response_time = None
+ for retry in range(1,3): # Retry 3 times
+ s = time.time()
+ with gevent.Timeout(10.0, False): # 10 sec timeout, dont raise exception
+ response = self.sendCmd("ping")
+ if response and "body" in response and response["body"] == "Pong!":
+ response_time = time.time()-s
+ break # All fine, exit from for loop
+ # Timeout reached or bad response
+ self.onConnectionError()
+ time.sleep(1)
+
+ if response_time:
+ self.log.debug("Ping: %.3f" % response_time)
+ else:
+ self.log.debug("Ping failed")
+ self.last_ping = response_time
+ return response_time
# Stop and remove from site
@@ -96,7 +133,7 @@ class Peer:
# On connection error
def onConnectionError(self):
self.connection_error += 1
- if self.connection_error > 5: # Dead peer
+ if self.connection_error >= 5: # Dead peer
self.remove()
diff --git a/src/Site/Site.py b/src/Site/Site.py
index 73c6423b..ed7a1a84 100644
--- a/src/Site/Site.py
+++ b/src/Site/Site.py
@@ -6,6 +6,7 @@ from Config import config
from Peer import Peer
from Worker import WorkerManager
from Crypt import CryptHash
+from Debug import Debug
import SiteManager
class Site:
@@ -26,7 +27,7 @@ class Site:
self.peer_blacklist = SiteManager.peer_blacklist # Ignore this peers (eg. myself)
self.last_announce = 0 # Last announce time to tracker
self.worker_manager = WorkerManager(self) # Handle site download from other peers
- self.bad_files = {} # SHA1 check failed files, need to redownload
+ self.bad_files = {} # SHA512 check failed files, need to redownload
self.content_updated = None # Content.js update time
self.last_downloads = [] # Files downloaded in run of self.download()
self.notifications = [] # Pending notifications displayed once on page load [error|ok|info, message, timeout]
@@ -35,10 +36,16 @@ class Site:
self.loadContent(init=True) # Load content.json
self.loadSettings() # Load settings from sites.json
- if not self.settings.get("auth_key"):
- self.settings["auth_key"] = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(12)) # To auth websocket
+ if not self.settings.get("auth_key"): # To auth user in site
+ self.settings["auth_key"] = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(24))
self.log.debug("New auth key: %s" % self.settings["auth_key"])
self.saveSettings()
+
+ if not self.settings.get("wrapper_key"): # To auth websocket permissions
+ self.settings["wrapper_key"] = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(12))
+ self.log.debug("New wrapper key: %s" % self.settings["wrapper_key"])
+ self.saveSettings()
+
self.websockets = [] # Active site websocket connections
# Add event listeners
@@ -53,7 +60,7 @@ class Site:
try:
new_content = json.load(open(content_path))
except Exception, err:
- self.log.error("Content.json load error: %s" % err)
+ self.log.error("Content.json load error: %s" % Debug.formatException(err))
return None
else:
return None # Content.json not exits
@@ -69,7 +76,7 @@ class Site:
if old_sha1 != new_sha1: changed.append(inner_path)
self.content = new_content
except Exception, err:
- self.log.error("Content.json parse error: %s" % err)
+ self.log.error("Content.json parse error: %s" % Debug.formatException(err))
return None # Content.json parse error
# Add to bad files
if not init:
@@ -114,7 +121,7 @@ class Site:
# Start downloading site
@util.Noparallel(blocking=False)
def download(self):
- self.log.debug("Start downloading...")
+ self.log.debug("Start downloading...%s" % self.bad_files)
self.announce()
found = self.needFile("content.json", update=self.bad_files.get("content.json"))
if not found: return False # Could not download content.json
@@ -152,12 +159,12 @@ class Site:
# Update content.json on peers
def publish(self, limit=3):
- self.log.info( "Publishing to %s/%s peers..." % (len(self.peers), limit) )
+ self.log.info( "Publishing to %s/%s peers..." % (limit, len(self.peers)) )
published = 0
for key, peer in self.peers.items(): # Send update command to each peer
result = {"exception": "Timeout"}
try:
- with gevent.Timeout(2, False): # 2 sec timeout
+ with gevent.Timeout(1, False): # 1 sec timeout
result = peer.sendCmd("update", {
"site": self.address,
"inner_path": "content.json",
@@ -165,7 +172,7 @@ class Site:
"peer": (config.ip_external, config.fileserver_port)
})
except Exception, err:
- result = {"exception": err}
+ result = {"exception": Debug.formatException(err)}
if result and "ok" in result:
published += 1
@@ -179,7 +186,7 @@ class Site:
# Check and download if file not exits
- def needFile(self, inner_path, update=False, blocking=True, peer=None):
+ def needFile(self, inner_path, update=False, blocking=True, peer=None, priority=0):
if os.path.isfile(self.getPath(inner_path)) and not update: # File exits, no need to do anything
return True
elif self.settings["serving"] == False: # Site not serving
@@ -194,7 +201,7 @@ class Site:
self.loadContent()
if not self.content: return False
- task = self.worker_manager.addTask(inner_path, peer)
+ task = self.worker_manager.addTask(inner_path, peer, priority=priority)
if blocking:
return task.get()
else:
@@ -223,32 +230,30 @@ class Site:
for protocol, ip, port in SiteManager.TRACKERS:
if protocol == "udp":
- self.log.debug("Announing to %s://%s:%s..." % (protocol, ip, port))
+ self.log.debug("Announcing to %s://%s:%s..." % (protocol, ip, port))
tracker = UdpTrackerClient(ip, port)
tracker.peer_port = config.fileserver_port
try:
tracker.connect()
tracker.poll_once()
- tracker.announce(info_hash=hashlib.sha1(self.address).hexdigest())
+ tracker.announce(info_hash=hashlib.sha1(self.address).hexdigest(), num_want=50)
back = tracker.poll_once()
- except Exception, err:
- self.log.error("Tracker error: %s" % err)
- continue
- if back: # Tracker announce success
peers = back["response"]["peers"]
- added = 0
- for peer in peers:
- if (peer["addr"], peer["port"]) in self.peer_blacklist: # Ignore blacklist (eg. myself)
- continue
- if self.addPeer(peer["addr"], peer["port"]): added += 1
- if added:
- self.worker_manager.onPeers()
- self.updateWebsocket(peers_added=added)
- self.log.debug("Found %s peers, new: %s" % (len(peers), added))
- break # Successful announcing, break the list
- else:
- self.log.error("Tracker bad response, trying next in list...") # Failed to announce, go to next
+ except Exception, err:
+ self.log.error("Tracker error: %s" % Debug.formatException(err))
time.sleep(1)
+ continue
+
+ added = 0
+ for peer in peers:
+ if (peer["addr"], peer["port"]) in self.peer_blacklist: # Ignore blacklist (eg. myself)
+ continue
+ if self.addPeer(peer["addr"], peer["port"]): added += 1
+ if added:
+ self.worker_manager.onPeers()
+ self.updateWebsocket(peers_added=added)
+ self.log.debug("Found %s peers, new: %s" % (len(peers), added))
+ break # Successful announcing, break the list
else:
pass # TODO: http tracker support
@@ -262,6 +267,32 @@ class Site:
self.bad_files[bad_file] = True
+ def deleteFiles(self):
+ self.log.debug("Deleting files from content.json...")
+ files = self.content["files"].keys() # Make a copy
+ files.append("content.json")
+ for inner_path in files:
+ path = self.getPath(inner_path)
+ if os.path.isfile(path): os.unlink(path)
+
+ self.log.debug("Deleting empty dirs...")
+ for root, dirs, files in os.walk(self.directory, topdown=False):
+ for dir in dirs:
+ path = os.path.join(root,dir)
+ if os.path.isdir(path) and os.listdir(path) == []:
+ os.removedirs(path)
+ self.log.debug("Removing %s" % path)
+ if os.path.isdir(self.directory) and os.listdir(self.directory) == []: os.removedirs(self.directory) # Remove sites directory if empty
+
+ if os.path.isdir(self.directory):
+ self.log.debug("Some unknown file remained in site data dir: %s..." % self.directory)
+ return False # Some files not deleted
+ else:
+ self.log.debug("Site data directory deleted: %s..." % self.directory)
+ return True # All clean
+
+
+
# - Events -
# Add event listeners
@@ -330,6 +361,7 @@ class Site:
if self.content["modified"] == content["modified"]: # Ignore, have the same content.json
return None
elif self.content["modified"] > content["modified"]: # We have newer
+ self.log.debug("We have newer content.json (Our: %s, Sent: %s)" % (self.content["modified"], content["modified"]))
return False
if content["modified"] > time.time()+60*60*24: # Content modified in the far future (allow 1 day window)
self.log.error("Content.json modify is in the future!")
@@ -341,18 +373,22 @@ class Site:
return CryptBitcoin.verify(sign_content, self.address, sign)
except Exception, err:
- self.log.error("Verify sign error: %s" % err)
+ self.log.error("Verify sign error: %s" % Debug.formatException(err))
return False
else: # Check using sha1 hash
if self.content and inner_path in self.content["files"]:
- return CryptHash.sha1sum(file) == self.content["files"][inner_path]["sha1"]
+ if "sha512" in self.content["files"][inner_path]: # Use sha512 to verify if possible
+ return CryptHash.sha512sum(file) == self.content["files"][inner_path]["sha512"]
+ else: # Backward compatiblity
+ return CryptHash.sha1sum(file) == self.content["files"][inner_path]["sha1"]
+
else: # File not in content.json
self.log.error("File not in content.json: %s" % inner_path)
return False
- # Verify all files sha1sum using content.json
+ # Verify all files sha512sum using content.json
def verifyFiles(self, quick_check=False): # Fast = using file size
bad_files = []
if not self.content: # No content.json, download it first
@@ -370,11 +406,10 @@ class Site:
else:
ok = self.verifyFile(inner_path, open(file_path, "rb"))
- if ok:
- self.log.debug("[OK] %s" % inner_path)
- else:
+ if not ok:
self.log.error("[ERROR] %s" % inner_path)
bad_files.append(inner_path)
+ self.log.debug("Site verified: %s files, quick_check: %s, bad files: %s" % (len(self.content["files"]), quick_check, bad_files))
return bad_files
@@ -383,7 +418,7 @@ class Site:
def signContent(self, privatekey=None):
if not self.content: # New site
self.log.info("Site not exits yet, loading default content.json values...")
- self.content = {"files": {}, "title": "%s - ZeroNet_" % self.address, "sign": "", "modified": 0.0, "description": "", "address": self.address, "ignore": ""} # Default content.json
+ self.content = {"files": {}, "title": "%s - ZeroNet_" % self.address, "sign": "", "modified": 0.0, "description": "", "address": self.address, "ignore": "", "zeronet_version": config.version} # Default content.json
self.log.info("Opening site data directory: %s..." % self.directory)
@@ -396,19 +431,21 @@ class Site:
if file_name == "content.json" or (self.content["ignore"] and re.match(self.content["ignore"], file_path.replace(self.directory+"/", "") )): # Dont add content.json and ignore regexp pattern definied in content.json
self.log.info("- [SKIPPED] %s" % file_path)
else:
- sha1sum = CryptHash.sha1sum(file_path) # Calculate sha sum of file
+ sha1sum = CryptHash.sha1sum(file_path) # Calculate sha1 sum of file
+ sha512sum = CryptHash.sha512sum(file_path) # Calculate sha512 sum of file
inner_path = re.sub("^%s/" % re.escape(self.directory), "", file_path)
- self.log.info("- %s (SHA1: %s)" % (file_path, sha1sum))
- hashed_files[inner_path] = {"sha1": sha1sum, "size": os.path.getsize(file_path)}
+ self.log.info("- %s (SHA512: %s)" % (file_path, sha512sum))
+ hashed_files[inner_path] = {"sha1": sha1sum, "sha512": sha512sum, "size": os.path.getsize(file_path)}
# Generate new content.json
- self.log.info("Adding timestamp and sha1sums to new content.json...")
+ self.log.info("Adding timestamp and sha512sums to new content.json...")
content = self.content.copy() # Create a copy of current content.json
- content["address"] = self.address # Add files sha1 hash
- content["files"] = hashed_files # Add files sha1 hash
+ content["address"] = self.address
+ content["files"] = hashed_files # Add files sha512 hash
content["modified"] = time.time() # Add timestamp
- del(content["sign"]) # Delete old site
+ content["zeronet_version"] = config.version # Signer's zeronet version
+ del(content["sign"]) # Delete old sign
# Signing content
from Crypt import CryptBitcoin
diff --git a/src/Site/SiteManager.py b/src/Site/SiteManager.py
index daf5479d..9779a639 100644
--- a/src/Site/SiteManager.py
+++ b/src/Site/SiteManager.py
@@ -37,7 +37,7 @@ def load():
# Checks if its a valid address
def isAddress(address):
- return re.match("^[A-Za-z0-9]{34}$", address)
+ return re.match("^[A-Za-z0-9]{26,35}$", address)
# Return site and start download site files
@@ -45,12 +45,20 @@ def need(address, all_file=True):
from Site import Site
if address not in sites: # Site not exits yet
if not isAddress(address): raise Exception("Not address: %s" % address)
+ logging.debug("Added new site: %s" % address)
sites[address] = Site(address)
+ sites[address].settings["serving"] = True # Maybe it was deleted before
site = sites[address]
if all_file: site.download()
return site
+def delete(address):
+ global sites
+ logging.debug("SiteManager deleted site: %s" % address)
+ del(sites[address])
+
+
# Lazy load sites
def list():
if sites == None: # Not loaded yet
diff --git a/src/Ui/UiRequest.py b/src/Ui/UiRequest.py
index 84767abf..4c57c916 100644
--- a/src/Ui/UiRequest.py
+++ b/src/Ui/UiRequest.py
@@ -122,7 +122,7 @@ class UiRequest:
inner_path=inner_path,
address=match.group("site"),
title=title,
- auth_key=site.settings["auth_key"],
+ wrapper_key=site.settings["wrapper_key"],
permissions=json.dumps(site.settings["permissions"]),
show_loadingscreen=json.dumps(not os.path.isfile(site.getPath(inner_path))),
homepage=config.homepage
@@ -158,7 +158,7 @@ class UiRequest:
else: # File not exits, try to download
site = SiteManager.need(match.group("site"), all_file=False)
self.sendHeader(content_type=self.getContentType(file_path)) # ?? Get Exception without this
- result = site.needFile(match.group("inner_path")) # Wait until file downloads
+ result = site.needFile(match.group("inner_path"), priority=1) # Wait until file downloads
return self.actionFile(file_path)
else: # Bad url
@@ -209,13 +209,13 @@ class UiRequest:
def actionWebsocket(self):
ws = self.env.get("wsgi.websocket")
if ws:
- auth_key = self.get["auth_key"]
- # Find site by auth_key
+ wrapper_key = self.get["wrapper_key"]
+ # Find site by wraper_key
site = None
for site_check in self.server.sites.values():
- if site_check.settings["auth_key"] == auth_key: site = site_check
+ if site_check.settings["wrapper_key"] == wrapper_key: site = site_check
- if site: # Correct auth key
+ if site: # Correct wrapper key
ui_websocket = UiWebsocket(ws, site, self.server)
site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events
ui_websocket.start()
@@ -223,8 +223,8 @@ class UiRequest:
if ui_websocket in site_check.websockets:
site_check.websockets.remove(ui_websocket)
return "Bye."
- else: # No site found by auth key
- self.log.error("Auth key not found: %s" % auth_key)
+ else: # No site found by wrapper key
+ self.log.error("Wrapper key not found: %s" % wraper_key)
return self.error403()
else:
start_response("400 Bad Request", [])
@@ -245,6 +245,7 @@ class UiRequest:
# Just raise an error to get console
def actionConsole(self):
+ sites = self.server.sites
raise Exception("Here is your console")
diff --git a/src/Ui/UiServer.py b/src/Ui/UiServer.py
index 61f32a8f..e14a72bc 100644
--- a/src/Ui/UiServer.py
+++ b/src/Ui/UiServer.py
@@ -6,6 +6,7 @@ from lib.geventwebsocket.handler import WebSocketHandler
from Ui import UiRequest
from Site import SiteManager
from Config import config
+from Debug import Debug
# Skip websocket handler if not necessary
class UiWSGIHandler(WSGIHandler):
@@ -48,19 +49,6 @@ class UiServer:
return self.ui_request.route(path)
- # Send a message to all connected client
- def sendMessage(self, message):
- sent = 0
- for ws in self.websockets:
- try:
- ws.send(message)
- sent += 1
- except Exception, err:
- self.log.error("addMessage error: %s" % err)
- self.server.websockets.remove(ws)
- return sent
-
-
# Reload the UiRequest class to prevent restarts in debug mode
def reload(self):
import imp
diff --git a/src/Ui/UiWebsocket.py b/src/Ui/UiWebsocket.py
index 8056fc9f..44c8e593 100644
--- a/src/Ui/UiWebsocket.py
+++ b/src/Ui/UiWebsocket.py
@@ -1,11 +1,13 @@
import json, gevent, time, sys, hashlib
from Config import config
from Site import SiteManager
+from Debug import Debug
class UiWebsocket:
def __init__(self, ws, site, server):
self.ws = ws
self.site = site
+ self.log = site.log
self.server = server
self.next_message_id = 1
self.waiting_cb = {} # Waiting for callback. Key: message_id, Value: function pointer
@@ -35,7 +37,7 @@ class UiWebsocket:
if config.debug: # Allow websocket errors to appear on /Debug
import sys
sys.modules["src.main"].DebugHook.handleError()
- self.site.log.error("WebSocket error: %s" % err)
+ self.log.error("WebSocket error: %s" % Debug.formatException(err))
return "Bye."
@@ -44,7 +46,7 @@ class UiWebsocket:
if channel in self.channels: # We are joined to channel
if channel == "siteChanged":
site = params[0] # Triggerer site
- site_info = self.siteInfo(site)
+ site_info = self.formatSiteInfo(site)
if len(params) > 1 and params[1]: # Extra data
site_info.update(params[1])
self.cmd("setSiteInfo", site_info)
@@ -64,9 +66,12 @@ class UiWebsocket:
def send(self, message, cb = None):
message["id"] = self.next_message_id # Add message id to allow response
self.next_message_id += 1
- self.ws.send(json.dumps(message))
- if cb: # Callback after client responsed
- self.waiting_cb[message["id"]] = cb
+ try:
+ self.ws.send(json.dumps(message))
+ if cb: # Callback after client responsed
+ self.waiting_cb[message["id"]] = cb
+ except Exception, err:
+ self.log.debug("Websocket send error: %s" % Debug.formatException(err))
# Handle incoming messages
@@ -91,6 +96,8 @@ class UiWebsocket:
self.actionSitePause(req["id"], req["params"])
elif cmd == "siteResume" and "ADMIN" in permissions:
self.actionSiteResume(req["id"], req["params"])
+ elif cmd == "siteDelete" and "ADMIN" in permissions:
+ self.actionSiteDelete(req["id"], req["params"])
elif cmd == "siteList" and "ADMIN" in permissions:
self.actionSiteList(req["id"], req["params"])
elif cmd == "channelJoinAllsite" and "ADMIN" in permissions:
@@ -107,7 +114,7 @@ class UiWebsocket:
if req["to"] in self.waiting_cb:
self.waiting_cb(req["result"]) # Call callback function
else:
- self.site.log.error("Websocket callback not found: %s" % req)
+ self.log.error("Websocket callback not found: %s" % req)
# Send a simple pong answer
@@ -116,18 +123,24 @@ class UiWebsocket:
# Format site info
- def siteInfo(self, site):
+ def formatSiteInfo(self, site):
+ content = site.content
+ if content and "files" in content: # Remove unnecessary data transfer
+ content = site.content.copy()
+ content["files"] = len(content["files"])
+ del(content["sign"])
+
ret = {
- "auth_id": self.site.settings["auth_key"][0:10],
- "auth_id_md5": hashlib.md5(self.site.settings["auth_key"][0:10]).hexdigest(),
+ "auth_key": self.site.settings["auth_key"],
+ "auth_key_sha512": hashlib.sha512(self.site.settings["auth_key"]).hexdigest()[0:64],
"address": site.address,
"settings": site.settings,
"content_updated": site.content_updated,
- "bad_files": site.bad_files.keys(),
- "last_downloads": site.last_downloads,
+ "bad_files": len(site.bad_files),
+ "last_downloads": len(site.last_downloads),
"peers": len(site.peers),
- "tasks": [task["inner_path"] for task in site.worker_manager.tasks],
- "content": site.content
+ "tasks": len([task["inner_path"] for task in site.worker_manager.tasks]),
+ "content": content
}
if site.settings["serving"] and site.content: ret["peers"] += 1 # Add myself if serving
return ret
@@ -135,7 +148,7 @@ class UiWebsocket:
# Send site details
def actionSiteInfo(self, to, params):
- ret = self.siteInfo(self.site)
+ ret = self.formatSiteInfo(self.site)
self.response(to, ret)
@@ -148,12 +161,13 @@ class UiWebsocket:
# Server variables
def actionServerInfo(self, to, params):
ret = {
- "ip_external": config.ip_external,
+ "ip_external": bool(config.ip_external),
"platform": sys.platform,
"fileserver_ip": config.fileserver_ip,
"fileserver_port": config.fileserver_port,
"ui_ip": config.ui_ip,
"ui_port": config.ui_port,
+ "version": config.version,
"debug": config.debug
}
self.response(to, ret)
@@ -167,7 +181,7 @@ class UiWebsocket:
SiteManager.load() # Reload sites
for site in self.server.sites.values():
if not site.content: continue # Broken site
- ret.append(self.siteInfo(site))
+ ret.append(self.formatSiteInfo(site))
self.response(to, ret)
@@ -199,6 +213,7 @@ class UiWebsocket:
site.settings["serving"] = False
site.saveSettings()
site.updateWebsocket()
+ site.worker_manager.stopWorkers()
else:
self.response(to, {"error": "Unknown site: %s" % address})
@@ -215,3 +230,18 @@ class UiWebsocket:
site.updateWebsocket()
else:
self.response(to, {"error": "Unknown site: %s" % address})
+
+
+ def actionSiteDelete(self, to, params):
+ address = params.get("address")
+ site = self.server.sites.get(address)
+ if site:
+ site.settings["serving"] = False
+ site.saveSettings()
+ site.worker_manager.running = False
+ site.worker_manager.stopWorkers()
+ site.deleteFiles()
+ SiteManager.delete(address)
+ site.updateWebsocket()
+ else:
+ self.response(to, {"error": "Unknown site: %s" % address})
diff --git a/src/Ui/media/Notifications.coffee b/src/Ui/media/Notifications.coffee
index d5190c5a..ec26f425 100644
--- a/src/Ui/media/Notifications.coffee
+++ b/src/Ui/media/Notifications.coffee
@@ -27,10 +27,15 @@ class Notifications
$(".notification-icon", elem).html("!")
else if type == "done"
$(".notification-icon", elem).html("")
+ else if type == "ask"
+ $(".notification-icon", elem).html("?")
else
$(".notification-icon", elem).html("i")
- $(".body", elem).html(body)
+ if typeof(body) == "string"
+ $(".body", elem).html(body)
+ else
+ $(".body", elem).html("").append(body)
elem.appendTo(@elem)
@@ -53,7 +58,10 @@ class Notifications
@close elem
return false
- @
+ # Close on button click within body (confirm dialog)
+ $(".button", elem).on "click", =>
+ @close elem
+ return false
close: (elem) ->
diff --git a/src/Ui/media/Wrapper.coffee b/src/Ui/media/Wrapper.coffee
index 6dfdffc2..a43ddbb2 100644
--- a/src/Ui/media/Wrapper.coffee
+++ b/src/Ui/media/Wrapper.coffee
@@ -55,12 +55,31 @@ class Wrapper
if @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened
@sendInner {"cmd": "wrapperOpenedWebsocket"}
@wrapperWsInited = true
- else if cmd == "wrapperNotification"
+ else if cmd == "wrapperNotification" # Display notification
+ message.params = @toHtmlSafe(message.params) # Escape html
@notifications.add("notification-#{message.id}", message.params[0], message.params[1], message.params[2])
+ else if cmd == "wrapperConfirm" # Display confirm message
+ @actionWrapperConfirm(message)
else # Send to websocket
@ws.send(message) # Pass message to websocket
+ # - Actions -
+
+ actionWrapperConfirm: (message) ->
+ message.params = @toHtmlSafe(message.params) # Escape html
+ if message.params[1] then caption = message.params[1] else caption = "ok"
+
+ body = $(""+message.params[0]+"")
+ button = $("#{caption}") # Add confirm button
+ button.on "click", => # Response on button click
+ @sendInner {"cmd": "response", "to": message.id, "result": "boom"} # Response to confirm
+ return false
+ body.append(button)
+
+ @notifications.add("notification-#{message.id}", "ask", body)
+
+
onOpenWebsocket: (e) =>
@ws.cmd "channelJoin", {"channel": "siteChanged"} # Get info on modifications
@log "onOpenWebsocket", @inner_ready, @wrapperWsInited
@@ -118,13 +137,14 @@ class Wrapper
setSiteInfo: (site_info) ->
if site_info.event? # If loading screen visible add event to it
# File started downloading
- if site_info.event[0] == "file_added" and site_info.bad_files.length
- @loading.printLine("#{site_info.bad_files.length} files needs to be downloaded")
+ if site_info.event[0] == "file_added" and site_info.bad_files
+ @loading.printLine("#{site_info.bad_files} files needs to be downloaded")
# File finished downloading
else if site_info.event[0] == "file_done"
@loading.printLine("#{site_info.event[1]} downloaded")
if site_info.event[1] == window.inner_path # File downloaded we currently on
@loading.hideScreen()
+ if not @site_info then @reloadSiteInfo()
if not $(".loadingscreen").length # Loading screen already removed (loaded +2sec)
@notifications.add("modified", "info", "New version of this page has just released.
Reload to see the modified content.")
# File failed downloading
@@ -144,9 +164,13 @@ class Wrapper
@site_info = site_info
+ toHtmlSafe: (unsafe) ->
+ return unsafe
+
+
log: (args...) ->
console.log "[Wrapper]", args...
-ws_url = "ws://#{window.location.hostname}:#{window.location.port}/Websocket?auth_key=#{window.auth_key}"
+ws_url = "ws://#{window.location.hostname}:#{window.location.port}/Websocket?wrapper_key=#{window.wrapper_key}"
window.wrapper = new Wrapper(ws_url)
diff --git a/src/Ui/media/Wrapper.css b/src/Ui/media/Wrapper.css
index 1f588186..cf50fe81 100644
--- a/src/Ui/media/Wrapper.css
+++ b/src/Ui/media/Wrapper.css
@@ -7,6 +7,12 @@ a { color: black }
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
#inner-iframe.back { transform: scale(0.95) translate(-300px, 0px); opacity: 0.4 }
+.button { padding: 5px 10px; margin-left: 10px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E; border-radius: 2px; text-decoration: none; transition: all 0.5s; }
+.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; transition: none }
+.button:active { position: relative; top: 1px }
+
+.button-Delete { background-color: #e74c3c; border-bottom-color: #c0392b; color: white }
+.button-Delete:hover { background-color: #FF5442; border-bottom-color: #8E2B21 }
/* Fixbutton */
@@ -43,6 +49,7 @@ a { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
/* Notification types */
+.notification-ask .notification-icon { background-color: #f39c12; }
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }
diff --git a/src/Ui/media/all.css b/src/Ui/media/all.css
index 16df57ea..01e91c31 100644
--- a/src/Ui/media/all.css
+++ b/src/Ui/media/all.css
@@ -12,6 +12,12 @@ a { color: black }
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; -webkit-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -moz-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -o-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -ms-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
#inner-iframe.back { -webkit-transform: scale(0.95) translate(-300px, 0px); -moz-transform: scale(0.95) translate(-300px, 0px); -o-transform: scale(0.95) translate(-300px, 0px); -ms-transform: scale(0.95) translate(-300px, 0px); transform: scale(0.95) translate(-300px, 0px) ; opacity: 0.4 }
+.button { padding: 5px 10px; margin-left: 10px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E; -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; text-decoration: none; -webkit-transition: all 0.5s; -moz-transition: all 0.5s; -o-transition: all 0.5s; -ms-transition: all 0.5s; transition: all 0.5s ; }
+.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none }
+.button:active { position: relative; top: 1px }
+
+.button-Delete { background-color: #e74c3c; border-bottom-color: #c0392b; color: white }
+.button-Delete:hover { background-color: #FF5442; border-bottom-color: #8E2B21 }
/* Fixbutton */
@@ -48,6 +54,7 @@ a { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
/* Notification types */
+.notification-ask .notification-icon { background-color: #f39c12; }
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }
diff --git a/src/Ui/media/all.js b/src/Ui/media/all.js
index 16923085..0d6ffc11 100644
--- a/src/Ui/media/all.js
+++ b/src/Ui/media/all.js
@@ -571,10 +571,16 @@ jQuery.extend( jQuery.easing,
$(".notification-icon", elem).html("!");
} else if (type === "done") {
$(".notification-icon", elem).html("");
+ } else if (type === "ask") {
+ $(".notification-icon", elem).html("?");
} else {
$(".notification-icon", elem).html("i");
}
- $(".body", elem).html(body);
+ if (typeof body === "string") {
+ $(".body", elem).html(body);
+ } else {
+ $(".body", elem).html("").append(body);
+ }
elem.appendTo(this.elem);
if (timeout) {
$(".close", elem).remove();
@@ -604,7 +610,12 @@ jQuery.extend( jQuery.easing,
return false;
};
})(this));
- return this;
+ return $(".button", elem).on("click", (function(_this) {
+ return function() {
+ _this.close(elem);
+ return false;
+ };
+ })(this));
};
Notifications.prototype.close = function(elem) {
@@ -771,12 +782,39 @@ jQuery.extend( jQuery.easing,
return this.wrapperWsInited = true;
}
} else if (cmd === "wrapperNotification") {
+ message.params = this.toHtmlSafe(message.params);
return this.notifications.add("notification-" + message.id, message.params[0], message.params[1], message.params[2]);
+ } else if (cmd === "wrapperConfirm") {
+ return this.actionWrapperConfirm(message);
} else {
return this.ws.send(message);
}
};
+ Wrapper.prototype.actionWrapperConfirm = function(message) {
+ var body, button, caption;
+ message.params = this.toHtmlSafe(message.params);
+ if (message.params[1]) {
+ caption = message.params[1];
+ } else {
+ caption = "ok";
+ }
+ body = $("" + message.params[0] + "");
+ button = $("" + caption + "");
+ button.on("click", (function(_this) {
+ return function() {
+ _this.sendInner({
+ "cmd": "response",
+ "to": message.id,
+ "result": "boom"
+ });
+ return false;
+ };
+ })(this));
+ body.append(button);
+ return this.notifications.add("notification-" + message.id, "ask", body);
+ };
+
Wrapper.prototype.onOpenWebsocket = function(e) {
this.ws.cmd("channelJoin", {
"channel": "siteChanged"
@@ -852,12 +890,15 @@ jQuery.extend( jQuery.easing,
Wrapper.prototype.setSiteInfo = function(site_info) {
if (site_info.event != null) {
- if (site_info.event[0] === "file_added" && site_info.bad_files.length) {
- this.loading.printLine("" + site_info.bad_files.length + " files needs to be downloaded");
+ if (site_info.event[0] === "file_added" && site_info.bad_files) {
+ this.loading.printLine("" + site_info.bad_files + " files needs to be downloaded");
} else if (site_info.event[0] === "file_done") {
this.loading.printLine("" + site_info.event[1] + " downloaded");
if (site_info.event[1] === window.inner_path) {
this.loading.hideScreen();
+ if (!this.site_info) {
+ this.reloadSiteInfo();
+ }
if (!$(".loadingscreen").length) {
this.notifications.add("modified", "info", "New version of this page has just released.
Reload to see the modified content.");
}
@@ -880,6 +921,10 @@ jQuery.extend( jQuery.easing,
return this.site_info = site_info;
};
+ Wrapper.prototype.toHtmlSafe = function(unsafe) {
+ return unsafe;
+ };
+
Wrapper.prototype.log = function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
@@ -890,7 +935,7 @@ jQuery.extend( jQuery.easing,
})();
- ws_url = "ws://" + window.location.hostname + ":" + window.location.port + "/Websocket?auth_key=" + window.auth_key;
+ ws_url = "ws://" + window.location.hostname + ":" + window.location.port + "/Websocket?wrapper_key=" + window.wrapper_key;
window.wrapper = new Wrapper(ws_url);
diff --git a/src/Ui/template/wrapper.html b/src/Ui/template/wrapper.html
index 46c0cc0d..7de9dac0 100644
--- a/src/Ui/template/wrapper.html
+++ b/src/Ui/template/wrapper.html
@@ -35,12 +35,12 @@
-
+
-
+
diff --git a/src/Worker/Worker.py b/src/Worker/Worker.py
index 018f98c7..7bb5ea66 100644
--- a/src/Worker/Worker.py
+++ b/src/Worker/Worker.py
@@ -1,5 +1,6 @@
import gevent, time, logging, shutil, os
from Peer import Peer
+from Debug import Debug
class Worker:
def __init__(self, manager, peer):
@@ -20,15 +21,20 @@ class Worker:
if not task: # Die, no more task
self.manager.log.debug("%s: No task found, stopping" % self.key)
break
+ if not task["time_started"]: task["time_started"] = time.time() # Task started now
if task["workers_num"] > 0: # Wait a bit if someone already working on it
self.manager.log.debug("%s: Someone already working on %s, sleeping 1 sec..." % (self.key, task["inner_path"]))
time.sleep(1)
+ self.manager.log.debug("%s: %s, task done after sleep: %s" % (self.key, task["inner_path"], task["done"]))
if task["done"] == False:
self.task = task
task["workers_num"] += 1
buff = self.peer.getFile(task["site"].address, task["inner_path"])
+ if self.running == False: # Worker no longer needed or got killed
+ self.manager.log.debug("%s: No longer needed, returning: %s" % (self.key, task["inner_path"]))
+ return None
if buff: # Download ok
correct = task["site"].verifyFile(task["inner_path"], buff)
else: # Download error
@@ -47,12 +53,12 @@ class Worker:
self.manager.doneTask(task)
self.task = None
else: # Hash failed
+ self.manager.log.debug("%s: Hash failed: %s" % (self.key, task["inner_path"]))
self.task = None
self.peer.hash_failed += 1
- if self.peer.hash_failed > 5: # Broken peer
+ if self.peer.hash_failed >= 3: # Broken peer
break
task["workers_num"] -= 1
- self.manager.log.error("%s: Hash failed: %s" % (self.key, task["inner_path"]))
time.sleep(1)
self.peer.onWorkerDone()
self.running = False
@@ -64,6 +70,11 @@ class Worker:
self.running = True
self.thread = gevent.spawn(self.downloader)
+
+ # Force stop the worker
def stop(self):
+ self.manager.log.debug("%s: Force stopping, thread: %s" % (self.key, self.thread))
self.running = False
+ if self.thread:
+ self.thread.kill(exception=Debug.Notify("Worker stopped"))
self.manager.removeWorker(self)
diff --git a/src/Worker/WorkerManager.py b/src/Worker/WorkerManager.py
index f54bc448..c11887ed 100644
--- a/src/Worker/WorkerManager.py
+++ b/src/Worker/WorkerManager.py
@@ -8,47 +8,61 @@ class WorkerManager:
def __init__(self, site):
self.site = site
self.workers = {} # Key: ip:port, Value: Worker.Worker
- self.tasks = [] # {"evt": evt, "workers_num": 0, "site": self.site, "inner_path": inner_path, "done": False, "time_start": time.time(), "peers": peers}
+ self.tasks = [] # {"evt": evt, "workers_num": 0, "site": self.site, "inner_path": inner_path, "done": False, "time_started": None, "time_added": time.time(), "peers": peers, "priority": 0}
+ self.running = True
self.log = logging.getLogger("WorkerManager:%s" % self.site.address_short)
self.process_taskchecker = gevent.spawn(self.checkTasks)
# Check expired tasks
def checkTasks(self):
- while 1:
- time.sleep(15) # Check every 30 sec
+ while self.running:
+ time.sleep(15) # Check every 15 sec
+
+ # Clean up workers
+ if not self.tasks and self.workers: # No task but workers still running
+ for worker in self.workers.values(): worker.stop()
+
if not self.tasks: continue
tasks = self.tasks[:] # Copy it so removing elements wont cause any problem
for task in tasks:
- if time.time() >= task["time_start"]+60: # Task timed out
- self.log.debug("Cleaning up task: %s" % task)
-
+ if (task["time_started"] and time.time() >= task["time_started"]+60) or (time.time() >= task["time_added"]+60 and not self.workers): # Task taking too long time, or no peer after 60sec kill it
+ self.log.debug("Timeout, Cleaning up task: %s" % task)
# Clean up workers
workers = self.findWorkers(task)
for worker in workers:
worker.stop()
-
# Remove task
self.failTask(task)
- elif time.time() >= task["time_start"]+15: # Task taking long time
- self.log.debug("Task taking long time, find more peers: %s" % task["inner_path"])
- task["site"].announce() # Find more peers
- if task["peers"]: # Release the peer olck
- self.log.debug("Task peer lock release: %s" % task["inner_path"])
- task["peers"] = []
- self.startWorkers()
- continue # One reannounce per loop
+
+ elif (task["time_started"] and time.time() >= task["time_started"]+15) or not self.workers: # Task started more than 15 sec ago or no workers
+ self.log.debug("Task taking more than 15 secs, find more peers: %s" % task["inner_path"])
+ task["site"].announce() # Find more peers
+ if task["peers"]: # Release the peer olck
+ self.log.debug("Task peer lock release: %s" % task["inner_path"])
+ task["peers"] = []
+ self.startWorkers()
+ break # One reannounce per loop
+ self.log.debug("checkTasks stopped running")
+
+
+
+
+ # Tasks sorted by this
+ def taskSorter(self, task):
+ if task["inner_path"] == "content.json": return 9999 # Content.json always prority
+ if task["inner_path"] == "index.html": return 9998 # index.html also important
+ priority = task["priority"]
+ if task["inner_path"].endswith(".js") or task["inner_path"].endswith(".css"): priority += 1 # download js and css files first
+ return priority-task["workers_num"] # Prefer more priority and less workers
# Returns the next free or less worked task
- def getTask(self, peer, only_free=False):
- best_task = None
- for task in self.tasks: # Find out the task with lowest worker number
+ def getTask(self, peer):
+ self.tasks.sort(key=self.taskSorter, reverse=True) # Sort tasks by priority and worker numbers
+ for task in self.tasks: # Find a task
if task["peers"] and peer not in task["peers"]: continue # This peer not allowed to pick this task
- if task["inner_path"] == "content.json": return task # Content.json always prority
- if not best_task or task["workers_num"] < best_task["workers_num"]: # If task has lower worker number then its better
- best_task = task
- return best_task
+ return task
# New peers added to site
@@ -56,17 +70,37 @@ class WorkerManager:
self.startWorkers()
+ # Add new worker
+ def addWorker(self, peer):
+ key = peer.key
+ if key not in self.workers and len(self.workers) < MAX_WORKERS: # We dont have worker for that peer and workers num less than max
+ worker = Worker(self, peer)
+ self.workers[key] = worker
+ worker.key = key
+ worker.start()
+ return worker
+ else: # We have woker for this peer or its over the limit
+ return False
+
+
# Start workers to process tasks
- def startWorkers(self):
- if len(self.workers) >= MAX_WORKERS: return False # Workers number already maxed
+ def startWorkers(self, peers=None):
+ if len(self.workers) >= MAX_WORKERS and not peers: return False # Workers number already maxed
if not self.tasks: return False # No task for workers
for key, peer in self.site.peers.iteritems(): # One worker for every peer
- if key not in self.workers and len(self.workers) < MAX_WORKERS: # We dont have worker for that peer and workers num less than max
- worker = Worker(self, peer)
- self.workers[key] = worker
- worker.key = key
- worker.start()
- self.log.debug("Added worker: %s, workers: %s/%s" % (key, len(self.workers), MAX_WORKERS))
+ if peers and peer not in peers: continue # If peers definied and peer not valid
+ worker = self.addWorker(peer)
+ if worker: self.log.debug("Added worker: %s, workers: %s/%s" % (key, len(self.workers), MAX_WORKERS))
+
+
+ # Stop all worker
+ def stopWorkers(self):
+ for worker in self.workers.values():
+ worker.stop()
+ tasks = self.tasks[:] # Copy
+ for task in tasks: # Mark all current task as failed
+ self.failTask(task)
+
# Find workers by task
@@ -76,21 +110,26 @@ class WorkerManager:
if worker.task == task: workers.append(worker)
return workers
+
# Ends and remove a worker
def removeWorker(self, worker):
worker.running = False
- del(self.workers[worker.key])
- self.log.debug("Removed worker, workers: %s/%s" % (len(self.workers), MAX_WORKERS))
+ if worker.key in self.workers:
+ del(self.workers[worker.key])
+ self.log.debug("Removed worker, workers: %s/%s" % (len(self.workers), MAX_WORKERS))
# Create new task and return asyncresult
- def addTask(self, inner_path, peer=None):
+ def addTask(self, inner_path, peer=None, priority = 0):
self.site.onFileStart(inner_path) # First task, trigger site download started
task = self.findTask(inner_path)
if task: # Already has task for that file
- if peer and task["peers"]: # This peer has new version too
+ if peer and task["peers"]: # This peer also has new version, add it to task possible peers
task["peers"].append(peer)
- self.startWorkers()
+ self.log.debug("Added peer %s to %s" % (peer.key, task["inner_path"]))
+ self.startWorkers([peer])
+ if priority:
+ task["priority"] += priority # Boost on priority
return task["evt"]
else: # No task for that file yet
evt = gevent.event.AsyncResult()
@@ -98,10 +137,10 @@ class WorkerManager:
peers = [peer] # Only download from this peer
else:
peers = None
- task = {"evt": evt, "workers_num": 0, "site": self.site, "inner_path": inner_path, "done": False, "time_start": time.time(), "peers": peers}
+ task = {"evt": evt, "workers_num": 0, "site": self.site, "inner_path": inner_path, "done": False, "time_added": time.time(), "time_started": None, "peers": peers, "priority": priority}
self.tasks.append(task)
- self.log.debug("New task: %s" % task)
- self.startWorkers()
+ self.log.debug("New task: %s, peer lock: %s" % (task, peers))
+ self.startWorkers(peers)
return evt
diff --git a/src/lib/BitcoinECC/newBitcoinECC.py b/src/lib/BitcoinECC/newBitcoinECC.py
new file mode 100644
index 00000000..b09386bc
--- /dev/null
+++ b/src/lib/BitcoinECC/newBitcoinECC.py
@@ -0,0 +1,460 @@
+import random
+import hashlib
+import base64
+
+class GaussInt:
+ def __init__(self,x,y,n,p=0):
+ if p:
+ self.x=x%p
+ self.y=y%p
+ self.n=n%p
+ else:
+ self.x=x
+ self.y=y
+ self.n=n
+
+ self.p=p
+
+ def __add__(self,b):
+ return GaussInt(self.x+b.x,self.y+b.y,self.n,self.p)
+
+ def __sub__(self,b):
+ return GaussInt(self.x-b.x,self.y-b.y,self.n,self.p)
+
+ def __mul__(self,b):
+ return GaussInt(self.x*b.x+self.n*self.y*b.y,self.x*b.y+self.y*b.x,self.n,self.p)
+
+ def __div__(self,b):
+ return GaussInt((self.x*b.x-self.n*self.y*b.y)/(b.x*b.x-self.n*b.y*b.y),(-self.x*b.y+self.y*b.x)/(b.x*b.x-self.n*b.y*b.y),self.n,self.p)
+
+ def __eq__(self,b):
+ return self.x==b.x and self.y==b.y
+
+ def __repr__(self):
+ if self.p:
+ return "%s+%s (%d,%d)"%(self.x,self.y,self.n,self.p)
+ else:
+ return "%s+%s (%d)"%(self.x,self.y,self.n)
+
+ def __pow__(self,n):
+ b=Base(n,2)
+ t=GaussInt(1,0,self.n)
+ while b:
+ t=t*t
+ if b.pop():
+ t=self*t
+
+ return t
+
+ def Inv(self):
+ return GaussInt(self.x/(self.x*self.x-self.n*self.y*self.y),-self.y/(self.x*self.x-self.n*self.y*self.y),self.n,self.p)
+
+ def Eval(self):
+ return self.x.Eval()+self.y.Eval()*math.sqrt(self.n)
+
+def Cipolla(a,p):
+ b=0
+ while pow((b*b-a)%p,(p-1)/2,p)==1:
+ b+=1
+
+ return (GaussInt(b,1,b**2-a,p)**((p+1)/2)).x
+
+def InvMod(a,n):
+ m=[]
+
+ s=n
+ while n:
+ m.append(a/n)
+ (a,n)=(n,a%n)
+
+ u=1
+ v=0
+ while m:
+ (u,v)=(v,u-m.pop()*v)
+
+ return u%s
+
+def Base(n,b):
+ l=[]
+ while n:
+ l.append(n%b)
+ n/=b
+
+ return l
+
+def MsgMagic(message):
+ return "\x18Bitcoin Signed Message:\n"+chr(len(message))+message
+
+def Hash(m,method):
+ h=hashlib.new(method)
+ h.update(m)
+
+ return h.digest()
+
+def b58encode(v):
+ #Encode a byte string to the Base58
+ digit="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+ base=len(digit)
+ val=0
+ for c in v:
+ val*=256
+ val+=ord(c)
+
+ result=""
+ while val:
+ (val,mod)=divmod(val,base)
+ result=digit[mod]+result
+
+ pad=0
+ for c in v:
+ if c=="\x00":
+ pad+=1
+ else:
+ break
+
+ return (digit[0]*pad)+result
+
+def b58decode(v):
+ #Decode a Base58 string to byte string
+ digit="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+ base=len(digit)
+ val=0
+ for c in v:
+ val*=base
+ val+=digit.find(c)
+
+ result=""
+ while val:
+ (val,mod)=divmod(val,256)
+ result=chr(mod)+result
+
+ pad=0
+ for c in v:
+ if c==digit[0]:
+ pad+=1
+ else:
+ break
+
+ return "\x00"*pad+result
+
+def Byte2Int(b):
+ n=0
+ for x in b:
+ n*=256
+ n+=ord(x)
+
+ return n
+
+def Byte2Hex(b):
+ #Convert a byte string to hex number
+ out=""
+ for x in b:
+ y=hex(ord(x))[2:]
+ if len(y)==1:
+ y="0"+y
+ out+="%2s"%y
+
+ return out
+
+def Int2Byte(n,b):
+ #Convert a integer to a byte string of length b
+ out=""
+
+ for _ in range(b):
+ (n,m)=divmod(n,256)
+ out=chr(m)+out
+
+ return out
+
+class EllipticCurvePoint:
+ #Main class
+ #It's a point on an Elliptic Curve
+
+ def __init__(self,x,a,b,p,n=0):
+ #We store the coordinate in x and the elliptic curve parameter.
+ #x is of length 3. This is the 3 projective coordinates of the point.
+ self.x=x[:]
+ self.a=a
+ self.b=b
+ self.p=p
+ self.n=n
+
+ def __add__(self,y):
+ #The main function to add self and y
+ #It uses the formulas I derived in projective coordinates.
+ #Projectives coordinates are more efficient than the usual (x,y) coordinates
+ #because we don't need to compute inverse mod p, which is faster.
+ z=EllipticCurvePoint([0,0,0],self.a,self.b,self.p)
+
+ if self==y:
+ d=(2*self.x[1]*self.x[2])%self.p
+ d3=pow(d,3,self.p)
+ n=(3*pow(self.x[0],2,self.p)+self.a*pow(self.x[2],2,self.p))%self.p
+
+ z.x[0]=(pow(n,2,self.p)*d*self.x[2]-2*d3*self.x[0])%self.p
+ z.x[1]=(3*self.x[0]*n*pow(d,2,self.p)-pow(n,3,self.p)*self.x[2]-self.x[1]*d3)%self.p
+ z.x[2]=(self.x[2]*d3)%self.p
+ else:
+ d=(y.x[0]*self.x[2]-y.x[2]*self.x[0])%self.p
+ d3=pow(d,3,self.p)
+ n=(y.x[1]*self.x[2]-self.x[1]*y.x[2])%self.p
+
+ z.x[0]=(y.x[2]*self.x[2]*pow(n,2,self.p)*d-d3*(y.x[2]*self.x[0]+y.x[0]*self.x[2]))%self.p
+ z.x[1]=(pow(d,2,self.p)*n*(2*self.x[0]*y.x[2]+y.x[0]*self.x[2])-pow(n,3,self.p)*self.x[2]*y.x[2]-self.x[1]*d3*y.x[2])%self.p
+ z.x[2]=(self.x[2]*d3*y.x[2])%self.p
+
+ return z
+
+ def __mul__(self,n):
+ #The fast multiplication of point n times by itself.
+ b=Base(n,2)
+ t=EllipticCurvePoint(self.x,self.a,self.b,self.p)
+ b.pop()
+ while b:
+ t+=t
+ if b.pop():
+ t+=self
+
+ return t
+
+ def __repr__(self):
+ #print a point in (x,y) coordinate.
+ return "x=%d\ny=%d\n"%((self.x[0]*InvMod(self.x[2],self.p))%self.p,(self.x[1]*InvMod(self.x[2],self.p))%self.p)
+
+ def __eq__(self,y):
+ #Does self==y ?
+ #It computes self cross product with x and check if the result is 0.
+ return self.x[0]*y.x[1]==self.x[1]*y.x[0] and self.x[1]*y.x[2]==self.x[2]*y.x[1] and self.x[2]*y.x[0]==self.x[0]*y.x[2] and self.a==y.a and self.b==y.b and self.p==y.p
+
+ def __ne__(self,y):
+ #Does self!=x ?
+ return not (self == y)
+
+ def Normalize(self):
+ #Transform projective coordinates of self to the usual (x,y) coordinates.
+ if self.x[2]:
+ self.x[0]=(self.x[0]*InvMod(self.x[2],self.p))%self.p
+ self.x[1]=(self.x[1]*InvMod(self.x[2],self.p))%self.p
+ self.x[2]=1
+ elif self.x[1]:
+ self.x[0]=(self.x[0]*InvMod(self.x[1],self.p))%self.p
+ self.x[1]=1
+ elif self.x[0]:
+ self.x[0]=1
+ else:
+ raise Exception
+
+ def Check(self):
+ #Is self on the curve ?
+ return (self.x[0]**3+self.a*self.x[0]*self.x[2]**2+self.b*self.x[2]**3-self.x[1]**2*self.x[2])%self.p==0
+
+
+ def CryptAddr(self,filename,password,Address):
+ txt=""
+ for tag in Address:
+ (addr,priv)=Address[tag]
+ if priv:
+ txt+="%s\t%s\t%s\n"%(tag,addr,priv)
+ else:
+ txt+="%s\t%s\t\n"%(tag,addr)
+
+ txt+="\x00"*(15-(len(txt)-1)%16)
+
+ password+="\x00"*(15-(len(password)-1)%16)
+ crypt=twofish.Twofish(password).encrypt(txt)
+
+ f=open(filename,"wb")
+ f.write(crypt)
+ f.close()
+
+ def GenerateD(self):
+ #Generate a private key. It's just a random number between 1 and n-1.
+ #Of course, this function isn't cryptographically secure.
+ #Don't use it to generate your key. Use a cryptographically secure source of randomness instead.
+ #return random.randint(1,self.n-1)
+ return random.SystemRandom().randint(1,self.n-1) # Better random fix
+
+ def CheckECDSA(self,sig,message,Q):
+ #Check a signature (r,s) of the message m using the public key self.Q
+ # and the generator which is self.
+ #This is not the one used by Bitcoin because the public key isn't known;
+ # only a hash of the public key is known. See the function VerifyMessageFromAddress.
+ (r,s)=sig
+
+ if Q.x[2]==0:
+ return False
+ if not Q.Check():
+ return False
+ if (Q*self.n).x[2]!=0:
+ return False
+ if r<1 or r>self.n-1 or s<1 or s>self.n-1:
+ return False
+
+ z=Byte2Int(Hash(Hash(MsgMagic(message),"SHA256"),"SHA256"))
+
+ w=InvMod(s,self.n)
+ u1=(z*w)%self.n
+ u2=(r*w)%self.n
+ R=self*u1+Q*u2
+ R.Normalize()
+
+ return (R.x[0]-r)%self.n==0
+
+ def SignMessage(self,message,priv):
+ #Sign a message. The private key is self.d.
+ (d,uncompressed)=self.DFromPriv(priv)
+
+ z=Byte2Int(Hash(Hash(MsgMagic(message),"SHA256"),"SHA256"))
+
+ r=0
+ s=0
+ while not r or not s:
+ #k=random.randint(1,self.n-1)
+ k=random.SystemRandom().randint(1,self.n-1) # Better random fix
+ R=self*k
+ R.Normalize()
+ r=R.x[0]%self.n
+ s=(InvMod(k,self.n)*(z+r*d))%self.n
+
+ val=27
+ if not uncompressed:
+ val+=4
+
+ return base64.standard_b64encode(chr(val)+Int2Byte(r,32)+Int2Byte(s,32))
+
+ def VerifyMessageFromAddress(self,addr,message,sig):
+ #Check a signature (r,s) for the message m signed by the Bitcoin
+ # address "addr".
+
+ sign=base64.standard_b64decode(sig)
+ (r,s)=(Byte2Int(sign[1:33]),Byte2Int(sign[33:65]))
+
+ z=Byte2Int(Hash(Hash(MsgMagic(message),"SHA256"),"SHA256"))
+
+ val=ord(sign[0])
+ if val<27 or val>=35:
+ return False
+
+ if val>=31:
+ uncompressed=False
+ val-=4
+ else:
+ uncompressed=True
+
+ x=r
+ y2=(pow(x,3,self.p) + self.a*x + self.b) % self.p
+ y=Cipolla(y2,self.p)
+
+ for _ in range(2):
+ kG=EllipticCurvePoint([x,y,1],self.a,self.b,self.p,self.n)
+ mzG=self*((-z)%self.n)
+ Q=(kG*s+mzG)*InvMod(r,self.n)
+
+ if self.AddressFromPublicKey(Q,uncompressed)==addr:
+ return True
+
+ y=self.p-y
+
+ return False
+
+ def AddressFromPrivate(self,priv):
+ #Transform a private key to a bitcoin address.
+ (d,uncompressed)=self.DFromPriv(priv)
+
+ return self.AddressFromD(d,uncompressed)
+
+ def PrivFromD(self,d,uncompressed):
+ #Encode a private key self.d to base58 encoding.
+ p=Int2Byte(d,32)
+ p="\x80"+p
+
+ if not uncompressed:
+ p+=chr(1)
+
+ cs=Hash(Hash(p,"SHA256"),"SHA256")[:4]
+
+ return b58encode(p+cs)
+
+ def DFromPriv(self,priv):
+ uncompressed=(len(priv)==51)
+ priv=b58decode(priv)
+
+ if uncompressed:
+ priv=priv[:-4]
+ else:
+ priv=priv[:-5]
+
+ return (Byte2Int(priv[1:]),uncompressed)
+
+ def AddressFromPublicKey(self,Q,uncompressed):
+ #Find the bitcoin address from the public key self.Q
+ #We do normalization to go from the projective coordinates to the usual
+ # (x,y) coordinates.
+ Q.Normalize()
+ if uncompressed:
+ pk=chr(4)+Int2Byte(Q.x[0],32)+Int2Byte(Q.x[1],32)
+ else:
+ pk=chr(2+Q.x[1]%2)+Int2Byte(Q.x[0],32)
+
+ kh=chr(0)+Hash(Hash(pk,"SHA256"),"RIPEMD160")
+ cs=Hash(Hash(kh,"SHA256"),"SHA256")[:4]
+
+ return b58encode(kh+cs)
+
+ def AddressFromD(self,d,uncompressed):
+ #Computes a bitcoin address given the private key self.d.
+ return self.AddressFromPublicKey(self*d,uncompressed)
+
+ def IsValid(self,addr):
+ adr=b58decode(addr)
+ kh=adr[:-4]
+ cs=adr[-4:]
+
+ verif=Hash(Hash(kh,"SHA256"),"SHA256")[:4]
+
+ return cs==verif
+
+ def AddressGenerator(self,k,uncompressed=True):
+ #Generate Bitcoin address and write them in the multibit format.
+ #Change the date as you like.
+ liste={}
+ for i in range(k):
+ d=self.GenerateD()
+ addr=self.AddressFromD(d,uncompressed)
+ priv=self.PrivFromD(d,uncompressed)
+ liste[i]=[addr,priv]
+ print "%s %s"%(addr, priv)
+
+ return liste
+
+def Bitcoin():
+ a=0
+ b=7
+ p=2**256-2**32-2**9-2**8-2**7-2**6-2**4-1
+ Gx=int("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",16)
+ Gy=int("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",16)
+ n=int("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16)
+
+ return EllipticCurvePoint([Gx,Gy,1],a,b,p,n)
+
+def main():
+ bitcoin=Bitcoin()
+
+ #Generate an adress from the private key
+ privkey = "PrivatekeyinBase58"
+ adr = bitcoin.AddressFromPrivate(privkey)
+ print "Address : ", adr
+
+ #Sign a message with the current address
+ m="Hello World"
+ sig=bitcoin.SignMessage("Hello World", privkey)
+ #Verify the message using only the bitcoin adress, the signature and the message.
+ #Not using the public key as it is not needed.
+ if bitcoin.VerifyMessageFromAddress(adr,m,sig):
+ print "Message verified"
+
+ #Generate some addresses
+ print "Here are some adresses and associated private keys"
+ bitcoin.AddressGenerator(10)
+
+if __name__ == "__main__": main()
diff --git a/src/main.py b/src/main.py
index 4b3dab92..46d576a7 100644
--- a/src/main.py
+++ b/src/main.py
@@ -19,8 +19,14 @@ if config.action == "main":
else:
logging.basicConfig(level=logging.DEBUG, stream=open(os.devnull,"w")) # No file logging if action is not main
+# Console logger
console_log = logging.StreamHandler()
-console_log.setFormatter(logging.Formatter('%(name)s %(message)s', "%H:%M:%S"))
+if config.action == "main": # Add time if main action
+ console_log.setFormatter(logging.Formatter('[%(asctime)s] %(name)s %(message)s', "%H:%M:%S"))
+else:
+ console_log.setFormatter(logging.Formatter('%(name)s %(message)s', "%H:%M:%S"))
+
+
logging.getLogger('').addHandler(console_log) # Add console logger
logging.getLogger('').name = "-" # Remove root prefix
@@ -67,11 +73,16 @@ def siteCreate():
logging.info("Generating new privatekey...")
from src.Crypt import CryptBitcoin
privatekey = CryptBitcoin.newPrivatekey()
- logging.info("-----------------------------------------------------------")
- logging.info("Site private key: %s (save it, required to modify the site)" % privatekey)
+ logging.info("----------------------------------------------------------------------")
+ logging.info("Site private key: %s" % privatekey)
+ logging.info(" !!! ^ Save it now, required to modify the site ^ !!!")
address = CryptBitcoin.privatekeyToAddress(privatekey)
- logging.info("Site address: %s" % address)
- logging.info("-----------------------------------------------------------")
+ logging.info("Site address: %s" % address)
+ logging.info("----------------------------------------------------------------------")
+
+ while True:
+ if raw_input("? Have you secured your private key? (yes, no) > ").lower() == "yes": break
+ else: logging.info("Please, secure it now, you going to need it to modify your site!")
logging.info("Creating directory structure...")
from Site import Site
@@ -81,6 +92,8 @@ def siteCreate():
logging.info("Creating content.json...")
site = Site(address)
site.signContent(privatekey)
+ site.settings["own"] = True
+ site.saveSettings()
logging.info("Site created!")
@@ -110,7 +123,7 @@ def siteVerify(address):
logging.info("Verifying site files...")
bad_files = site.verifyFiles()
if not bad_files:
- logging.info("[OK] All file sha1sum matches!")
+ logging.info("[OK] All file sha512sum matches!")
else:
logging.error("[ERROR] Error during verifying site files!")
@@ -133,7 +146,7 @@ def siteNeedFile(address, inner_path):
print site.needFile(inner_path, update=True)
-def sitePublish(address):
+def sitePublish(address, peer_ip=None, peer_port=15441):
from Site import Site
from File import FileServer # We need fileserver to handle incoming file requests
logging.info("Creating FileServer....")
@@ -145,8 +158,12 @@ def sitePublish(address):
return
site = file_server.sites[address]
site.settings["serving"] = True # Serving the site even if its disabled
- site.announce() # Gather peers
- site.publish(10) # Push to 10 peers
+ if peer_ip: # Announce ip specificed
+ site.addPeer(peer_ip, peer_port)
+ else: # Just ask the tracker
+ logging.info("Gathering peers from tracker")
+ site.announce() # Gather peers
+ site.publish(20) # Push to 20 peers
logging.info("Serving files....")
gevent.joinall([file_server_thread])