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
|
@ -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)),
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
101
src/Crypt/CryptConnection.py
Normal 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()
|
|
@ -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(" ", " ").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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
||||
]
|
||||
|
||||
|
|
|
@ -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
|
@ -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
|
|
@ -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")
|
||||
|
||||
|
|
123
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/content.json
vendored
Normal 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"
|
||||
}
|
385
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/css/all.css
vendored
Normal file
10
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data-default/data.json
vendored
Normal 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": [
|
||||
]
|
||||
}
|
25
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data-default/users/content-default.json
vendored
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
244
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/data.json
vendored
Normal 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\n\nZeroID\n- Sample Trusted authorization provider site with Bitmessage registration support\n\n"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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\n\nZeroName\n\n - New site for resolving namecoin domains and display registered ones\n\n\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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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\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\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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/autoupdate.png
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/direct_domains.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/domain.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/memory.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/multiuser.png
vendored
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/progressbar.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/slides.png
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/slots_memory.png
vendored
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/trayicon.png
vendored
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/zeroblog-comments.png
vendored
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/zeroid.png
vendored
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/zeroname.png
vendored
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/zerotalk-mark.png
vendored
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/zerotalk-upvote.png
vendored
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/img/zerotalk.png
vendored
Normal file
After Width: | Height: | Size: 26 KiB |
|
@ -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="
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"next_comment_id": 2,
|
||||
"comment": [
|
||||
{
|
||||
"comment_id": 1,
|
||||
"body": "Test me!",
|
||||
"post_id": 40,
|
||||
"date_added": 1432554679
|
||||
}
|
||||
],
|
||||
"comment_vote": {}
|
||||
}
|
|
@ -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="
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"next_comment_id": 2,
|
||||
"comment": [
|
||||
{
|
||||
"comment_id": 1,
|
||||
"body": "hello from Tor!",
|
||||
"post_id": 38,
|
||||
"date_added": 1432491109
|
||||
}
|
||||
],
|
||||
"comment_vote": {}
|
||||
}
|
25
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/data/users/content.json
vendored
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
54
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/dbschema.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/img/loading.gif
vendored
Normal file
After Width: | Height: | Size: 723 B |
137
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/index.html
vendored
Normal 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></li>
|
||||
<li>Inline <code>`code`</code></li>
|
||||
<li><code>```python<br>print "Code block"<br>```</code></li>
|
||||
<li>> 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 & 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 · 2 min read</span>
|
||||
<a href="?Post:23:title" class="comments-num">· <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 · 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>
|
||||
━
|
||||
<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>-->
|
||||
━
|
||||
<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>
|
1892
src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT/js/all.js
vendored
Normal file
26
src/Test/testdata/cert-rsa.pem
vendored
Normal 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
|
@ -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
|
@ -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
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc": {
|
||||
"certs": {},
|
||||
"master_seed": "024bceac1105483d66585d8a60eaf20aa8c3254b0f266e0d626ddb6114e2949a",
|
||||
"sites": {
|
||||
"1LzBg7WGjJXwJtre2iKwbZtRXezWzUMXDJ": {
|
||||
"auth_address": "15DZCFDwPjFGf9E2DFVQzZJ2voTWZEesRX",
|
||||
"auth_privatekey": "5KC4tMJdA8wJkNiVTuWCjNVBm7eeXWCSTRAnGABRaH5RhMEYyet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = ""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
10
src/lib/opensslVerify/gencert.cmd
Normal 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
|
70
src/lib/opensslVerify/openssl.cnf
Normal 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
|
BIN
src/lib/opensslVerify/openssl.exe
Normal file
BIN
src/lib/opensslVerify/ssleay32.dll
Normal 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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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:])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
10
src/main.py
|
@ -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
|
@ -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.")
|