Merge pull request #48 from cashubtc/tor/torpy

add tor
This commit is contained in:
calle
2022-11-01 22:34:39 +01:00
committed by GitHub
27 changed files with 2653 additions and 77 deletions

View File

@@ -8,6 +8,13 @@ CASHU_DIR=~/.cashu
MINT_HOST=127.0.0.1
MINT_PORT=3338
# use builtin tor, this overrides SOCKS_HOST and SOCKS_PORT
TOR=TRUE
# use custom tor proxy, use with TOR=false
SOCKS_HOST=localhost
SOCKS_PORT=9050
# MINT
MINT_PRIVATE_KEY=supersecretprivatekey

View File

@@ -4,9 +4,10 @@ on: [push, pull_request]
jobs:
poetry:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.9"]
poetry-version: ["1.2.1"]
steps:
@@ -22,6 +23,7 @@ jobs:
- name: Install dependencies
run: |
poetry install --with dev
shell: bash
- name: Run mint
env:
LIGHTNING: False
@@ -35,6 +37,7 @@ jobs:
LIGHTNING: False
MINT_HOST: localhost
MINT_PORT: 3338
TOR: False
run: |
poetry run pytest tests --cov-report xml --cov cashu
- name: Upload coverage to Codecov

2
MANIFEST.in Normal file
View File

@@ -0,0 +1,2 @@
include requirements.txt
recursive-include cashu/tor *

View File

@@ -106,7 +106,7 @@ cashu info
Returns:
```bash
Version: 0.4.2
Version: 0.5.0
Debug: False
Cashu dir: /home/user/.cashu
Wallet: wallet

View File

@@ -24,6 +24,11 @@ CASHU_DIR = env.str("CASHU_DIR", default=os.path.join(str(Path.home()), ".cashu"
CASHU_DIR = CASHU_DIR.replace("~", str(Path.home()))
assert len(CASHU_DIR), "CASHU_DIR not defined"
TOR = env.bool("TOR", default=True)
SOCKS_HOST = env.str("SOCKS_HOST", default=None)
SOCKS_PORT = env.int("SOCKS_PORT", default=9050)
LIGHTNING = env.bool("LIGHTNING", default=True)
LIGHTNING_FEE_PERCENT = env.float("LIGHTNING_FEE_PERCENT", default=1.0)
assert LIGHTNING_FEE_PERCENT >= 0, "LIGHTNING_FEE_PERCENT must be at least 0"
@@ -48,4 +53,4 @@ LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None)
LNBITS_KEY = env.str("LNBITS_KEY", default=None)
MAX_ORDER = 64
VERSION = "0.4.2"
VERSION = "0.5.0"

1609
cashu/tor/LICENCE_tor Executable file

File diff suppressed because it is too large Load Diff

0
cashu/tor/__init__.py Normal file
View File

BIN
cashu/tor/bundle/.DS_Store vendored Executable file

Binary file not shown.

389
cashu/tor/bundle/linux/LICENSE Executable file
View File

@@ -0,0 +1,389 @@
This file contains the license for Tor,
a free software project to provide anonymity on the Internet.
It also lists the licenses for other components used by Tor.
For more information about Tor, see https://www.torproject.org/.
If you got this file as a part of a larger bundle,
there may be other license terms that you should be aware of.
===============================================================================
Tor is distributed under the "3-clause BSD" license, a commonly used
software license that means Tor is both free software and open source:
Copyright (c) 2001-2004, Roger Dingledine
Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
Copyright (c) 2007-2019, The Tor Project, Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the names of the copyright owners nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
src/ext/strlcat.c and src/ext/strlcpy.c by Todd C. Miller are licensed
under the following license:
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
src/ext/tor_queue.h is licensed under the following license:
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
===============================================================================
src/ext/csiphash.c is licensed under the following license:
Copyright (c) 2013 Marek Majkowski <marek@popcount.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================================================
Trunnel is distributed under this license:
Copyright 2014 The Tor Project, Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the names of the copyright owners nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
getdelim.c is distributed under this license:
Copyright (c) 2011 The NetBSD Foundation, Inc.
All rights reserved.
This code is derived from software contributed to The NetBSD Foundation
by Christos Zoulas.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
===============================================================================
src/config/geoip and src/config/geoip6:
These files are based on the IPFire Location Database. For more
information, see https://location.ipfire.org/.
The data is distributed under a creative commons "BY-SA 4.0" license.
Find the full license terms at:
https://creativecommons.org/licenses/by-sa/4.0/
===============================================================================
m4/pc_from_ucontext.m4 is available under the following license. Note that
it is *not* built into the Tor software.
Copyright (c) 2005, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
m4/pkg.m4 is available under the following license. Note that
it is *not* built into the Tor software.
pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
serial 1 (pkg-config-0.24)
Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
As a special exception to the GNU General Public License, if you
distribute this file as part of a program that contains a
configuration script generated by Autoconf, you may include it under
the same distribution terms that you use for the rest of that program.
===============================================================================
src/ext/readpassphrase.[ch] are distributed under this license:
Copyright (c) 2000-2002, 2007 Todd C. Miller <Todd.Miller@courtesan.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Sponsored in part by the Defense Advanced Research Projects
Agency (DARPA) and Air Force Research Laboratory, Air Force
Materiel Command, USAF, under agreement number F39502-99-1-0512.
===============================================================================
src/ext/mulodi4.c is distributed under this license:
=========================================================================
compiler_rt License
=========================================================================
The compiler_rt library is dual licensed under both the
University of Illinois "BSD-Like" license and the MIT license.
As a user of this code you may choose to use it under either
license. As a contributor, you agree to allow your code to be
used under both.
Full text of the relevant licenses is included below.
=========================================================================
University of Illinois/NCSA
Open Source License
Copyright (c) 2009-2016 by the contributors listed in CREDITS.TXT
All rights reserved.
Developed by:
LLVM Team
University of Illinois at Urbana-Champaign
http://llvm.org
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal with the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimers.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimers in the documentation and/or other materials
provided with the distribution.
* Neither the names of the LLVM Team, University of Illinois
at Urbana-Champaign, nor the names of its contributors may
be used to endorse or promote products derived from this
Software without specific prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS WITH THE SOFTWARE.
=========================================================================
Copyright (c) 2009-2015 by the contributors listed in CREDITS.TXT
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
=========================================================================
Copyrights and Licenses for Third Party Software Distributed with LLVM:
=========================================================================
The LLVM software contains code written by third parties. Such
software will have its own individual LICENSE.TXT file in the
directory in which it appears. This file will describe the
copyrights, license, and restrictions which apply to that code.
The disclaimer of warranty in the University of Illinois Open
Source License applies to all code in the LLVM Distribution, and
nothing in any of the other licenses gives permission to use the
names of the LLVM Team or the University of Illinois to endorse
or promote products derived from this Software.
===============================================================================
If you got Tor as a static binary with OpenSSL included, then you should know:
"This product includes software developed by the OpenSSL Project
for use in the OpenSSL Toolkit (http://www.openssl.org/)"
===============================================================================

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
cashu/tor/bundle/linux/tor Executable file

Binary file not shown.

Binary file not shown.

BIN
cashu/tor/bundle/mac/tor Executable file

Binary file not shown.

34
cashu/tor/timeout.py Executable file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python
import os
import subprocess
import sys
import time
def main():
assert len(sys.argv) > 2, "Usage: timeout.py [seconds] [command...]"
# cmd = " ".join(sys.argv[2:]) # for with shell=True
cmd = sys.argv[2:]
timeout = int(sys.argv[1])
assert timeout > 0, "timeout (in seconds) must be a positive integer."
start_time = time.time()
pro = subprocess.Popen(cmd, shell=False)
while time.time() < start_time + timeout + 1:
time.sleep(1)
pro.terminate()
pro.wait()
pro.kill()
pro.wait()
# we kill the child processes as well (tor.py and tor) just to be sure
os.kill(pro.pid + 1, 15)
os.kill(pro.pid + 1, 9)
os.kill(pro.pid + 2, 15)
os.kill(pro.pid + 2, 9)
if __name__ == "__main__":
main()

177
cashu/tor/tor.py Executable file
View File

@@ -0,0 +1,177 @@
import os
import pathlib
import platform
import socket
import subprocess
import sys
import time
from loguru import logger
class TorProxy:
def __init__(self, timeout=False):
self.base_path = pathlib.Path(__file__).parent.resolve()
self.platform = platform.system()
self.timeout = 60 * 60 if timeout else 0 # seconds
self.tor_proc = None
self.pid_file = os.path.join(self.base_path, "tor.pid")
self.tor_pid = None
self.startup_finished = True
self.tor_running = self.is_running()
@classmethod
def check_platform(cls):
if platform.system() == "Linux":
if platform.machine() != "x86_64":
logger.debug("Builtin Tor not supported on this platform.")
return False
return True
def log_status(self):
logger.debug(f"Tor binary path: {self.tor_path()}")
logger.debug(f"Tor config path: {self.tor_config_path()}")
logger.debug(f"Tor running: {self.tor_running}")
logger.debug(
f"Tor port open: {self.is_port_open()}",
)
logger.debug(f"Tor PID in tor.pid: {self.read_pid()}")
logger.debug(f"Tor PID running: {self.signal_pid(self.read_pid())}")
def run_daemon(self, verbose=False):
if not self.check_platform() or self.tor_running:
return
self.log_status()
logger.debug("Starting Tor")
cmd = [f"{self.tor_path()}", "--defaults-torrc", f"{self.tor_config_path()}"]
if self.timeout:
logger.debug(f"Starting tor with timeout {self.timeout}s")
cmd = [
sys.executable,
os.path.join(self.base_path, "timeout.py"),
f"{self.timeout}",
] + cmd
env = dict(os.environ)
if platform.system() == "Linux":
env["LD_LIBRARY_PATH"] = os.path.dirname(self.tor_path())
elif platform.system() == "Darwin":
env["DYLD_LIBRARY_PATH"] = os.path.dirname(self.tor_path())
self.tor_proc = subprocess.Popen(
cmd,
env=env,
shell=False,
close_fds=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
start_new_session=True,
)
logger.debug("Running tor daemon with pid {}".format(self.tor_proc.pid))
with open(self.pid_file, "w", encoding="utf-8") as f:
f.write(str(self.tor_proc.pid))
self.wait_until_startup(verbose=verbose)
def stop_daemon(self, pid=None):
pid = pid or self.tor_proc.pid if self.tor_proc else None
if self.tor_proc and pid:
self.signal_pid(pid, 15) # sigterm
time.sleep(5)
self.signal_pid(pid, 9) # sigkill
if os.path.exists(self.pid_file):
os.remove(self.pid_file)
def tor_path(self):
PATHS = {
"Windows": os.path.join(self.base_path, "bundle", "win", "Tor", "tor.exe"),
"Linux": os.path.join(self.base_path, "bundle", "linux", "tor"),
"Darwin": os.path.join(self.base_path, "bundle", "mac", "tor"),
}
# make sure that file has correct permissions
try:
logger.debug(f"Setting permissions of {PATHS[platform.system()]} to 755")
os.chmod(PATHS[platform.system()], 0o755)
except:
logger.debug("Exception: could not set permissions of Tor binary")
return PATHS[platform.system()]
def tor_config_path(self):
return os.path.join(self.base_path, "torrc")
def is_running(self):
# another tor proxy is running
if not self.is_port_open():
return False
# our tor proxy running from a previous session
if self.signal_pid(self.read_pid()):
return True
# current attached process running
return self.tor_proc and self.tor_proc.poll() is None
def wait_until_startup(self, verbose=False):
if not self.check_platform():
return
if self.is_port_open():
return
if self.tor_proc is None:
raise Exception("Tor proxy not attached.")
if not self.tor_proc.stdout:
raise Exception("could not get tor stdout.")
if verbose:
print("Starting Tor...", end="", flush=True)
for line in self.tor_proc.stdout:
# print(line)
if verbose:
print(".", end="", flush=True)
if "Bootstrapped 100%" in str(line):
if verbose:
print("done", flush=True)
break
# tor is ready
self.startup_finished = True
return
def is_port_open(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
location = ("127.0.0.1", 9050)
try:
s.connect(location)
s.close()
return True
except Exception as e:
return False
def read_pid(self):
if not os.path.isfile(self.pid_file):
return None
with open(self.pid_file, "r") as f:
pid = f.readlines()
# check if pid is valid
if len(pid) == 0 or not int(pid[0]) > 0:
return None
return pid[0]
def signal_pid(self, pid, signal=0):
"""
Checks whether a process with pid is running (signal 0 is not a kill signal!)
or stops (signal 15) or kills it (signal 9).
"""
if not pid:
return False
if not int(pid) > 0:
return False
pid = int(pid)
try:
os.kill(pid, signal)
except:
return False
else:
return True
if __name__ == "__main__":
tor = TorProxy(timeout=True)
tor.run_daemon(verbose=True)
# time.sleep(5)
# logger.debug("Killing Tor")
# tor.stop_daemon()

254
cashu/tor/torrc Executable file
View File

@@ -0,0 +1,254 @@
## Configuration file for a typical Tor user
## Last updated 28 February 2019 for Tor 0.3.5.1-alpha.
## (may or may not work for much older or much newer versions of Tor.)
##
## Lines that begin with "## " try to explain what's going on. Lines
## that begin with just "#" are disabled commands: you can enable them
## by removing the "#" symbol.
##
## See 'man tor', or https://www.torproject.org/docs/tor-manual.html,
## for more options you can use in this file.
##
## Tor will look for this file in various places based on your platform:
## https://www.torproject.org/docs/faq#torrc
## Tor opens a SOCKS proxy on port 9050 by default -- even if you don't
## configure one below. Set "SOCKSPort 0" if you plan to run Tor only
## as a relay, and not make any local application connections yourself.
SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections.
#SOCKSPort 192.168.0.1:9100 # Bind to this address:port too.
## Entry policies to allow/deny SOCKS requests based on IP address.
## First entry that matches wins. If no SOCKSPolicy is set, we accept
## all (and only) requests that reach a SOCKSPort. Untrusted users who
## can access your SOCKSPort may be able to learn about the connections
## you make.
#SOCKSPolicy accept 192.168.0.0/16
#SOCKSPolicy accept6 FC00::/7
#SOCKSPolicy reject *
SOCKSPolicy accept 127.0.0.1
## Logs go to stdout at level "notice" unless redirected by something
## else, like one of the below lines. You can have as many Log lines as
## you want.
##
## We advise using "notice" in most cases, since anything more verbose
## may provide sensitive information to an attacker who obtains the logs.
##
## Send all messages of level 'notice' or higher to /usr/local/var/log/tor/notices.log
#Log notice file /usr/local/var/log/tor/notices.log
## Send every possible message to /usr/local/var/log/tor/debug.log
#Log debug file /usr/local/var/log/tor/debug.log
## Use the system log instead of Tor's logfiles
#Log notice syslog
## To send all messages to stderr:
#Log debug stderr
## Uncomment this to start the process in the background... or use
## --runasdaemon 1 on the command line. This is ignored on Windows;
## see the FAQ entry if you want Tor to run as an NT service.
#RunAsDaemon 1
## The directory for keeping all the keys/etc. By default, we store
## things in $HOME/.tor on Unix, and in Application Data\tor on Windows.
#DataDirectory /usr/local/var/lib/tor
## The port on which Tor will listen for local connections from Tor
## controller applications, as documented in control-spec.txt.
ControlPort 9051
## If you enable the controlport, be sure to enable one of these
## authentication methods, to prevent attackers from accessing it.
HashedControlPassword 16:3F85DAF2A2A34032603235343E19ABBE3CB6BF03F1443984F21EEE749F
#CookieAuthentication 1
############### This section is just for location-hidden services ###
## Once you have configured a hidden service, you can look at the
## contents of the file ".../hidden_service/hostname" for the address
## to tell people.
##
## HiddenServicePort x y:z says to redirect requests on port x to the
## address y:z.
#HiddenServiceDir /usr/local/var/lib/tor/hidden_service/
#HiddenServicePort 80 127.0.0.1:80
#HiddenServiceDir /usr/local/var/lib/tor/other_hidden_service/
#HiddenServicePort 80 127.0.0.1:80
#HiddenServicePort 22 127.0.0.1:22
################ This section is just for relays #####################
#
## See https://www.torproject.org/docs/tor-doc-relay for details.
## Required: what port to advertise for incoming Tor connections.
#ORPort 9001
## If you want to listen on a port other than the one advertised in
## ORPort (e.g. to advertise 443 but bind to 9090), you can do it as
## follows. You'll need to do ipchains or other port forwarding
## yourself to make this work.
#ORPort 443 NoListen
#ORPort 127.0.0.1:9090 NoAdvertise
## If you want to listen on IPv6 your numeric address must be explictly
## between square brackets as follows. You must also listen on IPv4.
#ORPort [2001:DB8::1]:9050
## The IP address or full DNS name for incoming connections to your
## relay. Leave commented out and Tor will guess.
Address 1.1.1.1
## If you have multiple network interfaces, you can specify one for
## outgoing traffic to use.
## OutboundBindAddressExit will be used for all exit traffic, while
## OutboundBindAddressOR will be used for all OR and Dir connections
## (DNS connections ignore OutboundBindAddress).
## If you do not wish to differentiate, use OutboundBindAddress to
## specify the same address for both in a single line.
#OutboundBindAddressExit 10.0.0.4
#OutboundBindAddressOR 10.0.0.5
## A handle for your relay, so people don't have to refer to it by key.
## Nicknames must be between 1 and 19 characters inclusive, and must
## contain only the characters [a-zA-Z0-9].
## If not set, "Unnamed" will be used.
#Nickname ididnteditheconfig
## Define these to limit how much relayed traffic you will allow. Your
## own traffic is still unthrottled. Note that RelayBandwidthRate must
## be at least 75 kilobytes per second.
## Note that units for these config options are bytes (per second), not
## bits (per second), and that prefixes are binary prefixes, i.e. 2^10,
## 2^20, etc.
#RelayBandwidthRate 100 KBytes # Throttle traffic to 100KB/s (800Kbps)
#RelayBandwidthBurst 200 KBytes # But allow bursts up to 200KB (1600Kb)
## Use these to restrict the maximum traffic per day, week, or month.
## Note that this threshold applies separately to sent and received bytes,
## not to their sum: setting "40 GB" may allow up to 80 GB total before
## hibernating.
##
## Set a maximum of 40 gigabytes each way per period.
#AccountingMax 40 GBytes
## Each period starts daily at midnight (AccountingMax is per day)
#AccountingStart day 00:00
## Each period starts on the 3rd of the month at 15:00 (AccountingMax
## is per month)
#AccountingStart month 3 15:00
## Administrative contact information for this relay or bridge. This line
## can be used to contact you if your relay or bridge is misconfigured or
## something else goes wrong. Note that we archive and publish all
## descriptors containing these lines and that Google indexes them, so
## spammers might also collect them. You may want to obscure the fact that
## it's an email address and/or generate a new address for this purpose.
##
## If you are running multiple relays, you MUST set this option.
##
#ContactInfo Random Person <nobody AT example dot com>
## You might also include your PGP or GPG fingerprint if you have one:
#ContactInfo 0xFFFFFFFF Random Person <nobody AT example dot com>
## Uncomment this to mirror directory information for others. Please do
## if you have enough bandwidth.
#DirPort 9030 # what port to advertise for directory connections
## If you want to listen on a port other than the one advertised in
## DirPort (e.g. to advertise 80 but bind to 9091), you can do it as
## follows. below too. You'll need to do ipchains or other port
## forwarding yourself to make this work.
#DirPort 80 NoListen
#DirPort 127.0.0.1:9091 NoAdvertise
## Uncomment to return an arbitrary blob of html on your DirPort. Now you
## can explain what Tor is if anybody wonders why your IP address is
## contacting them. See contrib/tor-exit-notice.html in Tor's source
## distribution for a sample.
#DirPortFrontPage /usr/local/etc/tor/tor-exit-notice.html
## Uncomment this if you run more than one Tor relay, and add the identity
## key fingerprint of each Tor relay you control, even if they're on
## different networks. You declare it here so Tor clients can avoid
## using more than one of your relays in a single circuit. See
## https://www.torproject.org/docs/faq#MultipleRelays
## However, you should never include a bridge's fingerprint here, as it would
## break its concealability and potentially reveal its IP/TCP address.
##
## If you are running multiple relays, you MUST set this option.
##
## Note: do not use MyFamily on bridge relays.
#MyFamily $keyid,$keyid,...
## Uncomment this if you want your relay to be an exit, with the default
## exit policy (or whatever exit policy you set below).
## (If ReducedExitPolicy, ExitPolicy, or IPv6Exit are set, relays are exits.
## If none of these options are set, relays are non-exits.)
#ExitRelay 1
## Uncomment this if you want your relay to allow IPv6 exit traffic.
## (Relays do not allow any exit traffic by default.)
#IPv6Exit 1
## Uncomment this if you want your relay to be an exit, with a reduced set
## of exit ports.
#ReducedExitPolicy 1
## Uncomment these lines if you want your relay to be an exit, with the
## specified set of exit IPs and ports.
##
## A comma-separated list of exit policies. They're considered first
## to last, and the first match wins.
##
## If you want to allow the same ports on IPv4 and IPv6, write your rules
## using accept/reject *. If you want to allow different ports on IPv4 and
## IPv6, write your IPv6 rules using accept6/reject6 *6, and your IPv4 rules
## using accept/reject *4.
##
## If you want to _replace_ the default exit policy, end this with either a
## reject *:* or an accept *:*. Otherwise, you're _augmenting_ (prepending to)
## the default exit policy. Leave commented to just use the default, which is
## described in the man page or at
## https://www.torproject.org/documentation.html
##
## Look at https://www.torproject.org/faq-abuse.html#TypicalAbuses
## for issues you might encounter if you use the default exit policy.
##
## If certain IPs and ports are blocked externally, e.g. by your firewall,
## you should update your exit policy to reflect this -- otherwise Tor
## users will be told that those destinations are down.
##
## For security, by default Tor rejects connections to private (local)
## networks, including to the configured primary public IPv4 and IPv6 addresses,
## and any public IPv4 and IPv6 addresses on any interface on the relay.
## See the man page entry for ExitPolicyRejectPrivate if you want to allow
## "exit enclaving".
##
#ExitPolicy accept *:6660-6667,reject *:* # allow irc ports on IPv4 and IPv6 but no more
#ExitPolicy accept *:119 # accept nntp ports on IPv4 and IPv6 as well as default exit policy
#ExitPolicy accept *4:119 # accept nntp ports on IPv4 only as well as default exit policy
#ExitPolicy accept6 *6:119 # accept nntp ports on IPv6 only as well as default exit policy
#ExitPolicy reject *:* # no exits allowed
## Bridge relays (or "bridges") are Tor relays that aren't listed in the
## main directory. Since there is no complete public list of them, even an
## ISP that filters connections to all the known Tor relays probably
## won't be able to block all the bridges. Also, websites won't treat you
## differently because they won't know you're running Tor. If you can
## be a real relay, please do; but if not, be a bridge!
##
## Warning: when running your Tor as a bridge, make sure than MyFamily is
## NOT configured.
#BridgeRelay 1
## By default, Tor will advertise your bridge to users through various
## mechanisms like https://bridges.torproject.org/. If you want to run
## a private bridge, for example because you'll give out your bridge
## address manually to your friends, uncomment this line:
#PublishServerDescriptor 0
## Configuration options can be imported from files or folders using the %include
## option with the value being a path. If the path is a file, the options from the
## file will be parsed as if they were written where the %include option is. If
## the path is a folder, all files on that folder will be parsed following lexical
## order. Files starting with a dot are ignored. Files on subfolders are ignored.
## The %include option can be used recursively.
#%include /etc/torrc.d/
#%include /etc/torrc.custom
#HTTPTunnelPort 8118

View File

@@ -19,7 +19,18 @@ from loguru import logger
from cashu.core.base import Proof
from cashu.core.helpers import sum_proofs
from cashu.core.migrations import migrate_databases
from cashu.core.settings import CASHU_DIR, DEBUG, ENV_FILE, LIGHTNING, MINT_URL, VERSION
from cashu.core.settings import (
CASHU_DIR,
DEBUG,
ENV_FILE,
LIGHTNING,
MINT_URL,
SOCKS_HOST,
SOCKS_PORT,
TOR,
VERSION,
)
from cashu.tor.tor import TorProxy
from cashu.wallet import migrations
from cashu.wallet.crud import (
get_lightning_invoices,
@@ -60,6 +71,13 @@ def cli(ctx, host: str, walletname: str):
ctx.obj["HOST"] = host
ctx.obj["WALLET_NAME"] = walletname
wallet = Wallet(ctx.obj["HOST"], os.path.join(CASHU_DIR, walletname))
if TOR and not TorProxy().check_platform():
print(
"WARNING: Your settings say TOR=true but the built-in Tor bundle is not supported on your system. Your IP will be visible to the mint! Please install Tor manually and set TOR=false and SOCKS_HOST=localhost and SOCKS_PORT=9050 in your Cashu config (recommended) or turn off Tor altogether by setting TOR=false (not recommended)."
)
print("")
ctx.obj["WALLET"] = wallet
asyncio.run(init_wallet(wallet))
pass
@@ -74,6 +92,35 @@ def coro(f):
return wrapper
@cli.command("pay", help="Pay Lightning invoice.")
@click.argument("invoice", type=str)
@click.option(
"--yes", "-y", default=False, is_flag=True, help="Skip confirmation.", type=bool
)
@click.pass_context
@coro
async def pay(ctx, invoice: str, yes: bool):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
wallet.status()
amount, fees = await wallet.get_pay_amount_with_fees(invoice)
if not yes:
click.confirm(
f"Pay {amount - fees} sat ({amount} sat incl. fees)?",
abort=True,
default=True,
)
print(f"Paying Lightning invoice ...")
assert amount > 0, "amount is not positive"
if wallet.available_balance < amount:
print("Error: Balance too low.")
return
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount)
await wallet.pay_lightning(send_proofs, invoice)
wallet.status()
@cli.command("invoice", help="Create Lighting invoice.")
@click.argument("amount", type=int)
@click.option("--hash", default="", help="Hash of the paid invoice.", type=str)
@@ -120,39 +167,18 @@ async def invoice(ctx, amount: int, hash: str):
return
@cli.command("pay", help="Pay Lightning invoice.")
@click.argument("invoice", type=str)
@cli.command("balance", help="Balance.")
@click.option(
"--yes", "-y", default=False, is_flag=True, help="Skip confirmation.", type=bool
"--verbose",
"-v",
default=False,
is_flag=True,
help="Show pending tokens as well.",
type=bool,
)
@click.pass_context
@coro
async def pay(ctx, invoice: str, yes: bool):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
wallet.status()
amount, fees = await wallet.get_pay_amount_with_fees(invoice)
if not yes:
click.confirm(
f"Pay {amount - fees} sat ({amount} sat incl. fees)?",
abort=True,
default=True,
)
print(f"Paying Lightning invoice ...")
assert amount > 0, "amount is not positive"
if wallet.available_balance < amount:
print("Error: Balance too low.")
return
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount)
await wallet.pay_lightning(send_proofs, invoice)
wallet.status()
@cli.command("balance", help="Balance.")
@click.pass_context
@coro
async def balance(ctx):
async def balance(ctx, verbose):
wallet: Wallet = ctx.obj["WALLET"]
keyset_balances = wallet.balance_per_keyset()
if len(keyset_balances) > 1:
@@ -163,14 +189,17 @@ async def balance(ctx):
f"Keyset: {k or 'undefined'} Balance: {v['balance']} sat (available: {v['available']} sat)"
)
print("")
if verbose:
print(
f"Balance: {wallet.balance} sat (available: {wallet.available_balance} sat in {len([p for p in wallet.proofs if not p.reserved])} tokens)"
)
else:
print(f"Balance: {wallet.available_balance} sat")
@cli.command("send", help="Send coins.")
@cli.command("send", help="Send tokens.")
@click.argument("amount", type=int)
@click.option("--lock", "-l", default=None, help="Lock coins (P2SH).", type=str)
@click.option("--lock", "-l", default=None, help="Lock tokens (P2SH).", type=str)
@click.pass_context
@coro
async def send(ctx, amount: int, lock: str):
@@ -186,19 +215,19 @@ async def send(ctx, amount: int, lock: str):
_, send_proofs = await wallet.split_to_send(
wallet.proofs, amount, lock, set_reserved=True
)
coin = await wallet.serialize_proofs(
token = await wallet.serialize_proofs(
send_proofs, hide_secrets=True if lock and not p2sh else False
)
print(coin)
print(token)
wallet.status()
@cli.command("receive", help="Receive coins.")
@click.argument("coin", type=str)
@click.option("--lock", "-l", default=None, help="Unlock coins.", type=str)
@cli.command("receive", help="Receive tokens.")
@click.argument("token", type=str)
@click.option("--lock", "-l", default=None, help="Unlock tokens.", type=str)
@click.pass_context
@coro
async def receive(ctx, coin: str, lock: str):
async def receive(ctx, token: str, lock: str):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
wallet.status()
@@ -215,25 +244,25 @@ async def receive(ctx, coin: str, lock: str):
signature = p2shscripts[0].signature
else:
script, signature = None, None
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(token))]
_, _ = await wallet.redeem(proofs, scnd_script=script, scnd_siganture=signature)
wallet.status()
@cli.command("burn", help="Burn spent coins.")
@click.argument("coin", required=False, type=str)
@click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent coins.")
@cli.command("burn", help="Burn spent tokens.")
@click.argument("token", required=False, type=str)
@click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent tokens.")
@click.option(
"--force", "-f", default=False, is_flag=True, help="Force check on all coins."
"--force", "-f", default=False, is_flag=True, help="Force check on all tokens."
)
@click.pass_context
@coro
async def burn(ctx, coin: str, all: bool, force: bool):
async def burn(ctx, token: str, all: bool, force: bool):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
if not (all or coin or force) or (coin and all):
if not (all or token or force) or (token and all):
print(
"Error: enter a coin or use --all to burn all pending coins or --force to check all coins."
"Error: enter a token or use --all to burn all pending tokens or --force to check all tokens."
)
return
if all:
@@ -244,13 +273,13 @@ async def burn(ctx, coin: str, all: bool, force: bool):
proofs = wallet.proofs
else:
# check only the specified ones
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(token))]
wallet.status()
await wallet.invalidate(proofs)
wallet.status()
@cli.command("pending", help="Show pending coins.")
@cli.command("pending", help="Show pending tokens.")
@click.pass_context
@coro
async def pending(ctx):
@@ -264,8 +293,8 @@ async def pending(ctx):
groupby(sorted_proofs, key=itemgetter("send_id"))
):
grouped_proofs = list(value)
coin = await wallet.serialize_proofs(grouped_proofs)
coin_hidden_secret = await wallet.serialize_proofs(
token = await wallet.serialize_proofs(grouped_proofs)
token_hidden_secret = await wallet.serialize_proofs(
grouped_proofs, hide_secrets=True
)
reserved_date = datetime.utcfromtimestamp(
@@ -274,7 +303,7 @@ async def pending(ctx):
print(
f"#{i} Amount: {sum_proofs(grouped_proofs)} sat Time: {reserved_date} ID: {key}\n"
)
print(f"With secret: {coin}\n\nSecretless: {coin_hidden_secret}\n")
print(f"With secret: {token}\n\nSecretless: {token_hidden_secret}\n")
print(f"--------------------------\n")
wallet.status()
@@ -287,16 +316,16 @@ async def lock(ctx):
p2shscript = await wallet.create_p2sh_lock()
txin_p2sh_address = p2shscript.address
print("---- Pay to script hash (P2SH) ----\n")
print("Use a lock to receive coins that only you can unlock.")
print("Use a lock to receive tokens that only you can unlock.")
print("")
print(f"Public receiving lock: P2SH:{txin_p2sh_address}")
print("")
print(
f"Anyone can send coins to this lock:\n\ncashu send <amount> --lock P2SH:{txin_p2sh_address}"
f"Anyone can send tokens to this lock:\n\ncashu send <amount> --lock P2SH:{txin_p2sh_address}"
)
print("")
print(
f"Only you can receive coins from this lock:\n\ncashu receive <coin> --lock P2SH:{txin_p2sh_address}\n"
f"Only you can receive tokens from this lock:\n\ncashu receive <token> --lock P2SH:{txin_p2sh_address}\n"
)
@@ -314,7 +343,7 @@ async def locks(ctx):
print(f"Script: {l.script}")
print(f"Signature: {l.signature}")
print("")
print(f"Receive: cashu receive <coin> --lock P2SH:{l.address}")
print(f"Receive: cashu receive <token> --lock P2SH:{l.address}")
print("")
print(f"--------------------------\n")
else:
@@ -387,10 +416,15 @@ async def wallets(ctx):
@coro
async def info(ctx):
print(f"Version: {VERSION}")
print(f"Wallet: {ctx.obj['WALLET_NAME']}")
if DEBUG:
print(f"Debug: {DEBUG}")
print(f"Cashu dir: {CASHU_DIR}")
if ENV_FILE:
print(f"Settings: {ENV_FILE}")
print(f"Wallet: {ctx.obj['WALLET_NAME']}")
if TOR:
print(f"Tor enabled: {TOR}")
if SOCKS_HOST:
print(f"Socks proxy: {SOCKS_HOST}:{SOCKS_PORT}")
print(f"Mint URL: {MINT_URL}")
return

View File

@@ -35,8 +35,9 @@ from cashu.core.script import (
step2_carol_sign_tx,
)
from cashu.core.secp import PublicKey
from cashu.core.settings import DEBUG, VERSION
from cashu.core.settings import DEBUG, SOCKS_HOST, SOCKS_PORT, TOR, VERSION
from cashu.core.split import amount_split
from cashu.tor.tor import TorProxy
from cashu.wallet.crud import (
get_keyset,
get_proofs,
@@ -54,11 +55,30 @@ from cashu.wallet.crud import (
class LedgerAPI:
keys: Dict[int, str]
keyset: str
tor: TorProxy
def __init__(self, url):
self.url = url
self.s = requests.Session()
self.s.headers.update({"Client-version": VERSION})
def _set_requests(self):
s = requests.Session()
s.headers.update({"Client-version": VERSION})
socks_host, socks_port = None, None
if TOR and TorProxy().check_platform():
self.tor = TorProxy(timeout=True)
self.tor.run_daemon(verbose=True)
socks_host, socks_port = "localhost", 9050
else:
socks_host, socks_port = SOCKS_HOST, SOCKS_PORT
if socks_host and socks_port:
proxies = {
"http": f"socks5://{socks_host}:{socks_port}",
"https": f"socks5://{socks_host}:{socks_port}",
}
s.proxies.update(proxies)
s.headers.update({"User-Agent": scrts.token_urlsafe(8)})
return s
def _construct_proofs(
self, promises: List[BlindedSignature], secrets: List[str], rs: List[str]
@@ -154,6 +174,7 @@ class LedgerAPI:
"""
async def _get_keys(self, url):
self.s = self._set_requests()
resp = self.s.get(
url + "/keys",
)
@@ -168,6 +189,7 @@ class LedgerAPI:
return keyset
async def _get_keysets(self, url):
self.s = self._set_requests()
resp = self.s.get(
url + "/keysets",
)
@@ -178,6 +200,7 @@ class LedgerAPI:
def request_mint(self, amount):
"""Requests a mint from the server and returns Lightning invoice."""
self.s = self._set_requests()
resp = self.s.get(self.url + "/mint", params={"amount": amount})
resp.raise_for_status()
return_dict = resp.json()
@@ -189,7 +212,7 @@ class LedgerAPI:
secrets = [self._generate_secret() for s in range(len(amounts))]
await self._check_used_secrets(secrets)
payloads, rs = self._construct_outputs(amounts, secrets)
self.s = self._set_requests()
resp = self.s.post(
self.url + "/mint",
json=payloads.dict(),
@@ -245,6 +268,7 @@ class LedgerAPI:
"proofs": {i: proofs_include for i in range(len(proofs))},
}
self.s = self._set_requests()
resp = self.s.post(
self.url + "/split",
json=split_payload.dict(include=_splitrequest_include_fields(proofs)),
@@ -277,6 +301,7 @@ class LedgerAPI:
"proofs": {i: {"secret"} for i in range(len(proofs))},
}
self.s = self._set_requests()
resp = self.s.post(
self.url + "/check",
json=payload.dict(include=_check_spendable_include_fields(proofs)),
@@ -289,6 +314,7 @@ class LedgerAPI:
async def check_fees(self, payment_request: str):
"""Checks whether the Lightning payment is internal."""
payload = CheckFeesRequest(pr=payment_request)
self.s = self._set_requests()
resp = self.s.post(
self.url + "/checkfees",
json=payload.dict(),
@@ -313,6 +339,7 @@ class LedgerAPI:
"proofs": {i: proofs_include for i in range(len(proofs))},
}
self.s = self._set_requests()
resp = self.s.post(
self.url + "/melt",
json=payload.dict(include=_meltequest_include_fields(proofs)),
@@ -527,9 +554,10 @@ class Wallet(LedgerAPI):
return sum_proofs([p for p in self.proofs if not p.reserved])
def status(self):
print(
f"Balance: {self.balance} sat (available: {self.available_balance} sat in {len([p for p in self.proofs if not p.reserved])} tokens)"
)
# print(
# f"Balance: {self.balance} sat (available: {self.available_balance} sat in {len([p for p in self.proofs if not p.reserved])} tokens)"
# )
print(f"Balance: {self.available_balance} sat")
def balance_per_keyset(self):
return {

15
poetry.lock generated
View File

@@ -397,6 +397,14 @@ python-versions = ">=3.6.8"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "PySocks"
version = "1.7.1"
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pytest"
version = "7.1.3"
@@ -658,7 +666,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "6ba135e91c4ec10d7f55569ff65531717da6d7070d41355173f06f2cdfc1797f"
content-hash = "aa0c3cf3a023b4143939128be203cf0c519341abc7cd7ef0b200694f8b925b78"
[metadata.files]
anyio = [
@@ -974,6 +982,11 @@ pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
PySocks = [
{file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
{file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
]
pytest = [
{file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
{file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "cashu"
version = "0.4.2"
version = "0.5.0"
description = "Ecash wallet and mint."
authors = ["calle <callebtc@protonmail.com>"]
license = "MIT"
@@ -23,6 +23,7 @@ secp256k1 = "^0.14.0"
sqlalchemy-aio = "^0.17.0"
python-bitcoinlib = "^0.11.2"
h11 = "0.12.0"
PySocks = "^1.7.1"
[tool.poetry.dev-dependencies]
black = {version = "^22.8.0", allow-prereleases = true}

View File

@@ -23,6 +23,7 @@ py==1.11.0 ; python_version >= "3.7" and python_version < "4.0"
pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0"
pydantic==1.10.2 ; python_version >= "3.7" and python_version < "4.0"
pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0"
pysocks==1.7.1 ; python_version >= "3.7" and python_version < "4.0"
pytest-asyncio==0.19.0 ; python_version >= "3.7" and python_version < "4.0"
pytest==7.1.3 ; python_version >= "3.7" and python_version < "4.0"
python-bitcoinlib==0.11.2 ; python_version >= "3.7" and python_version < "4.0"

View File

@@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli:cli"]}
setuptools.setup(
name="cashu",
version="0.4.2",
version="0.5.0",
description="Ecash wallet and mint with Bitcoin Lightning support",
long_description=long_description,
long_description_content_type="text/markdown",

View File

@@ -1,11 +1,9 @@
import time
from typing import List
import pytest
import pytest_asyncio
from cashu.core.base import BlindedMessage, Proof
from cashu.core.helpers import async_unwrap, sum_proofs
from cashu.core.migrations import migrate_databases
SERVER_ENDPOINT = "http://localhost:3338"
@@ -13,8 +11,7 @@ SERVER_ENDPOINT = "http://localhost:3338"
import os
from cashu.core.db import Database
from cashu.core.settings import MAX_ORDER, MINT_PRIVATE_KEY
from cashu.lightning.lnbits import LNbitsWallet
from cashu.core.settings import MAX_ORDER
from cashu.mint import migrations
from cashu.mint.ledger import Ledger

22
tests/test_tor.py Normal file
View File

@@ -0,0 +1,22 @@
import pytest
import requests
from cashu.tor.tor import TorProxy
# @pytest.mark.skip
def test_tor_setup():
s = requests.Session()
tor = TorProxy(timeout=False)
tor.run_daemon()
socks_host, socks_port = "localhost", 9050
proxies = {
"http": f"socks5://{socks_host}:{socks_port}",
"https": f"socks5://{socks_host}:{socks_port}",
}
s.proxies.update(proxies)
resp = s.get("https://google.com")
resp.raise_for_status()