Version 0.3.1, rev238, Connection encryption using TLS, One click site clone feature, Encryption stats, Disable encryption startup parameter, Disable ssl compression startup parameter, Exchange supported encryption methods at handshake, Alternative open port checker, Option to store site privatekey in users.json, Torrent tracker swap, Test for bip32 based site creation, cloning and sslcert creation, Fix for Chrome plugin on OSX, Separate siteSign websocket command, Update pybitcointools to major speedup, Re-add sslwrap for python 0.2.9+, Disable SSL compression to save memory and better performance

This commit is contained in:
HelloZeroNet 2015-06-10 00:29:30 +02:00
parent f0597afe1f
commit a78907cc9d
64 changed files with 4141 additions and 213 deletions

View File

@ -31,6 +31,7 @@ class UiRequestPlugin(object):
def actionStats(self):
import gc, sys
from Ui import UiRequest
from Crypt import CryptConnection
hpy = None
if self.get.get("size") == "1": # Calc obj size
@ -47,16 +48,17 @@ class UiRequestPlugin(object):
yield """
<style>
* { font-family: monospace }
table * { text-align: right; padding: 0px 10px }
table td, table th { text-align: right; padding: 0px 10px }
</style>
"""
# Memory
try:
yield "rev%s | " % config.rev
yield "IP external: %s | " % config.ip_external
yield "%s | " % config.ip_external
yield "Opened: %s | " % main.file_server.port_opened
yield "Recv: %.2fMB, Sent: %.2fMB | " % (float(main.file_server.bytes_recv)/1024/1024, float(main.file_server.bytes_sent)/1024/1024)
yield "Crypt: %s | " % CryptConnection.manager.crypt_supported
yield "In: %.2fMB, Out: %.2fMB | " % (float(main.file_server.bytes_recv)/1024/1024, float(main.file_server.bytes_sent)/1024/1024)
yield "Peerid: %s | " % main.file_server.peer_id
import psutil
process = psutil.Process(os.getpid())
@ -64,7 +66,7 @@ class UiRequestPlugin(object):
yield "Mem: %.2fMB | " % mem
yield "Threads: %s | " % len(process.threads())
yield "CPU: usr %.2fs sys %.2fs | " % process.cpu_times()
yield "Open files: %s | " % len(process.open_files())
yield "Files: %s | " % len(process.open_files())
yield "Sockets: %s | " % len(process.connections())
yield "Calc size <a href='?size=1'>on</a> <a href='?size=0'>off</a>"
except Exception, err:
@ -73,15 +75,20 @@ class UiRequestPlugin(object):
# Connections
yield "<b>Connections</b> (%s, total made: %s):<br>" % (len(main.file_server.connections), main.file_server.last_connection_id)
yield "<table><tr> <th>id</th> <th>protocol</th> <th>type</th> <th>ip</th> <th>open</th> <th>ping</th> <th>buff</th>"
yield "<th>idle</th> <th>open</th> <th>delay</th> <th>sent</th> <th>received</th> <th>last sent</th> <th>waiting</th> <th>version</th> <th>peerid</th> </tr>"
yield "<table><tr> <th>id</th> <th>proto</th> <th>type</th> <th>ip</th> <th>open</th> <th>crypt</th> <th>ping</th> <th>buff</th>"
yield "<th>idle</th> <th>open</th> <th>delay</th> <th>out</th> <th>in</th> <th>last sent</th> <th>waiting</th> <th>version</th> <th>peerid</th> </tr>"
for connection in main.file_server.connections:
if "cipher" in dir(connection.sock):
cipher = connection.sock.cipher()[0]
else:
cipher = connection.crypt
yield self.formatTableRow([
("%3d", connection.id),
("%s", connection.protocol),
("%s", connection.type),
("%s:%s", (connection.ip, connection.port)),
("%s", connection.handshake.get("port_opened")),
("<span title='%s'>%s</span>", (connection.crypt, cipher)),
("%6.3f", connection.last_ping_delay),
("%s", connection.incomplete_buff_recv),
("since", max(connection.last_send_time, connection.last_recv_time)),

View File

@ -3,8 +3,8 @@ import ConfigParser
class Config(object):
def __init__(self):
self.version = "0.3.0"
self.rev = 196
self.version = "0.3.1"
self.rev = 238
self.parser = self.createArguments()
argv = sys.argv[:] # Copy command line arguments
argv = self.parseConfig(argv) # Add arguments from config file
@ -77,7 +77,7 @@ class Config(object):
# PeerPing
action = subparsers.add_parser("peerPing", help='Send Ping command to peer')
action.add_argument('peer_ip', help='Peer ip')
action.add_argument('peer_port', help='Peer port')
action.add_argument('peer_port', help='Peer port', nargs='?')
# PeerGetFile
action = subparsers.add_parser("peerGetFile", help='Request and print a file content from peer')
@ -118,8 +118,10 @@ class Config(object):
parser.add_argument('--fileserver_port',help='FileServer bind port', default=15441, type=int, metavar='port')
parser.add_argument('--disable_udp', help='Disable UDP connections', action='store_true')
parser.add_argument('--proxy', help='Socks proxy address', metavar='ip:port')
parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup', type='bool', choices=[True, False], default=use_openssl)
parser.add_argument('--ip_external', help='External ip (tested on start if None)', metavar='ip')
parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup', type='bool', choices=[True, False], default=use_openssl)
parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true')
parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory', type='bool', choices=[True, False], default=True)
parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript, metavar='executable_path')

View File

@ -4,9 +4,10 @@ import gevent, msgpack
from Config import config
from Debug import Debug
from util import StreamingMsgpack
from Crypt import CryptConnection
class Connection(object):
__slots__ = ("sock", "ip", "port", "peer_id", "id", "protocol", "type", "server", "unpacker", "req_id", "handshake", "connected", "event_connected", "closed", "start_time", "last_recv_time", "last_message_time", "last_send_time", "last_sent_time", "incomplete_buff_recv", "bytes_recv", "bytes_sent", "last_ping_delay", "last_req_time", "last_cmd", "name", "updateName", "waiting_requests")
__slots__ = ("sock", "ip", "port", "peer_id", "id", "protocol", "type", "server", "unpacker", "req_id", "handshake", "crypt", "connected", "event_connected", "closed", "start_time", "last_recv_time", "last_message_time", "last_send_time", "last_sent_time", "incomplete_buff_recv", "bytes_recv", "bytes_sent", "last_ping_delay", "last_req_time", "last_cmd", "name", "updateName", "waiting_requests")
def __init__(self, server, ip, port, sock=None):
self.sock = sock
@ -22,6 +23,8 @@ class Connection(object):
self.unpacker = None # Stream incoming socket messages here
self.req_id = 0 # Last request id
self.handshake = {} # Handshake info got from peer
self.crypt = None # Connection encryption method
self.connected = False
self.event_connected = gevent.event.AsyncResult() # Solves on handshake received
self.closed = False
@ -76,6 +79,7 @@ class Connection(object):
# Handle incoming connection
def handleIncomingConnection(self, sock):
self.log("Incoming connection...")
self.type = "in"
self.messageLoop()
@ -90,10 +94,9 @@ class Connection(object):
self.connected = True
self.unpacker = msgpack.Unpacker()
sock = self.sock
try:
while True:
buff = sock.recv(16*1024)
buff = self.sock.recv(16*1024)
if not buff: break # Connection closed
self.last_recv_time = time.time()
self.incomplete_buff_recv += 1
@ -118,12 +121,32 @@ class Connection(object):
"version": config.version,
"protocol": "v2",
"peer_id": self.server.peer_id,
"fileserver_port": config.fileserver_port,
"fileserver_port": self.server.port,
"port_opened": self.server.port_opened,
"rev": config.rev
"rev": config.rev,
"crypt_supported": CryptConnection.manager.crypt_supported,
"crypt": self.crypt
}
def setHandshake(self, handshake):
self.handshake = handshake
if handshake.get("port_opened", None) == False: # Not connectable
self.port = 0
else:
self.port = handshake["fileserver_port"] # Set peer fileserver port
# Check if we can encrypt the connection
if handshake.get("crypt_supported"):
if handshake.get("crypt"): # Recommended crypt by server
crypt = handshake["crypt"]
else: # Select the best supported on both sides
crypt = CryptConnection.manager.selectCrypt(handshake["crypt_supported"])
if crypt:
self.crypt = crypt
self.event_connected.set(True) # Mark handshake as done
# Handle incoming message
def handleMessage(self, message):
self.last_message_time = time.time()
@ -133,29 +156,31 @@ class Connection(object):
del self.waiting_requests[message["to"]]
elif message["to"] == 0: # Other peers handshake
ping = time.time()-self.start_time
if config.debug_socket: self.log("Got handshake response: %s, ping: %s" % (message, ping))
if config.debug_socket: self.log("Handshake response: %s, ping: %s" % (message, ping))
self.last_ping_delay = ping
self.handshake = message
if self.handshake.get("port_opened", None) == False: # Not connectable
self.port = 0
else:
self.port = message["fileserver_port"] # Set peer fileserver port
self.event_connected.set(True) # Mark handshake as done
# Server switched to crypt, lets do it also
if message.get("crypt"):
self.crypt = message["crypt"]
server = (self.type == "in")
self.log("Crypt out connection using: %s (server side: %s)..." % (self.crypt, server))
self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server)
self.sock.do_handshake()
self.setHandshake(message)
else:
self.log("Unknown response: %s" % message)
elif message.get("cmd"): # Handhsake request
if message["cmd"] == "handshake":
self.handshake = message["params"]
if self.handshake.get("port_opened", None) == False: # Not connectable
self.port = 0
else:
self.port = self.handshake["fileserver_port"] # Set peer fileserver port
self.event_connected.set(True) # Mark handshake as done
if config.debug_socket: self.log("Handshake request: %s" % message)
self.setHandshake(message["params"])
data = self.handshakeInfo()
data["cmd"] = "response"
data["to"] = message["req_id"]
self.send(data)
self.send(data) # Send response to handshake
# Sent crypt request to client
if self.crypt:
server = (self.type == "in")
self.log("Crypt in connection using: %s (server side: %s)..." % (self.crypt, server))
self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server)
else:
self.server.handleRequest(self, message)
else: # Old style response, no req_id definied

View File

@ -1,11 +1,12 @@
from gevent.server import StreamServer
from gevent.pool import Pool
import socket, os, logging, random, string, time
import socket, os, logging, random, string, time, sys
import gevent, msgpack
import cStringIO as StringIO
from Debug import Debug
from Connection import Connection
from Config import config
from Crypt import CryptConnection
class ConnectionServer:
@ -39,12 +40,13 @@ class ConnectionServer:
self.stream_server = StreamServer((ip.replace("*", ""), port), self.handleIncomingConnection, spawn=self.pool, backlog=100)
if request_handler: self.handleRequest = request_handler
CryptConnection.manager.loadCerts()
def start(self):
self.running = True
self.log.debug("Binding to: %s:%s, (msgpack: %s), supported crypt: %s" % (self.ip, self.port, ".".join(map(str, msgpack.version)), CryptConnection.manager.crypt_supported ) )
try:
self.log.debug("Binding to: %s:%s (msgpack: %s)" % (self.ip, self.port, ".".join(map(str, msgpack.version))))
self.stream_server.serve_forever() # Start normal connection server
except Exception, err:
self.log.info("StreamServer bind error, must be running already: %s" % err)
@ -152,7 +154,6 @@ class ConnectionServer:
connection.close()
# -- TESTING --
def testCreateServer():

View File

@ -0,0 +1,101 @@
import sys, logging, os
from Config import config
import gevent
from util import SslPatch
class CryptConnectionManager:
def __init__(self):
# OpenSSL params
if sys.platform.startswith("win"):
self.openssl_bin = "src\\lib\\opensslVerify\\openssl.exe"
else:
self.openssl_bin = "openssl"
self.openssl_env = {"OPENSSL_CONF": "src/lib/opensslVerify/openssl.cnf"}
self.crypt_supported = [] # Supported cryptos
# Select crypt that supported by both sides
# Return: Name of the crypto
def selectCrypt(self, client_supported):
for crypt in self.crypt_supported:
if crypt in client_supported:
return crypt
return False
# Wrap socket for crypt
# Return: wrapped socket
def wrapSocket(self, sock, crypt, server=False):
if crypt == "tls-rsa":
ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES128-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK"
if server:
return gevent.ssl.wrap_socket(sock, server_side=server, keyfile='%s/key-rsa.pem' % config.data_dir, certfile='%s/cert-rsa.pem' % config.data_dir, ciphers=ciphers)
else:
return gevent.ssl.wrap_socket(sock, ciphers=ciphers)
else:
return sock
def removeCerts(self):
for file_name in ["cert-rsa.pem", "key-rsa.pem"]:
file_path = "%s/%s" % (config.data_dir, file_name)
if os.path.isfile(file_path): os.unlink(file_path)
# Loand and create cert files is necessary
def loadCerts(self):
if config.disable_encryption: return False
if self.loadSslRsaCert():
self.crypt_supported.append("tls-rsa")
# Try to create RSA server cert + sign for connection encryption
# Return: True on success
def loadSslRsaCert(self):
import subprocess
if os.path.isfile("%s/cert-rsa.pem" % config.data_dir) and os.path.isfile("%s/key-rsa.pem" % config.data_dir):
return True # Files already exits
back = subprocess.Popen(
"%s req -x509 -newkey rsa:2048 -sha256 -batch -keyout %s/key-rsa.pem -out %s/cert-rsa.pem -nodes -config %s" % (self.openssl_bin, config.data_dir, config.data_dir, self.openssl_env["OPENSSL_CONF"]),
shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env
).stdout.read().strip()
logging.debug("Generating RSA cert and key PEM files...%s" % back)
if os.path.isfile("%s/cert-rsa.pem" % config.data_dir) and os.path.isfile("%s/key-rsa.pem" % config.data_dir):
return True
else:
logging.error("RSA ECC SSL cert generation failed, cert or key files not exits.")
return False
# Not used yet: Missing on some platform
def createSslEccCert(self):
return False
import subprocess
# Create ECC privatekey
back = subprocess.Popen(
"%s ecparam -name prime256v1 -genkey -out %s/key-ecc.pem" % (self.openssl_bin, config.data_dir),
shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env
).stdout.read().strip()
self.log.debug("Generating ECC privatekey PEM file...%s" % back)
# Create ECC cert
back = subprocess.Popen(
"%s req -new -key %s/key-ecc.pem -x509 -nodes -out %s/cert-ecc.pem -config %s" % (self.openssl_bin, config.data_dir, config.data_dir, self.openssl_env["OPENSSL_CONF"]),
shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env
).stdout.read().strip()
self.log.debug("Generating ECC cert PEM file...%s" % back)
if os.path.isfile("%s/cert-ecc.pem" % config.data_dir) and os.path.isfile("%s/key-ecc.pem" % config.data_dir):
return True
else:
self.logging.error("ECC SSL cert generation failed, cert or key files not exits.")
return False
manager = CryptConnectionManager()

View File

@ -63,6 +63,46 @@ class FileServer(ConnectionServer):
def testOpenport(self, port = None):
time.sleep(1) # Wait for port open
if not port: port = self.port
back = self.testOpenportPortchecker(port)
if back["result"] == True: # Successful port check
return back
else: # Alternative port checker
return self.testOpenportCanyouseeme(port)
def testOpenportPortchecker(self, port = None):
self.log.info("Checking port %s using portchecker.co..." % port)
try:
data = urllib2.urlopen("http://portchecker.co/check", "port=%s" % port, timeout=20.0).read()
message = re.match('.*<div id="results-wrapper">(.*?)</div>', data, re.DOTALL).group(1)
message = re.sub("<.*?>", "", message.replace("<br>", " ").replace("&nbsp;", " ").strip()) # Strip http tags
except Exception, err:
message = "Error: %s" % Debug.formatException(err)
if "closed" in message:
self.log.info("[BAD :(] Port closed: %s" % message)
if port == self.port:
self.port_opened = False # Self port, update port_opened status
match = re.match(".*targetIP.*?value=\"(.*?)\"", data, re.DOTALL) # Try find my external ip in message
if match: # Found my ip in message
config.ip_external = match.group(1)
SiteManager.peer_blacklist.append((config.ip_external, self.port)) # Add myself to peer blacklist
else:
config.ip_external = False
return {"result": False, "message": message}
else:
self.log.info("[OK :)] Port open: %s" % message)
if port == self.port: # Self port, update port_opened status
self.port_opened = True
match = re.match(".*targetIP.*?value=\"(.*?)\"", data, re.DOTALL) # Try find my external ip in message
if match: # Found my ip in message
config.ip_external = match.group(1)
SiteManager.peer_blacklist.append((config.ip_external, self.port)) # Add myself to peer blacklist
else:
config.ip_external = False
return {"result": True, "message": message}
def testOpenportCanyouseeme(self, port = None):
self.log.info("Checking port %s using canyouseeme.org..." % port)
try:
data = urllib2.urlopen("http://www.canyouseeme.org/", "port=%s" % port, timeout=20.0).read()

View File

@ -313,6 +313,69 @@ class Site:
return len(published)
# Copy this site
def clone(self, address, privatekey=None, address_index=None, overwrite=False):
import shutil
new_site = SiteManager.site_manager.need(address, all_file=False)
default_dirs = [] # Dont copy these directories (has -default version)
for dir_name in os.listdir(self.storage.directory):
if "-default" in dir_name:
default_dirs.append(dir_name.replace("-default", ""))
self.log.debug("Cloning to %s, ignore dirs: %s" % (address, default_dirs))
# Copy root content.json
if not new_site.storage.isFile("content.json") and not overwrite: # Content.json not exits yet, create a new one from source site
content_json = self.storage.loadJson("content.json")
del content_json["domain"]
content_json["title"] = "my"+content_json["title"]
content_json["cloned_from"] = self.address
if address_index: content_json["address_index"] = address_index # Site owner's BIP32 index
new_site.storage.writeJson("content.json", content_json)
new_site.content_manager.loadContent("content.json", add_bad_files=False, load_includes=False)
# Copy files
for content_inner_path, content in self.content_manager.contents.items():
for file_relative_path in sorted(content["files"].keys()):
file_inner_path = self.content_manager.toDir(content_inner_path)+file_relative_path # Relative to content.json
file_inner_path = file_inner_path.strip("/") # Strip leading /
if file_inner_path.split("/")[0] in default_dirs: # Dont copy directories that has -default postfixed alternative
self.log.debug("[SKIP] %s (has default alternative)" % file_inner_path)
continue
file_path = self.storage.getPath(file_inner_path)
# Copy the file normally to keep the -default postfixed dir and file to allow cloning later
file_path_dest = new_site.storage.getPath(file_inner_path)
self.log.debug("[COPY] %s to %s..." % (file_inner_path, file_path_dest))
dest_dir = os.path.dirname(file_path_dest)
if not os.path.isdir(dest_dir): os.makedirs(dest_dir)
shutil.copy(file_path, file_path_dest)
# If -default in path, create a -default less copy of the file
if "-default" in file_inner_path:
file_path_dest = new_site.storage.getPath(file_inner_path.replace("-default", ""))
if new_site.storage.isFile(file_path_dest) and not overwrite: # Don't overwrite site files with default ones
self.log.debug("[SKIP] Default file: %s (already exits)" % file_inner_path)
continue
self.log.debug("[COPY] Default file: %s to %s..." % (file_inner_path, file_path_dest))
dest_dir = os.path.dirname(file_path_dest)
if not os.path.isdir(dest_dir): os.makedirs(dest_dir)
shutil.copy(file_path, file_path_dest)
# Sign if content json
if file_path_dest.endswith("/content.json"):
new_site.storage.onUpdated(file_inner_path.replace("-default", ""))
new_site.content_manager.loadContent(file_inner_path.replace("-default", ""), add_bad_files=False, load_includes=False)
if privatekey: new_site.content_manager.sign(file_inner_path.replace("-default", ""), privatekey)
if privatekey: new_site.content_manager.sign("content.json", privatekey)
# Rebuild DB
if new_site.storage.isFile("dbschema.json"): new_site.storage.rebuildDb()
return new_site
# Check and download if file not exits
def needFile(self, inner_path, update=False, blocking=True, peer=None, priority=0):
if self.storage.isFile(inner_path) and not update: # File exits, no need to do anything

View File

@ -12,12 +12,13 @@ TRACKERS = [
#("udp", "www.eddie4.nl", 6969),
#("udp", "trackr.sytes.net", 80),
#("udp", "tracker4.piratux.com", 6969)
("http", "exodus.desync.com:80/announce", None),
#("http", "exodus.desync.com:80/announce", None), Off
("http", "tracker.aletorrenty.pl:2710/announce", None),
#("http", "torrent.gresille.org/announce", None), # Slow
#("http", "announce.torrentsmd.com:6969/announce", None), # Off
#("http", "i.bandito.org/announce", None), # Off
("http", "retracker.telecom.kz/announce", None)
("http", "retracker.telecom.kz/announce", None),
("http", "torrent.gresille.org/announce", None),
]

View File

@ -22,8 +22,6 @@ class SiteStorage:
raise Exception("Directory not exists: %s" % self.directory)
# Load db from dbschema.json
def openDb(self, check=True):
schema = self.loadJson("dbschema.json")
@ -55,6 +53,8 @@ class SiteStorage:
# Rebuild sql cache
def rebuildDb(self, delete_db=True):
self.has_db = self.isFile("dbschema.json")
if not self.has_db: return False
self.event_db_busy = gevent.event.AsyncResult()
schema = self.loadJson("dbschema.json")
db_path = self.getPath(schema["db_file"])
@ -111,9 +111,6 @@ class SiteStorage:
raise err
return res
# Open file object
def open(self, inner_path, mode="rb"):

141
src/Test/BenchmarkSsl.py Normal file
View File

@ -0,0 +1,141 @@
#!/usr/bin/python2
from gevent import monkey; monkey.patch_all()
import os, time, sys, socket, ssl
sys.path.append(os.path.abspath("src")) # Imports relative to src dir
import cStringIO as StringIO
import gevent
from gevent.queue import Queue, Empty, JoinableQueue
from gevent.server import StreamServer
from gevent.pool import Pool
from util import SslPatch
# Server
socks = []
data = os.urandom(1024*100)
data += "\n"
def handle(sock_raw, addr):
socks.append(sock_raw)
sock = sock_raw
#sock = ctx.wrap_socket(sock, server_side=True)
#if sock_raw.recv( 1, gevent.socket.MSG_PEEK ) == "\x16":
# sock = gevent.ssl.wrap_socket(sock_raw, server_side=True, keyfile='key-cz.pem', certfile='cert-cz.pem', ciphers=ciphers, ssl_version=ssl.PROTOCOL_TLSv1)
#fp = os.fdopen(sock.fileno(), 'rb', 1024*512)
try:
while True:
line = sock.recv(16*1024)
if not line: break
if line == "bye\n":
break
elif line == "gotssl\n":
sock.sendall("yes\n")
sock = gevent.ssl.wrap_socket(sock_raw, server_side=True, keyfile='src/Test/testdata/key-rsa.pem', certfile='src/Test/testdata/cert-rsa.pem', ciphers=ciphers)
else:
sock.sendall(data)
except Exception, err:
print err
try:
sock.shutdown(gevent.socket.SHUT_WR)
sock.close()
except:
pass
socks.remove(sock_raw)
pool = Pool(1000) # do not accept more than 10000 connections
server = StreamServer(('127.0.0.1', 1234), handle) #
server.start()
# Client
total_num = 0
total_bytes = 0
clipher = None
ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDH+AES128:ECDHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES128-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK"
# ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
def getData():
global total_num, total_bytes, clipher
data = None
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#sock = socket.ssl(s)
#sock = ssl.wrap_socket(sock)
sock.connect(("127.0.0.1", 1234))
#sock.do_handshake()
#clipher = sock.cipher()
sock.send("gotssl\n")
if sock.recv(128) == "yes\n":
sock = ssl.wrap_socket(sock, ciphers=ciphers)
sock.do_handshake()
clipher = sock.cipher()
for req in range(100):
sock.sendall("req\n")
buff = StringIO.StringIO()
data = sock.recv(16*1024)
buff.write(data)
if not data:
break
while not data.endswith("\n"):
data = sock.recv(16*1024)
if not data: break
buff.write(data)
total_num += 1
total_bytes += buff.tell()
if not data:
print "No data"
sock.shutdown(gevent.socket.SHUT_WR)
sock.close()
s = time.time()
def info():
import psutil, os
process = psutil.Process(os.getpid())
while 1:
print total_num, "req", (total_bytes/1024), "kbytes", "transfered in", time.time()-s, "using", clipher, "Mem:", process.get_memory_info()[0] / float(2 ** 20)
time.sleep(1)
gevent.spawn(info)
for test in range(10):
clients = []
for i in range(10): # Thread
clients.append(gevent.spawn(getData))
gevent.joinall(clients)
print total_num, "req", (total_bytes/1024), "kbytes", "transfered in", time.time()-s
# Separate client/server process:
# 10*10*100:
# Raw: 10000 req 1000009 kbytes transfered in 5.39999985695
# RSA 2048: 10000 req 1000009 kbytes transfered in 27.7890000343 using ('ECDHE-RSA-AES256-SHA', 'TLSv1/SSLv3', 256)
# ECC: 10000 req 1000009 kbytes transfered in 26.1959998608 using ('ECDHE-ECDSA-AES256-SHA', 'TLSv1/SSLv3', 256)
# ECC: 10000 req 1000009 kbytes transfered in 28.2410001755 using ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 13.3828125
#
# 10*100*10:
# Raw: 10000 req 1000009 kbytes transfered in 7.02700018883 Mem: 14.328125
# RSA 2048: 10000 req 1000009 kbytes transfered in 44.8860001564 using ('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 20.078125
# ECC: 10000 req 1000009 kbytes transfered in 37.9430000782 using ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 20.0234375
#
# 1*100*100:
# Raw: 10000 req 1000009 kbytes transfered in 4.64400005341 Mem: 14.06640625
# RSA: 10000 req 1000009 kbytes transfered in 24.2300000191 using ('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 19.7734375
# ECC: 10000 req 1000009 kbytes transfered in 22.8849999905 using ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 17.8125
# AES128: 10000 req 1000009 kbytes transfered in 21.2839999199 using ('AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 14.1328125
# ECC+128: 10000 req 1000009 kbytes transfered in 20.496999979 using ('ECDHE-ECDSA-AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 14.40234375
#
#
# Single process:
# 1*100*100
# RSA: 10000 req 1000009 kbytes transfered in 41.7899999619 using ('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 26.91015625
#
# 10*10*100
# RSA: 10000 req 1000009 kbytes transfered in 40.1640000343 using ('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 14.94921875

View File

@ -1,6 +1,9 @@
import sys, os, unittest, urllib, time
sys.path.append(os.path.abspath("src")) # Imports relative to src dir
from Config import config
config.data_dir = "src/Test/testdata" # Use test data for unittests
from Crypt import CryptBitcoin
from Ui import UiRequest
@ -301,11 +304,91 @@ class TestCase(unittest.TestCase):
self.assertFalse(site.content_manager.verifyFile("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", StringIO(json.dumps(signed_content)), ignore_same=False))
def testClone(self):
from Site import Site
from Site import SiteManager
from User import UserManager
import shutil
site = Site("1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT") # Privatekey: 5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv
self.assertEqual(site.storage.directory, "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT")
# Remove old files
if os.path.isdir("src/Test/testdata/159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL"):
shutil.rmtree("src/Test/testdata/159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL")
self.assertFalse(os.path.isfile("src/Test/testdata/159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL/content.json"))
# Clone 1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT to 15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc
new_site = site.clone("159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL", "5JU2p5h3R7B1WrbaEdEDNZR7YHqRLGcjNcqwqVQzX2H4SuNe2ee", address_index=1) # Privatekey: 5JU2p5h3R7B1WrbaEdEDNZR7YHqRLGcjNcqwqVQzX2H4SuNe2ee
# Check if clone was successful
self.assertEqual(new_site.address, "159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL")
self.assertTrue(new_site.storage.isFile("content.json"))
self.assertTrue(new_site.storage.isFile("index.html"))
self.assertTrue(new_site.storage.isFile("data/users/content.json"))
self.assertTrue(new_site.storage.isFile("data/zeroblog.db"))
# Test re-cloning (updating)
# Changes in non-data files should be overwritten
new_site.storage.write("index.html", "this will be overwritten")
self.assertEqual(new_site.storage.read("index.html"), "this will be overwritten")
# Changes in data file should be kept after re-cloning
changed_contentjson = new_site.storage.loadJson("content.json")
changed_contentjson["description"] = "Update Description Test"
new_site.storage.writeJson("content.json", changed_contentjson)
changed_data = new_site.storage.loadJson("data/data.json")
changed_data["title"] = "UpdateTest"
new_site.storage.writeJson("data/data.json", changed_data)
# Re-clone the site
site.clone("159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL")
self.assertEqual(new_site.storage.loadJson("data/data.json")["title"], "UpdateTest")
self.assertEqual(new_site.storage.loadJson("content.json")["description"], "Update Description Test")
self.assertNotEqual(new_site.storage.read("index.html"), "this will be overwritten")
def testUserNewsite(self):
from User import UserManager
user = UserManager.user_manager.get()
user.sites = {} # Reset user data
self.assertEqual(user.master_address, "15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc")
self.assertEqual(user.getAddressAuthIndex("15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc"), 1458664252141532163166741013621928587528255888800826689784628722366466547364755811L)
address, address_index, site_data = user.getNewSiteData()
self.assertEqual(CryptBitcoin.hdPrivatekey(user.master_seed, address_index), site_data["privatekey"]) # Re-generate privatekey based on address_index
user.sites = {} # Reset user data
self.assertNotEqual(user.getSiteData(address)["auth_address"], address) # Site address and auth address is different
self.assertEqual(user.getSiteData(address)["auth_privatekey"], site_data["auth_privatekey"]) # Re-generate auth_privatekey for site
def testSslCert(self):
from Crypt import CryptConnection
# Remove old certs
if os.path.isfile("%s/cert-rsa.pem" % config.data_dir): os.unlink("%s/cert-rsa.pem" % config.data_dir)
if os.path.isfile("%s/key-rsa.pem" % config.data_dir): os.unlink("%s/key-rsa.pem" % config.data_dir)
CryptConnection.manager.loadCerts()
self.assertIn("tls-rsa", CryptConnection.manager.crypt_supported)
self.assertEqual(CryptConnection.manager.selectCrypt(["tls-rsa", "unknown"]), "tls-rsa")
self.assertTrue(os.path.isfile("%s/cert-rsa.pem" % config.data_dir))
self.assertTrue(os.path.isfile("%s/key-rsa.pem" % config.data_dir))
if __name__ == "__main__":
import logging
logging.getLogger().setLevel(level=logging.FATAL)
unittest.main(verbosity=2)
#unittest.main(verbosity=2, defaultTest="TestCase.testUserContentCert")
logging.getLogger().setLevel(level=logging.DEBUG)
#unittest.main(verbosity=2)
unittest.main(verbosity=2, defaultTest="TestCase.testClone")

View File

@ -0,0 +1,123 @@
{
"address": "1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8",
"background-color": "white",
"description": "Blogging platform Demo",
"domain": "Blog.ZeroNetwork.bit",
"files": {
"css/all.css": {
"sha512": "65ddd3a2071a0f48c34783aa3b1bde4424bdea344630af05a237557a62bd55dc",
"size": 112710
},
"data-default/data.json": {
"sha512": "3f5c5a220bde41b464ab116cce0bd670dd0b4ff5fe4a73d1dffc4719140038f2",
"size": 196
},
"data-default/users/content-default.json": {
"sha512": "0603ce08f7abb92b3840ad0cf40e95ea0b3ed3511b31524d4d70e88adba83daa",
"size": 679
},
"data/data.json": {
"sha512": "0f2321c905b761a05c360a389e1de149d952b16097c4ccf8310158356e85fb52",
"size": 31126
},
"data/img/autoupdate.png": {
"sha512": "d2b4dc8e0da2861ea051c0c13490a4eccf8933d77383a5b43de447c49d816e71",
"size": 24460
},
"data/img/direct_domains.png": {
"sha512": "5f14b30c1852735ab329b22496b1e2ea751cb04704789443ad73a70587c59719",
"size": 16185
},
"data/img/domain.png": {
"sha512": "ce87e0831f4d1e95a95d7120ca4d33f8273c6fce9f5bbedf7209396ea0b57b6a",
"size": 11881
},
"data/img/memory.png": {
"sha512": "dd56515085b4a79b5809716f76f267ec3a204be3ee0d215591a77bf0f390fa4e",
"size": 12775
},
"data/img/multiuser.png": {
"sha512": "88e3f795f9b86583640867897de6efc14e1aa42f93e848ed1645213e6cc210c6",
"size": 29480
},
"data/img/progressbar.png": {
"sha512": "23d592ae386ce14158cec34d32a3556771725e331c14d5a4905c59e0fe980ebf",
"size": 13294
},
"data/img/slides.png": {
"sha512": "1933db3b90ab93465befa1bd0843babe38173975e306286e08151be9992f767e",
"size": 14439
},
"data/img/slots_memory.png": {
"sha512": "82a250e6da909d7f66341e5b5c443353958f86728cd3f06e988b6441e6847c29",
"size": 9488
},
"data/img/trayicon.png": {
"sha512": "e7ae65bf280f13fb7175c1293dad7d18f1fcb186ebc9e1e33850cdaccb897b8f",
"size": 19040
},
"data/img/zeroblog-comments.png": {
"sha512": "efe4e815a260e555303e5c49e550a689d27a8361f64667bd4a91dbcccb83d2b4",
"size": 24001
},
"data/img/zeroid.png": {
"sha512": "b46d541a9e51ba2ddc8a49955b7debbc3b45fd13467d3c20ef104e9d938d052b",
"size": 18875
},
"data/img/zeroname.png": {
"sha512": "bab45a1bb2087b64e4f69f756b2ffa5ad39b7fdc48c83609cdde44028a7a155d",
"size": 36031
},
"data/img/zerotalk-mark.png": {
"sha512": "a335b2fedeb8d291ca68d3091f567c180628e80f41de4331a5feb19601d078af",
"size": 44862
},
"data/img/zerotalk-upvote.png": {
"sha512": "b1ffd7f948b4f99248dde7efe256c2efdfd997f7e876fb9734f986ef2b561732",
"size": 41092
},
"data/img/zerotalk.png": {
"sha512": "54d10497a1ffca9a4780092fd1bd158c15f639856d654d2eb33a42f9d8e33cd8",
"size": 26606
},
"dbschema.json": {
"sha512": "7b756e8e475d4d6b345a24e2ae14254f5c6f4aa67391a94491a026550fe00df8",
"size": 1529
},
"img/loading.gif": {
"sha512": "8a42b98962faea74618113166886be488c09dad10ca47fe97005edc5fb40cc00",
"size": 723
},
"img/post/slides.png": {
"sha512": "1933db3b90ab93465befa1bd0843babe38173975e306286e08151be9992f767e",
"size": 14439
},
"index.html": {
"sha512": "4989c09b0f8ebcb84200bf39ac387d0cb8387846f44c993c553f5638472dc9c6",
"size": 4665
},
"js/all.js": {
"sha512": "034c97535f3c9b3fbebf2dcf61a38711dae762acf1a99168ae7ddc7e265f582c",
"size": 201178
}
},
"ignore": "((js|css)/(?!all.(js|css))|data/.*db|data/users/.*/.*)",
"includes": {
"data/users/content.json": {
"signers": [],
"signers_required": 1
}
},
"modified": 1433033839.187,
"sign": [
23288026089939741768236374425021091801956985530578920099613839712827977534400,
96520925254285126876579021588512602074631663615139311676453830936350975122022
],
"signers_sign": "G7W/oNvczE5nPTFYVOqv8+GOpQd23LS/Dc1Q6xQ1NRDDHlYzmoSE63UQ7Za05kD0rwIYXbuUSr8z8p6RhZmnUs8=",
"signs": {
"1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8": "G9i0Q/ZkhZ0H5O8ofEvXbEgGhwVJYbLxyxOwsGYxgtzIaBjiDg/HKe9l7nPRqDD3/bRG9oPH5kIQd4152vY3lI8="
},
"signs_required": 1,
"title": "ZeroBlog",
"zeronet_version": "0.3.0"
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
{
"title": "MyZeroBlog",
"description": "My ZeroBlog.",
"links": "- [Source code](https://github.com/HelloZeroNet)",
"next_post_id": 1,
"demo": false,
"modified": 1432515193,
"post": [
]
}

View File

@ -0,0 +1,25 @@
{
"files": {},
"ignore": ".*",
"modified": 1432466966.003,
"signs": {
"1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8": "HChU28lG4MCnAiui6wDAaVCD4QUrgSy4zZ67+MMHidcUJRkLGnO3j4Eb1N0AWQ86nhSBwoOQf08Rha7gRyTDlAk="
},
"user_contents": {
"cert_signers": {
"zeroid.bit": [ "1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz" ]
},
"permission_rules": {
".*": {
"files_allowed": "data.json",
"max_size": 10000
},
"bitid/.*@zeroid.bit": { "max_size": 40000 },
"bitmsg/.*@zeroid.bit": { "max_size": 15000 }
},
"permissions": {
"banexample@zeroid.bit": false,
"nofish@zeroid.bit": { "max_size": 20000 }
}
}
}

View File

@ -0,0 +1,244 @@
{
"title": "ZeroBlog",
"description": "Demo for decentralized, self publishing blogging platform.",
"links": "- [Source code](https://github.com/HelloZeroNet)\n- [Create new blog](?Post:3:How+to+have+a+blog+like+this)",
"next_post_id": 42,
"demo": false,
"modified": 1433033806,
"post": [
{
"post_id": 41,
"title": "Changelog: May 31, 2015",
"date_published": 1433033779.604,
"body": " - rev194\n - Ugly OpenSSL memory leak fix\n - Added Docker and Vargant files (thanks to n3r0-ch)\n\nZeroBlog\n - Comment editing, Deleting, Replying added\n\nNew official site: http://zeronet.io/"
},
{
"post_id": 40,
"title": "Trusted authorization providers",
"date_published": 1432549828.319,
"body": "What is it good for?\n\n - It allows you to have multi-user sites without need of a bot that listen to new user registration requests.\n - You can use the same username across sites\n - The site owner can give you (or revoke) permissions based on your ZeroID username\n\nHow does it works?\n\n - You visit an authorization provider site (eg zeroid.bit)\n - You enter the username you want to register and sent the request to the authorization provider site owner (zeroid supports bitmessage and simple http request).\n - The authorization provider process your request and it he finds everything all right (unique username, other anti-spam methods) he sends you a certificate for the username registration.\n - If a site trust your authorization provider you can post your own content (comments, topics, upvotes, etc.) using this certificate without ever contacting the site owner.\n\nWhat sites currently supports ZeroID?\n\n - You can post comments to ZeroBlog using your ZeroID\n - Later, if everyone is updated to 0.3.0 a new ZeroTalk is also planned that supports ZeroID certificates\n\nWhy is it necessary?\n\n - To have some kind of control over the users of your site. (eg. remove misbehaving users)\n\nOther info\n\n - ZeroID is a standard site, anyone can clone it and have his/her own one\n - You can stop seeding ZeroID site after you got your cert"
},
{
"post_id": 39,
"title": "Changelog: May 25, 2015",
"date_published": 1432511642.167,
"body": "- Version 0.3.0, rev187\n- Trusted authorization provider support: Easier multi-user sites by allowing site owners to define tusted third-party user certificate signers. (more info about it in the next days)\n- `--publish` option to siteSign to publish automatically after the new files signed.\n- `cryptSign` command line command to sign message using private key.\n- New, more stable OpenSSL layer that also works on OSX.\n- New json table format support.\n- DbCursor SELECT parameters bugfix.\n- Faster multi-threaded peer discovery from trackers.\n- New http trackers added.\n- Wait for dbschema.json file to execute query.\n- Handle json import errors.\n- More compact json writeJson storage command output.\n- Workaround to make non target=_top links work.\n- Cleaner UiWebsocket command router.\n- Notify other local users on local file changes.\n- Option to wait file download before execute query.\n- fileRules, certAdd, certSelect, certSet websocket API commands.\n- Allow more file errors on big sites.\n- On stucked downloads skip worker's current file instead of stopping it.\n- NoParallel parameter bugfix.\n- RateLimit interval bugfix.\n- Updater skips non-writeable files.\n- Try to close OpenSSL dll before update.\n\nZeroBlog:\n- Rewritten to use SQL database\n- Commenting on posts (**Please note: The comment publishing and distribution can be slow until most of the clients is not updated to version 0.3.0**)\n\n![comments](data/img/zeroblog-comments.png)\n\nZeroID\n- Sample Trusted authorization provider site with Bitmessage registration support\n\n![comments](data/img/zeroid.png)"
},
{
"post_id": 38,
"title": "Status report: Trusted authorization providers",
"date_published": 1431286381.226,
"body": "Currently working on a new feature that allows to create multi-user sites more easily. For example it will allows us to have comments on ZeroBlog (without contacting the site owner).\n\nCurrent status:\n\n - Sign/verification process: 90%\n - Sample trusted authorization provider site: 70%\n - ZeroBlog modifications: 30%\n - Authorization UI enhacements: 10%\n - Total progress: 60%\n \nEta.: 1-2weeks\n\n### Update: May 18, 2015:\n\nThings left:\n - More ZeroBlog modifications on commenting interface\n - Bitmessage support in Sample trusted authorization provider site\n - Test everything on multiple platform/browser and machine\n - Total progress: 80%\n\nIf no major flaw discovered it should be out this week."
},
{
"post_id": 37,
"title": "Changelog: May 3, 2015",
"date_published": 1430652299.794,
"body": " - rev134\n - Removed ZeroMQ dependencies and support (if you are on pre 0.2.0 version please, upgrade)\n - Save CPU and memory on file requests by streaming content directly to socket without loading to memory and encoding with msgpack.\n - Sites updates without re-download all content.json by querying the modified files from peers.\n - Fix urllib memory leak\n - SiteManager testsuite\n - Fix UiServer security testsuite\n - Announce to tracker on site resume\n\nZeroBoard:\n\n - Only last 100 messages loaded by default\n - Typo fix"
},
{
"post_id": 36,
"title": "Changelog: Apr 29, 2015",
"date_published": 1430388168.315,
"body": " - rev126\n - You can install the \"127.0.0.1:43110-less\" extension from [Chrome Web Store](https://chrome.google.com/webstore/detail/zeronet-protocol/cpkpdcdljfbnepgfejplkhdnopniieop). (thanks to g0ld3nrati0!)\n - You can disable the use of openssl using `--use_openssl False`\n - OpenSSL disabled on OSX because of possible segfault. You can enable it again using `zeronet.py --use_openssl True`,<br> please [give your feedback](https://github.com/HelloZeroNet/ZeroNet/issues/94)!\n - Update on non existent file bugfix\n - Save 20% memory using Python slots\n\n![Memory save](data/img/slots_memory.png)"
},
{
"post_id": 35,
"title": "Changelog: Apr 27, 2015",
"date_published": 1430180561.716,
"body": " - Revision 122\n - 40x faster signature verification by using OpenSSL if available\n - Added OpenSSL benchmark: beat my CPU at http://127.0.0.1:43110/Benchmark :)\n - Fixed UiServer socket memory leak"
},
{
"post_id": 34,
"title": "Slides about ZeroNet",
"date_published": 1430081791.43,
"body": "Topics:\n - ZeroNet cryptography\n - How site downloading works\n - Site updates\n - Multi-user sites\n - Current status of the project / Future plans\n\n<a href=\"https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000&slide=id.g9a1cce9ee_0_4\"><img src=\"data/img/slides.png\"/></a>\n\n[Any feedback is welcome!](http://127.0.0.1:43110/Talk.ZeroNetwork.bit/?Topic:18@2/Presentation+about+how+ZeroNet+works) \n\nThanks! :)"
},
{
"post_id": 33,
"title": "Changelog: Apr 24, 2014",
"date_published": 1429873756.187,
"body": " - Revision 120\n - Batched publishing to avoid update flood: Only send one update in every 7 seconds\n - Protection against update flood by adding update queue: Only allows 1 update in every 10 second for the same file\n - Fix stucked notification icon\n - Fix websocket error when writing to not-owned sites"
},
{
"post_id": 32,
"title": "Changelog: Apr 20, 2014",
"date_published": 1429572874,
"body": " - Revision 115\n - For faster pageload times allow browser cache on css/js/font files\n - Support for experimental chrome extension that allows to browse zeronet sites using `http://talk.zeronetwork.bit` and/or `http://zero/1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F`\n - Allow to browse memory content in /Stats\n - Peers uses Site's logger to save some memory\n - Give not-that-good peers on initial PEX if required\n - Allows more than one `--ui_restrict` ip address\n - Disable ssl monkey patching to avoid ssl error in Debian Jessie\n - Fixed websocket error when writing not-allowed files\n - Fixed bigsite file not found error\n - Fixed loading screen stays on screen even after index.html loaded\n\nZeroHello:\n\n - Site links converted to 127.0.0.1:43110 -less if using chrome extension\n\n![direct domains](data/img/direct_domains.png)"
},
{
"post_id": 31,
"title": "Changelog: Apr 17, 2014",
"date_published": 1429319617.201,
"body": " - Revision 101\n - Revision numbering between version\n - Allow passive publishing\n - Start Zeronet when Windows starts option to system tray icon\n - Add peer ping time to publish timeout\n - Passive connected peers always get the updates\n - Pex count bugfix\n - Changed the topright button hamburger utf8 character to more supported one and removed click anim\n - Passive peers only need 3 connection\n - Passive connection store on tracker bugfix\n - Not exits file bugfix\n - You can compare your computer speed (bitcoin crypto, sha512, sqlite access) to mine: http://127.0.0.1:43110/Benchmark :)\n\nZeroTalk:\n\n - Only quote the last message\n - Message height bugfix\n\nZeroHello:\n\n - Changed the burger icon to more supported one\n - Added revision display"
},
{
"post_id": 30,
"title": "Changelog: Apr 16, 2015",
"date_published": 1429135541.581,
"body": "Apr 15:\n\n - Version 0.2.9\n - To get rid of dead ips only send peers over pex that messaged within 2 hour\n - Only ask peers from 2 sources using pex every 20 min\n - Fixed mysterious notification icon disappearings\n - Mark peers as bad if publish is timed out (5s+)"
},
{
"post_id": 29,
"title": "Changelog: Apr 15, 2015",
"date_published": 1429060414.445,
"body": " - Sexy system tray icon with statistics instead of ugly console. (sorry, Windows only yet)\n - Total sent/received bytes stats\n - Faster connections and publishing by don't send passive peers using PEX and don't store them on trackers\n\n![Tray icon](data/img/trayicon.png)"
},
{
"post_id": 28,
"title": "Changelog: Apr 14, 2015",
"date_published": 1428973199.042,
"body": " - Experimental socks proxy support (Tested using Tor)\n - Tracker-less peer exchange between peers\n - Http bittorrent tracker support\n - Option to disable udp connections (udp tracker)\n - Other stability/security fixes\n\nTo use ZeroNet over Tor network start it with `zeronet.py --proxy 127.0.0.1:9050 --disable_udp`\n\nIt's still an experimental feature, there is lot work/fine tuning needed to make it work better and more secure (eg. by supporting hidden service peer addresses to allow connection between Tor clients). \nIn this mode you can only access to sites where there is at least one peer with peer exchange support. (client updated to latest commit)\n\nIf no more bug found i'm going to tag it as 0.2.9 in the next days."
},
{
"post_id": 27,
"title": "Changelog: Apr 9, 2015",
"date_published": 1428626164.266,
"body": " - Packaged windows dependencies for windows to make it easier to install: [ZeroBundle](https://github.com/HelloZeroNet/ZeroBundle)\n - ZeroName site downloaded at startup, so first .bit domain access is faster.\n - Fixed updater bug. (argh)"
},
{
"post_id": 26,
"title": "Changelog: Apr 7, 2015",
"date_published": 1428454413.286,
"body": " - Fix for big sites confirmation display\n - Total objects in memory stat\n - Memory optimizations\n - Retry bad files in every 20min\n - Load files to db when executing external siteSign command\n - Fix for endless reconnect bug\n \nZeroTalk:\n \n - Added experimental P2P new bot\n - Bumped size limit to 20k for every user :)\n - Reply button\n\nExperimenting/researching possibilities of i2p/tor support (probably using DHT)\n\nAny help/suggestion/idea greatly welcomed: [github issue](https://github.com/HelloZeroNet/ZeroNet/issues/60)"
},
{
"post_id": 25,
"title": "Changelog: Apr 2, 2015",
"date_published": 1428022346.555,
"body": " - Better passive mode by making sure to keep 5 active connections\n - Site connection and msgpack unpacker stats\n - No more sha1 hash added to content.json (it was only for backward compatibility with old clients)\n - Keep connection logger object to prevent some exception\n - Retry upnp port opening 3 times\n - Publish received content updates to more peers to make sure the better distribution\n\nZeroTalk: \n\n - Changed edit icon to more clear pencil\n - Single line breaks also breaks the line"
},
{
"post_id": 24,
"title": "Changelog: Mar 29, 2015",
"date_published": 1427758356.109,
"body": " - Version 0.2.8\n - Namecoin (.bit) domain support!\n - Possible to disable backward compatibility with old version to save some memory\n - Faster content publishing (commenting, posting etc.)\n - Display error on internal server errors\n - Better progress bar\n - Crash and bugfixes\n - Removed coppersurfer tracker (its down atm), added eddie4\n - Sorry, the auto updater broken for this version: please overwrite your current `update.py` file with the [latest one from github](https://raw.githubusercontent.com/HelloZeroNet/ZeroNet/master/update.py), run it and restart ZeroNet.\n - Fixed updater\n\n![domain](data/img/domain.png)\n\nZeroName\n\n - New site for resolving namecoin domains and display registered ones\n\n![ZeroName](data/img/zeroname.png)\nZeroHello\n\n - Automatically links to site's domain names if its specificed in content.json `domain` field\n\n"
},
{
"post_id": 22,
"title": "Changelog: Mar 23, 2015",
"date_published": 1427159576.994,
"body": " - Version 0.2.7\n - Plugin system: Allows extend ZeroNet without modify the core source\n - Comes with 3 plugin:\n - Multiuser: User login/logout based on BIP32 master seed, generate new master seed on visit (disabled by default to enable it just remove the disabled- from the directory name)\n - Stats: /Stats url moved to separate plugin for demonstration reasons\n - DonationMessage: Puts a little donation link to the bottom of every page (disabled by default)\n - Reworked module import system\n - Lazy user auth_address generatation\n - Allow to send prompt dialog to user from server-side\n - Update script remembers plugins enabled/disabled status\n - Multiline notifications\n - Cookie parser\n\nZeroHello in multiuser mode:\n\n - Logout button\n - Identicon generated based on logined user xpub address\n\n![Multiuser](data/img/multiuser.png)"
},
{
"post_id": 21,
"title": "Changelog: Mar 19, 2015",
"date_published": 1426818095.915,
"body": " - Version 0.2.6\n - SQL database support that allows easier site development and faster page load times\n - Updated [ZeroFrame API Reference](http://zeronet.readthedocs.org/en/latest/site_development/zeroframe_api_reference/)\n - Added description of new [dbschema.json](http://zeronet.readthedocs.org/en/latest/site_development/dbschema_json/) file\n - SiteStorage class for file operations\n - Incoming connection firstchar errorfix\n - dbRebuild and dbQuery commandline actions\n - [Goals donation page](http://zeronet.readthedocs.org/en/latest/zeronet_development/donate/)\n\nZeroTalk\n\n - Rewritten to use SQL queries (falls back nicely to use json files on older version)"
},
{
"post_id": 20,
"title": "Changelog: Mar 14, 2015",
"date_published": 1426386779.836,
"body": "\n - Save significant amount of memory by remove unused msgpack unpackers\n - Log unhandled exceptions\n - Connection checker error bugfix\n - Working on database support, you can follow the progress on [reddit](http://www.reddit.com/r/zeronet/comments/2yq7e8/a_json_caching_layer_for_quicker_development_and/)\n\n![memory usage](data/img/memory.png)"
},
{
"post_id": 19,
"title": "Changelog: Mar 10, 2015",
"date_published": 1426041044.008,
"body": " - Fixed ZeroBoard and ZeroTalk registration: It was down last days, sorry, I haven't tested it after recent modifications, but I promise I will from now :)\n - Working hard on documentations, after trying some possibilities, I chosen readthedocs.org: http://zeronet.readthedocs.org\n - The API reference is now up-to-date, documented demo sites working method and also updated other parts\n\n[Please, tell me what you want to see in the docs, Thanks!](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Topic:14@2/New+ZeroNet+documentation)"
},
{
"post_id": 18,
"title": "Changelog: Mar 8, 2015",
"date_published": 1425865493.306,
"body": " - [Better uPnp Puncher](https://github.com/HelloZeroNet/ZeroNet/blob/master/src/util/UpnpPunch.py), if you have problems with port opening please try this.\n\nZeroTalk: \n - Comment upvoting\n - Topic groups, if you know any other article about ZeroNet please, post [here](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Topics:8@2/Articles+about+ZeroNet)"
},
{
"post_id": 17,
"title": "Changelog: Mar 5, 2015",
"date_published": 1425606285.111,
"body": " - Connection pinging and timeout\n - Request timeout\n - Verify content at signing (size, allowed files)\n - Smarter coffeescript recompile\n - More detailed stats\n\nZeroTalk: \n - Topic upvote\n - Even more source code realign\n\n![ZeroTalk upvote](data/img/zerotalk-upvote.png)"
},
{
"post_id": 16,
"title": "Changelog: Mar 1, 2015",
"date_published": 1425259087.503,
"body": "ZeroTalk: \n - Reordered source code to allow more more feature in the future\n - Links starting with http://127.0.0.1:43110/ automatically converted to relative links (proxy support)\n - Comment reply (by clicking on comment's creation date)"
},
{
"post_id": 15,
"title": "Changelog: Feb 25, 2015",
"date_published": 1424913197.035,
"body": " - Version 0.2.5\n - Pure-python upnp port opener (Thanks to sirMackk!)\n - Site download progress bar\n - We are also on [Gitter chat](https://gitter.im/HelloZeroNet/ZeroNet)\n - More detailed connection statistics (ping, buff, idle, delay, sent, received)\n - First char failed bugfix\n - Webebsocket disconnect on slow connection bugfix\n - Faster site update\n\n![Progressbar](data/img/progressbar.png)\n\nZeroTalk: \n\n - Sort after 100ms idle\n - Colored usernames\n - Limit reload rate to 500ms\n\nZeroHello\n\n - [iframe render fps test](/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/render.html) ([more details on ZeroTalk](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Topic:7@2/Slow+rendering+in+Chrome))\n"
},
{
"post_id": 14,
"title": "Changelog: Feb 24, 2015",
"date_published": 1424734437.473,
"body": " - Version 0.2.4\n - New, experimental network code and protocol\n - peerPing and peerGetFile commands\n - Connection share and reuse between sites\n - Don't retry bad file more than 3 times in 20 min\n - Multi-threaded include file download\n - Really shuffle peers before publish\n - Simple internal stats page: http://127.0.0.1:43110/Stats\n - Publish bugfix for sites with more then 10 peers\n\n_If someone on very limited resources its recommended to wait some time until most of the peers is updates to new network code, because the backward compatibility is a little bit tricky and using more memory._"
},
{
"post_id": 13,
"title": "Changelog: Feb 19, 2015",
"date_published": 1424394659.345,
"body": " - Version 0.2.3\n - One click source code download from github, auto unpack and restart \n - Randomize peers before publish and work start\n - Switched to upnpc-shared.exe it has better virustotal reputation (4/53 vs 19/57)\n\n![Autoupdate](data/img/autoupdate.png)\n\nZeroTalk:\n\n - Topics also sorted by topic creation date\n\n_New content and file changes propagation is a bit broken yet. Already working on better network code that also allows passive content publishing. It will be out in 1-2 weeks._"
},
{
"post_id": 12,
"title": "Changelog: Feb 16, 2015",
"date_published": 1424134864.167,
"body": "Feb 16: \n - Version 0.2.2\n - LocalStorage support using WrapperAPI\n - Bugfix in user management\n\nZeroTalk: \n - Topics ordered by date of last post\n - Mark updated topics since your last visit\n\n![Mark](data/img/zerotalk-mark.png)"
},
{
"post_id": 11,
"title": "Changelog: Feb 14, 2015",
"date_published": 1423922572.778,
"body": " - Version 0.2.1\n - Site size limit: Default 10MB, asks permission to store more, test it here: [ZeroNet windows requirement](/1ZeroPYmW4BGwmT6Z54jwPgTWpbKXtTra)\n - Browser open wait until UiServer started\n - Peer numbers stored in sites.json for faster warmup\n - Silent WSGIHandler error\n - siteSetLimit WrapperAPI command\n - Grand ADMIN permission to wrapperframe\n\nZeroHello: \n\n - Site modify time also include sub-file changes (ZeroTalk last comment)\n - Better changetime date format"
},
{
"post_id": 10,
"title": "Changelog: Feb 11, 2015",
"date_published": 1423701015.643,
"body": "ZeroTalk:\n - Link-type posts\n - You can Edit or Delete your previous Comments and Topics\n - [Uploaded source code to github](https://github.com/HelloZeroNet/ZeroTalk)"
},
{
"post_id": 9,
"title": "Changelog: Feb 10, 2015",
"date_published": 1423532194.094,
"body": " - Progressive publish timeout based on file size\n - Better tracker error log\n - Viewport support in content.json and ZeroFrame API to allow better mobile device layout\n - Escape ZeroFrame notification messages to avoid js injection\n - Allow select all data in QueryJson\n\nZeroTalk:\n - Display topic's comment number and last comment time (requires ZeroNet today's commits from github)\n - Mobile device optimized layout"
},
{
"post_id": 8,
"title": "Changelog: Feb 9, 2015",
"date_published": 1423522387.728,
"body": " - Version 0.2.0\n - New bitcoin ECC lib (pybitcointools)\n - Hide notify errors\n - Include support for content.json\n - File permissions (signer address, filesize, allowed filenames)\n - Multisig ready, new, Bitcoincore compatible sign format\n - Faster, multi threaded content publishing\n - Multiuser, ready, BIP32 based site auth using bitcoin address/privatekey\n - Simple json file query language\n - Websocket api fileGet support\n\nZeroTalk: \n - [Decentralized forum demo](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Home)\n - Permission request/username registration\n - Everyone has an own file that he able to modify, sign and publish decentralized way, without contacting the site owner\n - Topic creation\n - Per topic commenting\n\n![ZeroTalk screenshot](data/img/zerotalk.png)"
},
{
"post_id": 7,
"title": "Changelog: Jan 29, 2015",
"date_published": 1422664081.662,
"body": "The default tracker (tracker.pomf.se) is down since yesterday and its resulting some warning messages. To make it disappear please update to latest version from [GitHub](https://github.com/HelloZeroNet/ZeroNet).\n\nZeroNet:\n- Added better tracker error handling\n- Updated alive [trackers list](https://github.com/HelloZeroNet/ZeroNet/blob/master/src/Site/SiteManager.py) (if anyone have more, please [let us know](http://www.reddit.com/r/zeronet/comments/2sgjsp/changelog/co5y07h))\n\nIf you want to stay updated about the project status: <br>\nWe have created a [@HelloZeronet](https://twitter.com/HelloZeroNet) Twitter account"
},
{
"post_id": 6,
"title": "Changelog: Jan 27, 2015",
"date_published": 1422394676.432,
"body": "ZeroNet\n* You can use `start.py` to start zeronet and open in browser automatically\n* Send timeout 50sec (workaround for some strange problems until we rewrite the network code without zeromq)\n* Reworked Websocket API to make it unified and allow named and unnamed parameters\n* Reload `content.json` when changed using fileWrite API command\n* Some typo fix\n\nZeroBlog\n* Allow edit post on mainpage\n* Also change blog title in `content.json` when modified using inline editor\n\nZeroHello\n* Update failed warning changed to No peers found when seeding own site."
},
{
"post_id": 4,
"title": "Changelog: Jan 25, 2015",
"date_published": 1422224700.583,
"body": "ZeroNet\n- Utf-8 site titles fixed\n- Changes in DebugMedia merger to allow faster, node.js based coffeescript compiler\n\nZeroBlog\n- Inline editor rewritten to simple textarea, so copy/paste, undo/redo now working correctly\n- Read more button to folded posts with `---`\n- ZeroBlog running in demo mode, so anyone can try the editing tools\n- Base html tag fixed\n- Markdown cheat-sheet\n- Confirmation if you want to close the browser tab while editing\n\nHow to update your running blog?\n- Backup your `content.json` and `data.json` files\n- Copy the files in the `data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8` directory to your site.\n"
},
{
"post_id": 3,
"title": "How to have a blog like this",
"date_published": 1422140400,
"body": "* Stop ZeroNet\n* Create a new site using `python zeronet.py siteCreate` command\n* Copy all file from **data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8** to **data/[Your new site address displayed when executed siteCreate]** directory\n* Delete **data** directory and rename **data-default** to **data** to get a clean, empty site\n* Rename **data/users/content-default.json** file to **data/users/content.json**\n* Execute `zeronet.py siteSign [yoursiteaddress] --inner_path data/users/content.json` to sign commenting rules\n* Start ZeroNet\n* Add/Modify content\n* Click on the `Sign & Publish new content` button\n* Congratulations! Your site is ready to access.\n\n_Note: You have to start commands with `..\\python\\python zeronet.py...` if you downloaded ZeroBundle package_"
},
{
"post_id": 2,
"title": "Changelog: Jan 24, 2015",
"date_published": 1422105774.057,
"body": "* Version 0.1.6\n* Only serve .html files with wrapper frame\n* Http parameter support in url\n* Customizable background-color for wrapper in content.json\n* New Websocket API commands (only allowed on own sites):\n - fileWrite: Modify site's files in hdd from javascript\n - sitePublish: Sign new content and Publish to peers\n* Prompt value support in ZeroFrame (used for prompting privatekey for publishing in ZeroBlog)\n\n---\n\n## Previous changes:\n\n### Jan 20, 2014\n- Version 0.1.5\n- Detect computer wakeup from sleep and acts as startup (check open port, site changes)\n- Announce interval changed from 10min to 20min\n- Delete site files command support\n- Stop unfinished downloads on pause, delete\n- Confirm dialog support to WrapperApi\n\nZeroHello\n- Site Delete menuitem\n- Browser back button doesn't jumps to top\n\n### Jan 19, 2014:\n- Version 0.1.4\n- WIF compatible new private addresses\n- Proper bitcoin address verification, vanity address support: http://127.0.0.1:43110/1ZEro9ZwiZeEveFhcnubFLiN3v7tDL4bz\n- No hash error on worker kill\n- Have you secured your private key? confirmation\n\n### Jan 18, 2014:\n- Version 0.1.3\n- content.json hashing changed from sha1 to sha512 (trimmed to 256bits) for better security, keep hasing to sha1 for backward compatiblility yet\n- Fixed fileserver_port argument parsing\n- Try to ping peer before asking any command if no communication for 20min\n- Ping timeout / retry\n- Reduce websocket bw usage\n- Separate wrapper_key for websocket auth and auth_key to identify user\n- Removed unnecessary from wrapper iframe url\n\nZeroHello:\n- Compatiblilty with 0.1.3 websocket changes while maintaining backward compatibility\n- Better error report on file update fail\n\nZeroBoard:\n- Support for sha512 hashed auth_key, but keeping md5 key support for older versions yet\n\n### Jan 17, 2014:\n- Version 0.1.2\n- Better error message logging\n- Kill workers on download done\n- Retry on socket error\n- Timestamping console messages\n\n### Jan 16:\n- Version to 0.1.1\n- Version info to websocket api\n- Add publisher's zeronet version to content.json\n- Still chasing network publish problems, added more debug info\n\nZeroHello:\n- Your and the latest ZeroNet version added to top right corner (please update if you dont see it)\n"
},
{
"post_id": 1,
"title": "ZeroBlog features",
"date_published": 1422105061,
"body": "Initial version (Jan 24, 2014):\n\n* Site avatar generated by site address\n* Distraction-free inline edit: Post title, date, body, Site title, description, links\n* Post format using [markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet)\n* Code block [syntax highlight](#code-highlight-demos) using [highlight.js](https://highlightjs.org/)\n* Create & Delete post\n* Sign & Publish from web\n* Fold blog post: Content after first `---` won't appear at listing\n* Shareable, friendly post urls\n\n\nTodo:\n\n* ~~Better content editor (contenteditable seemed like a good idea, but tricky support of copy/paste makes it more pain than gain)~~\n* Image upload to post & blog avatar\n* Paging\n* Searching\n* ~~Quick cheat-sheet using markdown~~\n\n---\n\n## Code highlight demos\n### Server-side site publishing (UiWebsocket.py):\n```py\ndef actionSitePublish(self, to, params):\n\tsite = self.site\n\tif not site.settings[\"own\"]: return self.response(to, \"Forbidden, you can only modify your own sites\")\n\n\t# Signing\n\tsite.loadContent(True) # Reload content.json, ignore errors to make it up-to-date\n\tsigned = site.signContent(params[0]) # Sign using private key sent by user\n\tif signed:\n\t\tself.cmd(\"notification\", [\"done\", \"Private key correct, site signed!\", 5000]) # Display message for 5 sec\n\telse:\n\t\tself.cmd(\"notification\", [\"error\", \"Site sign failed: invalid private key.\"])\n\t\tself.response(to, \"Site sign failed\")\n\t\treturn\n\tsite.loadContent(True) # Load new content.json, ignore errors\n\n\t# Publishing\n\tif not site.settings[\"serving\"]: # Enable site if paused\n\t\tsite.settings[\"serving\"] = True\n\t\tsite.saveSettings()\n\t\tsite.announce()\n\n\tpublished = site.publish(5) # Publish to 5 peer\n\n\tif published>0: # Successfuly published\n\t\tself.cmd(\"notification\", [\"done\", \"Site published to %s peers.\" % published, 5000])\n\t\tself.response(to, \"ok\")\n\t\tsite.updateWebsocket() # Send updated site data to local websocket clients\n\telse:\n\t\tif len(site.peers) == 0:\n\t\t\tself.cmd(\"notification\", [\"info\", \"No peers found, but your site is ready to access.\"])\n\t\t\tself.response(to, \"No peers found, but your site is ready to access.\")\n\t\telse:\n\t\t\tself.cmd(\"notification\", [\"error\", \"Site publish failed.\"])\n\t\t\tself.response(to, \"Site publish failed.\")\n```\n\n\n### Client-side site publish (ZeroBlog.coffee)\n```coffee\n# Sign and Publish site\npublish: =>\n\tif not @server_info.ip_external # No port open\n\t\t@cmd \"wrapperNotification\", [\"error\", \"To publish the site please open port <b>#{@server_info.fileserver_port}</b> on your router\"]\n\t\treturn false\n\t@cmd \"wrapperPrompt\", [\"Enter your private key:\", \"password\"], (privatekey) => # Prompt the private key\n\t\t$(\".publishbar .button\").addClass(\"loading\")\n\t\t@cmd \"sitePublish\", [privatekey], (res) =>\n\t\t\t$(\".publishbar .button\").removeClass(\"loading\")\n\t\t\t@log \"Publish result:\", res\n\n\treturn false # Ignore link default event\n```\n\n"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,15 @@
{
"cert_auth_type": "web",
"cert_sign": "G4YB7y749GI6mJboyI7cNNfyMwOS0rcVXLmgq8qmCC4TCaRqup3TGWm8hzeru7+B5iXhq19Ruz286bNVKgNbnwU=",
"cert_user_id": "newzeroid@zeroid.bit",
"files": {
"data.json": {
"sha512": "2378ef20379f1db0c3e2a803bfbfda2b68515968b7e311ccc604406168969d34",
"size": 161
}
},
"modified": 1432554679.913,
"signs": {
"1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q": "GzX/Ht6ms1dOnqB3kVENvDnxpH+mqA0Zlg3hWy0iwgxpyxWcA4zgmwxcEH41BN9RrvCaxgSd2m1SG1/8qbQPzDY="
}
}

View File

@ -0,0 +1,12 @@
{
"next_comment_id": 2,
"comment": [
{
"comment_id": 1,
"body": "Test me!",
"post_id": 40,
"date_added": 1432554679
}
],
"comment_vote": {}
}

View File

@ -0,0 +1,15 @@
{
"cert_auth_type": "web",
"cert_sign": "HBsTrjTmv+zD1iY93tSci8n9DqdEtYwzxJmRppn4/b+RYktcANGm5tXPOb+Duw3AJcgWDcGUvQVgN1D9QAwIlCw=",
"cert_user_id": "toruser@zeroid.bit",
"files": {
"data.json": {
"sha512": "4868b5e6d70a55d137db71c2e276bda80437e0235ac670962acc238071296b45",
"size": 168
}
},
"modified": 1432491109.11,
"signs": {
"1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9": "HMy7ZwwqE0Sk8O+5hTx/ejFW6KtIDbID6fGblCodUTpz4mJZ5GwApBHSVLMYL43vvGT/vKZOiQoJ5tQTeFVbbkk="
}
}

View File

@ -0,0 +1,12 @@
{
"next_comment_id": 2,
"comment": [
{
"comment_id": 1,
"body": "hello from Tor!",
"post_id": 38,
"date_added": 1432491109
}
],
"comment_vote": {}
}

View File

@ -0,0 +1,25 @@
{
"files": {},
"ignore": ".*",
"modified": 1432466966.003,
"signs": {
"1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8": "HChU28lG4MCnAiui6wDAaVCD4QUrgSy4zZ67+MMHidcUJRkLGnO3j4Eb1N0AWQ86nhSBwoOQf08Rha7gRyTDlAk="
},
"user_contents": {
"cert_signers": {
"zeroid.bit": [ "1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz" ]
},
"permission_rules": {
".*": {
"files_allowed": "data.json",
"max_size": 10000
},
"bitid/.*@zeroid.bit": { "max_size": 40000 },
"bitmsg/.*@zeroid.bit": { "max_size": 15000 }
},
"permissions": {
"bad@zeroid.bit": false,
"nofish@zeroid.bit": { "max_size": 100000 }
}
}
}

View File

@ -0,0 +1,54 @@
{
"db_name": "ZeroID",
"db_file": "data/zeroblog.db",
"version": 2,
"maps": {
"users/.+/data.json": {
"to_table": [
"comment",
{"node": "comment_vote", "table": "comment_vote", "key_col": "comment_uri", "val_col": "vote"}
]
},
"users/.+/content.json": {
"to_keyvalue": [ "cert_user_id" ]
},
"data.json": {
"to_table": [ "post" ],
"to_keyvalue": [ "title", "description", "links", "next_post_id", "demo", "modified" ]
}
},
"tables": {
"comment": {
"cols": [
["comment_id", "INTEGER"],
["post_id", "INTEGER"],
["body", "TEXT"],
["date_added", "INTEGER"],
["json_id", "INTEGER REFERENCES json (json_id)"]
],
"indexes": ["CREATE UNIQUE INDEX comment_key ON comment(json_id, comment_id)", "CREATE INDEX comment_post_id ON comment(post_id)"],
"schema_changed": 1426195823
},
"comment_vote": {
"cols": [
["comment_uri", "TEXT"],
["vote", "INTEGER"],
["json_id", "INTEGER REFERENCES json (json_id)"]
],
"indexes": ["CREATE INDEX comment_vote_comment_uri ON comment_vote(comment_uri)", "CREATE INDEX comment_vote_json_id ON comment_vote(json_id)"],
"schema_changed": 1426195822
},
"post": {
"cols": [
["post_id", "INTEGER"],
["title", "TEXT"],
["body", "TEXT"],
["date_published", "INTEGER"],
["json_id", "INTEGER REFERENCES json (json_id)"]
],
"indexes": ["CREATE UNIQUE INDEX post_uri ON post(json_id, post_id)", "CREATE INDEX post_id ON post(post_id)"],
"schema_changed": 1426195823
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

View File

@ -0,0 +1,137 @@
<!DOCTYPE html>
<html>
<head>
<title>ZeroBlog Demo</title>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="css/all.css" />
<base href="" target="_top" id="base">
<script>base.href = document.location.href.replace("/media", "").replace("index.html", "").replace(/[&?]wrapper=False/, "") // Make hashtags work</script>
</head>
<body>
<!-- editbar -->
<div class="editbar bottombar">
<ul class="markdown-help">
<li># H1</li>
<li>## H2</li>
<li>### H3</li>
<li><i>_italic_</i></li>
<li><b>**bold**</b></li>
<li>~~<s>strikethrough</s>~~</li>
<li>- Lists</li>
<li>1. Numbered lists</li>
<li>[Links](http://www.zeronet.io)</li>
<li>[References][1]<br>[1]: Can be used</li>
<li>![image alt](img/logo.png)</li>
<li>Inline <code>`code`</code></li>
<li><code>```python<br>print "Code block"<br>```</code></li>
<li>&gt; Quotes</li>
<li>--- Horizontal rule</li>
</ul>
<a href="#Markdown+help" class="icon-help">?</a> Editing: <span class="object">Post:21.body</span> <a href="#Save" class="button save">Save</a> <a href="#Delete" class="button button-delete button-outline delete">Delete</a> <a href="#Cancel" class="cancel">Cancel</a>
</div>
<!-- EOF editbar -->
<!-- publishbar -->
<div class="publishbar bottombar">
<small>Content changed</small> <a href="#Publish" class="button button-outline button-ok publish">Sign &amp; Publish new content</a>
</div>
<!-- EOF publishbar -->
<!-- left -->
<div class="left" data-object="Site">
<a href="?Home" class="nolink"><div class="avatar"> </div></a>
<h1><a href="?Home" class="nolink" data-editable="title" data-editable-mode="simple"></a></h1>
<h2 data-editable="description"></h2>
<hr>
<div class="links" data-editable="links">
</div>
</div>
<!-- EOF left -->
<!-- right -->
<div class="right">
<!-- Post listing -->
<div class="posts">
<a href="#New+Post" class="button button-outline new">Add new post</a>
<!-- Template: post -->
<div class="post template" data-object="Post:23" data-deletable="True">
<h1 class="title"><a href="?Post:23:Title" data-editable="title" data-editable-mode="simple" class="editable">Title</a></h1>
<div class="details">
<span class="published" data-editable="date_published" data-editable-mode="timestamp">21 hours ago &middot; 2 min read</span>
<a href="?Post:23:title" class="comments-num">&middot; <div class='icon-comment'></div> <span class="num">3 comments</span></a>
</div>
<div class="body" data-editable="body">Body</div>
<a class="more" href="#"><span class='readmore'>Read more</span></a>
</div>
<!-- EOF Template: post -->
</div>
<!-- EOF Post listing -->
<!-- Single Post show -->
<div class="post post-full" data-object="Post:23" data-deletable="True">
<h1 class="title"><a href="?Post:23:Title" data-editable="title" data-editable-mode="simple" class="editable">Title</a></h1>
<div class="details"> <span class="published" data-editable="date_published" data-editable-mode="timestamp">21 hours ago &middot; 2 min read</span> </div>
<div class="body" data-editable="body"></div>
<h2 id="Comments"><span class="comments-num">0</span> Comments:</h2>
<!-- New comment -->
<div class="comment comment-new">
<div class="info">
<a class="user_name certselect" href="#Change+user" title='Change user'>Please sign in</a>
&#9473;
<span class="added">new comment</span>
</div>
<div class="comment-body">
<a class="button button-submit button-certselect certselect" href="#Change+user"><div class='icon-profile'></div>Sign in as...</a>
<textarea class="comment-textarea"></textarea>
<a href="#Submit+comment" class="button button-submit button-submit-comment">Submit comment</a>
<div style='float: right; margin-top: -6px'>
<div class="user-size user-size-used"></div>
<div class="user-size"></div>
</div>
<div style="clear: both"></div>
</div>
</div>
<!-- EOF New comment -->
<div class="comments">
<!-- Template: Comment -->
<div class="comment template">
<div class="info">
<span class="user_name">user_name</span>
<!--<span class="cert_domain"></span>-->
&#9473;
<span class="added">1 day ago</span>
<a href="#Reply" class="reply"><div class="icon icon-reply"></div> <span class="reply-text">Reply</span></a>
</div>
<div class="comment-body">Body</div>
</div>
<!-- EOF Template: Comment -->
</div>
</div>
<!-- EOF Single Post sho -->
</div>
<!-- EOF right -->
<div style="clear: both"></div>
<script type="text/javascript" src="js/all.js" asyc></script>
</body>
</html>

File diff suppressed because one or more lines are too long

26
src/Test/testdata/cert-rsa.pem vendored Normal file
View File

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEVzCCAz+gAwIBAgIJAMT6z6CFolWoMA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJOWTERMA8GA1UEBwwITmV3IFlvcmsxFTATBgNVBAoM
DEV4YW1wbGUsIExMQzEYMBYGA1UEAwwPRXhhbXBsZSBDb21wYW55MR8wHQYJKoZI
hvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE1MDYwNzIyNDcwMVoXDTE1MDcw
NzIyNDcwMVowfzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhO
ZXcgWW9yazEVMBMGA1UECgwMRXhhbXBsZSwgTExDMRgwFgYDVQQDDA9FeGFtcGxl
IENvbXBhbnkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxQLG4PNW2okO2+seSyXduFDj6RsEE
uLKmPrLzS8phW+VKZ7WaAnCQOX4ZX5oUNLIHSbsi57JBEzSxSMAbipaKilRbLtp4
ObwDMUS+CTR2cohaYf7jCYrkWCeUWw/QT3zd65KXnysNmdPt8MTsgipiwACHPBZ6
lMfzYPv0LoYNSubXAGp3gkXdh8oSFd1s4Dz0UC0g7D+McstyN72BWpQxkKpir8wN
NYaXX02vXdXag1LHcG3tvQYE35Ssp0D7AyDOfnWOXIJBosPqAPdC9Bn2EYqs105u
KxJvLL2fwRC5OWyFlxgl2/WbaVlxPTG+tCt1Z8PzSt/Ba4beXNXWZvdPAgMBAAGj
gdUwgdIwHQYDVR0OBBYEFHpqhZLf14i9oSNLl6JCGn0Oj3ykMB8GA1UdIwQYMBaA
FHpqhZLf14i9oSNLl6JCGn0Oj3ykMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMEoG
A1UdEQRDMEGCC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb22CEG1haWwuZXhh
bXBsZS5jb22CD2Z0cC5leGFtcGxlLmNvbTAsBglghkgBhvhCAQ0EHxYdT3BlblNT
TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBADtW79uP
gSB3iiOjvGTZqiYY0Uiw6CwUVtDGHSCv/daOboGnCsdKe4TcmjRg+GG8FQGxTpcg
OJouUiCb+ATWnn0af+3wQefzMB1HISwhw+c62PcR6h+eFLITDJxVem/zK7Hhxwpq
z8ud25+W1fLED2QdKIN7RyhtAS0bTq/XoZpkC0FXNa3GY2+HzP3gdIr4QGd+q+pf
HWGg/+I9m7Uk/8trxvcQH+adohJXMIYJB8mGcHprtO28megMzQjzlnycu+fHDM/L
hb6VxJU5rYe/Kfx2QHlCiRibTidCiaG1LJnpYxyNLF6pavstbziCfiruP6ki20+r
bMzABTIXLaYxeUQ=
-----END CERTIFICATE-----

28
src/Test/testdata/key-rsa.pem vendored Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxQLG4PNW2okO2
+seSyXduFDj6RsEEuLKmPrLzS8phW+VKZ7WaAnCQOX4ZX5oUNLIHSbsi57JBEzSx
SMAbipaKilRbLtp4ObwDMUS+CTR2cohaYf7jCYrkWCeUWw/QT3zd65KXnysNmdPt
8MTsgipiwACHPBZ6lMfzYPv0LoYNSubXAGp3gkXdh8oSFd1s4Dz0UC0g7D+Mcsty
N72BWpQxkKpir8wNNYaXX02vXdXag1LHcG3tvQYE35Ssp0D7AyDOfnWOXIJBosPq
APdC9Bn2EYqs105uKxJvLL2fwRC5OWyFlxgl2/WbaVlxPTG+tCt1Z8PzSt/Ba4be
XNXWZvdPAgMBAAECggEAFKWmjgF4G4gXPy9DJBQXt1jfsjefsD8bgEX5bmG7t0+P
C/W8r63tEr+/spDyT7w7qKGsDMrUdsQX9Ta2Of8Qvh6S6PQyOqLvBagti71iwRFi
VLTpjeTEqwihw6Q/2VIJB4izULoXt8PdbfAH4EzhRxN6fEZBkBHYoL8BWlY5Az8u
tg6gjsoZFP1AiSByir0d+tf5adDVZieo5O8WYahi95TGqtkoM49YzX74I2dJ1jWY
MNqMYxoPwvf+olNB3rMPZIGb4VMpNzsgbV73fNVyCMa9TNJSQUkELrrkym3us45w
6kaBE7uU3wciQjeZOnRyDL4MnKGSUb0eJY0JjkQnkQKBgQDYx6GeXXDl6rcCovUR
gdAvJco29rT4OPi1xbqHf9mUoBuNdXpwd2/bo4NlBfEVrZlSMZ/Vl2eTLAaYm6JI
WwshIjnRU8Pe/4cffDsVRuXmdWPduXeqHqo8jwKDT9hOYcUiidWUgRdbfo108NQs
XSvnGXXS0je58GZMTeC+A9Q1gwKBgQDRUlOkr2g7j5w2nuo2yRIKuRAdI4A1UZA/
xddw3e6RLLq4vTF/6X0Wb1bsrGUhX9aJ/khWa7arN45q/KJfxwnV+I7wuPw3VQKA
zH5tF1It7bSl7FgYFXJMqYkC77V7IdQL/PuM0vS8oewlpZInDCpJjPf/7bG7iUaw
/Bb74N5ZRQKBgBGz+9ba+qVMDbYBaNINL9sp0uG6M/0xad4uT5VRM4uXp6hdt6oH
lvLw34IYgh+rFaJIuyzOOH8kUUWVMCOIi9gg22fk11IWvAouMwUBzTSM0aMBymvy
JSTc6O+gTaHZCihP6Uk/YZDvPM4X/LvCwBsXUS/uSu68Wx5QHdJmraXRAoGAGbr8
/Slyrp+gnDY8pC9jQF4vVOWgRO2Zxb0UFpOxV7cf3MWk6AxTjAZzsPQgGlIllSDk
03q6IaHap9wWOZ/F3b+IEp8qocKZZCu+/rn3KB4oLp021v8L5dCRPwMoU9J8tlyK
r2zfGLDuzlHj/VjJefESKyuUxXDCd88FJEEoE+ECgYEAmsaUuUNP4/VH32DOzVEt
wjhYvrph9fAcv67Mm+hbnBn7ACAnCJa0Ny8ZGzHSKck4WW+Drtyn6tv8naTsPnJn
GYfZeQ6uqk46F2sMOJHpmRsoJ+1Jt3GtoEedqms2o3OsnMbseiBsPhLr4GaJOCc7
+c+EexCfljR19TVV4EDhVRw=
-----END PRIVATE KEY-----

19
src/Test/testdata/sites.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL": {
"auth_key": "4YV2yD0tRsQkujR313PyTaLI",
"own": false,
"permissions": [],
"serving": true,
"size": 0,
"wrapper_key": "eRdtnDMCarLs"
},
"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT": {
"auth_key": "VyVxJU0AJqYGNTvITwASeSJA",
"modified": 1433079881.276243,
"own": false,
"permissions": [],
"serving": true,
"size": 722338,
"wrapper_key": "amGBy0GlD6nD"
}
}

12
src/Test/testdata/users.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc": {
"certs": {},
"master_seed": "024bceac1105483d66585d8a60eaf20aa8c3254b0f266e0d626ddb6114e2949a",
"sites": {
"1LzBg7WGjJXwJtre2iKwbZtRXezWzUMXDJ": {
"auth_address": "15DZCFDwPjFGf9E2DFVQzZJ2voTWZEesRX",
"auth_privatekey": "5KC4tMJdA8wJkNiVTuWCjNVBm7eeXWCSTRAnGABRaH5RhMEYyet"
}
}
}
}

View File

@ -183,7 +183,10 @@ class UiRequest(object):
else: query_string = "?wrapper=False"
if self.isProxyRequest(): # Its a remote proxy request
server_url = "http://%s:%s" % (self.env["SERVER_NAME"], self.env["SERVER_PORT"])
if self.env["REMOTE_ADDR"] == "127.0.0.1": # Local client, the server address also should be 127.0.0.1
server_url = "http://127.0.0.1:%s" % self.env["SERVER_PORT"]
else: # Remote client, use SERVER_NAME as server's real address
server_url = "http://%s:%s" % (self.env["SERVER_NAME"], self.env["SERVER_PORT"])
homepage = "http://zero/"+config.homepage
else: # Use relative path
server_url = ""

View File

@ -96,7 +96,7 @@ class UiWebsocket(object):
permissions = permissions[:]
permissions.append("ADMIN")
admin_commands = ("sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "channelJoinAllsite", "serverUpdate", "certSet")
admin_commands = ("sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "siteClone", "channelJoinAllsite", "serverUpdate", "certSet")
if cmd == "response": # It's a response to a command
return self.actionResponse(req["to"], req["result"])
@ -150,6 +150,7 @@ class UiWebsocket(object):
"workers": len(site.worker_manager.workers),
"content": content
}
if site.settings["own"]: ret["privatekey"] = bool(self.user.getSiteData(site.address, create=create_user).get("privatekey"))
if site.settings["serving"] and content: ret["peers"] += 1 # Add myself if serving
return ret
@ -205,7 +206,8 @@ class UiWebsocket(object):
self.response(to, ret)
def actionSitePublish(self, to, privatekey=None, inner_path="content.json"):
# Sign content.json
def actionSiteSign(self, to, privatekey=None, inner_path="content.json"):
site = self.site
extend = {} # Extended info for signing
if not inner_path.endswith("content.json"): # Find the content.json first
@ -220,29 +222,44 @@ class UiWebsocket(object):
if not site.settings["own"] and self.user.getAuthAddress(self.site.address) not in self.site.content_manager.getValidSigners(inner_path):
return self.response(to, "Forbidden, you can only modify your own sites")
if not privatekey: # Get privatekey from users.json
if privatekey == "stored":
privatekey = self.user.getSiteData(self.site.address).get("privatekey")
if not privatekey: # Get privatekey from users.json auth_address
privatekey = self.user.getAuthPrivatekey(self.site.address)
# Signing
site.content_manager.loadContent(add_bad_files=False) # Reload content.json, ignore errors to make it up-to-date
signed = site.content_manager.sign(inner_path, privatekey, extend=extend) # Sign using private key sent by user
if signed:
if inner_path == "content_json": self.cmd("notification", ["done", "Private key correct, content signed!", 5000]) # Display message for 5 sec
#if inner_path == "content_json": self.cmd("notification", ["done", "Private key correct, content signed!", 5000]) # Display message for 5 sec
pass
else:
self.cmd("notification", ["error", "Content sign failed: invalid private key."])
self.response(to, "Site sign failed")
return
site.content_manager.loadContent(add_bad_files=False) # Load new content.json, ignore errors
self.response(to, "ok")
return inner_path
# Sign and publish content.json
def actionSitePublish(self, to, privatekey=None, inner_path="content.json", sign=True):
if sign:
inner_path = self.actionSiteSign(to, privatekey, inner_path)
if not inner_path:
return
# Publishing
if not site.settings["serving"]: # Enable site if paused
site.settings["serving"] = True
site.saveSettings()
site.announce()
if not self.site.settings["serving"]: # Enable site if paused
self.site.settings["serving"] = True
self.site.saveSettings()
self.site.announce()
event_name = "publish %s %s" % (site.address, inner_path)
thread = RateLimit.callAsync(event_name, 7, site.publish, 5, inner_path) # Only publish once in 7 second to 5 peers
event_name = "publish %s %s" % (self.site.address, inner_path)
thread = RateLimit.callAsync(event_name, 7, self.site.publish, 5, inner_path) # Only publish once in 7 second to 5 peers
notification = "linked" not in dir(thread) # Only display notification on first callback
thread.linked = True
thread.link(lambda thread: self.cbSitePublish(to, thread, notification)) # At the end callback with request id and thread
@ -258,8 +275,13 @@ class UiWebsocket(object):
if notification: site.updateWebsocket() # Send updated site data to local websocket clients
else:
if len(site.peers) == 0:
if notification: self.cmd("notification", ["info", "No peers found, but your content is ready to access."])
self.response(to, "No peers found, but your content is ready to access.")
if sys.modules["main"].file_server.port_opened:
if notification: self.cmd("notification", ["info", "No peers found, but your content is ready to access."])
self.response(to, "No peers found, but your content is ready to access.")
else:
if notification: self.cmd("notification", ["info", "Your network connection is restricted. Please, open <b>"+str(config.fileserver_port)+"</b> port <br>on your router to make your site accessible for everyone."])
self.response(to, "Port not opened.")
else:
if notification: self.cmd("notification", ["error", "Content publish failed."])
self.response(to, "Content publish failed.")
@ -472,6 +494,18 @@ class UiWebsocket(object):
self.response(to, {"error": "Unknown site: %s" % address})
def actionSiteClone(self, to, address):
self.cmd("notification", ["info", "Cloning site..."])
site = self.server.sites.get(address)
# Generate a new site from user's bip32 seed
new_address, new_address_index, new_site_data = self.user.getNewSiteData()
new_site = site.clone(new_address, new_site_data["privatekey"], address_index=new_address_index)
new_site.settings["own"] = True
new_site.saveSettings()
self.cmd("notification", ["done", "Site cloned<script>window.top.location = '/%s'</script>" % new_address])
gevent.spawn(new_site.announce)
def actionSiteSetLimit(self, to, size_limit):
self.site.settings["size_limit"] = size_limit
self.site.saveSettings()

View File

@ -34,13 +34,17 @@ class User(object):
self.log.debug("Saved")
def getAddressAuthIndex(self, address):
return int(address.encode("hex"), 16)
# Get user site data
# Return: {"auth_address": "xxx", "auth_privatekey": "xxx"}
def getSiteData(self, address, create=True):
if not address in self.sites: # Genreate new BIP32 child key based on site address
if not create: return {"auth_address": None, "auth_privatekey": None} # Dont create user yet
s = time.time()
address_id = int(address.encode("hex"), 16) # Convert site address to int
address_id = self.getAddressAuthIndex(address) # Convert site address to int
auth_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, address_id)
self.sites[address] = {
"auth_address": CryptBitcoin.privatekeyToAddress(auth_privatekey),
@ -51,6 +55,21 @@ class User(object):
return self.sites[address]
# Get data for a new, unique site
# Return: [site_address, bip32_index, {"auth_address": "xxx", "auth_privatekey": "xxx", "privatekey": "xxx"}]
def getNewSiteData(self):
import random
bip32_index = random.randrange(2**256) % 100000000
site_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, bip32_index)
site_address = CryptBitcoin.privatekeyToAddress(site_privatekey)
if site_address in self.sites: raise Exception("Random error: site exits!")
# Save to sites
self.getSiteData(site_address)
self.sites[site_address]["privatekey"] = site_privatekey
self.save()
return site_address, bip32_index, self.sites[site_address]
# Get BIP32 address from site address
# Return: BIP32 auth address
def getAuthAddress(self, address, create=True):

View File

@ -0,0 +1,10 @@
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -nodes -config openssl.cnf
REM openssl ecparam -name secp521r1 -genkey -param_enc explicit -out key-ecc.pem -config openssl.cnf
openssl ecparam -name secp256r1 -genkey -out key-ecc.pem
openssl req -new -key key-ecc.pem -x509 -nodes -out cert-ecc.pem -config openssl.cnf
@echo off
REM openssl ecparam -genkey -name prime256v1 -out key.pem
REM openssl req -new -key key.pem -out csr.pem
REM openssl req -x509 -days 365 -key key.pem -in csr.pem -out certificate.pem

View File

@ -0,0 +1,70 @@
[ req ]
prompt = no
default_bits = 2048
default_keyfile = server-key.pem
distinguished_name = subject
req_extensions = req_ext
x509_extensions = x509_ext
string_mask = utf8only
# The Subject DN can be formed using X501 or RFC 4514 (see RFC 4519 for a description).
# Its sort of a mashup. For example, RFC 4514 does not provide emailAddress.
[ subject ]
countryName = US
stateOrProvinceName = NY
localityName = New York
organizationName = Example, LLC
# Use a friendly name here because its presented to the user. The server's DNS
# names are placed in Subject Alternate Names. Plus, DNS names here is deprecated
# by both IETF and CA/Browser Forums. If you place a DNS name here, then you
# must include the DNS name in the SAN too (otherwise, Chrome and others that
# strictly follow the CA/Browser Baseline Requirements will fail).
commonName = Example Company
emailAddress = test@example.com
# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ...
[ x509_ext ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alternate_names
nsComment = "OpenSSL Generated Certificate"
# RFC 5280, Section 4.2.1.12 makes EKU optional
# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused
# extendedKeyUsage = serverAuth, clientAuth
# Section req_ext is used when generating a certificate signing request. I.e., openssl req ...
[ req_ext ]
subjectKeyIdentifier = hash
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alternate_names
nsComment = "OpenSSL Generated Certificate"
# RFC 5280, Section 4.2.1.12 makes EKU optional
# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused
# extendedKeyUsage = serverAuth, clientAuth
[ alternate_names ]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = mail.example.com
DNS.4 = ftp.example.com
# Add these if you need them. But usually you don't want them or
# need them in production. You may need them for development.
# DNS.5 = localhost
# DNS.6 = localhost.localdomain
# DNS.7 = 127.0.0.1
# IPv6 localhost
# DNS.8 = ::1

Binary file not shown.

Binary file not shown.

View File

@ -23,16 +23,26 @@ def make_request(*args):
raise Exception(p)
def parse_addr_args(*args):
# Valid input formats: blockr_unspent([addr1, addr2,addr3])
# blockr_unspent(addr1, addr2, addr3)
# blockr_unspent([addr1, addr2, addr3], network)
# blockr_unspent(addr1, addr2, addr3, network)
# Where network is 'btc' or 'testnet'
network = 'btc'
addr_args = args
if len(args) >= 1 and args[-1] in ('testnet', 'btc'):
network = args[-1]
addr_args = args[:-1]
if len(addr_args) == 1 and isinstance(addr_args, list):
addr_args = addr_args[0]
return network, addr_args
# Gets the unspent outputs of one or more addresses
def unspent(*args):
# Valid input formats: unspent([addr1, addr2,addr3])
# unspent(addr1, addr2, addr3)
if len(args) == 0:
return []
elif isinstance(args[0], list):
addrs = args[0]
else:
addrs = args
def bci_unspent(*args):
network, addrs = parse_addr_args(*args)
u = []
for a in addrs:
try:
@ -61,11 +71,7 @@ def blockr_unspent(*args):
# blockr_unspent([addr1, addr2, addr3], network)
# blockr_unspent(addr1, addr2, addr3, network)
# Where network is 'btc' or 'testnet'
network = 'btc'
addr_args = args
if len(args) >= 1 and args[-1] in ('testnet', 'btc'):
network = args[-1]
addr_args = args[:-1]
network, addr_args = parse_addr_args(*args)
if network == 'testnet':
blockr_url = 'https://tbtc.blockr.io/api/v1/address/unspent/'
@ -95,6 +101,41 @@ def blockr_unspent(*args):
return o
def helloblock_unspent(*args):
network, addrs = parse_addr_args(*args)
if network == 'testnet':
url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s'
elif network == 'btc':
url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s'
o = []
for addr in addrs:
for offset in xrange(0, 10**9, 500):
res = make_request(url % (addr, offset))
data = json.loads(res)["data"]
if not len(data["unspents"]):
break
elif offset:
sys.stderr.write("Getting more unspents: %d\n" % offset)
for dat in data["unspents"]:
o.append({
"output": dat["txHash"]+':'+str(dat["index"]),
"value": dat["value"],
})
return o
unspent_getters = {
'bci': bci_unspent,
'blockr': blockr_unspent,
'helloblock': helloblock_unspent
}
def unspent(*args, **kwargs):
f = unspent_getters.get(kwargs.get('source', ''), bci_unspent)
return f(*args)
# Gets the transaction output history of a given set of addresses,
# including whether or not they have been spent
def history(*args):
@ -145,7 +186,7 @@ def history(*args):
# Pushes a transaction to the network using https://blockchain.info/pushtx
def pushtx(tx):
def bci_pushtx(tx):
if not re.match('^[0-9a-fA-F]*$', tx):
tx = tx.encode('hex')
return make_request('https://blockchain.info/pushtx', 'tx='+tx)
@ -184,6 +225,17 @@ def helloblock_pushtx(tx):
return make_request('https://mainnet.helloblock.io/v1/transactions',
'rawTxHex='+tx)
pushtx_getters = {
'bci': bci_pushtx,
'blockr': blockr_pushtx,
'helloblock': helloblock_pushtx
}
def pushtx(*args, **kwargs):
f = pushtx_getters.get(kwargs.get('source', ''), bci_pushtx)
return f(*args)
def last_block_height():
data = make_request('https://blockchain.info/latestblock')
@ -213,11 +265,54 @@ def blockr_fetchtx(txhash, network='btc'):
return jsondata['data']['tx']['hex']
def fetchtx(txhash):
try:
return bci_fetchtx(txhash)
except:
return blockr_fetchtx(txhash)
def helloblock_fetchtx(txhash, network='btc'):
if not re.match('^[0-9a-fA-F]*$', txhash):
txhash = txhash.encode('hex')
if network == 'testnet':
url = 'https://testnet.helloblock.io/v1/transactions/'
elif network == 'btc':
url = 'https://mainnet.helloblock.io/v1/transactions/'
else:
raise Exception(
'Unsupported network {0} for helloblock_fetchtx'.format(network))
data = json.loads(make_request(url + txhash))["data"]["transaction"]
o = {
"locktime": data["locktime"],
"version": data["version"],
"ins": [],
"outs": []
}
for inp in data["inputs"]:
o["ins"].append({
"script": inp["scriptSig"],
"outpoint": {
"index": inp["prevTxoutIndex"],
"hash": inp["prevTxHash"],
},
"sequence": 4294967295
})
for outp in data["outputs"]:
o["outs"].append({
"value": outp["value"],
"script": outp["scriptPubKey"]
})
from bitcoin.transaction import serialize
from bitcoin.transaction import txhash as TXHASH
tx = serialize(o)
assert TXHASH(tx) == txhash
return tx
fetchtx_getters = {
'bci': bci_fetchtx,
'blockr': blockr_fetchtx,
'helloblock': helloblock_fetchtx
}
def fetchtx(*args, **kwargs):
f = fetchtx_getters.get(kwargs.get('source', ''), bci_fetchtx)
return f(*args)
def firstbits(address):
@ -257,6 +352,26 @@ def get_block_header_data(inp):
'nonce': j['nonce'],
}
def blockr_get_block_header_data(height, network='btc'):
if network == 'testnet':
blockr_url = "https://tbtc.blockr.io/api/v1/block/raw/"
elif network == 'btc':
blockr_url = "https://btc.blockr.io/api/v1/block/raw/"
else:
raise Exception(
'Unsupported network {0} for blockr_get_block_header_data'.format(network))
k = json.loads(make_request(blockr_url + str(height)))
j = k['data']
return {
'version': j['version'],
'hash': j['hash'],
'prevhash': j['previousblockhash'],
'timestamp': j['time'],
'merkle_root': j['merkleroot'],
'bits': int(j['bits'], 16),
'nonce': j['nonce'],
}
def get_txs_in_block(inp):
j = _get_block(inp)

View File

@ -28,13 +28,15 @@ def deserialize_header(inp):
def mk_merkle_proof(header, hashes, index):
nodes = [h.decode('hex')[::-1] for h in hashes]
if len(nodes) % 2 and len(nodes) > 2:
nodes.append(nodes[-1])
layers = [nodes]
while len(nodes) > 1:
newnodes = []
for i in range(0, len(nodes) - 1, 2):
newnodes.append(bin_sha256(bin_sha256(nodes[i] + nodes[i+1])))
if len(nodes) % 2:
newnodes.append(bin_sha256(bin_sha256(nodes[-1] + nodes[-1])))
if len(newnodes) % 2 and len(newnodes) > 2:
newnodes.append(newnodes[-1])
nodes = newnodes
layers.append(nodes)
# Sanity check, make sure merkle root is valid

View File

@ -11,30 +11,7 @@ def send(frm, to, value, fee=10000):
# Takes privkey, "address1:value1,address2:value2" (satoshis), fee (satoshis)
def sendmultitx(frm, tovalues, fee=10000):
outs = []
outvalue = 0
tv = tovalues.split(",")
for a in tv:
outs.append(a)
outvalue += int(a.split(":")[1])
u = unspent(privtoaddr(frm))
u2 = select(u, int(outvalue)+int(fee))
argz = u2 + outs + [frm, fee]
tx = mksend(*argz)
tx2 = signall(tx, frm)
return pushtx(tx2)
# Takes address, address, value (satoshis), fee(satoshis)
def preparetx(frm, to, value, fee=10000):
tovalues = to + ":" + str(value)
return preparemultitx(frm, tovalues, fee)
# Takes address, address:value, address:value ... (satoshis), fee(satoshis)
def preparemultitx(frm, *args):
def sendmultitx(frm, tovalues, fee=10000, **kwargs):
tv, fee = args[:-1], int(args[-1])
outs = []
outvalue = 0
@ -42,7 +19,30 @@ def preparemultitx(frm, *args):
outs.append(a)
outvalue += int(a.split(":")[1])
u = unspent(frm)
u = unspent(privtoaddr(frm), **kwargs)
u2 = select(u, int(outvalue)+int(fee))
argz = u2 + outs + [frm, fee]
tx = mksend(*argz)
tx2 = signall(tx, frm)
return pushtx(tx2, **kwargs)
# Takes address, address, value (satoshis), fee(satoshis)
def preparetx(frm, to, value, fee=10000, **kwargs):
tovalues = to + ":" + str(value)
return preparemultitx(frm, tovalues, fee, **kwargs)
# Takes address, address:value, address:value ... (satoshis), fee(satoshis)
def preparemultitx(frm, *args, **kwargs):
tv, fee = args[:-1], int(args[-1])
outs = []
outvalue = 0
for a in tv:
outs.append(a)
outvalue += int(a.split(":")[1])
u = unspent(frm, **kwargs)
u2 = select(u, int(outvalue)+int(fee))
argz = u2 + outs + [frm, fee]
return mksend(*argz)
@ -96,14 +96,14 @@ def sign_coinvault_tx(tx, priv):
# Inspects a transaction
def inspect(tx):
def inspect(tx, **kwargs):
d = deserialize(tx)
isum = 0
ins = {}
for _in in d['ins']:
h = _in['outpoint']['hash']
i = _in['outpoint']['index']
prevout = deserialize(fetchtx(h))['outs'][i]
prevout = deserialize(fetchtx(h, **kwargs))['outs'][i]
isum += prevout['value']
a = script_to_address(prevout['script'])
ins[a] = ins.get(a, 0) + prevout['value']

View File

@ -59,8 +59,12 @@ def crack_electrum_wallet(mpk, pk, n, for_change=0):
return subtract_privkeys(pk, offset)
# Below code ASSUMES binary inputs and compressed pubkeys
PRIVATE = b'\x04\x88\xAD\xE4'
PUBLIC = b'\x04\x88\xB2\x1E'
MAINNET_PRIVATE = b'\x04\x88\xAD\xE4'
MAINNET_PUBLIC = b'\x04\x88\xB2\x1E'
TESTNET_PRIVATE = b'\x04\x35\x83\x94'
TESTNET_PUBLIC = b'\x04\x35\x87\xCF'
PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE]
PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC]
# BIP32 child key derivation
@ -69,23 +73,23 @@ def raw_bip32_ckd(rawtuple, i):
vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple
i = int(i)
if vbytes == PRIVATE:
if vbytes in PRIVATE:
priv = key
pub = privtopub(key)
else:
pub = key
if i >= 2**31:
if vbytes == PUBLIC:
if vbytes in PUBLIC:
raise Exception("Can't do private derivation on public key!")
I = hmac.new(chaincode, b'\x00'+priv[:32]+encode(i, 256, 4), hashlib.sha512).digest()
else:
I = hmac.new(chaincode, pub+encode(i, 256, 4), hashlib.sha512).digest()
if vbytes == PRIVATE:
newkey = add_privkeys(I[:32]+b'\x01', priv)
if vbytes in PRIVATE:
newkey = add_privkeys(I[:32]+B'\x01', priv)
fingerprint = bin_hash160(privtopub(key))[:4]
if vbytes == PUBLIC:
if vbytes in PUBLIC:
newkey = add_pubkeys(compress(privtopub(I[:32])), key)
fingerprint = bin_hash160(key)[:4]
@ -96,7 +100,7 @@ def bip32_serialize(rawtuple):
vbytes, depth, fingerprint, i, chaincode, key = rawtuple
i = encode(i, 256, 4)
chaincode = encode(hash_to_int(chaincode), 256, 32)
keydata = b'\x00'+key[:-1] if vbytes == PRIVATE else key
keydata = b'\x00'+key[:-1] if vbytes in PRIVATE else key
bindata = vbytes + from_int_to_byte(depth % 256) + fingerprint + i + chaincode + keydata
return changebase(bindata+bin_dbl_sha256(bindata)[:4], 256, 58)
@ -110,13 +114,14 @@ def bip32_deserialize(data):
fingerprint = dbin[5:9]
i = decode(dbin[9:13], 256)
chaincode = dbin[13:45]
key = dbin[46:78]+b'\x01' if vbytes == PRIVATE else dbin[45:78]
key = dbin[46:78]+b'\x01' if vbytes in PRIVATE else dbin[45:78]
return (vbytes, depth, fingerprint, i, chaincode, key)
def raw_bip32_privtopub(rawtuple):
vbytes, depth, fingerprint, i, chaincode, key = rawtuple
return (PUBLIC, depth, fingerprint, i, chaincode, privtopub(key))
newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC
return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key))
def bip32_privtopub(data):
@ -127,9 +132,9 @@ def bip32_ckd(data, i):
return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i))
def bip32_master_key(seed):
def bip32_master_key(seed, vbytes=MAINNET_PRIVATE):
I = hmac.new(from_string_to_bytes("Bitcoin seed"), seed, hashlib.sha512).digest()
return bip32_serialize((PRIVATE, 0, b'\x00'*4, 0, I[32:], I[:32]+b'\x01'))
return bip32_serialize((vbytes, 0, b'\x00'*4, 0, I[32:], I[:32]+b'\x01'))
def bip32_bin_extract_key(data):
@ -156,7 +161,8 @@ def raw_crack_bip32_privkey(parent_pub, priv):
pprivkey = subtract_privkeys(key, I[:32]+b'\x01')
return (PRIVATE, pdepth, pfingerprint, pi, pchaincode, pprivkey)
newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE
return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey)
def crack_bip32_privkey(parent_pub, priv):
@ -171,7 +177,7 @@ def coinvault_pub_to_bip32(*args):
vals = map(int, args[34:])
I1 = ''.join(map(chr, vals[:33]))
I2 = ''.join(map(chr, vals[35:67]))
return bip32_serialize((PUBLIC, 0, b'\x00'*4, 0, I2, I1))
return bip32_serialize((MAINNET_PUBLIC, 0, b'\x00'*4, 0, I2, I1))
def coinvault_priv_to_bip32(*args):
@ -180,11 +186,11 @@ def coinvault_priv_to_bip32(*args):
vals = map(int, args[34:])
I2 = ''.join(map(chr, vals[35:67]))
I3 = ''.join(map(chr, vals[72:104]))
return bip32_serialize((PRIVATE, 0, b'\x00'*4, 0, I2, I3+b'\x01'))
return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00'*4, 0, I2, I3+b'\x01'))
def bip32_descend(*args):
if len(args) == 2:
if len(args) == 2 and isinstance(args[1], list):
key, path = args
else:
key, path = args[0], map(int, args[1:])

View File

@ -36,6 +36,8 @@ def getG():
def inv(a, n):
if a == 0:
return 0
lm, hm = 1, 0
low, high = a % n, n
while low > 1:
@ -79,95 +81,75 @@ def sum(obj):
return _sum(obj)
# Elliptic curve Jordan form functions
# P = (m, n, p, q) where m/n = x, p/q = y
def isinf(p):
return p[0] == 0 and p[1] == 0
def jordan_isinf(p):
return p[0][0] == 0 and p[1][0] == 0
def to_jacobian(p):
o = (p[0], p[1], 1)
return o
def mulcoords(c1, c2):
return (c1[0] * c2[0] % P, c1[1] * c2[1] % P)
def jacobian_double(p):
if not p[1]:
return (0, 0, 0)
ysq = (p[1] ** 2) % P
S = (4 * p[0] * ysq) % P
M = (3 * p[0] ** 2 + A * p[2] ** 4) % P
nx = (M**2 - 2 * S) % P
ny = (M * (S - nx) - 8 * ysq ** 2) % P
nz = (2 * p[1] * p[2]) % P
return (nx, ny, nz)
def mul_by_const(c, v):
return (c[0] * v % P, c[1])
def jacobian_add(p, q):
if not p[1]:
return q
if not q[1]:
return p
U1 = (p[0] * q[2] ** 2) % P
U2 = (q[0] * p[2] ** 2) % P
S1 = (p[1] * q[2] ** 3) % P
S2 = (q[1] * p[2] ** 3) % P
if U1 == U2:
if S1 != S2:
return (0, 0, 1)
return jacobian_double(p)
H = U2 - U1
R = S2 - S1
H2 = (H * H) % P
H3 = (H * H2) % P
U1H2 = (U1 * H2) % P
nx = (R ** 2 - H3 - 2 * U1H2) % P
ny = (R * (U1H2 - nx) - S1 * H3) % P
nz = H * p[2] * q[2]
return (nx, ny, nz)
def addcoords(c1, c2):
return ((c1[0] * c2[1] + c2[0] * c1[1]) % P, c1[1] * c2[1] % P)
def from_jacobian(p):
z = inv(p[2], P)
return ((p[0] * z**2) % P, (p[1] * z**3) % P)
def subcoords(c1, c2):
return ((c1[0] * c2[1] - c2[0] * c1[1]) % P, c1[1] * c2[1] % P)
def invcoords(c):
return (c[1], c[0])
def jordan_add(a, b):
if jordan_isinf(a):
return b
if jordan_isinf(b):
return a
if (a[0][0] * b[0][1] - b[0][0] * a[0][1]) % P == 0:
if (a[1][0] * b[1][1] - b[1][0] * a[1][1]) % P == 0:
return jordan_double(a)
else:
return ((0, 1), (0, 1))
xdiff = subcoords(b[0], a[0])
ydiff = subcoords(b[1], a[1])
m = mulcoords(ydiff, invcoords(xdiff))
x = subcoords(subcoords(mulcoords(m, m), a[0]), b[0])
y = subcoords(mulcoords(m, subcoords(a[0], x)), a[1])
return (x, y)
def jordan_double(a):
if jordan_isinf(a):
return ((0, 1), (0, 1))
num = addcoords(mul_by_const(mulcoords(a[0], a[0]), 3), (A, 1))
den = mul_by_const(a[1], 2)
m = mulcoords(num, invcoords(den))
x = subcoords(mulcoords(m, m), mul_by_const(a[0], 2))
y = subcoords(mulcoords(m, subcoords(a[0], x)), a[1])
return (x, y)
def jordan_multiply(a, n):
if jordan_isinf(a) or n == 0:
return ((0, 0), (0, 0))
def jacobian_multiply(a, n):
if a[1] == 0 or n == 0:
return (0, 0, 1)
if n == 1:
return a
if n < 0 or n >= N:
return jordan_multiply(a, n % N)
return jacobian_multiply(a, n % N)
if (n % 2) == 0:
return jordan_double(jordan_multiply(a, n//2))
return jacobian_double(jacobian_multiply(a, n//2))
if (n % 2) == 1:
return jordan_add(jordan_double(jordan_multiply(a, n//2)), a)
def to_jordan(p):
return ((p[0], 1), (p[1], 1))
def from_jordan(p):
return (p[0][0] * inv(p[0][1], P) % P, p[1][0] * inv(p[1][1], P) % P)
return (p[0][0] * inv(p[0][1], P) % P, p[1][0] * inv(p[1][1], P) % P)
return jacobian_add(jacobian_double(jacobian_multiply(a, n//2)), a)
def fast_multiply(a, n):
return from_jordan(jordan_multiply(to_jordan(a), n))
return from_jacobian(jacobian_multiply(to_jacobian(a), n))
def fast_add(a, b):
return from_jordan(jordan_add(to_jordan(a), to_jordan(b)))
return from_jacobian(jacobian_add(to_jacobian(a), to_jacobian(b)))
# Functions for handling pubkey and privkey formats
@ -181,7 +163,7 @@ def get_pubkey_format(pub):
two = 2
three = 3
four = 4
if isinstance(pub, (tuple, list)): return 'decimal'
elif len(pub) == 65 and pub[0] == four: return 'bin'
elif len(pub) == 130 and pub[0:2] == '04': return 'hex'
@ -535,11 +517,11 @@ def ecdsa_raw_recover(msghash, vrs):
beta = pow(x*x*x+A*x+B, (P+1)//4, P)
y = beta if v % 2 ^ beta % 2 else (P - beta)
z = hash_to_int(msghash)
Gz = jordan_multiply(((Gx, 1), (Gy, 1)), (N - z) % N)
XY = jordan_multiply(((x, 1), (y, 1)), s)
Qr = jordan_add(Gz, XY)
Q = jordan_multiply(Qr, inv(r, N))
Q = from_jordan(Q)
Gz = jacobian_multiply((Gx, Gy, 1), (N - z) % N)
XY = jacobian_multiply((x, y, 1), s)
Qr = jacobian_add(Gz, XY)
Q = jacobian_multiply(Qr, inv(r, N))
Q = from_jacobian(Q)
if ecdsa_raw_verify(msghash, vrs, Q):
return Q

View File

@ -60,7 +60,7 @@ if sys.version_info.major == 2:
def from_byte_to_int(a):
return ord(a)
def from_byes_to_string(s):
def from_bytes_to_string(s):
return s
def from_string_to_bytes(a):

View File

@ -38,7 +38,7 @@ if sys.version_info.major == 3:
return encode(decode(string, frm), to, minlen)
def bin_to_b58check(inp, magicbyte=0):
inp_fmtd = from_int_to_byte(magicbyte)+inp
inp_fmtd = from_int_to_byte(int(magicbyte))+inp
leadingzbytes = 0
for x in inp_fmtd:
@ -84,7 +84,8 @@ if sys.version_info.major == 3:
pad_size = minlen - len(result_bytes)
padding_element = b'\x00' if base == 256 else b'0'
padding_element = b'\x00' if base == 256 else b'1' \
if base == 58 else b'0'
if (pad_size > 0):
result_bytes = padding_element*pad_size + result_bytes

View File

@ -227,7 +227,7 @@ def script_to_address(script, vbyte=0):
if script[:3] == b'\x76\xa9\x14' and script[-2:] == b'\x88\xac' and len(script) == 25:
return bin_to_b58check(script[3:-2], vbyte) # pubkey hash addresses
else:
if vbyte == 111:
if vbyte in [111, 196]:
# Testnet
scripthash_byte = 196
else:
@ -313,7 +313,7 @@ def mk_multisig_script(*args): # [pubs],k or pub1,pub2...pub[n],k
else:
pubs = list(filter(lambda x: len(str(x)) >= 32, args))
k = int(args[len(pubs)])
return serialize_script([k]+pubs+[len(pubs), 174])
return serialize_script([k]+pubs+[len(pubs)]) + 'ae'
# Signing and verifying

View File

@ -5,20 +5,32 @@ from bitcoin import *
if len(sys.argv) == 1:
print "pybtctool <command> <arg1> <arg2> ..."
else:
cmd = sys.argv[2] if sys.argv[1][0] == '-' else sys.argv[1]
cmdargs, preargs, kwargs = [], [], {}
i = 2
# Process first arg tag
if sys.argv[1] == '-s':
args = re.findall(r'\S\S*',sys.stdin.read())+sys.argv[3:]
preargs.extend(re.findall(r'\S\S*', sys.stdin.read()))
elif sys.argv[1] == '-B':
args = [sys.stdin.read()]+sys.argv[3:]
preargs.extend([sys.stdin.read()])
elif sys.argv[1] == '-b':
args = [sys.stdin.read()[:-1]]+sys.argv[3:] # remove trailing \n
preargs.extend([sys.stdin.read()[:-1]])
elif sys.argv[1] == '-j':
args = [json.loads(sys.stdin.read())]+sys.argv[3:]
preargs.extend([json.loads(sys.stdin.read())])
elif sys.argv[1] == '-J':
args = json.loads(sys.stdin.read())+sys.argv[3:]
preargs.extend(json.loads(sys.stdin.read()))
else:
cmd = sys.argv[1]
args = sys.argv[2:]
o = vars()[cmd](*args)
if isinstance(o,(list,dict)): print json.dumps(o)
else: print o
i = 1
while i < len(sys.argv):
if sys.argv[i][:2] == '--':
kwargs[sys.argv[i][2:]] = sys.argv[i+1]
i += 2
else:
cmdargs.append(sys.argv[i])
i += 1
cmd = cmdargs[0]
args = preargs + cmdargs[1:]
o = vars()[cmd](*args, **kwargs)
if isinstance(o, (list, dict)):
print json.dumps(o)
else:
print o

View File

@ -5,12 +5,11 @@ except ImportError:
from distutils.core import setup
setup(name='bitcoin',
version='1.1.25',
version='1.1.28',
description='Python Bitcoin Tools',
author='Vitalik Buterin',
author_email='vbuterin@gmail.com',
url='http://github.com/vbuterin/pybitcointools',
install_requires='six==1.8.0',
packages=['bitcoin'],
scripts=['pybtctool'],
include_package_data=True,

View File

@ -222,6 +222,20 @@ class TestTransaction(unittest.TestCase):
tx2 = apply_multisignatures(tx1, 0, mscript, [sig1, sig3])
print("Outputting transaction: ", tx2)
# https://github.com/vbuterin/pybitcointools/issues/71
def test_multisig(self):
script = mk_multisig_script(["0254236f7d1124fc07600ad3eec5ac47393bf963fbf0608bcce255e685580d16d9",
"03560cad89031c412ad8619398bd43b3d673cb5bdcdac1afc46449382c6a8e0b2b"],
2)
self.assertEqual(p2sh_scriptaddr(script), "33byJBaS5N45RHFcatTSt9ZjiGb6nK4iV3")
self.assertEqual(p2sh_scriptaddr(script, 0x05), "33byJBaS5N45RHFcatTSt9ZjiGb6nK4iV3")
self.assertEqual(p2sh_scriptaddr(script, 5), "33byJBaS5N45RHFcatTSt9ZjiGb6nK4iV3")
self.assertEqual(p2sh_scriptaddr(script, 0xc4), "2MuABMvWTgpZRd4tAG25KW6YzvcoGVZDZYP")
self.assertEqual(p2sh_scriptaddr(script, 196), "2MuABMvWTgpZRd4tAG25KW6YzvcoGVZDZYP")
class TestDeterministicGenerate(unittest.TestCase):
@classmethod
@ -298,6 +312,70 @@ class TestBIP0032(unittest.TestCase):
)
)
def test_all_testnet(self):
test_vectors = [
[[], 'tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m'],
[['pub'], 'tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp'],
[[2**31], 'tprv8bxNLu25VazNnppTCP4fyhyCvBHcYtzE3wr3cwYeL4HA7yf6TLGEUdS4QC1vLT63TkjRssqJe4CvGNEC8DzW5AoPUw56D1Ayg6HY4oy8QZ9'],
[[2**31, 1], 'tprv8e8VYgZxtHsSdGrtvdxYaSrryZGiYviWzGWtDDKTGh5NMXAEB8gYSCLHpFCywNs5uqV7ghRjimALQJkRFZnUrLHpzi2pGkwqLtbubgWuQ8q'],
[[2**31, 1, 2**31 + 2], 'tprv8gjmbDPpbAirVSezBEMuwSu1Ci9EpUJWKokZTYccSZSomNMLytWyLdtDNHRbucNaRJWWHANf9AzEdWVAqahfyRjVMKbNRhBmxAM8EJr7R15'],
[[2**31, 1, 2**31 + 2, 'pub', 2, 1000000000], 'tpubDHNy3kAG39ThyiwwsgoKY4iRenXDRtce8qdCFJZXPMCJg5dsCUHayp84raLTpvyiNA9sXPob5rgqkKvkN8S7MMyXbnEhGJMW64Cf4vFAoaF']
]
mk = bip32_master_key(safe_from_hex('000102030405060708090a0b0c0d0e0f'), TESTNET_PRIVATE)
for tv in test_vectors:
left, right = self._full_derive(mk, tv[0]), tv[1]
self.assertEqual(
left,
right,
"Test vector does not match. Details:\n%s\n%s\n%s\n\%s" % (
left,
tv[0],
[x.encode('hex') if isinstance(x, str) else x for x in bip32_deserialize(left)],
[x.encode('hex') if isinstance(x, str) else x for x in bip32_deserialize(right)],
)
)
def test_extra(self):
master = bip32_master_key(safe_from_hex("000102030405060708090a0b0c0d0e0f"))
# m/0
assert bip32_ckd(master, "0") == "xprv9uHRZZhbkedL37eZEnyrNsQPFZYRAvjy5rt6M1nbEkLSo378x1CQQLo2xxBvREwiK6kqf7GRNvsNEchwibzXaV6i5GcsgyjBeRguXhKsi4R"
assert bip32_privtopub(bip32_ckd(master, "0")) == "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1"
# m/1
assert bip32_ckd(master, "1") == "xprv9uHRZZhbkedL4yTpidDvuVfrdUkTbhDHviERRBkbzbNDZeMjWzqzKAdxWhzftGDSxDmBdakjqHiZJbkwiaTEXJdjZAaAjMZEE3PMbMrPJih"
assert bip32_privtopub(bip32_ckd(master, "1")) == "xpub68Gmy5EVb2BdHTYHpekwGdcbBWax19w9HwA2DaADYvuCSSgt4YAErxxSN1KWSnmyqkwRNbnTj3XiUBKmHeC8rTjLRPjSULcDKQQgfgJDppq"
# m/0/0
assert bip32_ckd(bip32_ckd(master, "0"), "0") == "xprv9ww7sMFLzJMzur2oEQDB642fbsMS4q6JRraMVTrM9bTWBq7NDS8ZpmsKVB4YF3mZecqax1fjnsPF19xnsJNfRp4RSyexacULXMKowSACTRc"
assert bip32_privtopub(bip32_ckd(bip32_ckd(master, "0"), "0")) == "xpub6AvUGrnEpfvJ8L7GLRkBTByQ9uBvUHp9o5VxHrFxhvzV4dSWkySpNaBoLR9FpbnwRmTa69yLHF3QfcaxbWT7gWdwws5k4dpmJvqpEuMWwnj"
# m/0'
assert bip32_ckd(master, 2**31) == "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"
assert bip32_privtopub(bip32_ckd(master, 2**31)) == "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
# m/1'
assert bip32_ckd(master, 2**31 + 1) == "xprv9uHRZZhk6KAJFszJGW6LoUFq92uL7FvkBhmYiMurCWPHLJZkX2aGvNdRUBNnJu7nv36WnwCN59uNy6sxLDZvvNSgFz3TCCcKo7iutQzpg78"
assert bip32_privtopub(bip32_ckd(master, 2**31 + 1)) == "xpub68Gmy5EdvgibUN4mNXdMAcCZh4jpWiebYvh9WkKTkqvGD6tu4ZtXUAwuKSyF5DFZVmotf9UHFTGqSXo9qyDBSn47RkaN6Aedt9JbL7zcgSL"
# m/1'
assert bip32_ckd(master, 1 + 2**31) == "xprv9uHRZZhk6KAJFszJGW6LoUFq92uL7FvkBhmYiMurCWPHLJZkX2aGvNdRUBNnJu7nv36WnwCN59uNy6sxLDZvvNSgFz3TCCcKo7iutQzpg78"
assert bip32_privtopub(bip32_ckd(master, 1 + 2**31)) == "xpub68Gmy5EdvgibUN4mNXdMAcCZh4jpWiebYvh9WkKTkqvGD6tu4ZtXUAwuKSyF5DFZVmotf9UHFTGqSXo9qyDBSn47RkaN6Aedt9JbL7zcgSL"
# m/0'/0
assert bip32_ckd(bip32_ckd(master, 2**31), "0") == "xprv9wTYmMFdV23N21MM6dLNavSQV7Sj7meSPXx6AV5eTdqqGLjycVjb115Ec5LgRAXscPZgy5G4jQ9csyyZLN3PZLxoM1h3BoPuEJzsgeypdKj"
assert bip32_privtopub(bip32_ckd(bip32_ckd(master, 2**31), "0")) == "xpub6ASuArnXKPbfEVRpCesNx4P939HDXENHkksgxsVG1yNp9958A33qYoPiTN9QrJmWFa2jNLdK84bWmyqTSPGtApP8P7nHUYwxHPhqmzUyeFG"
# m/0'/0'
assert bip32_ckd(bip32_ckd(master, 2**31), 2**31) == "xprv9wTYmMFmpgaLB5Hge4YtaGqCKpsYPTD9vXWSsmdZrNU3Y2i4WoBykm6ZteeCLCCZpGxdHQuqEhM6Gdo2X6CVrQiTw6AAneF9WSkA9ewaxtS"
assert bip32_privtopub(bip32_ckd(bip32_ckd(master, 2**31), 2**31)) == "xpub6ASuArnff48dPZN9k65twQmvsri2nuw1HkS3gA3BQi12Qq3D4LWEJZR3jwCAr1NhsFMcQcBkmevmub6SLP37bNq91SEShXtEGUbX3GhNaGk"
# m/44'/0'/0'/0/0
assert bip32_ckd(bip32_ckd(bip32_ckd(bip32_ckd(bip32_ckd(master, 44 + 2**31), 2**31), 2**31), 0), 0) == "xprvA4A9CuBXhdBtCaLxwrw64Jaran4n1rgzeS5mjH47Ds8V67uZS8tTkG8jV3BZi83QqYXPcN4v8EjK2Aof4YcEeqLt688mV57gF4j6QZWdP9U"
assert bip32_privtopub(bip32_ckd(bip32_ckd(bip32_ckd(bip32_ckd(bip32_ckd(master, 44 + 2**31), 2**31), 2**31), 0), 0)) == "xpub6H9VcQiRXzkBR4RS3tU6RSXb8ouGRKQr1f1NXfTinCfTxvEhygCiJ4TDLHz1dyQ6d2Vz8Ne7eezkrViwaPo2ZMsNjVtFwvzsQXCDV6HJ3cV"
class TestStartingAddressAndScriptGenerationConsistency(unittest.TestCase):
@classmethod
@ -308,8 +386,27 @@ class TestStartingAddressAndScriptGenerationConsistency(unittest.TestCase):
for i in range(5):
a = privtoaddr(random_key())
self.assertEqual(a, script_to_address(address_to_script(a)))
self.assertEqual(a, script_to_address(address_to_script(a), 0))
self.assertEqual(a, script_to_address(address_to_script(a), 0x00))
b = privtoaddr(random_key(), 5)
self.assertEqual(b, script_to_address(address_to_script(b)))
self.assertEqual(b, script_to_address(address_to_script(b), 0))
self.assertEqual(b, script_to_address(address_to_script(b), 0x00))
self.assertEqual(b, script_to_address(address_to_script(b), 5))
self.assertEqual(b, script_to_address(address_to_script(b), 0x05))
for i in range(5):
a = privtoaddr(random_key(), 0x6f)
self.assertEqual(a, script_to_address(address_to_script(a), 111))
self.assertEqual(a, script_to_address(address_to_script(a), 0x6f))
b = privtoaddr(random_key(), 0xc4)
self.assertEqual(b, script_to_address(address_to_script(b), 111))
self.assertEqual(b, script_to_address(address_to_script(b), 0x6f))
self.assertEqual(b, script_to_address(address_to_script(b), 196))
self.assertEqual(b, script_to_address(address_to_script(b), 0xc4))
class TestRipeMD160PythonBackup(unittest.TestCase):

View File

@ -74,6 +74,10 @@ class Actions:
logging.info("Creating UiServer....")
ui_server = UiServer()
logging.info("Removing old SSL certs...")
from Crypt import CryptConnection
CryptConnection.manager.removeCerts()
logging.info("Creating FileServer....")
file_server = FileServer()
@ -231,7 +235,9 @@ class Actions:
# Peer
def peerPing(self, peer_ip, peer_port):
def peerPing(self, peer_ip, peer_port=None):
if not peer_port:
peer_port = config.fileserver_port
logging.info("Opening a simple connection server")
global file_server
from Connection import ConnectionServer
@ -243,7 +249,7 @@ class Actions:
for i in range(5):
s = time.time()
print peer.ping(),
print "Response time: %.3fs" % (time.time()-s)
print "Response time: %.3fs (crypt: %s)" % (time.time()-s, peer.connection.crypt)
time.sleep(1)

77
src/util/SslPatch.py Normal file
View File

@ -0,0 +1,77 @@
# https://journal.paul.querna.org/articles/2011/04/05/openssl-memory-use/
# Disable SSL compression to save massive memory and cpu
import logging
from Config import config
def disableSSLCompression():
import ctypes
import ctypes.util
try:
openssl = ctypes.CDLL(ctypes.util.find_library('ssl') or ctypes.util.find_library('crypto') or 'libeay32', ctypes.RTLD_GLOBAL)
openssl.SSL_COMP_get_compression_methods.restype = ctypes.c_void_p
except Exception, err:
logging.debug("Disable SSL compression failed: %s (normal on Windows)" % err)
return False
openssl.sk_zero.argtypes = [ctypes.c_void_p]
openssl.sk_zero(openssl.SSL_COMP_get_compression_methods())
logging.debug("Disabled SSL compression on %s" % openssl)
if config.disable_sslcompression:
disableSSLCompression()
# https://github.com/gevent/gevent/issues/477
# Re-add sslwrap to Python 2.7.9
__ssl__ = __import__('ssl')
try:
_ssl = __ssl__._ssl
except AttributeError:
_ssl = __ssl__._ssl2
OldSSLSocket = __ssl__.SSLSocket
class NewSSLSocket(OldSSLSocket):
#Fix SSLSocket constructor
def __init__(
self, sock, keyfile=None, certfile=None, server_side=False,
cert_reqs=__ssl__.CERT_REQUIRED, ssl_version=2, ca_certs=None,
do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None,
server_hostname=None, _context=None
):
OldSSLSocket.__init__(
self, sock, keyfile=keyfile, certfile=certfile,
server_side=server_side, cert_reqs=cert_reqs,
ssl_version=ssl_version, ca_certs=ca_certs,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs, ciphers=ciphers
)
def new_sslwrap(
sock, server_side=False, keyfile=None, certfile=None,
cert_reqs=__ssl__.CERT_NONE, ssl_version=__ssl__.PROTOCOL_SSLv23,
ca_certs=None, ciphers=None
):
context = __ssl__.SSLContext(ssl_version)
context.verify_mode = cert_reqs or __ssl__.CERT_NONE
if ca_certs:
context.load_verify_locations(ca_certs)
if certfile:
context.load_cert_chain(certfile, keyfile)
if ciphers:
context.set_ciphers(ciphers)
caller_self = inspect.currentframe().f_back.f_locals['self']
return context._wrap_socket(sock, server_side=server_side, ssl_sock=caller_self)
if not hasattr(_ssl, 'sslwrap'):
import inspect
_ssl.sslwrap = new_sslwrap
__ssl__.SSLSocket = NewSSLSocket
logging.debug("Missing sslwrap, readded.")