From 64a94e495ad5c92597eeda43f812258325f025e7 Mon Sep 17 00:00:00 2001 From: qo-op Date: Wed, 9 Jun 2021 03:54:30 +0200 Subject: [PATCH] Telegram : https://t.me/onenation_madeinzion_astroport --- scraper2sms.sh | 2 +- sms/AIDE.sh | 8 +- sms/ERASE.sh | 38 +++ sms/NEW.sh | 124 ++++++- sms/jaklis/.env | 5 + sms/jaklis/.env.template | 6 + sms/jaklis/README.md | 78 +++++ sms/jaklis/jaklis.py | 249 ++++++++++++++ .../lib/__pycache__/cesium.cpython-36.pyc | Bin 0 -> 4118 bytes .../__pycache__/cesiumCommon.cpython-36.pyc | Bin 0 -> 1725 bytes .../lib/__pycache__/likes.cpython-36.pyc | Bin 0 -> 5953 bytes .../lib/__pycache__/messaging.cpython-36.pyc | Bin 0 -> 6602 bytes .../lib/__pycache__/natools.cpython-36.pyc | Bin 0 -> 9296 bytes .../lib/__pycache__/offers.cpython-36.pyc | Bin 0 -> 3538 bytes .../lib/__pycache__/profiles.cpython-36.pyc | Bin 0 -> 3044 bytes sms/jaklis/lib/cesium.py | 119 +++++++ sms/jaklis/lib/cesiumCommon.py | 51 +++ sms/jaklis/lib/crypt.py | 31 ++ sms/jaklis/lib/gva.py | 59 ++++ sms/jaklis/lib/gvaBalance.py | 50 +++ sms/jaklis/lib/gvaHistory.py | 256 ++++++++++++++ sms/jaklis/lib/gvaPay.py | 172 ++++++++++ sms/jaklis/lib/likes.py | 241 +++++++++++++ sms/jaklis/lib/messaging.py | 235 +++++++++++++ sms/jaklis/lib/natools.py | 297 ++++++++++++++++ sms/jaklis/lib/offers.py | 137 ++++++++ sms/jaklis/lib/profiles.py | 125 +++++++ sms/jaklis/paiements.py | 94 +++++ sms/jaklis/requirements.txt | 7 + sms/jaklis/setup.sh | 12 + sms/key_create_dunikey.py | 27 ++ sms/natools.py | 322 ++++++++++++++++++ 32 files changed, 2725 insertions(+), 20 deletions(-) create mode 100755 sms/ERASE.sh create mode 100644 sms/jaklis/.env create mode 100755 sms/jaklis/.env.template create mode 100755 sms/jaklis/README.md create mode 100755 sms/jaklis/jaklis.py create mode 100644 sms/jaklis/lib/__pycache__/cesium.cpython-36.pyc create mode 100644 sms/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc create mode 100644 sms/jaklis/lib/__pycache__/likes.cpython-36.pyc create mode 100644 sms/jaklis/lib/__pycache__/messaging.cpython-36.pyc create mode 100644 sms/jaklis/lib/__pycache__/natools.cpython-36.pyc create mode 100644 sms/jaklis/lib/__pycache__/offers.cpython-36.pyc create mode 100644 sms/jaklis/lib/__pycache__/profiles.cpython-36.pyc create mode 100755 sms/jaklis/lib/cesium.py create mode 100755 sms/jaklis/lib/cesiumCommon.py create mode 100755 sms/jaklis/lib/crypt.py create mode 100755 sms/jaklis/lib/gva.py create mode 100755 sms/jaklis/lib/gvaBalance.py create mode 100755 sms/jaklis/lib/gvaHistory.py create mode 100755 sms/jaklis/lib/gvaPay.py create mode 100755 sms/jaklis/lib/likes.py create mode 100755 sms/jaklis/lib/messaging.py create mode 100755 sms/jaklis/lib/natools.py create mode 100644 sms/jaklis/lib/offers.py create mode 100755 sms/jaklis/lib/profiles.py create mode 100755 sms/jaklis/paiements.py create mode 100755 sms/jaklis/requirements.txt create mode 100755 sms/jaklis/setup.sh create mode 100755 sms/key_create_dunikey.py create mode 100755 sms/natools.py diff --git a/scraper2sms.sh b/scraper2sms.sh index ae9206f..f2996f6 100755 --- a/scraper2sms.sh +++ b/scraper2sms.sh @@ -18,7 +18,7 @@ D=$(echo "$SEM2" | awk -F 'au ' '{print $2}' | cut -d ' ' -f 1) echo "SEMAINES: (1) $A - $B / (2) $C - $D" DAY=$(date +%A | awk '{ print toupper( substr( $0, 1, 1 ) ) substr( $0, 2 ); }') -DOM=$(date +%d) && TOM=$(($DOM + 1)) && ND=$(($DOM + 7)) # remove leading 0? $(echo "$(($DOM + 1))") +DOM=$(date +%-d) && TOM=$(($DOM + 1)) && ND=$(($DOM + 7)) # remove leading 0? MONTH=$(date +%m) YEAR=$(date +%y) diff --git a/sms/AIDE.sh b/sms/AIDE.sh index 6c2c9dc..ef9811a 100755 --- a/sms/AIDE.sh +++ b/sms/AIDE.sh @@ -5,11 +5,13 @@ # License: AGPL-3.0 (https://choosealicense.com/licenses/agpl-3.0/) ################################################################################ echo "EXECUTE AIDE.sh ($1=phone)" -PHONE="$1" +phone="$1" MESS="[Aide] -A VENIR... +Envoyer 'O' active votre embarquement Neonaute. +Envoyer 'J' pour recevoir les missions du jour. +Envoyer 'X' pour vous désinscrire. " # Send response SMS -gammu-smsd-inject TEXT "$PHONE" -text "$MESS" +gammu-smsd-inject TEXT "$phone" -text "$MESS" exit 0 diff --git a/sms/ERASE.sh b/sms/ERASE.sh new file mode 100755 index 0000000..e28cece --- /dev/null +++ b/sms/ERASE.sh @@ -0,0 +1,38 @@ +#!/bin/bash +################################################################################ +# Author: Fred (support@qo-op.com) +# Version: 0.1 +# License: AGPL-3.0 (https://choosealicense.com/licenses/agpl-3.0/) +################################################################################ +MY_PATH="`dirname \"$0\"`" # relative +MY_PATH="`( cd \"$MY_PATH\" && pwd )`" # absolutized and normalized +cd $MY_PATH + +echo "EXECUTE ERASE.sh ($1=xzuid)" +xzuid="$1" + +[[ $xzuid == "" ]] && echo "NO $xzuid" && exit 1 +[[ ! -d ~/.zen/SMS/$xzuid ]] && echo "NO $xzuid" && exit 0 + +if [[ -f /var/www/nextcloud/occ ]]; then +######################################################################## +echo "## DELETE Nextcloud : $xzuid " +######################################################################## + sudo su -s /bin/sh www-data -c "export OC_PASS=\"$phone\" && php /var/www/nextcloud/occ user:delete $xzuid" + [[ ! $? == 0 ]] && echo "FAILED" && exit 1 +fi + +./natools.py decrypt -f pubsec -k "$HOME/.zen/secret.dunikey" -i "$HOME/.zen/SMS/$xzuid/secret.dunikey.encrypt" -o "/tmp/erase.dunikey" +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 + +~/.zen/astrXbian/zen/jaklis/jaklis.py -k /tmp/erase.dunikey -n "https://data.gchange.fr" erase +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 + +~/.zen/astrXbian/zen/jaklis/jaklis.py -k /tmp/secret.dunikey -n "https://g1.data.e-is.pro" erase +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 + +rm -Rf ~/.zen/SMS/$xzuid + +cd - + +exit 0 diff --git a/sms/NEW.sh b/sms/NEW.sh index fe881e2..1249fa0 100755 --- a/sms/NEW.sh +++ b/sms/NEW.sh @@ -4,31 +4,123 @@ # Version: 0.1 # License: AGPL-3.0 (https://choosealicense.com/licenses/agpl-3.0/) ################################################################################ -echo "EXECUTE NEW.sh ($1=phone, $2=uid)" -PHONE="$1" -EXTRA="$2" +MY_PATH="`dirname \"$0\"`" # relative +MY_PATH="`( cd \"$MY_PATH\" && pwd )`" # absolutized and normalized +cd $MY_PATH -## CREATE UNIQUE XZUID -XZUID=$(./diceware.sh 1 | xargs)${RANDOM:0:2}$(./diceware.sh 1 | xargs) +ME="${0##*/}" +echo "EXECUTE $ME ($1=phone, $2=uid)" +phone="$1" +EXTRA="$2" +######################################################################## +echo "## GET COMPUTER G1PUB" +######################################################################## +[[ ! -f ~/.zen/secret.dunikey ]] && echo "Crypto is missing... please provide ~/.zen/secret.dunikey by installing 'astrXbian' TestNet" && exit 1 +G1PUB=$(cat ~/.zen/secret.dunikey | grep 'pub:' | cut -d ' ' -f 2) +echo $G1PUB + +######################################################################## +echo "## CREATE UNIQUE XZUID" +######################################################################## +XZUID=$(./diceware.sh 1 | xargs)${RANDOM:0:5} while [[ -d ~/.zen/sms/$XZUID ]] do - XZUID=$(./diceware.sh 1 | xargs)${RANDOM:0:2}$(./diceware.sh 1 | xargs) + XZUID=$(./diceware.sh 1 | xargs)${RANDOM:0:5} done -mkdir -p ~/.zen/sms/$XZUID -echo $XZUID +mkdir -p ~/.zen/SMS/$XZUID +echo "$XZUID" -exit +######################################################################## +echo "## CREATE hash" # = sha256sum $phone +######################################################################## +hash=$(echo $phone | sha256sum | cut -d ' ' -f1) +htest=$(ls ~/.zen/SMS/*/$hash 2>dev/null) +if [[ $htest ]]; then + echo "$phone already regsitered in $htest" + xzuid=$(echo "$htest" | rev | cut -d '/' -f 2 | rev) + gammu-smsd-inject TEXT "$phone" -text "Salut $xzuid. identifiez-vous sur https://taurus.copylaradio.com utilisez votre numéro de mobile comme mot de passe." && exit 1 + exit 1 +fi +touch ~/.zen/SMS/$XZUID/$hash +######################################################################## +echo "## encrypt ~/.zen/SMS/$XZUID/phone.encrypt" +######################################################################## +echo $phone > /tmp/phone +./natools.py encrypt -p $G1PUB -i /tmp/phone -o ~/.zen/SMS/$XZUID/phone.encrypt +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 +echo "$hash" -## CREATE NEXTCLOUD PROFILE -sudo su -s /bin/sh www-data -c "export OC_PASS=\"$PHONE\" && php /var/www/nextcloud/occ user:add --password-from-env --group=\"astroport\" --display-name=\"$XZUID\" $XZUID" +######################################################################## +echo "## CREATE secret.dunikey" +######################################################################## +salt="$(./diceware.sh 3 | xargs)" +pepper="$(./diceware.sh 3 | xargs)" +./key_create_dunikey.py "$salt" "$pepper" +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 +g1pub=$(cat /tmp/secret.dunikey | grep "pub" | cut -d ' ' -f 2) +g1sec=$(cat /tmp/secret.dunikey | grep "sec" | cut -d ' ' -f 2) +######################################################################## +echo "## CREATE $g1pub QRCODE" +######################################################################## +qrencode -s 6 -o "$HOME/.zen/SMS/$XZUID/qrcode.png" "$g1pub" +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 + +######################################################################## +echo "## encrypt secret.dunikey.encrypt" +######################################################################## +./natools.py encrypt -p $G1PUB -i /tmp/secret.dunikey -o ~/.zen/SMS/$XZUID/secret.dunikey.encrypt +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 + + +######################################################################## +echo "GETTING AVATAR" +######################################################################## +curl -s https://thispersondoesnotexist.com/image -o ~/.zen/SMS/$XZUID/image.jpg + +######################################################################## +echo "CREATING GCHANGE+ PROFILE" +######################################################################## +./jaklis/jaklis.py -k /tmp/secret.dunikey -n "https://data.gchange.fr" set --name "$XZUID" --avatar "$HOME/.zen/SMS/$XZUID/image.jpg" #GCHANGE+ +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 + +######################################################################## +echo "CREATING CESIUM+ PROFILE" +######################################################################## +./jaklis/jaklis.py -k /tmp/secret.dunikey -n "https://g1.data.e-is.pro" set --name "$XZUID" --avatar "$HOME/.zen/SMS/$XZUID/image.jpg" #CESIUM+ +[[ ! $? == 0 ]] && echo "FAILED" && exit 1 + +if [[ -f /var/www/nextcloud/occ ]]; then +######################################################################## +echo "## REGISTER Nextcloud : $XZUID / $phone " +######################################################################## + sudo su -s /bin/sh www-data -c "export OC_PASS=\"$phone\" && php /var/www/nextcloud/occ user:add --password-from-env --group=\"astroport\" --display-name=\"$XZUID\" $XZUID" + [[ ! $? == 0 ]] && echo "FAILED" && exit 1 +fi ## REPONSE SMS -MESS="[OASIS] -$XZUID +MESS="Néonaute $XZUID, +Votre nouvelle Identité Interplanétaire est ACTIVE. +Vous rejoignez le JEu de l'évasion... + +Escape Game DIY : https://oasis.astroport.com +Telegram : https://t.me/onenation_madeinzion_astroport + +Monnaie Libre +$salt +$pepper +https://gchange.fr et https://cesium.app + +Chaton NextCloud : https://taurus.copylaradio.com +Datacenter P2P : https://copylaradio.com + +MadeInZion +Crypto Nation P2P LIBRE. " + # Send response SMS -gammu-smsd-inject TEXT "$PHONE" -text "$MESS" +gammu-smsd-inject TEXT "$phone" -text "$MESS" +[[ -f /tmp/sms ]] && gammu-smsd-inject TEXT "$phone" -text "$(cat /tmp/sms)" - -exit +cd - +exit 0 diff --git a/sms/jaklis/.env b/sms/jaklis/.env new file mode 100644 index 0000000..ea6bd51 --- /dev/null +++ b/sms/jaklis/.env @@ -0,0 +1,5 @@ +DUNIKEY="/.zen/secret.dunikey" # Chemin du fichier de trousseau Ḡ1 de l'émetteur, au format PubSec +POD="https://data.gchange.fr" # Noeud Gchange utilisé pour l'envoi du message +#POD="https://g1.data.duniter.fr" # Noeud Cecium+ utilisé pour l'envoi du message +#POD="https://g1.data.le-sou.org" # Adresse du pod Cesium de secours +#POD="https://g1.data.e-is.pro" diff --git a/sms/jaklis/.env.template b/sms/jaklis/.env.template new file mode 100755 index 0000000..dd81446 --- /dev/null +++ b/sms/jaklis/.env.template @@ -0,0 +1,6 @@ +DUNIKEY="" # Chemin de la clé privé Ḡ1 de l'émetteur, au format PubSec +#POD="https://g1.data.duniter.fr" # Adresse du pod Cesium ou Gchange à utiliser +POD="https://g1.data.le-sou.org" # Adresse du pod Cesium de secours +#POD="https://data.gchange.fr" # Adresse du pod ḠChange à utiliser + +NODE="https://g1.librelois.fr/gva" diff --git a/sms/jaklis/README.md b/sms/jaklis/README.md new file mode 100755 index 0000000..e0ff54d --- /dev/null +++ b/sms/jaklis/README.md @@ -0,0 +1,78 @@ +# Client CLI for Cesium+/Ḡchange pod +## Installation + +Linux: +``` +bash setup.sh +``` + +Autre: +``` +Débrouillez-vous. +``` + +## Utilisation + +*Python 3.9 minimum* + +Renseignez optionnellement le fichier **.env** (Généré lors de la première tentative d'execution, ou à copier depuis .env.template). + +``` +./jaklis.py -h +``` + +``` +usage: jaklis.py [-h] [-v] [-k KEY] [-n NODE] {read,send,delete,get,set,erase,like,unlike,pay,history,balance} ... + +Client CLI pour Cesium+ et Ḡchange + +optional arguments: + -h, --help show this help message and exit + -v, --version Affiche la version actuelle du programme + -k KEY, --key KEY Chemin vers mon trousseau de clé (PubSec) + -n NODE, --node NODE Adresse du noeud Cesium+, Gchange ou Duniter à utiliser + +Commandes de jaklis: + {read,send,delete,get,set,erase,like,unlike,pay,history,balance} + read Lecture des messages + send Envoi d'un message + delete Supression d'un message + get Voir un profile Cesium+ + set Configurer son profile Cesium+ + erase Effacer son profile Cesium+ + like Voir les likes d'un profile / Liker un profile (option -s NOTE) + unlike Supprimer un like + pay Payer en Ḡ1 + history Voir l'historique des transactions d'un compte Ḡ1 + balance Voir le solde d'un compte Ḡ1 + +``` + +Utilisez `./jaklis CMD -h` où `CMD` est la commande souhaité pour obtenir l'aide détaillé de cette commande. + +### Exemples: + +Lire les 10 derniers messages de mon compte indiqué dans le fichier `.env` (par defaut 3 messages): +``` +./jaklis read -n10 +``` + +Envoyer un message à la clé publique `Do99s6wQR2JLfhirPdpAERSjNbmjjECzGxHNJMiNKT3P` avec un fichier de trousseau particulier: +``` +./jaklis.py -k /home/saucisse/mon_fichier_de_trousseau.dunikey send -d Do99s6wQR2JLfhirPdpAERSjNbmjjECzGxHNJMiNKT3P -t "Objet du message" -m "Corps de mon message" +``` + +Noter 4 étoiles le profile `S9EJbjbaGPnp26VuV6fKjR7raE1YkNhUGDgoydHvAJ1` sur gchange: +``` +./jaklis.py -n https://data.gchange.fr like -p S9EJbjbaGPnp26VuV6fKjR7raE1YkNhUGDgoydHvAJ1 -s 4 +``` + +Paramétrer mon profile Cesium+: +``` +./jaklis.py set -n "Sylvain Durif" -v "Bugarach" -a "42 route de Vénus" -d "Christ cosmique" -pos 48.539927 2.6608169 -s https://www.creationmonetaire.info -A mon_avatar.png +``` + +Effacer mon profile Gchange: +``` +./jaklis.py -n https://data.gchange.fr erase +``` diff --git a/sms/jaklis/jaklis.py b/sms/jaklis/jaklis.py new file mode 100755 index 0000000..24ffc17 --- /dev/null +++ b/sms/jaklis/jaklis.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 + +import argparse, sys, os, getpass, string, random +from os.path import join, dirname +from shutil import copyfile +from dotenv import load_dotenv +from duniterpy.key import SigningKey + +__version__ = "0.0.2" + +MY_PATH = os.path.realpath(os.path.dirname(sys.argv[0])) + '/' + +# Get variables environment +if not os.path.isfile(MY_PATH + '.env'): + copyfile(MY_PATH + ".env.template",MY_PATH + ".env") +dotenv_path = join(dirname(__file__),MY_PATH + '.env') +load_dotenv(dotenv_path) + +# Parse arguments +parser = argparse.ArgumentParser(description="Client CLI pour Cesium+ et Ḡchange") +parser.add_argument('-v', '--version', action='store_true', help="Affiche la version actuelle du programme") +parser.add_argument('-k', '--key', help="Chemin vers mon trousseau de clé (PubSec)") +parser.add_argument('-n', '--node', help="Adresse du noeud Cesium+, Gchange ou Duniter à utiliser") + +subparsers = parser.add_subparsers(title="Commandes de jaklis", dest="cmd") +read_cmd = subparsers.add_parser('read', help="Lecture des messages") +send_cmd = subparsers.add_parser('send', help="Envoi d'un message") +delete_cmd = subparsers.add_parser('delete', help="Supression d'un message") +getProfile_cmd = subparsers.add_parser('get', help="Voir un profile Cesium+") +setProfile_cmd = subparsers.add_parser('set', help="Configurer son profile Cesium+") +eraseProfile_cmd = subparsers.add_parser('erase', help="Effacer son profile Cesium+") +stars_cmd = subparsers.add_parser('stars', help="Voir les étoiles d'un profile / Noter un profile (option -s NOTE)") +unstars_cmd = subparsers.add_parser('unstars', help="Supprimer un star") +getoffer_cmd = subparsers.add_parser('getoffer', help="Obtenir les informations d'une annonce gchange") +setoffer_cmd = subparsers.add_parser('setoffer', help="Créer une annonce gchange") +deleteoffer_cmd = subparsers.add_parser('deleteoffer', help="Supprimer une annonce gchange") +pay_cmd = subparsers.add_parser('pay', help="Payer en Ḡ1") +history_cmd = subparsers.add_parser('history', help="Voir l'historique des transactions d'un compte Ḡ1") +balance_cmd = subparsers.add_parser('balance', help="Voir le solde d'un compte Ḡ1") + +# Messages management +read_cmd.add_argument('-n', '--number',type=int, default=3, help="Affiche les NUMBER derniers messages") +read_cmd.add_argument('-j', '--json', action='store_true', help="Sort au format JSON") +read_cmd.add_argument('-o', '--outbox', action='store_true', help="Lit les messages envoyés") + +send_cmd.add_argument('-d', '--destinataire', required=True, help="Destinataire du message") +send_cmd.add_argument('-t', '--titre', help="Titre du message à envoyer") +send_cmd.add_argument('-m', '--message', help="Message à envoyer") +send_cmd.add_argument('-f', '--fichier', help="Envoyer le message contenu dans le fichier 'FICHIER'") +send_cmd.add_argument('-o', '--outbox', action='store_true', help="Envoi le message sur la boite d'envoi") + +delete_cmd.add_argument('-i', '--id', action='append', nargs='+', required=True, help="ID(s) du/des message(s) à supprimer") +delete_cmd.add_argument('-o', '--outbox', action='store_true', help="Suppression d'un message envoyé") + +# Profiles management +setProfile_cmd.add_argument('-n', '--name', help="Nom du profile") +setProfile_cmd.add_argument('-d', '--description', help="Description du profile") +setProfile_cmd.add_argument('-v', '--ville', help="Ville du profile") +setProfile_cmd.add_argument('-a', '--adresse', help="Adresse du profile") +setProfile_cmd.add_argument('-pos', '--position', nargs=2, help="Points géographiques (lat + lon)") +setProfile_cmd.add_argument('-s', '--site', help="Site web du profile") +setProfile_cmd.add_argument('-A', '--avatar', help="Chemin vers mon avatar en PNG") + +getProfile_cmd.add_argument('-p', '--profile', help="Nom du profile") +getProfile_cmd.add_argument('-a', '--avatar', action='store_true', help="Récupérer également l'avatar au format raw base64") + +# Likes management +stars_cmd.add_argument('-p', '--profile', help="Profile cible") +stars_cmd.add_argument('-n', '--number', type=int, help="Nombre d'étoile") +unstars_cmd.add_argument('-p', '--profile', help="Profile à dénoter") + +# Offers management +getoffer_cmd.add_argument('-i', '--id', help="Annonce cible à récupérer") +setoffer_cmd.add_argument('-t', '--title', help="Titre de l'annonce à créer") +setoffer_cmd.add_argument('-d', '--description', help="Description de l'annonce à créer") +setoffer_cmd.add_argument('-c', '--category', help="Categorie de l'annonce à créer") +setoffer_cmd.add_argument('-l', '--localisation', nargs=2, help="Localisation de l'annonce à créer (lat + lon)") +setoffer_cmd.add_argument('-p', '--picture', help="Image de l'annonce à créer") +setoffer_cmd.add_argument('-ci', '--city', help="Ville de l'annonce à créer") +setoffer_cmd.add_argument('-pr', '--price', help="Prix de l'annonce à créer") +deleteoffer_cmd.add_argument('-i', '--id', help="Annonce cible à supprimer") + +# GVA usage +pay_cmd.add_argument('-p', '--pubkey', help="Destinataire du paiement") +pay_cmd.add_argument('-a', '--amount', type=float, help="Montant de la transaction") +pay_cmd.add_argument('-c', '--comment', default="", help="Commentaire de la transaction") +pay_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") +pay_cmd.add_argument('-v', '--verbose', action='store_true', help="Affiche le résultat JSON de la transaction") + +history_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +history_cmd.add_argument('-n', '--number',type=int, default=10, help="Affiche les NUMBER dernières transactions") +history_cmd.add_argument('-j', '--json', action='store_true', help="Affiche le résultat en format JSON") +history_cmd.add_argument('--nocolors', action='store_true', help="Affiche le résultat en noir et blanc") + +balance_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +balance_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") + + +args = parser.parse_args() +cmd = args.cmd + +if args.version: + print(__version__) + sys.exit(0) + +if not cmd: + parser.print_help() + sys.exit(1) + +def createTmpDunikey(): + # Generate pseudo-random nonce + nonce=[] + for _ in range(32): + nonce.append(random.choice(string.ascii_letters + string.digits)) + nonce = ''.join(nonce) + keyPath = "/tmp/secret.dunikey-" + nonce + + key = SigningKey.from_credentials(getpass.getpass("Identifiant: "), getpass.getpass("Mot de passe: "), None) + key.save_pubsec_file(keyPath) + + return keyPath + +# Check if we need dunikey +try: + pubkey = args.pubkey +except: + pubkey = False +try: + profile = args.profile +except: + profile = False + +if cmd in ('history','balance','get') and (pubkey or profile): + noNeedDunikey = True + keyPath = False + try: + dunikey = args.pubkey + except: + dunikey = args.profile +else: + noNeedDunikey = False + if args.key: + dunikey = args.key + keyPath = False + else: + dunikey = os.getenv('DUNIKEY') + if not dunikey: + keyPath = createTmpDunikey() + dunikey = keyPath + else: + keyPath = False + if not os.path.isfile(dunikey): + HOME = os.getenv("HOME") + dunikey = HOME + dunikey + if not os.path.isfile(dunikey): + sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) + sys.exit(1) + + +# Construct CesiumPlus object +if cmd in ("read","send","delete","set","get","erase","stars","unstars","getoffer","setoffer","deleteoffer"): + from lib.cesium import CesiumPlus + + if args.node: + pod = args.node + else: + pod = os.getenv('POD') + if not pod: + pod="https://g1.data.le-sou.org" + + cesium = CesiumPlus(dunikey, pod, noNeedDunikey) + + # Messaging + if cmd == "read": + cesium.read(args.number, args.outbox, args.json) + elif cmd == "send": + if args.fichier: + with open(args.fichier, 'r') as f: + msgT = f.read() + titre = msgT.splitlines(True)[0].replace('\n', '') + msg = ''.join(msgT.splitlines(True)[1:]) + if args.titre: + titre = args.titre + msg = msgT + elif args.titre and args.message: + titre = args.titre + msg = args.message + else: + titre = input("Indiquez le titre du message: ") + msg = input("Indiquez le contenu du message: ") + + cesium.send(titre, msg, args.destinataire, args.outbox) + + elif cmd == "delete": + cesium.delete(args.id[0], args.outbox) + + # Profiles + elif cmd == "set": + cesium.set(args.name, args.description, args.ville, args.adresse, args.position, args.site, args.avatar) + elif cmd == "get": + cesium.get(args.profile, args.avatar) + elif cmd == "erase": + cesium.erase() + + # Stars + elif cmd == "stars": + if args.number or args.number == 0: + cesium.like(args.number, args.profile) + else: + cesium.readLikes(args.profile) + elif cmd == "unstars": + cesium.unLike(args.profile) + + # Offers + elif cmd == "getoffer": + cesium.getOffer(args.id) + elif cmd == "setoffer": + cesium.setOffer(args.title, args.description, args.city, args.localisation, args.category, args.price, args.picture) + elif cmd == "deleteoffer": + cesium.deleteOffer(args.id) + +# Construct GVA object +elif cmd in ("pay","history","balance"): + from lib.gva import GvaApi + + if args.node: + node = args.node + else: + node = os.getenv('NODE') + if not node: + node="https://g1.librelois.fr/gva" + + if args.pubkey: + destPubkey = args.pubkey + else: + destPubkey = False + + gva = GvaApi(dunikey, node, destPubkey, noNeedDunikey) + + if cmd == "pay": + gva.pay(args.amount, args.comment, args.mempool, args.verbose) + if cmd == "history": + gva.history(args.json, args.nocolors, args.number) + if cmd == "balance": + gva.balance(args.mempool) + + +if keyPath: + os.remove(keyPath) diff --git a/sms/jaklis/lib/__pycache__/cesium.cpython-36.pyc b/sms/jaklis/lib/__pycache__/cesium.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b28451597ef7d40b68f322717c51cce12d6bf753 GIT binary patch literal 4118 zcmcIn+j88-5ykyR>@G>svSQ1LtT@?BRh8@M<+cqb8|29azFC(APUr+ zm2Ko<6e`)yI(awh=DnzwZ$_JH&dIj&?Py!q9&5~H-fN9{+&(v>YfL}X`u-d2L>u%C z89wAyI?W%Jd0rMWeD?JhU+#Z5e7^s9|J%MPyU%&d4n&!^S~5K31^a6`A8kJ38L#>B zd0&^_Gf^I=8LzO~r`2DjC%lqAEo?>i>p}@ocAg#|b5SLd)wEtTLb*lKJwW6qq~Zk%&n*NQIYor`%27k*D4^j$%(mM)tXaN4Of zCnb|%1Vr6!7!KWZ#um#k1qcDUQUGY9yUa`ClMjI-X-rFTdxu}K4$ zk4CccYXf1;ukdAK56zJ_b6$O1TTDOKA8CKPIddClY}d}%t-Z027QCySa~x-L!m zf+y)DMJ%K(;$p<56Hg`xk#sO*WiFlMMVTg;ho{nFBs+1Hr0EcuQUmMKVd*HXaX*SH zzIRvpM|*b}PfEt6H7?Ua+C^Dlxu2B9aXLceqktEQIGxmprs!c4eP4iLG-=mqopEXA zn7=_g+(s7#1en&(;^(BM52bgU7IB7msnE9F4m|@@3ib}9Y7-sB$}uTcH}!4fhQ1ar z(o9U!!sVbX^5C}^ti{WG^-Y2mXP8s>;V_HVoT`|yU6>np<^3cIVBLE=JwpO6n>I@R40G$mq zk`)^7Ls+xUYrHngflA75r*Y5SnU7hk@qtZa79i6D!eJ92vyFi?D_-{V(;u_wn{>MO@^2+P6aI9SBSR!)JP>c zz#Se{cr<}WbKyPU(dGi|^Fu^-7l_;^g%wO%Z6bzh;oM$jsJP0w2iVmYXcsA`dw|s0 z<_e(Hdkzu((1LvrPISUxWeR-C8f?&E#HbEq{ zB$jAH#rAod5Gw0zWuf>SdeY9QYI&=DbPCKXn=he0Pn>N%pg-emXT{Ei;v#*>)VGJv zRD_A(A(SX5#3vZzP6WC%&K7iDt{Ar@%+?BNSD=AP86zkVttpqd(IP7k$pA%s$b34e z*GQi<7CR@FUhI4igEio_JEs8AzCUgEjS(fZ4VqlDz!5|?OA8`DdBKwtDh!uF0zT=S z6Td?jITfnPqE^mkV6j`yNc`u1;J}Skjs{K$VxRW%gryw*B6s^kXx1v@& z!CQ~e7H*jn{k0XmUc6;3=!?uGW?Ke*!S)1Eu)2aQ1)DMwby;ICGSV0bB0QA1mmETA z8Lc^l==TqpZN1V$KpH7ibSphDVQc0eN#`o^d5Q;Pv^lD`4aKIbMqa$*YWn#UYnMjC zD54%5X)ubY2gely$>ON8K&wmAfr+1B2C(Me+)UEQ&028R}h-UwP*r)=a&#YDp zs}|WBpq4qiz7o6@#lAaeFO(mHzJ4(OU(DijqbI%LkZO$KPzJ+cUb1OMayT5moW@!E zL{M=k`qYti3f3x=;sFim3=;d)9Z*LI6XbvKjJhS?D2YxwLEi*KHx*T^p5^&o$MXX( z=&9ZbNcNOge@FbDR(wF+`_$b+7j5Ee<4&^j)hg%^Q73uSp=Y`JJVNQMKBw9Oo~SDd z>FNCNu2iiM`83s%y*e)xuM#yy0X-?z_i2k++W1u#N$-B^y3gr=R^L3g^i6#SWQu0| E566$5D*ylh literal 0 HcmV?d00001 diff --git a/sms/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc b/sms/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d7cd6b97d7b52e8239ff527d87ded539d50c8eb GIT binary patch literal 1725 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 literal 0 HcmV?d00001 diff --git a/sms/jaklis/lib/__pycache__/messaging.cpython-36.pyc b/sms/jaklis/lib/__pycache__/messaging.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7bb05560fd929ab7569ab0986632902e068a737 GIT binary patch literal 6602 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 literal 0 HcmV?d00001 diff --git a/sms/jaklis/lib/__pycache__/natools.cpython-36.pyc b/sms/jaklis/lib/__pycache__/natools.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dca2c57c994ba8907706bf48f3363e7bee15d085 GIT binary patch literal 9296 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 QW#6Z;8=Cq({CX$&A9YvmbN~PV literal 0 HcmV?d00001 diff --git a/sms/jaklis/lib/cesium.py b/sms/jaklis/lib/cesium.py new file mode 100755 index 0000000..e755519 --- /dev/null +++ b/sms/jaklis/lib/cesium.py @@ -0,0 +1,119 @@ +import re, string, random, base64 +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX +from lib.messaging import ReadFromCesium, SendToCesium, DeleteFromCesium +from lib.profiles import Profiles +from lib.likes import ReadLikes, SendLikes, UnLikes +from lib.offers import Offers + +class CesiumPlus(CesiumCommon): + + #################### Messaging #################### + + def read(self, nbrMsg, outbox, isJSON): + readCesium = ReadFromCesium(self.dunikey, self.pod) + jsonMsg = readCesium.sendDocument(nbrMsg, outbox) + if isJSON: + jsonFormat = readCesium.jsonMessages(jsonMsg, nbrMsg, outbox) + print(jsonFormat) + else: + readCesium.readMessages(jsonMsg, nbrMsg, outbox) + + def send(self, title, msg, recipient, outbox): + sendCesium = SendToCesium(self.dunikey, self.pod) + sendCesium.recipient = recipient + + # Generate pseudo-random nonce + nonce=[] + for _ in range(32): + nonce.append(random.choice(string.ascii_letters + string.digits)) + sendCesium.nonce = base64.b64decode(''.join(nonce)) + + finalDoc = sendCesium.configDoc(sendCesium.encryptMsg(title), sendCesium.encryptMsg(msg)) # Configure JSON document to send + sendCesium.sendDocument(finalDoc, outbox) # Send final signed document + + def delete(self, idsMsgList, outbox): + deleteCesium = DeleteFromCesium(self.dunikey, self.pod) + # deleteCesium.issuer = recipient + for idMsg in idsMsgList: + finalDoc = deleteCesium.configDoc(idMsg, outbox) + deleteCesium.sendDocument(finalDoc, idMsg) + + #################### Profiles #################### + + def set(self, name=None, description=None, ville=None, adresse=None, position=None, site=None, avatar=None): + setProfile = Profiles(self.dunikey, self.pod) + document = setProfile.configDocSet(name, description, ville, adresse, position, site, avatar) + result = setProfile.sendDocument(document,'set') + + print(result) + return result + + def get(self, profile=None, avatar=None): + getProfile = Profiles(self.dunikey, self.pod, self.noNeedDunikey) + if not profile: + profile = self.pubkey + if not re.match(PUBKEY_REGEX, profile) or len(profile) > 45: + scope = 'title' + else: + scope = '_id' + + document = getProfile.configDocGet(profile, scope, avatar) + resultJSON = getProfile.sendDocument(document, 'get') + result = getProfile.parseJSON(resultJSON) + + print(result) + + def erase(self): + eraseProfile = Profiles(self.dunikey, self.pod) + document = eraseProfile.configDocErase() + result = eraseProfile.sendDocument(document,'erase') + + print(result) + + #################### Likes #################### + + def readLikes(self, profile=False): + likes = ReadLikes(self.dunikey, self.pod, self.noNeedDunikey) + document = likes.configDoc(profile) + result = likes.sendDocument(document) + result = likes.parseResult(result) + + print(result) + + def like(self, stars, profile=False): + likes = SendLikes(self.dunikey, self.pod) + document = likes.configDoc(profile, stars) + if document: + likes.sendDocument(document, profile) + + def unLike(self, pubkey, silent=False): + likes = UnLikes(self.dunikey, self.pod) + idLike = likes.checkLike(pubkey) + if idLike: + document = likes.configDoc(idLike) + likes.sendDocument(document, silent) + + #################### Offer #################### + + def setOffer(self, title=None, description=None, city=None, localisation=None, category=None, price=None, picture=None): + setOffer = Offers(self.dunikey, self.pod) + document = setOffer.configDocSet(title, description, city, localisation, category, price, picture) + result = setOffer.sendDocumentSet(document,'set') + + print(result) + return result + + def getOffer(self, id, avatar=None): + getOffer = Offers(self.dunikey, self.pod, self.noNeedDunikey) + + resultJSON = getOffer.sendDocumentGet(id, 'get') + result = getOffer.parseJSON(resultJSON) + + print(result) + + def deleteOffer(self, id): + eraseOffer = Offers(self.dunikey, self.pod) + document = eraseOffer.configDocErase(id) + result = eraseOffer.sendDocumentSet(document,'delete', id) + + print(result) diff --git a/sms/jaklis/lib/cesiumCommon.py b/sms/jaklis/lib/cesiumCommon.py new file mode 100755 index 0000000..b68337d --- /dev/null +++ b/sms/jaklis/lib/cesiumCommon.py @@ -0,0 +1,51 @@ +import sys, re, json +from hashlib import sha256 +from lib.natools import fmt, sign, get_privkey + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +def pp_json(json_thing, sort=True, indents=4): + # Print beautifull JSON + if type(json_thing) is str: + print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) + else: + print(json.dumps(json_thing, sort_keys=sort, indent=indents)) + return None + +class CesiumCommon: + def __init__(self, dunikey, pod, noNeedDunikey=False): + self.pod = pod + self.noNeedDunikey = noNeedDunikey + # Get my pubkey from my private key + try: + self.dunikey = dunikey + if not dunikey: + raise ValueError("Dunikey is empty") + except: + sys.stderr.write("Please fill the path to your private key (PubSec)\n") + sys.exit(1) + + if noNeedDunikey: + self.pubkey = self.dunikey + else: + self.pubkey = get_privkey(dunikey, "pubsec").pubkey + + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + def signDoc(self, document): + # Generate hash of document + hashDoc = sha256(document.encode()).hexdigest().upper() + + # Generate signature of document + signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() + + # Build final document + data = {} + data['hash'] = hashDoc + data['signature'] = signature + signJSON = json.dumps(data) + finalJSON = {**json.loads(signJSON), **json.loads(document)} + + return json.dumps(finalJSON) diff --git a/sms/jaklis/lib/crypt.py b/sms/jaklis/lib/crypt.py new file mode 100755 index 0000000..ee4cfb2 --- /dev/null +++ b/sms/jaklis/lib/crypt.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import base64, base58, sys, string, random +from natools import get_privkey, box_decrypt, box_encrypt, fmt + +def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str: + if arg in args and len(args) > args.index(arg)+n: + return args[args.index(arg)+n] + else: + return default + +cmd = sys.argv[1] + +dunikey = getargv("-k", "private.dunikey") +msg = getargv("-m", "test") +pubkey = getargv("-p") + +def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(dunikey, "pubsec"), pubkey).decode() + +def encrypt(msg): + return fmt["64"](box_encrypt(msg.encode(), get_privkey(dunikey, "pubsec"), pubkey)).decode() + +if cmd == 'decrypt': + clear = decrypt(msg) + print(clear) +elif cmd == 'encrypt': + clear = encrypt(msg) + print(clear) + diff --git a/sms/jaklis/lib/gva.py b/sms/jaklis/lib/gva.py new file mode 100755 index 0000000..0ec1ad6 --- /dev/null +++ b/sms/jaklis/lib/gva.py @@ -0,0 +1,59 @@ +import sys, re +from lib.natools import get_privkey +from lib.gvaPay import Transaction, PUBKEY_REGEX +from lib.gvaHistory import History +from lib.gvaBalance import Balance + +class GvaApi(): + def __init__(self, dunikey, node, pubkey, noNeedDunikey=False): + self.noNeedDunikey = noNeedDunikey + self.dunikey = dunikey + self.node = node + if noNeedDunikey: + self.pubkey = self.dunikey + else: + self.pubkey = get_privkey(dunikey, "pubsec").pubkey + + if pubkey: + self.destPubkey = pubkey + else: + self.destPubkey = self.pubkey + + try: + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + try: + if not re.match(PUBKEY_REGEX, self.destPubkey) or len(self.destPubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + #################### Payments #################### + + def pay(self, amount, comment, mempool, verbose): + gva = Transaction(self.dunikey, self.node, self.destPubkey, amount, comment, mempool, verbose) + gva.genDoc() + gva.checkTXDoc() + gva.signDoc() + return gva.sendTXDoc() + + def history(self, isJSON=False, noColors=False, number=10): + gva = History(self.dunikey, self.node, self.destPubkey) + gva.sendDoc(number) + transList = gva.parseHistory() + + if isJSON: + transJson = gva.jsonHistory(transList) + print(transJson) + else: + gva.printHistory(transList, noColors) + + def balance(self, useMempool): + gva = Balance(self.dunikey, self.node, self.destPubkey, useMempool) + balanceValue = gva.sendDoc() + print(balanceValue) diff --git a/sms/jaklis/lib/gvaBalance.py b/sms/jaklis/lib/gvaBalance.py new file mode 100755 index 0000000..b148e7e --- /dev/null +++ b/sms/jaklis/lib/gvaBalance.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Balance: + + def __init__(self, dunikey, node, pubkey, useMempool=False): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build balance generation document + queryBuild = gql( + """ + query ($pubkey: String!){ + balance(script: $pubkey) { + amount + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey + } + + # Send balance document + try: + balanceResult = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération du solde:\n" + message + "\n") + sys.exit(1) + + balanceValue = balanceResult['balance']['amount']/100 + # print(balanceValue) + return balanceValue diff --git a/sms/jaklis/lib/gvaHistory.py b/sms/jaklis/lib/gvaHistory.py new file mode 100755 index 0000000..cfcad0f --- /dev/null +++ b/sms/jaklis/lib/gvaHistory.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast, time +from datetime import datetime +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class History: + + def __init__(self, dunikey, node, pubkey): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.node = node + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self, number): + # Build history generation document + queryBuild = gql( + """ + query ($pubkey: String!, $number: Int!){ + txsHistoryBc( + pubkeyOrScript: $pubkey + pagination: { pageSize: $number, ord: DESC } + ) { + both { + pageInfo { + hasPreviousPage + hasNextPage + } + edges { + direction + node { + currency + issuers + outputs + comment + writtenTime + } + } + } + } + txsHistoryMp(pubkey: $pubkey) { + receiving { + currency + issuers + comment + outputs + writtenTime + } + receiving { + currency + issuers + comment + outputs + writtenTime + } + } + balance(script: $pubkey) { + amount + base + } + node { + peer { + currency + } + } + currentUd { + amount + base + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey, + "number": number + } + + # Send history document + try: + self.historyDoc = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération de l'historique:\n" + message + "\n") + sys.exit(1) + + + def parseHistory(self): + trans = [] + i = 0 + + currentBase = int(self.historyDoc['currentUd']['base']) + self.UD = self.historyDoc['currentUd']['amount']/100 + + + # Parse transactions in blockchain + resBc = [] + resBc = self.historyDoc['txsHistoryBc']['both']['edges'] + for j, transaction in enumerate(resBc): + # print(transaction) + direction = resBc[j]['direction'] + transaction = resBc[j]['node'] + output = transaction['outputs'][0] + outPubkey = output.split("SIG(")[1].replace(')','') + # if direction == 'RECEIVED' or self.pubkey != outPubkey: + trans.append(i) + trans[i] = [] + trans[i].append(direction) + trans[i].append(transaction['writtenTime']) + if direction == 'SENT': + trans[i].append(outPubkey) + amount = int('-' + output.split(':')[0]) + else: + trans[i].append(transaction['issuers'][0]) + amount = int(output.split(':')[0]) + base = int(output.split(':')[1]) + applyBase = base-currentBase + amount = round(amount*pow(10,applyBase)/100, 2) + # if referential == 'DU': amount = round(amount/UD, 2) + trans[i].append(amount) + trans[i].append(round(amount/self.UD, 2)) + trans[i].append(transaction['comment']) + trans[i].append(base) + i += 1 + + # Parse transactions in mempool + for direction in self.historyDoc['txsHistoryMp']: + resBc = [] + resBc = self.historyDoc['txsHistoryMp'][direction] + for j, transaction in enumerate(resBc): + # print(transaction) + transaction = resBc[j] + output = transaction['outputs'][0] + outPubkey = output.split("SIG(")[1].replace(')','') + # if direction == 'RECEIVING' or self.pubkey != outPubkey: + trans.append(i) + trans[i] = [] + trans[i].append(direction) + trans[i].append(int(time.time())) + if direction == 'SENDING': + trans[i].append(outPubkey) + amount = int('-' + output.split(':')[0]) + else: + trans[i].append(transaction['issuers'][0]) + amount = int(output.split(':')[0]) + base = int(output.split(':')[1]) + applyBase = base-currentBase + amount = round(amount*pow(10,applyBase)/100, 2) + # if referential == 'DU': amount = round(amount/UD, 2) + trans[i].append(amount) + trans[i].append(round(amount/self.UD, 2)) + trans[i].append(transaction['comment']) + trans[i].append(base) + i += 1 + + # Order transactions by date + trans.sort(key=lambda x: x[1]) + + # Keep only base if there is base change + lastBase = 0 + for i in trans: + if i[6] == lastBase: i[6] = None + else: lastBase = i[6] + + return trans + + def printHistory(self, trans, noColors): + # Get balance + balance = self.historyDoc['balance']['amount']/100 + balanceUD = round(balance/self.UD, 2) + + # Get currency + currency = self.historyDoc['node']['peer']['currency'] + if currency == 'g1': currency = 'Ḡ1' + elif currency == 'g1-test': currency = 'GT' + # if referential == 'DU': currency = 'DU/' + currency.lower() + + # Get terminal size + rows = int(os.popen('stty size', 'r').read().split()[1]) + + # Display history + print('+', end='') + print('-'.center(rows-1, '-')) + if noColors: isBold = isBoldEnd = '' + else: + isBold = '\033[1m' + isBoldEnd = '\033[0m' + print(isBold + "|{: <19} | {: <12} | {: <7} | {: <7} | {: <30}".format(" Date"," De / À"," {0}".format(currency)," DU/{0}".format(currency.lower()),"Commentaire") + isBoldEnd) + print('|', end='') + for t in trans: + if t[0] == "RECEIVED": color = "green" + elif t[0] == "SENT": color = "blue" + elif t[0] == "receiving": color = "yellow" + elif t[0] == "sending": color = "red" + else: color = None + if noColors: + color = None + if t[0] in ('RECEIVING','SENDING'): + comment = '(EN ATTENTE) ' + t[5] + else: + comment = t[5] + else: + comment = t[5] + + date = datetime.fromtimestamp(t[1]).strftime("%d/%m/%Y à %H:%M") + print('-'.center(rows-1, '-')) + if t[6]: + print('|', end='') + print(' Changement de base : {0} '.format(t[6]).center(rows-1, '#')) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + printKey = t[2][0:8] + '\u2026' + t[2][-3:] + if noColors: + print(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment)) + else: + print(colored(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment), color)) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + print(isBold + 'Solde du compte: {0} {1} ({2} DU/{3})'.format(balance, currency, balanceUD, currency.lower()).center(rows-1, ' ') + isBoldEnd) + print('+', end='') + print(''.center(rows-1, '-')) + if not noColors: + print(colored('Reçus', 'green'), '-', colored('En cours de réception', 'yellow'), '-', colored('Envoyé', 'blue'), '-', colored("En cours d'envoi", 'red')) + + return trans + + def jsonHistory(self, transList): + dailyJSON = [] + for i, trans in enumerate(transList): + dailyJSON.append(i) + dailyJSON[i] = {} + dailyJSON[i]['date'] = trans[1] + dailyJSON[i]['pubkey'] = trans[2] + dailyJSON[i]['amount'] = trans[3] + dailyJSON[i]['amountUD'] = trans[4] + dailyJSON[i]['comment'] = trans[5] + + dailyJSON = json.dumps(dailyJSON, indent=2) + # If we want to write JSON to a file + #jsonFile = open("history-{0}.json".format(self.pubkey[0:8]), "w") + #jsonFile.writelines(dailyJSON + '\n') + #jsonFile.close() + return dailyJSON + diff --git a/sms/jaklis/lib/gvaPay.py b/sms/jaklis/lib/gvaPay.py new file mode 100755 index 0000000..e017c88 --- /dev/null +++ b/sms/jaklis/lib/gvaPay.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Transaction: + + def __init__(self, dunikey, node, recipient, amount, comment='', useMempool=False, verbose=False): + self.dunikey = dunikey + self.recipient = recipient + self.amount = int(amount*100) + self.comment = comment + self.issuer = get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + self.verbose = verbose + self.node = node + self._isChange = False + + try: + if not re.match(PUBKEY_REGEX, recipient) or len(recipient) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + + try: + if recipient == self.issuer: + raise ValueError('Le destinataire ne peut pas être vous même.') + except: + sys.stderr.write("Le destinataire ne peut pas être vous même.\n") + raise + + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def genDoc(self): + # Build TX generation document + if self.verbose: print("useMempool:", str(self.useMempool)) + queryBuild = gql( + """ + query ($recipient: String!, $issuer: String!, $amount: Int!, $comment: String!, $useMempool: Boolean!){ genTx( + amount: $amount + comment: $comment + issuer: $issuer + recipient: $recipient + useMempoolSources: $useMempool + ) + } + """ + ) + paramsBuild = { + "recipient": self.recipient, + "issuer": self.issuer, + "amount": int(self.amount), + "comment": self.comment, + "useMempool": self.useMempool + } + + # Send TX document + try: + # self.txDoc = [] + self.txDoc = self.client.execute(queryBuild, variable_values=paramsBuild)['genTx'] + if self.verbose: print(self.txDoc[0]) + return self.txDoc + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la génération du document:\n" + message + "\n") + raise + + + # Check document + def checkTXDoc(self): + issuerRaw=[];outAmount=[];outPubkey=[];commentRaw=[] + for docs in self.txDoc: + docList = docs.splitlines() + for i, line in enumerate(docList): + if re.search("Issuers:", line): + issuerRaw.append(docList[(i + 1) % len(docList)]) + if re.search("Outputs:", line): + outputRaw = docList[(i + 1) % len(docList)].split(":") + outAmount.append(int(outputRaw[0])) + outPubkey.append(outputRaw[2].split("SIG(")[1].replace(')','')) + if re.search("Comment:", line): + commentRaw.append(line.split(': ', 1)[1]) + + # Check if it's only a change transaction + if all(i == self.issuer for i in outPubkey): + print("Le document contient une transaction de change") + self.isChange = True + # Check validity of the document + elif all(i != self.issuer for i in issuerRaw) or sum(outAmount) != self.amount or all(i != self.recipient for i in outPubkey) or all(i != self.comment for i in commentRaw): + sys.stderr.write(colored("Le document généré est corrompu !\nLe noeud " + self.node + "a peut être un dysfonctionnement.\n", 'red')) + sys.stderr.write(colored(issuerRaw[0] + " envoi " + str(outAmount[0]) + " vers " + outPubkey[0] + " with comment: " + commentRaw[0] + "\n", "yellow")) + raise ValueError('Le document généré est corrompu !') + else: + print("Le document généré est conforme.") + self.isChange = False + return self.txDoc + + def signDoc(self): + # Sign TX documents + signature=[] + self.signedDoc=[] + for i, docs in enumerate(self.txDoc): + signature.append(fmt["64"](sign(docs.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(docs.encode())])) + self.signedDoc.append(docs + signature[i].decode()) + return self.signedDoc + + + def sendTXDoc(self): + # Build TX documents + txResult=[] + for docs in self.signedDoc: + querySign = gql( + """ + mutation ($signedDoc: String!){ tx( + rawTx: $signedDoc + ) { + version + issuers + outputs + } + } + """ + ) + paramsSign = { + "signedDoc": docs + } + + # Send TX Signed document + try: + txResult.append(str(self.client.execute(querySign, variable_values=paramsSign))) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la transaction:\n" + message + "\n") + if self.verbose: + sys.stderr.write("Document final:\n" + docs) + raise ValueError(message) + else: + if self.isChange: + self.send() + else: + print(colored("Transaction effectué avec succès !", "green")) + if self.verbose: + print(docs) + break + + return txResult + + def _getIsChange(self): + return self._isChange + def _setIsChange(self, newChange): + if self.verbose: print("_setIsChange: ", str(newChange)) + self._isChange = newChange + if newChange: self.useMempool == True + isChange = property(_getIsChange, _setIsChange) + + def send(self): + result = self.genDoc() + result = self.checkTXDoc() + result = self.signDoc() + result = self.sendTXDoc() + return result + diff --git a/sms/jaklis/lib/likes.py b/sms/jaklis/lib/likes.py new file mode 100755 index 0000000..ade2a38 --- /dev/null +++ b/sms/jaklis/lib/likes.py @@ -0,0 +1,241 @@ +import os, sys, ast, requests, json, base58, base64, time, string, random, re +from lib.natools import fmt, sign, get_privkey, box_decrypt, box_encrypt +from time import sleep +from hashlib import sha256 +from datetime import datetime +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX + +class ReadLikes(CesiumCommon): + # Configure JSON document to send + def configDoc(self, profile): + if not profile: profile = self.pubkey + + data = {} + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = [ + {'term': {'index': 'user'}}, + {'term': {'type': 'profile'}}, + {'term': {'id': profile}}, + {'term': {'kind': 'STAR'}} + ] + # data['query']['bool']['should'] = {'term':{'issuer': self.issuer}} + data['size'] = 5000 + data['_source'] = ['issuer','level'] + data['aggs'] = { + 'level_sum': { + 'sum': { + 'field': 'level' + } + } + } + + return json.dumps(data) + + def sendDocument(self, document): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/like/record/_search'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + # print(result.text) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') + + def parseResult(self, result): + result = json.loads(result) + totalLikes = result['hits']['total'] + totalValue = result['aggregations']['level_sum']['value'] + if totalLikes: + score = totalValue/totalLikes + else: + score = 0 + raw = result['hits']['hits'] + finalPrint = {} + finalPrint['likes'] = [] + for i in raw: + issuer = i['_source']['issuer'] + # print(issuer) + gProfile = self.getProfile(issuer) + try: + pseudo = gProfile['title'] + except: + pseudo = '' + try: + payTo = gProfile['pubkey'] + except: + payTo = '' + id = i['_id'] + level = i['_source']['level'] + if issuer == self.pubkey: + finalPrint['yours'] = { 'id' : id, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level } + else: + finalPrint['likes'].append({ 'issuer' : issuer, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level }) + finalPrint['score'] = score + + return json.dumps(finalPrint) + + def getProfile(self, profile): + headers = { + 'Content-type': 'application/json', + } + + data = {} + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = [ + {'term': {'_index': 'user'}}, + {'term': {'_type': 'profile'}}, + {'term': {'_id': profile}} + ] + data['_source'] = ['title','pubkey'] + + data = json.dumps(data) + + result = requests.post('{0}/user/profile/_search'.format(self.pod), headers=headers, data=data) + result = json.loads(result.text)['hits']['hits'] + for i in result: + return i['_source'] + + +#################### Like class #################### + + +class SendLikes(CesiumCommon): + # Configure JSON document to send + def configDoc(self, profile, likes): + if not profile: profile = self.pubkey + if likes not in range(0, 6): + sys.stderr.write('Votre like doit être compris entre 0 et 5.\n') + return False + + + timeSent = int(time.time()) + + data = {} + data['version'] = 2 + data['index'] = "user" + data['type'] = "profile" + data['id'] = profile + data['kind'] = "STAR" + data['level'] = likes + data['time'] = timeSent + data['issuer'] = self.pubkey + + document = json.dumps(data) + + # Generate hash of document + hashDoc = sha256(document.encode()).hexdigest().upper() + + # Generate signature of document + signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() + + # Build final document + data = {} + data['hash'] = hashDoc + data['signature'] = signature + signJSON = json.dumps(data) + finalJSON = {**json.loads(signJSON), **json.loads(document)} + finalDoc = json.dumps(finalJSON) + + return finalDoc + + def sendDocument(self, document, pubkey): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/user/profile/:id/_like'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + print("Profile liké avec succès !") + return result.text + elif result.status_code == 400: + resultJson = json.loads(result.text) + if 'DuplicatedDocumentException' in resultJson['error']: + rmLike = UnLikes(self.dunikey, self.pod) + idLike = rmLike.checkLike(pubkey) + if idLike: + document = rmLike.configDoc(idLike) + rmLike.sendDocument(document, True) + sleep(0.5) + self.sendDocument(document, pubkey) + return resultJson['error'] + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') + else: + resultJson = json.loads(result.text) + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') + + +#################### Unlike class #################### + + +class UnLikes(CesiumCommon): + # Check if you liked this profile + def checkLike(self, pubkey): + readProfileLikes = ReadLikes(self.dunikey, self.pod) + document = readProfileLikes.configDoc(pubkey) + result = readProfileLikes.sendDocument(document) + result = readProfileLikes.parseResult(result) + result = json.loads(result) + + if 'yours' in result: + myLike = result['yours']['id'] + return myLike + else: + sys.stderr.write("Vous n'avez pas liké ce profile\n") + return False + + # Configure JSON document to send + def configDoc(self, idLike): + timeSent = int(time.time()) + + data = {} + data['version'] = 2 + data['index'] = "like" + data['type'] = "record" + data['id'] = idLike + data['issuer'] = self.pubkey + data['time'] = timeSent + + document = json.dumps(data) + + # Generate hash of document + hashDoc = sha256(document.encode()).hexdigest().upper() + + # Generate signature of document + signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() + + # Build final document + data = {} + data['hash'] = hashDoc + data['signature'] = signature + signJSON = json.dumps(data) + finalJSON = {**json.loads(signJSON), **json.loads(document)} + finalDoc = json.dumps(finalJSON) + + return finalDoc + + def sendDocument(self, document, silent): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + if not silent: + print("Like supprimé avec succès !") + return result.text + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') diff --git a/sms/jaklis/lib/messaging.py b/sms/jaklis/lib/messaging.py new file mode 100755 index 0000000..93541a6 --- /dev/null +++ b/sms/jaklis/lib/messaging.py @@ -0,0 +1,235 @@ +import os, sys, ast, requests, json, base58, base64 +from time import time +from datetime import datetime +from lib.natools import fmt, get_privkey, box_decrypt, box_encrypt +from lib.cesiumCommon import CesiumCommon, pp_json, PUBKEY_REGEX + + +#################### Reading class #################### + + +class ReadFromCesium(CesiumCommon): + # Configure JSON document to send + def configDoc(self, nbrMsg, outbox): + boxType = "issuer" if outbox else "recipient" + + data = {} + data['sort'] = { "time": "desc" } + data['from'] = 0 + data['size'] = nbrMsg + data['_source'] = ['issuer','recipient','title','content','time','nonce','read_signature'] + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = {} + data['query']['bool']['filter']['term'] = {} + data['query']['bool']['filter']['term'][boxType] = self.pubkey + + document = json.dumps(data) + return document + + def sendDocument(self, nbrMsg, outbox): + boxType = "outbox" if outbox else "inbox" + + document = self.configDoc(nbrMsg, outbox) + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/message/{1}/_search'.format(self.pod, boxType), headers=headers, data=document) + if result.status_code == 200: + return result.json()["hits"] + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text) + + # Parse JSON result and display messages + def readMessages(self, msgJSON, nbrMsg, outbox): + def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() + + # Get terminal size + rows = int(os.popen('stty size', 'r').read().split()[1]) + + totalMsg = msgJSON["total"] + if nbrMsg > totalMsg: + nbrMsg = totalMsg + + if totalMsg == 0: + print("Aucun message à afficher.") + return True + else: + infoTotal = " Nombre de messages: " + str(nbrMsg) + "/" + str(totalMsg) + " " + print(infoTotal.center(rows, '#')) + for hits in msgJSON["hits"]: + self.idMsg = hits["_id"] + msgSrc = hits["_source"] + self.issuer = msgSrc["issuer"] + nonce = msgSrc["nonce"] + nonce = base58.b58decode(nonce) + self.dateS = msgSrc["time"] + date = datetime.fromtimestamp(self.dateS).strftime(", le %d/%m/%Y à %H:%M ") + if outbox: + startHeader = " À " + msgSrc["recipient"] + else: + startHeader = " De " + self.issuer + headerMsg = startHeader + date + "(ID: {})".format(self.idMsg) + " " + + print('-'.center(rows, '-')) + print(headerMsg.center(rows+9, '-')) + print('-'.center(rows, '-')) + try: + self.title = decrypt(msgSrc["title"]) + self.content = decrypt(msgSrc["content"]) + except Exception as e: + sys.stderr.write(str(e) + '\n') + pp_json(hits) + continue + print('\033[1m' + self.title + '\033[0m') + print(self.content) + + print(infoTotal.center(rows, '#')) + + # Parse JSON result and display messages + def jsonMessages(self, msgJSON, nbrMsg, outbox): + def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() + + totalMsg = msgJSON["total"] + if nbrMsg > totalMsg: + nbrMsg = totalMsg + + if totalMsg == 0: + print("Aucun message à afficher") + return True + else: + data = [] + # data.append({}) + # data[0]['total'] = totalMsg + for i, hits in enumerate(msgJSON["hits"]): + self.idMsg = hits["_id"] + msgSrc = hits["_source"] + self.issuer = msgSrc["issuer"] + nonce = msgSrc["nonce"] + nonce = base58.b58decode(nonce) + self.date = msgSrc["time"] + + if outbox: + pubkey = msgSrc["recipient"] + else: + pubkey = self.issuer + + try: + self.title = decrypt(msgSrc["title"]) + self.content = decrypt(msgSrc["content"]) + except Exception as e: + sys.stderr.write(str(e) + '\n') + pp_json(hits) + continue + + data.append(i) + data[i] = {} + data[i]['id'] = self.idMsg + data[i]['date'] = self.date + data[i]['pubkey'] = pubkey + data[i]['title'] = self.title + data[i]['content'] = self.content + + data = json.dumps(data, indent=2) + return data + + +#################### Sending class #################### + + +class SendToCesium(CesiumCommon): + def encryptMsg(self, msg): + return fmt["64"](box_encrypt(msg.encode(), get_privkey(self.dunikey, "pubsec"), self.recipient, self.nonce)).decode() + + def configDoc(self, title, msg): + b58nonce = base58.b58encode(self.nonce).decode() + + # Get current timestamp + timeSent = int(time()) + + # Generate custom JSON + data = {} + data['issuer'] = self.pubkey + data['recipient'] = self.recipient + data['title'] = title + data['content'] = msg + data['time'] = timeSent + data['nonce'] = b58nonce + data['version'] = 2 + document = json.dumps(data) + + return self.signDoc(document) + + + def sendDocument(self, document, outbox): + boxType = "outbox" if outbox else "inbox" + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get result + try: + result = requests.post('{0}/message/{1}?pubkey={2}'.format(self.pod, boxType, self.recipient), headers=headers, data=document) + except Exception as e: + sys.stderr.write("Impossible d'envoyer le message:\n" + str(e)) + sys.exit(1) + else: + if result.status_code == 200: + print("Message envoyé avec succès !") + print("ID: " + result.text) + return result + else: + sys.stderr.write("Erreur inconnue:" + '\n') + print(str(pp_json(result.text)) + '\n') + + +#################### Deleting class #################### + + +class DeleteFromCesium(CesiumCommon): + def configDoc(self, idMsg, outbox): + # Get current timestamp + timeSent = int(time()) + + boxType = "outbox" if outbox else "inbox" + + # Generate document to customize + data = {} + data['version'] = 2 + data['index'] = "message" + data['type'] = boxType + data['id'] = idMsg + data['issuer'] = self.pubkey + data['time'] = timeSent + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocument(self, document, idMsg): + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get result + try: + result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) + if result.status_code == 404: + raise ValueError("Message introuvable") + elif result.status_code == 403: + raise ValueError("Vous n'êtes pas l'auteur de ce message.") + except Exception as e: + sys.stderr.write("Impossible de supprimer le message {0}:\n".format(idMsg) + str(e) + "\n") + return False + else: + if result.status_code == 200: + print("Message {0} supprimé avec succès !".format(idMsg)) + return result + else: + sys.stderr.write("Erreur inconnue.") diff --git a/sms/jaklis/lib/natools.py b/sms/jaklis/lib/natools.py new file mode 100755 index 0000000..18f06d1 --- /dev/null +++ b/sms/jaklis/lib/natools.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 + +""" + CopyLeft 2020 Pascal Engélibert + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" + +__version__ = "1.3.1" + +import os, sys, duniterpy.key, libnacl, base58, base64, getpass + +def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str: + if arg in args and len(args) > args.index(arg)+n: + return args[args.index(arg)+n] + else: + return default + +def read_data(data_path, b=True): + if data_path == "-": + if b: + return sys.stdin.buffer.read() + else: + return sys.stdin.read() + else: + return open(os.path.expanduser(data_path), "rb" if b else "r").read() + +def write_data(data, result_path): + if result_path == "-": + os.fdopen(sys.stdout.fileno(), 'wb').write(data) + else: + open(os.path.expanduser(result_path), "wb").write(data) + +def encrypt(data, pubkey): + return duniterpy.key.PublicKey(pubkey).encrypt_seal(data) + +def decrypt(data, privkey): + return privkey.decrypt_seal(data) + +def box_encrypt(data, privkey, pubkey, nonce=None, attach_nonce=False): + signer = libnacl.sign.Signer(privkey.seed) + sk = libnacl.public.SecretKey(libnacl.crypto_sign_ed25519_sk_to_curve25519(signer.sk)) + verifier = libnacl.sign.Verifier(base58.b58decode(pubkey).hex()) + pk = libnacl.public.PublicKey(libnacl.crypto_sign_ed25519_pk_to_curve25519(verifier.vk)) + box = libnacl.public.Box(sk.sk, pk.pk) + data = box.encrypt(data, nonce) if nonce else box.encrypt(data) + return data if attach_nonce else data[24:] + +def box_decrypt(data, privkey, pubkey, nonce=None): + signer = libnacl.sign.Signer(privkey.seed) + sk = libnacl.public.SecretKey(libnacl.crypto_sign_ed25519_sk_to_curve25519(signer.sk)) + verifier = libnacl.sign.Verifier(base58.b58decode(pubkey).hex()) + pk = libnacl.public.PublicKey(libnacl.crypto_sign_ed25519_pk_to_curve25519(verifier.vk)) + box = libnacl.public.Box(sk.sk, pk.pk) + return box.decrypt(data, nonce) if nonce else box.decrypt(data) + +def sign(data, privkey): + return privkey.sign(data) + +def verify(data, pubkey): + try: + ret = libnacl.sign.Verifier(duniterpy.key.PublicKey(pubkey).hex_pk()).verify(data) + sys.stderr.write("Signature OK!\n") + return ret + except ValueError: + sys.stderr.write("Bad signature!\n") + exit(1) + +def get_privkey(privkey_path, privkey_format): + if privkey_format == "pubsec": + if privkey_path == "*": + privkey_path = "privkey.pubsec" + return duniterpy.key.SigningKey.from_pubsec_file(privkey_path) + + elif privkey_format == "cred": + if privkey_path == "*": + privkey_path = "-" + if privkey_path == "-": + return duniterpy.key.SigningKey.from_credentials(getpass.getpass("Password: "), getpass.getpass("Salt: ")) + else: + return duniterpy.key.SigningKey.from_credentials_file(privkey_path) + + elif privkey_format == "seedh": + if privkey_path == "*": + privkey_path = "authfile.seedhex" + return duniterpy.key.SigningKey.from_seedhex(read_data(privkey_path, False)) + + elif privkey_format == "wif": + if privkey_path == "*": + privkey_path = "authfile.wif" + return duniterpy.key.SigningKey.from_wif_or_ewif_file(privkey_path) + + elif privkey_format == "wifh": + if privkey_path == "*": + privkey_path = "authfile.wif" + return duniterpy.key.SigningKey.from_wif_or_ewif_hex(privkey_path) + + elif privkey_format == "ssb": + if privkey_path == "*": + privkey_path = "secret" + return duniterpy.key.SigningKey.from_ssb_file(privkey_path) + + elif privkey_format == "key": + if privkey_path == "*": + privkey_path = "authfile.key" + return duniterpy.key.SigningKey.from_private_key(privkey_path) + + print("Error: unknown privkey format") + +def fill_pubkey(pubkey, length=32): + while pubkey[0] == 0: + pubkey = pubkey[1:] + return b"\x00"*(length-len(pubkey)) + pubkey + +def pubkey_checksum(pubkey, length=32, clength=3): + return base58.b58encode(libnacl.crypto_hash_sha256(libnacl.crypto_hash_sha256(fill_pubkey(base58.b58decode(pubkey), length)))).decode()[:clength] + +# returns (pubkey:bytes|None, deprecated_length:bool) +def check_pubkey(pubkey): + if ":" in pubkey: + parts = pubkey.split(":") + if len(parts[1]) < 3 or len(parts[1]) > 32: + return (None, False) + for i in range(32, 0, -1): + if pubkey_checksum(parts[0], i, len(parts[1])) == parts[1]: + return (parts[0], i < 32) + return (None, False) + return (pubkey, False) + +fmt = { + "raw": lambda data: data, + "16": lambda data: data.hex().encode(), + "32": lambda data: base64.b32encode(data), + "58": lambda data: base58.b58encode(data), + "64": lambda data: base64.b64encode(data), + "64u": lambda data: base64.urlsafe_b64encode(data), + "85": lambda data: base64.b85encode(data), +} + +defmt = { + "raw": lambda data: data, + "16": lambda data: bytes.fromhex(data), + "32": lambda data: base64.b32decode(data), + "58": lambda data: base58.b58decode(data), + "64": lambda data: base64.b64decode(data), + "85": lambda data: base64.b85decode(data), +} + +def show_help(): + print("""Usage: +python3 natools.py [options] + +Commands: + encrypt Encrypt data + decrypt Decrypt data + box-encrypt Encrypt data (NaCl box) + box-decrypt Decrypt data (NaCl box) + sign Sign data + verify Verify data + pubkey Display pubkey + pk Display b58 pubkey shorthand + +Options: + -c Display pubkey checksum + -f Private key format (default: cred) + key cred pubsec seedh ssb wif wifh + -i Input file path (default: -) + -I Input format: raw 16 32 58 64 85 (default: raw) + -k Privkey file path (* for auto) (default: *) + -n Nonce (b64, 24 bytes) (for NaCl box) + -N Attach nonce to output (for NaCl box encryption) + --noinc Do not include msg after signature + -o Output file path (default: -) + -O Output format: raw 16 32 58 64 64u 85 (default: raw) + -p Pubkey (base58) + + --help Show help + --version Show version + --debug Debug mode (display full errors) + +Note: "-" means stdin or stdout. +""") + +if __name__ == "__main__": + + if "--help" in sys.argv: + show_help() + exit() + + if "--version" in sys.argv: + print(__version__) + exit() + + privkey_format = getargv("-f", "cred") + data_path = getargv("-i", "-") + privkey_path = getargv("-k", "*") + pubkey = getargv("-p") + result_path = getargv("-o", "-") + output_format = getargv("-O", "raw") + input_format = getargv("-I", "raw") + + if pubkey: + pubkey, len_deprecated = check_pubkey(pubkey) + if not pubkey: + print("Invalid pubkey checksum! Please check spelling.") + exit(1) + if len(base58.b58decode(pubkey)) > 32: + print("Invalid pubkey: too long!") + exit(1) + if len_deprecated: + print("Warning: valid pubkey checksum, but deprecated format (truncating zeros)") + + try: + if sys.argv[1] == "encrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + write_data(fmt[output_format](encrypt(defmt[input_format](read_data(data_path)), pubkey)), result_path) + + elif sys.argv[1] == "decrypt": + write_data(fmt[output_format](decrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format))), result_path) + + elif sys.argv[1] == "box-encrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + nonce = getargv("-n", None) + if nonce: + nonce = base64.b64decode(nonce) + attach_nonce = "-N" in sys.argv + write_data(fmt[output_format](box_encrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format), pubkey, nonce, attach_nonce)), result_path) + + elif sys.argv[1] == "box-decrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + nonce = getargv("-n", None) + if nonce: + nonce = base64.b64decode(nonce) + write_data(fmt[output_format](box_decrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format), pubkey, nonce)), result_path) + + elif sys.argv[1] == "sign": + data = defmt[input_format](read_data(data_path)) + signed = sign(data, get_privkey(privkey_path, privkey_format)) + + if "--noinc" in sys.argv: + signed = signed[:len(signed)-len(data)] + + write_data(fmt[output_format](signed), result_path) + + elif sys.argv[1] == "verify": + if not pubkey: + print("Please provide pubkey!") + exit(1) + write_data(fmt[output_format](verify(defmt[input_format](read_data(data_path)), pubkey)), result_path) + + elif sys.argv[1] == "pubkey": + if pubkey: + if "-c" in sys.argv and output_format == "58": + write_data("{}:{}".format(pubkey, pubkey_checksum(pubkey)).encode(), result_path) + else: + write_data(fmt[output_format](base58.b58decode(pubkey)), result_path) + else: + pubkey = get_privkey(privkey_path, privkey_format).pubkey + if "-c" in sys.argv and output_format == "58": + write_data("{}:{}".format(pubkey, pubkey_checksum(pubkey)).encode(), result_path) + else: + write_data(fmt[output_format](base58.b58decode(pubkey)), result_path) + + elif sys.argv[1] == "pk": + if not pubkey: + pubkey = get_privkey(privkey_path, privkey_format).pubkey + if "-c" in sys.argv: + print("{}:{}".format(pubkey, pubkey_checksum(pubkey))) + else: + print(pubkey) + + else: + show_help() + + except Exception as e: + if "--debug" in sys.argv: + 0/0 # DEBUG MODE (raise error when handling error to display backtrace) + sys.stderr.write("Error: {}\n".format(e)) + show_help() + exit(1) diff --git a/sms/jaklis/lib/offers.py b/sms/jaklis/lib/offers.py new file mode 100644 index 0000000..c8358a2 --- /dev/null +++ b/sms/jaklis/lib/offers.py @@ -0,0 +1,137 @@ +import sys, re, json, requests, base64 +from time import time +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX + +class Offers(CesiumCommon): + # Configure JSON document SET to send + def configDocSet(self, title, description, city, localisation, category, price: float, picture): + timeSent = int(time()) + +# {"parent":"cat90","localizedNames":{"en":"Fruits & Vegetables","es-ES":"Frutas y Vegetales","fr-FR":"Fruits & Légumes"},"name":"Fruits & Légumes","id":"cat92"} + + data = {} + if title: data['title'] = title + if description: data['description'] = description + if city: data['city'] = city + if localisation: + geoPoint = {} + geoPoint['lat'] = localisation[0] + geoPoint['lon'] = localisation[1] + data['geoPoint'] = geoPoint + if picture: + picture = open(picture, 'rb').read() + picture = base64.b64encode(picture).decode() + data['thumbnail'] = {} + data['thumbnail']['_content'] = picture + data['thumbnail']['_content_type'] = "image/png" + # if category: data['category'] = category + # else: data['category'] = {"parent":"cat24","localizedNames":{"en":"CD / Music","es-ES":"CDs / M\u00fasica","fr-FR":"CD / Musique"},"name":"CD / Musique","id":"cat26"} + # data['category'] = {"parent":"cat90","localizedNames":{"en":"Fruits & Vegetables","es-ES":"Frutas y Vegetales","fr-FR":"Fruits & Légumes"},"name":"Fruits & Légumes","id":"cat92"} + data['category'] = {"parent":"cat24","localizedNames":{"en":"DVD / Films","es-ES":"DVDs / Cine","fr-FR":"DVD / Films"},"name":"DVD / Films","id":"cat25"} + if price: data['price'] = float(price) * 100 + data['type'] = 'offer' + data['time'] = timeSent + data['creationTime'] = timeSent + data['issuer'] = self.pubkey + data['pubkey'] = self.pubkey + data['version'] = 2 + data['currency'] = 'g1' + data['unit'] = None + data['fees'] = None + data['feesCurrency'] = None + if picture: data['picturesCount'] = 1 + else: data['picturesCount'] = 0 + data['stock'] = 1 + data['tags'] = [] + + document = json.dumps(data) + + return self.signDoc(document) + + # Configure JSON document SET to send + def configDocErase(self, id): + timeSent = int(time()) + +# "currency":"g1","unit":null,"fees":null,"feesCurrency":null,"picturesCount":0,"stock":0,"tags":[],"id":"AXehXeyZaml2THvBAeS5","creationTime":1613320117} +#AXehXeyZaml2THvBAeS5 + + + offerToDeleteBrut = self.sendDocumentGet(id, 'get') + offerToDelete = json.loads(self.parseJSON(offerToDeleteBrut)) + + title = offerToDelete['title'] + creationTime = offerToDelete['time'] + issuer = offerToDelete['issuer'] + pubkey = offerToDelete['pubkey'] + + data = {} + data['title'] = title + data['time'] = timeSent + data['creationTime'] = creationTime + data['id'] = id + data['issuer'] = issuer + data['pubkey'] = pubkey + data['version'] = 2 + data['type'] = "offer" + data['currency'] = "g1" + data['unit'] = None + data['fees'] = None + data['feesCurrency'] = None + data['picturesCount'] = 0 + data['stock'] = 0 + data['tags'] = [] + + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocumentGet(self, id, type): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + if type == 'set': + reqQuery = '{0}/market/record'.format(self.pod) + elif type == 'get': + reqQuery = '{0}/market/record/{1}?_source=category,title,description,issuer,time,creationTime,location,address,city,price,unit,currency,thumbnail._content_type,picturesCount,type,stock,fees,feesCurrency,geoPoint,pubkey,freePrice'.format(self.pod, id) + elif type == 'erase': + reqQuery = '{0}/market/delete'.format(self.pod) + + result = requests.get(reqQuery, headers=headers) + if result.status_code == 200: + # print(result.text) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document...\n" + result.text + '\n') + + + def sendDocumentSet(self, document, type, id=None): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + if type == 'set': + reqQuery = '{0}/market/record'.format(self.pod) + if type == 'delete': + reqQuery = '{0}/market/record/{1}/_update'.format(self.pod, id) + + result = requests.post(reqQuery, headers=headers, data=document) + if result.status_code == 200: + # print(result.text) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document...\n" + result.text + '\n') + + def parseJSON(self, doc): + doc = json.loads(doc)['_source'] + if doc: + # pubkey = { "pubkey": doc['issuer'] } + # rest = { "description": doc['description'] } + # final = {**pubkey, **rest} + return json.dumps(doc, indent=2) + else: + return 'Profile vide' diff --git a/sms/jaklis/lib/profiles.py b/sms/jaklis/lib/profiles.py new file mode 100755 index 0000000..5386c89 --- /dev/null +++ b/sms/jaklis/lib/profiles.py @@ -0,0 +1,125 @@ +import sys, re, json, requests, base64 +from time import time +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX + + +class Profiles(CesiumCommon): + # Configure JSON document SET to send + def configDocSet(self, name, description, city, address, pos, socials, avatar): + timeSent = int(time()) + + data = {} + if name: data['title'] = name + if description: data['description'] = description + if address: data['address'] = address + if city: data['city'] = city + if pos: + geoPoint = {} + geoPoint['lat'] = pos[0] + geoPoint['lon'] = pos[1] + data['geoPoint'] = geoPoint + if socials: + data['socials'] = [] + data['socials'].append({}) + data['socials'][0]['type'] = "web" + data['socials'][0]['url'] = socials + if avatar: + avatar = open(avatar, 'rb').read() + avatar = base64.b64encode(avatar).decode() + data['avatar'] = {} + data['avatar']['_content'] = avatar + data['avatar']['_content_type'] = "image/png" + data['time'] = timeSent + data['issuer'] = self.pubkey + data['version'] = 2 + data['tags'] = [] + + document = json.dumps(data) + + return self.signDoc(document) + + # Configure JSON document GET to send + def configDocGet(self, profile, scope='title', getAvatar=None): + + if getAvatar: + avatar = "avatar" + else: + avatar = "avatar._content_type" + + data = { + "query": { + "bool": { + "should":[ + { + "match":{ + scope:{ + "query": profile,"boost":2 + } + } + },{ + "prefix": {scope: profile} + } + ] + } + },"highlight": { + "fields": { + "title":{}, + "tags":{} + } + },"from":0, + "size":100, + "_source":["title", avatar,"description","city","address","socials.url","creationTime","membersCount","type"], + "indices_boost":{"user":100,"page":1,"group":0.01 + } + } + + document = json.dumps(data) + + return document + + # Configure JSON document SET to send + def configDocErase(self): + timeSent = int(time()) + + data = {} + data['time'] = timeSent + data['id'] = self.pubkey + data['issuer'] = self.pubkey + data['version'] = 2 + data['index'] = "user" + data['type'] = "profile" + + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocument(self, document, type): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + if type == 'set': + reqQuery = '{0}/user/profile?pubkey={1}/_update?pubkey={1}'.format(self.pod, self.pubkey) + elif type == 'get': + reqQuery = '{0}/user,page,group/profile,record/_search'.format(self.pod) + elif type == 'erase': + reqQuery = '{0}/history/delete'.format(self.pod) + + result = requests.post(reqQuery, headers=headers, data=document) + if result.status_code == 200: + # print(result.text) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document...\n" + result.text + '\n') + + def parseJSON(self, doc): + doc = json.loads(doc)['hits']['hits'] + if doc: + pubkey = { "pubkey": doc[0]['_id'] } + rest = doc[0]['_source'] + final = {**pubkey, **rest} + return json.dumps(final, indent=2) + else: + return 'Profile vide' diff --git a/sms/jaklis/paiements.py b/sms/jaklis/paiements.py new file mode 100755 index 0000000..dde58da --- /dev/null +++ b/sms/jaklis/paiements.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +""" +ZetCode Tkinter tutorial + +In this example, we use the pack +manager to create a review example. + +Author: Jan Bodnar +Website: www.zetcode.com +""" + +import PySimpleGUI as sg +from lib.gva import GvaApi +import sys, os, threading +from shutil import copyfile +from os.path import join, dirname +from dotenv import load_dotenv +from lib.natools import get_privkey +import requests + +class StdoutRedirector(object): + def __init__(self, text_widget): + self.text_widget = text_widget + + def write(self, s): + self.text_widget.insert('end', s) + self.text_widget.see('end') + + def flush(self): + pass + + +MY_PATH = os.path.realpath(os.path.dirname(sys.argv[0])) + '/' + +# Get variables environment +if not os.path.isfile(MY_PATH + '.env'): + copyfile(MY_PATH + ".env.template",MY_PATH + ".env") +dotenv_path = join(dirname(__file__),MY_PATH + '.env') +load_dotenv(dotenv_path) + +dunikey = os.getenv('DUNIKEY') +if not os.path.isfile(dunikey): + HOME = os.getenv("HOME") + dunikey = HOME + dunikey + if not os.path.isfile(dunikey): + sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) + sys.exit(1) +node = os.getenv('NODE') +issuer = get_privkey(dunikey, "pubsec").pubkey + + +def ProceedPaiement(recipient, amount, comment): + if not recipient: + raise ValueError("Veuillez indiquer un destinataire de paiement") + elif not amount: + raise ValueError("Veuillez indiquer le montant de la transaction") + + amount = int(float(amount.replace(',','.'))*100) + print("Paiement en cours vers", recipient) + gva = GvaApi(dunikey, node, recipient) + gva.pay(amount, comment, False, False) + + recipient = amount = comment = None + + +sg.theme('DarkGrey2') +layout = [ [sg.Text('Noeud utilisé: ' + node)], + [sg.Text('Votre clé publique: ' + issuer)], + [sg.Text('')], + [sg.Text('Destinataire: '), sg.InputText(size=(55, None),default_text=issuer)], + [sg.Text('Montant: '), sg.InputText(size=(7, None)), sg.Text('Ḡ1')], + [sg.Text('Commentaire:'), sg.InputText(size=(55, None))], + [sg.Button('Envoyer')] ] + +# Create the Window +window = sg.Window('Paiement Ḡ1 - GVA', layout) +# availablePubkeys = requests.get('https://g1-stats.axiom-team.fr/data/wallets-g1.txt') +while True: + try: + event, values = window.read() + if event == sg.WIN_CLOSED: + break + if event == 'Envoyer': + ProceedPaiement(values[0], values[1], values[2]) + except Exception as e: + loc = window.CurrentLocation() + sg.popup(e, title="ERREUR", button_color=('black','red'), location=(loc)) + else: + loc = window.CurrentLocation() + sg.popup(f'Transaction effectué avec succès !', title="Envoyé", location=(loc)) + + +window.close() diff --git a/sms/jaklis/requirements.txt b/sms/jaklis/requirements.txt new file mode 100755 index 0000000..afcf4b4 --- /dev/null +++ b/sms/jaklis/requirements.txt @@ -0,0 +1,7 @@ +wheel +base58 +pybase64 +duniterpy +termcolor +python-dotenv +requests diff --git a/sms/jaklis/setup.sh b/sms/jaklis/setup.sh new file mode 100755 index 0000000..222e4ba --- /dev/null +++ b/sms/jaklis/setup.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +for i in gcc python3-pip python3-setuptools libpq-dev python3-dev python3-wheel; do + if [ $(dpkg-query -W -f='${Status}' $i 2>/dev/null | grep -c "ok installed") -eq 0 ]; then + [[ ! $j ]] && sudo apt update + sudo apt install -y $i + j=1 + fi +done + +pip3 install -r requirements.txt +chmod u+x jaklis.py diff --git a/sms/key_create_dunikey.py b/sms/key_create_dunikey.py new file mode 100755 index 0000000..de0673b --- /dev/null +++ b/sms/key_create_dunikey.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# This Python script gets Duniter creddentials as arguments, and writes a PubSec file that should be compatible with Cesium and Silkaj(DuniterPy) clients. +# launch with : +# python3 key_create_dnuikey.py + +# depends on duniterpy 0.56 + +### Licence - WTFPL +# This script was written my Matograine, in the hope that it will be helpful. +# Do What The Fuck you like with it. There is : +# * no guarantee that this will work +# * no support of any kind +# +# If this is helpful, please consider making a donation to the developper's pubkey : 78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8 +# Have fun + +from sys import argv +from duniterpy.key import SigningKey + +# path to save to +path = "/tmp/secret.dunikey" + +key = SigningKey.from_credentials(argv[1], argv[2], None) +key.save_pubsec_file(path) +print( + key.pubkey, +) diff --git a/sms/natools.py b/sms/natools.py new file mode 100755 index 0000000..63f14a0 --- /dev/null +++ b/sms/natools.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 + +""" + CopyLeft 2020 Pascal Engélibert + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" + +__version__ = "1.3.1" + +import os, sys, duniterpy.key, libnacl, base58, base64, getpass + +def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str: + if arg in args and len(args) > args.index(arg)+n: + return args[args.index(arg)+n] + else: + return default + +def read_data(data_path, b=True): + if data_path == "-": + if b: + return sys.stdin.buffer.read() + else: + return sys.stdin.read() + else: + return open(os.path.expanduser(data_path), "rb" if b else "r").read() + +def write_data(data, result_path): + if result_path == "-": + os.fdopen(sys.stdout.fileno(), 'wb').write(data) + else: + open(os.path.expanduser(result_path), "wb").write(data) + +def encrypt(data, pubkey): + return duniterpy.key.PublicKey(pubkey).encrypt_seal(data) + +def decrypt(data, privkey): + return privkey.decrypt_seal(data) + +def box_encrypt(data, privkey, pubkey, nonce=None, attach_nonce=False): + signer = libnacl.sign.Signer(privkey.seed) + sk = libnacl.public.SecretKey(libnacl.crypto_sign_ed25519_sk_to_curve25519(signer.sk)) + verifier = libnacl.sign.Verifier(base58.b58decode(pubkey).hex()) + pk = libnacl.public.PublicKey(libnacl.crypto_sign_ed25519_pk_to_curve25519(verifier.vk)) + box = libnacl.public.Box(sk.sk, pk.pk) + data = box.encrypt(data, nonce) if nonce else box.encrypt(data) + return data if attach_nonce else data[24:] + +def box_decrypt(data, privkey, pubkey, nonce=None): + signer = libnacl.sign.Signer(privkey.seed) + sk = libnacl.public.SecretKey(libnacl.crypto_sign_ed25519_sk_to_curve25519(signer.sk)) + verifier = libnacl.sign.Verifier(base58.b58decode(pubkey).hex()) + pk = libnacl.public.PublicKey(libnacl.crypto_sign_ed25519_pk_to_curve25519(verifier.vk)) + box = libnacl.public.Box(sk.sk, pk.pk) + return box.decrypt(data, nonce) if nonce else box.decrypt(data) + +def sign(data, privkey): + return privkey.sign(data) + +def verify(data, pubkey): + try: + ret = libnacl.sign.Verifier(duniterpy.key.PublicKey(pubkey).hex_pk()).verify(data) + sys.stderr.write("Signature OK!\n") + return ret + except ValueError: + sys.stderr.write("Bad signature!\n") + exit(1) + +def get_privkey(privkey_path, privkey_format): + if privkey_format == "pubsec": + if privkey_path == "*": + privkey_path = "privkey.pubsec" + return duniterpy.key.SigningKey.from_pubsec_file(privkey_path) + + elif privkey_format == "cred": + if privkey_path == "*": + privkey_path = "-" + if privkey_path == "-": + return duniterpy.key.SigningKey.from_credentials(getpass.getpass("Salt: "), getpass.getpass("Password: ")) + else: + return duniterpy.key.SigningKey.from_credentials_file(privkey_path) + + elif privkey_format == "seedh": + if privkey_path == "*": + privkey_path = "authfile.seedhex" + return duniterpy.key.SigningKey.from_seedhex(read_data(privkey_path, False)) + + elif privkey_format == "wif": + if privkey_path == "*": + privkey_path = "authfile.wif" + return duniterpy.key.SigningKey.from_wif_or_ewif_file(privkey_path) + + elif privkey_format == "wifh": + if privkey_path == "*": + privkey_path = "authfile.wif" + return duniterpy.key.SigningKey.from_wif_or_ewif_hex(privkey_path) + + elif privkey_format == "ssb": + if privkey_path == "*": + privkey_path = "secret" + return duniterpy.key.SigningKey.from_ssb_file(privkey_path) + + elif privkey_format == "key": + if privkey_path == "*": + privkey_path = "authfile.key" + return duniterpy.key.SigningKey.from_private_key(privkey_path) + + elif privkey_format == "ipfs-keystore": + if privkey_path == "*": + privkey_path = "key_self" + return duniterpy.key.SigningKey(read_data(privkey_path)[4:36]) + + print("Error: unknown privkey format") + +def format_privkey(privkey, output_privkey_format): + if output_privkey_format == "pubsec": + return "Type: PubSec\nVersion: 1\npub: {}\nsec: {}".format(privkey.pubkey, base58.b58encode(privkey.sk).decode()).encode() + + elif output_privkey_format == "seedh": + return privkey.hex_seed() + + elif output_privkey_format == "ipfs-keystore": + return b"\x08\x01\x12@"+privkey.sk + + print("Error: unknown output privkey format") + +def fill_pubkey(pubkey, length=32): + while pubkey[0] == 0: + pubkey = pubkey[1:] + return b"\x00"*(length-len(pubkey)) + pubkey + +def pubkey_checksum(pubkey, length=32, clength=3): + return base58.b58encode(libnacl.crypto_hash_sha256(libnacl.crypto_hash_sha256(fill_pubkey(base58.b58decode(pubkey), length)))).decode()[:clength] + +# returns (pubkey:bytes|None, deprecated_length:bool) +def check_pubkey(pubkey): + if ":" in pubkey: + parts = pubkey.split(":") + if len(parts[1]) < 3 or len(parts[1]) > 32: + return (None, False) + for i in range(32, 0, -1): + if pubkey_checksum(parts[0], i, len(parts[1])) == parts[1]: + return (parts[0], i < 32) + return (None, False) + return (pubkey, False) + +fmt = { + "raw": lambda data: data, + "16": lambda data: data.hex().encode(), + "32": lambda data: base64.b32encode(data), + "58": lambda data: base58.b58encode(data), + "64": lambda data: base64.b64encode(data), + "64u": lambda data: base64.urlsafe_b64encode(data), + "85": lambda data: base64.b85encode(data), +} + +defmt = { + "raw": lambda data: data, + "16": lambda data: bytes.fromhex(data), + "32": lambda data: base64.b32decode(data), + "58": lambda data: base58.b58decode(data), + "64": lambda data: base64.b64decode(data), + "85": lambda data: base64.b85decode(data), +} + +def show_help(): + print("""Usage: +python3 natools.py [options] + +Commands: + encrypt Encrypt data + decrypt Decrypt data + box-encrypt Encrypt data (NaCl box) + box-decrypt Decrypt data (NaCl box) + sign Sign data + verify Verify data + pubkey Display pubkey + privkey Display private key + pk Display b58 pubkey shorthand + +Options: + -c Display pubkey checksum + -f Private key format (default: cred) + key cred pubsec seedh ssb wif wifh ipfs-keystore + -F Output private key format (default: pubsec) + pubsec seedh ipfs-keystore + -i Input file path (default: -) + -I Input format: raw 16 32 58 64 85 (default: raw) + -k Privkey file path (* for auto) (default: *) + -n Nonce (b64, 24 bytes) (for NaCl box) + -N Attach nonce to output (for NaCl box encryption) + --noinc Do not include msg after signature + -o Output file path (default: -) + -O Output format: raw 16 32 58 64 64u 85 (default: raw) + -p Pubkey (base58) + + --help Show help + --version Show version + --debug Debug mode (display full errors) + +Note: "-" means stdin or stdout. +""") + +if __name__ == "__main__": + + if "--help" in sys.argv: + show_help() + exit() + + if "--version" in sys.argv: + print(__version__) + exit() + + privkey_format = getargv("-f", "cred") + output_privkey_format = getargv("-F", "pubsec") + data_path = getargv("-i", "-") + privkey_path = getargv("-k", "*") + pubkey = getargv("-p") + result_path = getargv("-o", "-") + output_format = getargv("-O", "raw") + input_format = getargv("-I", "raw") + + if pubkey: + pubkey, len_deprecated = check_pubkey(pubkey) + if not pubkey: + print("Invalid pubkey checksum! Please check spelling.") + exit(1) + if len(base58.b58decode(pubkey)) > 32: + print("Invalid pubkey: too long!") + exit(1) + if len_deprecated: + print("Warning: valid pubkey checksum, but deprecated format (truncating zeros)") + + try: + if sys.argv[1] == "encrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + write_data(fmt[output_format](encrypt(defmt[input_format](read_data(data_path)), pubkey)), result_path) + + elif sys.argv[1] == "decrypt": + write_data(fmt[output_format](decrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format))), result_path) + + elif sys.argv[1] == "box-encrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + nonce = getargv("-n", None) + if nonce: + nonce = base64.b64decode(nonce) + attach_nonce = "-N" in sys.argv + write_data(fmt[output_format](box_encrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format), pubkey, nonce, attach_nonce)), result_path) + + elif sys.argv[1] == "box-decrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + nonce = getargv("-n", None) + if nonce: + nonce = base64.b64decode(nonce) + write_data(fmt[output_format](box_decrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format), pubkey, nonce)), result_path) + + elif sys.argv[1] == "sign": + data = defmt[input_format](read_data(data_path)) + signed = sign(data, get_privkey(privkey_path, privkey_format)) + + if "--noinc" in sys.argv: + signed = signed[:len(signed)-len(data)] + + write_data(fmt[output_format](signed), result_path) + + elif sys.argv[1] == "verify": + if not pubkey: + print("Please provide pubkey!") + exit(1) + write_data(fmt[output_format](verify(defmt[input_format](read_data(data_path)), pubkey)), result_path) + + elif sys.argv[1] == "pubkey": + if pubkey: + if "-c" in sys.argv and output_format == "58": + write_data("{}:{}".format(pubkey, pubkey_checksum(pubkey)).encode(), result_path) + else: + write_data(fmt[output_format](base58.b58decode(pubkey)), result_path) + else: + pubkey = get_privkey(privkey_path, privkey_format).pubkey + if "-c" in sys.argv and output_format == "58": + write_data("{}:{}".format(pubkey, pubkey_checksum(pubkey)).encode(), result_path) + else: + write_data(fmt[output_format](base58.b58decode(pubkey)), result_path) + + elif sys.argv[1] == "privkey": + privkey = get_privkey(privkey_path, privkey_format) + write_data(fmt[output_format](format_privkey(privkey, output_privkey_format)), result_path) + + elif sys.argv[1] == "pk": + if not pubkey: + pubkey = get_privkey(privkey_path, privkey_format).pubkey + if "-c" in sys.argv: + print("{}:{}".format(pubkey, pubkey_checksum(pubkey))) + else: + print(pubkey) + + else: + show_help() + + except Exception as e: + if "--debug" in sys.argv: + 0/0 # DEBUG MODE (raise error when handling error to display backtrace) + sys.stderr.write("Error: {}\n".format(e)) + show_help() + exit(1)