diff --git a/django/finances/admin.py b/django/finances/admin.py index 766fba6..ba6f402 100644 --- a/django/finances/admin.py +++ b/django/finances/admin.py @@ -2,7 +2,16 @@ from django.contrib import admin from .models import OutputStat, HistoricalPrice class OutputStatAdmin(admin.ModelAdmin): - list_display = ('type_ref_hash', 'value', 'confirmed_at_block_height', 'confirmed_at_block_time', 'get_spent_status', 'spent', 'network', 'user') + list_display = ('type_ref_hash', + 'value', + 'confirmed_at_block_height', + 'confirmed_at_block_time', + 'get_spent_status', + 'spent', + 'network', + 'user', + 'next_enc_input_attrs', + 'last_error') list_filter = ('network', 'spent') search_fields = ('type_ref_hash',) ordering = ('-confirmed_at_block_time',) diff --git a/django/finances/electrum.py b/django/finances/electrum.py index bb8459b..10f57dd 100644 --- a/django/finances/electrum.py +++ b/django/finances/electrum.py @@ -88,7 +88,8 @@ def checkup_label(label_id, loop): if utxo_resp: txid, index, address, value, blocktime, utxo_data = utxo_resp if utxo_data: - output.next_input_attributes = utxo_data + #output.next_input_attributes = utxo_data + output.set_next_input_attributes(utxo_data) if blocktime: HistoricalPrice.get_or_create_from_api(timestamp=blocktime) @@ -98,9 +99,6 @@ def checkup_label(label_id, loop): conn.last_error = None # reset error if needed unspents = loop.run_until_complete(interact_addr(conn, server_info, "blockchain.scripthash.listunspent", address)) - - - utxo_value = 0 utxo_height = 0 diff --git a/django/finances/migrations/0012_auto_20240701_0932.py b/django/finances/migrations/0012_auto_20240701_0932.py new file mode 100644 index 0000000..f357d8c --- /dev/null +++ b/django/finances/migrations/0012_auto_20240701_0932.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.25 on 2024-07-01 09:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finances', '0011_alter_outputstat_network'), + ] + + operations = [ + migrations.RemoveField( + model_name='outputstat', + name='next_input_attributes', + ), + migrations.AddField( + model_name='outputstat', + name='next_enc_input_attrs', + field=models.TextField(default=None, null=True), + ), + ] diff --git a/django/finances/models.py b/django/finances/models.py index c184486..fb51d48 100644 --- a/django/finances/models.py +++ b/django/finances/models.py @@ -7,7 +7,10 @@ from pymempool import MempoolAPI from labelbase.receivers import compute_type_ref_hash from django.conf import settings from jsonfield import JSONField +import json from django.contrib.auth.models import User +from shared.encryption import get_fernet_key, cipher_suite + import logging @@ -42,7 +45,18 @@ class OutputStat(models.Model): confirmed_at_block_time = models.IntegerField(default=0) last_error = JSONField(default={}) - next_input_attributes = JSONField(default={}) # will be used for fee estimation + next_enc_input_attrs = models.TextField(default=None, null=True) # will be used for fee estimation + + def set_next_input_attributes(self, data): + json_data = json.dumps(data).encode('utf-8') + encrypted_data = cipher_suite.encrypt(json_data) + self.next_enc_input_attrs = encrypted_data.decode('utf-8') + + def next_input_attributes(self): + encrypted_data = self.next_enc_input_attrs.encode('utf-8') + decrypted_data = cipher_suite.decrypt(encrypted_data) + return json.loads(decrypted_data.decode('utf-8')) + MAINNET = 'mainnet' TESTNET = 'testnet' diff --git a/django/finances/signals.py b/django/finances/signals.py index 40fdbe6..22cc9a2 100644 --- a/django/finances/signals.py +++ b/django/finances/signals.py @@ -1,15 +1,10 @@ from django.contrib.auth.signals import user_logged_in +from django.contrib import messages from django.dispatch import receiver from finances.models import HistoricalPrice -from django.contrib import messages - - - - @receiver(user_logged_in) def perform_tasks_on_login(sender, user, request, **kwargs): - """ """ if user.profile.update_utxo_on_login: from finances.tasks import check_all_outputs from labelbase.models import Label @@ -17,4 +12,7 @@ def perform_tasks_on_login(sender, user, request, **kwargs): if Label.objects.filter(labelbase__user_id=user.id).exists(): messages.info(request, "Sync in progress: We are checking your unspent transaction outputs now.") # Store nearest price information. - HistoricalPrice.get_or_create_from_api(-1) + try: + HistoricalPrice.get_or_create_from_api(-1) + except Exception as ex: + logger.error(ex, exc_info=True) diff --git a/django/finances/tasks.py b/django/finances/tasks.py index fb43e22..7816cbb 100644 --- a/django/finances/tasks.py +++ b/django/finances/tasks.py @@ -1,8 +1,6 @@ import logging from background_task import background from background_task.management.commands.remove_completed import _remove_completed_task - - from labelbase.models import Label from finances.electrum import checkup_label diff --git a/django/importer/forms.py b/django/importer/forms.py index d3ae1ca..e14fe52 100644 --- a/django/importer/forms.py +++ b/django/importer/forms.py @@ -8,3 +8,8 @@ class UploadFileForm(forms.Form): choices=IMPORTER_CHOICES ) file = forms.FileField() + passphrase = forms.CharField( + widget=forms.PasswordInput(), + required=False, + max_length=100 + ) diff --git a/django/importer/models.py b/django/importer/models.py index 82cafed..11c8f31 100644 --- a/django/importer/models.py +++ b/django/importer/models.py @@ -2,15 +2,18 @@ from django.db import models from django.contrib.auth.models import User from labelbase.models import Labelbase from uuid_upload_path import upload_to +from django.conf import settings -IMPORTER_CHOICES = ( +IMPORTER_CHOICES = [ ("BIP-0329", "BIP-329 .jsonl"), # TODO: ("BIP-0329-7z-enc" , "BIP-329 (encrypted) .7z"), ("csv-bluewallet", "BlueWallet .csv"), ("csv-bitbox", "BitBox .csv"), - ("pocket-accointing", "Pocket Accointing .csv") -) + ("pocket-accointing", "Pocket Accointing .csv"), +] +if settings.SELF_HOSTED: + IMPORTER_CHOICES.append(("samourai", "Samourai .txt, (v2)")) class UploadedData(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) diff --git a/django/importer/pocket.py b/django/importer/pocket.py index bbc88ca..9d8af30 100644 --- a/django/importer/pocket.py +++ b/django/importer/pocket.py @@ -1,7 +1,5 @@ import csv -import json -from labelbase.models import Label -from labelbase.models import Labelbase +import json def validate_csv_format(csv_file_path): diff --git a/django/importer/samourai.py b/django/importer/samourai.py new file mode 100644 index 0000000..f4a1cae --- /dev/null +++ b/django/importer/samourai.py @@ -0,0 +1,153 @@ +import json +from Crypto.Cipher import AES +from Crypto.Hash import SHA256 +from Crypto.Protocol.KDF import PBKDF2 +import hashlib +import re +import base64 + +from labelbase.serializers import LabelSerializer +from labelbase.models import Label + +import logging +logger = logging.getLogger('labelbase') + +DefaultPBKDF2Iterations = 5000 +DefaultPBKDF2HMACSHA256Iterations = 15000 +DefaultSamouraiImportLabel = "Imported form samourai.txt" + + + + + +def decrypt_v1(payload, password, iterations=DefaultPBKDF2Iterations): + # V1 uses PBKDF2 for key derivation and AES for decryption + AESBlockSize = 16 + cipherdata = base64.b64decode(payload) + iv = cipherdata[:AESBlockSize] + input_data = cipherdata[AESBlockSize:] + key = PBKDF2(password, iv, dkLen=32, count=iterations) + cipher = AES.new(key, AES.MODE_CBC, iv) + decrypted = cipher.decrypt(input_data) + return decrypted.rstrip(b"\x00").decode('utf-8') + + +def decrypt_v2(payload, password, iterations=DefaultPBKDF2HMACSHA256Iterations): + # V2 uses SHA256 for key derivation and AES for decryption + encrypted_bytes = base64.b64decode(payload.replace("\n", "")) + salt = encrypted_bytes[8:16] + cipher_text = encrypted_bytes[16:] + key_iv = PBKDF2(password, salt, dkLen=48, count=iterations, hmac_hash_module=SHA256) + key = key_iv[:32] + iv = key_iv[32:] + cipher = AES.new(key, AES.MODE_CBC, iv) + decrypted = cipher.decrypt(cipher_text) + pad_len = decrypted[-1] + decrypted = decrypted[:-pad_len] + return decrypted.decode('utf-8') + + +def import_samourai_labels(labelbase, content, passphrase): + content = content.decode('utf-8') + pattern = re.compile(r'\{.*?\}') + match = pattern.search(content) + imported_lables = 0 + payload = None + if match: + json_content = match.group(0) + try: + logger.info(f"json_content {json_content}") + data = json.loads(json_content) + logger.info(f"data: {data}") + version = data.get("version", 1) + payload = data.get("payload", "") + logger.info(f"version: {version}, payload {payload}, passphrase {passphrase}") + + if payload: + if version in [1, "1"]: + decrypted_data = decrypt_v1(payload, passphrase) + elif version in [2, "2"]: + decrypted_data = decrypt_v2(payload, passphrase) + else: + logger.error(f"Unsupported backup version: {version}") + raise ValueError(f"Unsupported backup version: {version}") + logger.info(decrypted_data) + samourai_data = json.loads(decrypted_data) + logger.info(samourai_data) + + """ + DOC/KB: If the labelbase where you import your samourai.txt into, labelbase will set the fingerprint, + """ + labels = Label.objects.filter(labelbase__id=labelbase.id) + if labels.count() == 0: + if not labelbase.fingerprint: + labelbase.fingerprint = samourai_data.get('wallet').get('fingerprint') + if samourai_data.get('wallet').get('testnet'): + labelbase.network == labelbase.TESTNET + else: + labelbase.network == labelbase.MAINNET + labelbase.save() + + xpub = samourai_data.get('wallet', {}).get('accounts')[0].get('xpub') + ypub = samourai_data.get('wallet', {}).get('bip49_accounts')[0].get('ypub') + zpub = samourai_data.get('wallet', {}).get('bip84_accounts')[0].get('zpub') + + for pub in [xpub, ypub, zpub]: + if pub: + _data = { + "type": Label.TYPE_XPUB, + "ref": pub, + "label": DefaultSamouraiImportLabel, + } + _data["labelbase"] = labelbase.id + serializer = LabelSerializer(data=_data) + if serializer.is_valid(): + serializer.save() + imported_lables += 1 + + utxo_notes = samourai_data.get('meta', {}).get('utxo_notes') + logger.info(utxo_notes) + for note in utxo_notes: + _data = { + "type": Label.TYPE_TX, + "ref": note[0], + "label": note[1], + } + _data["labelbase"] = labelbase.id + serializer = LabelSerializer(data=_data) + if serializer.is_valid(): + serializer.save() + imported_lables += 1 + blocked_utxos = samourai_data.get('meta', {}).get('blocked_utxos',{}).get('blocked') + logger.info(blocked_utxos) + for utxo in blocked_utxos: + _data = { + "type": Label.TYPE_OUTPUT, + "ref": utxo.get('id','').replace("-", ":"), + "label": DefaultSamouraiImportLabel, + "spendable": False + } + _data["labelbase"] = labelbase.id + serializer = LabelSerializer(data=_data) + if serializer.is_valid(): + serializer.save() + imported_lables += 1 + return imported_lables + + else: + print("No payload found in the JSON content.") + logger.error("No payload found in the JSON content.") + return imported_lables + except json.JSONDecodeError as e: + print(f"JSONDecodeError: {e}") + logger.error(f"JSONDecodeError: {e}") + return imported_lables + except Exception as ex: + print(f"An error occurred: {ex}") + logger.error(f"An error occurred: {ex}") + logger.error(ex, exc_info=True) + return imported_lables + else: + print("No JSON found in file.") + logger.error("No JSON found in file.") + return imported_lables diff --git a/django/importer/tasks.py b/django/importer/tasks.py index a29b72b..00e30aa 100644 --- a/django/importer/tasks.py +++ b/django/importer/tasks.py @@ -2,9 +2,10 @@ from background_task import background import json import decimal -from labelbase.models import Labelbase from labelbase.serializers import LabelSerializer +import logging +logger = logging.getLogger('labelbase') from .models import UploadedData @@ -12,93 +13,132 @@ EOLSTOP = [b"", "", None, "\n"] @background(schedule=1) -def process_uploaded_data(uploaded_data_id, loop=None): - imported_lables = 0 - uploaded_data = UploadedData.objects.get(pk=uploaded_data_id) - labelbase = uploaded_data.labelbase - fp = uploaded_data.file.open() +def process_uploaded_data(uploaded_data_id, passphrase=None, loop=None): + try: + uploaded_data = UploadedData.objects.get(pk=uploaded_data_id) + imported_lables = 0 + labelbase = uploaded_data.labelbase + fp = uploaded_data.file.open() - # BIP-0329 - if uploaded_data.import_type == "BIP-0329": - while True: - buf = fp.readline() - if buf in EOLSTOP: - break - data = json.loads(buf) - data["labelbase"] = labelbase.id - serializer = LabelSerializer(data=data) - if serializer.is_valid(): - serializer.save() - imported_lables += 1 - elif uploaded_data.import_type == "BIP-0329-7z-enc": - # TODO: Implementation needed. - pass - # Bitbox App - elif uploaded_data.import_type == "csv-bitbox": - while True: - buf = fp.readline() - if buf in EOLSTOP: - break - try: - buf = str(buf.decode("utf-8")) - sbuf = buf.split(",") - # Time,Type,Amount,Unit,Fee,Address,Transaction ID,Note - for elem in [("tx", 6), ("addr", 5)]: - data = { - "type": elem[0], - "ref": sbuf[elem[1]], - "label": " ".join(sbuf[7:]), - } - data["labelbase"] = labelbase.id - serializer = LabelSerializer(data=data) - if serializer.is_valid(): - serializer.save() - imported_lables += 1 - else: - messages.add_message( - request, - messages.ERROR, - 'Could not process line "{}".'.format(buf), - ) - except Exception as ex: - messages.add_message( - request, - messages.ERROR, - 'Could not process line "{}", {}.'.format(buf, ex), - ) - # Pocket Accointing - elif uploaded_data.import_type == "pocket-accointing": - fp.close() - csv_file_path = fp.name - mempool_api = labelbase.get_mempool_api() - from .pocket import validate_csv_format, parse_csv_to_json - if validate_csv_format(csv_file_path): - for item in parse_csv_to_json(csv_file_path): - label = "Got {} {} for {:.2f} {} with reference: {} #Pocket".format(item[0].get('outSellAmount'), item[1].get( - 'inBuyAsset'), decimal.Decimal(item[2].get('inBuyAmount')), item[2].get('inBuyAsset'), item[1].get('operationId')) - txid = item[0].get('operationId') - tx = mempool_api.get_transaction(txid) - potential_utxos = [] - vouts = tx.get("vout", []) - for i in range(len(vouts)): - if vouts[i].get('value', 0) == decimal.Decimal(item[0].get('outSellAmount'))*100000000: - potential_utxos.append("{}:{}".format(txid, i )) - data = {} - if len(potential_utxos) == 1: - # label UTXO/output of tx - data = { - "type": "output", - "ref": potential_utxos[0], - "label": label, - } - if len(potential_utxos) > 1: - # mark tx, add warning tag + # BIP-0329 + if uploaded_data.import_type == "BIP-0329": + while True: + buf = fp.readline() + if buf in EOLSTOP: + break + data = json.loads(buf) + data["labelbase"] = labelbase.id + serializer = LabelSerializer(data=data) + if serializer.is_valid(): + serializer.save() + imported_lables += 1 + elif uploaded_data.import_type == "BIP-0329-7z-enc": + # TODO: Implementation needed. + pass + elif uploaded_data.import_type == "samourai": + buf = fp.read() + logger.info(buf) + print(buf) + from .samourai import import_samourai_labels + import_samourai_labels(labelbase, buf, passphrase) + # Bitbox App + elif uploaded_data.import_type == "csv-bitbox": + while True: + buf = fp.readline() + if buf in EOLSTOP: + break + try: + buf = str(buf.decode("utf-8")) + sbuf = buf.split(",") + # Time,Type,Amount,Unit,Fee,Address,Transaction ID,Note + for elem in [("tx", 6), ("addr", 5)]: + data = { + "type": elem[0], + "ref": sbuf[elem[1]], + "label": " ".join(sbuf[7:]), + } + data["labelbase"] = labelbase.id + serializer = LabelSerializer(data=data) + if serializer.is_valid(): + serializer.save() + imported_lables += 1 + else: + messages.add_message( + request, + messages.ERROR, + 'Could not process line "{}".'.format(buf), + ) + except Exception as ex: + messages.add_message( + request, + messages.ERROR, + 'Could not process line "{}", {}.'.format(buf, ex), + ) + # Pocket Accointing + elif uploaded_data.import_type == "pocket-accointing": + fp.close() + csv_file_path = fp.name + mempool_api = labelbase.get_mempool_api() + from .pocket import validate_csv_format, parse_csv_to_json + if validate_csv_format(csv_file_path): + for item in parse_csv_to_json(csv_file_path): + label = "Got {} {} for {:.2f} {} with reference: {} #Pocket".format(item[0].get('outSellAmount'), item[1].get( + 'inBuyAsset'), decimal.Decimal(item[2].get('inBuyAmount')), item[2].get('inBuyAsset'), item[1].get('operationId')) + txid = item[0].get('operationId') + tx = mempool_api.get_transaction(txid) + potential_utxos = [] + vouts = tx.get("vout", []) + for i in range(len(vouts)): + if vouts[i].get('value', 0) == decimal.Decimal(item[0].get('outSellAmount'))*100000000: + potential_utxos.append("{}:{}".format(txid, i )) + data = {} + if len(potential_utxos) == 1: + # label UTXO/output of tx + data = { + "type": "output", + "ref": potential_utxos[0], + "label": label, + } + if len(potential_utxos) > 1: + # mark tx, add warning tag + data = { + "type": "tx", + "ref": txid, + "label": "{} {}".format(label, "#W001_UTXO_NOT_FOUND"), + } + if data: + data["labelbase"] = labelbase.id + serializer = LabelSerializer(data=data) + if serializer.is_valid(): + serializer.save() + imported_lables += 1 + else: + # messages.add_message( + # request, + # messages.ERROR, + # 'Could not process record "{}".'.format(item), + # ) + pass + else: + print("ERROR") # TODO + # BlueWallet + elif uploaded_data.import_type == "csv-bluewallet": + header_row = True + while True: + buf = fp.readline() + if buf in EOLSTOP: + break + if header_row: + header_row = False + continue + try: + buf = str(buf.decode("utf-8")) + sbuf = buf.split(",") data = { "type": "tx", - "ref": txid, - "label": "{} {}".format(label, "#W001_UTXO_NOT_FOUND"), + "ref": sbuf[1], + "label": " ".join(sbuf[3:]), } - if data: data["labelbase"] = labelbase.id serializer = LabelSerializer(data=data) if serializer.is_valid(): @@ -108,50 +148,20 @@ def process_uploaded_data(uploaded_data_id, loop=None): # messages.add_message( # request, # messages.ERROR, - # 'Could not process record "{}".'.format(item), + # 'Could not process line "{}".'.format(buf), # ) pass - else: - print("ERROR") # TODO - # BlueWallet - elif uploaded_data.import_type == "csv-bluewallet": - header_row = True - while True: - buf = fp.readline() - if buf in EOLSTOP: - break - if header_row: - header_row = False - continue - try: - buf = str(buf.decode("utf-8")) - sbuf = buf.split(",") - data = { - "type": "tx", - "ref": sbuf[1], - "label": " ".join(sbuf[3:]), - } - data["labelbase"] = labelbase.id - serializer = LabelSerializer(data=data) - if serializer.is_valid(): - serializer.save() - imported_lables += 1 - else: + except Exception as ex: # messages.add_message( # request, # messages.ERROR, - # 'Could not process line "{}".'.format(buf), + # 'Could not process line "{}", {}.'.format(buf, ex), # ) pass - except Exception as ex: - # messages.add_message( - # request, - # messages.ERROR, - # 'Could not process line "{}", {}.'.format(buf, ex), - # ) - pass - # Clean up – Note: Currently we delete the upload from the file system, - # later we can store the messages.add_message messages, the state and the - # amount of importet labels in it to propagate the messages to the - # frontend/user interface. - uploaded_data.delete() + # Clean up – Note: Currently we delete the upload from the file system, + # later we can store the messages.add_message messages, the state and the + # amount of importet labels in it to propagate the messages to the + # frontend/user interface. + uploaded_data.delete() + except Exception as ex: + logger.error(ex, exc_info=True) diff --git a/django/importer/templatetags/importer_tags.py b/django/importer/templatetags/importer_tags.py index dc43461..a10cfb0 100644 --- a/django/importer/templatetags/importer_tags.py +++ b/django/importer/templatetags/importer_tags.py @@ -10,27 +10,4 @@ def genericlabeluploadform(labelbase_id): form.fields["labelbase_id"].initial = labelbase_id form.fields["import_type"].initial = "BIP-0329" return form - - -@register.simple_tag -def bip0329labeluploadform(labelbase_id): - form = UploadFileForm() - form.fields["labelbase_id"].initial = labelbase_id - form.fields["import_type"].initial = "BIP-0329" - return form - - -@register.simple_tag -def csvBlueWalletlabeluploadform(labelbase_id): - form = UploadFileForm() - form.fields["labelbase_id"].initial = labelbase_id - form.fields["import_type"].initial = "csv-bluewallet" - return form - - -@register.simple_tag -def csvBitBoxLabeluploadform(labelbase_id): - form = UploadFileForm() - form.fields["labelbase_id"].initial = labelbase_id - form.fields["import_type"].initial = "csv-bitbox" - return form + diff --git a/django/importer/views.py b/django/importer/views.py index 62e1dd1..7b72d57 100644 --- a/django/importer/views.py +++ b/django/importer/views.py @@ -30,7 +30,7 @@ def upload_labels(request): file=request.FILES["file"], ) # Schedule the background task to process the uploaded data - process_uploaded_data(uploaded_data.id) + process_uploaded_data(uploaded_data.id, passphrase=form.cleaned_data.get("passphrase", None)) messages.add_message( request, messages.INFO, diff --git a/django/labelbase/api.py b/django/labelbase/api.py index 2ec8a2d..5781795 100644 --- a/django/labelbase/api.py +++ b/django/labelbase/api.py @@ -10,6 +10,9 @@ from labelbase.models import Labelbase, Label from labelbase.serializers import LabelbaseSerializer, LabelSerializer +import logging +logger = logging.getLogger('labelbase') + class LabelbaseAPIView(APIView): """ Labelbase @@ -90,7 +93,7 @@ class LabelAPIView(APIView): "spendable": request.data.get("spendable", "null"), } - + #logger.debug(f"data: {data}") serializer = LabelSerializer(data=data) if serializer.is_valid(): serializer.save() diff --git a/django/labelbase/models.py b/django/labelbase/models.py index 6ba61fc..f719a63 100644 --- a/django/labelbase/models.py +++ b/django/labelbase/models.py @@ -130,7 +130,7 @@ class Label(models.Model): TYPE_PUBKEY = "pubkey" TYPE_INPUT = "input" TYPE_OUTPUT = "output" - TYPE_XPUT = "xpub" + TYPE_XPUB = "xpub" TYPE_CHOICES = [ (TYPE_TX, "tx"), @@ -138,7 +138,7 @@ class Label(models.Model): (TYPE_PUBKEY, "pubkey"), (TYPE_INPUT, "input"), (TYPE_OUTPUT, "output"), - (TYPE_XPUT, "xpub"), + (TYPE_XPUB, "xpub"), ] type = models.CharField( diff --git a/django/labelbase/static/Cloud.png b/django/labelbase/static/Cloud.png new file mode 100644 index 0000000..5882b4a Binary files /dev/null and b/django/labelbase/static/Cloud.png differ diff --git a/django/labellabor/settings.py b/django/labellabor/settings.py index 9e9fc19..e63be18 100644 --- a/django/labellabor/settings.py +++ b/django/labellabor/settings.py @@ -178,6 +178,9 @@ DATABASES = { "PASSWORD": proj_config.get("database", "password"), 'HOST': 'localhost', 'PORT': 3306, + 'OPTIONS': { + 'charset': 'utf8mb4', + }, } } diff --git a/django/labellabor/urls.py b/django/labellabor/urls.py index 5c6bf8a..13ef4b5 100644 --- a/django/labellabor/urls.py +++ b/django/labellabor/urls.py @@ -37,6 +37,7 @@ from .views import ( AboutView, EncryptionView, InteroperationalView, + CloudView, ExportLabelsView, # StatsAndKPIView, TreeMapsView, @@ -262,6 +263,11 @@ urlpatterns = [ InteroperationalView.as_view(), name="interoperational" ), + path( + "cloud", + CloudView.as_view(), + name="cloud" + ), path( "outputstat//update//", login_required(OutputStatUpdateRedirectView.as_view()), diff --git a/django/labellabor/views.py b/django/labellabor/views.py index e83cd0f..0072354 100644 --- a/django/labellabor/views.py +++ b/django/labellabor/views.py @@ -101,6 +101,22 @@ class BitcoinAddressDatatableView(BaseDatatableView): pub = key.derive(f"m/0/{idx}").key sc = script.p2wpkh(pub) address = sc.address(NETWORKS["main"]) + elif derivation == 'm/44' and xpub.startswith("tpub"): + # BIP 44 - Legacy Addresses (P2PKH) + pub = key.derive(f"m/0/{idx}").key + sc = script.p2pkh(pub) + address = sc.address(NETWORKS["test"]) + elif derivation == 'm/49' and xpub.startswith("upub"): + # BIP 49 - SegWit Addresses (P2SH-P2WPKH) + pub = key.derive(f"m/0/{idx}").key + witness_script = script.p2wpkh(pub) + sc = script.p2sh(witness_script) + address = sc.address(NETWORKS["test"]) + elif derivation == 'm/84' and xpub.startswith("vpub"): + # BIP 84 - Native SegWit Addresses (P2WPKH) + pub = key.derive(f"m/0/{idx}").key + sc = script.p2wpkh(pub) + address = sc.address(NETWORKS["test"]) else: continue @@ -171,6 +187,10 @@ class InteroperationalView(TemplateView): template_name = "interoperational.html" +class CloudView(TemplateView): + template_name = "cloud.html" + + class HomeView(TemplateView): template_name = "home.html" diff --git a/django/requirements.txt b/django/requirements.txt index 34f0759..4f9e659 100644 --- a/django/requirements.txt +++ b/django/requirements.txt @@ -1,11 +1,12 @@ gunicorn asgiref==3.4.1 -certifi==2023.07.22 +certifi==2024.07.04 cffi==1.15.1 charset-normalizer==2.0.12 coreapi==2.3.3 coreschema==0.0.4 cryptography==42.0.4 +pycryptodome Django==3.2.25 django-appconf==1.0.5 django-bootstrap-form==3.4 diff --git a/django/templates/_base.html b/django/templates/_base.html index 1c1a6da..fe1e9f7 100644 --- a/django/templates/_base.html +++ b/django/templates/_base.html @@ -184,7 +184,7 @@ {% endif %} {% endwith %}
  • Fix & Manage
  • - + {% comment %}
  • UTXOs Health
  • {% endcomment %}
  • Tree Map
  • {% comment %}
  • Stats & KPIs
  • {% endcomment %} {% if request.user.profile.use_fiatfinances %} diff --git a/django/templates/_labelbase_header_info_menu.html b/django/templates/_labelbase_header_info_menu.html index f16d081..77b9b88 100644 --- a/django/templates/_labelbase_header_info_menu.html +++ b/django/templates/_labelbase_header_info_menu.html @@ -8,30 +8,35 @@ {% endif %}
    +
    - - - {% comment %} - - Export - {% endcomment %} +--> + + + + + +
    - + + +
    + {% addtoblock "js" %} + $(document).ready(function () { - var form = $('#importLabelbaseModal form'); - form.on('submit', function () { - // Disable the OK button on form submission - $('#importLabelbaseModal button[type="submit"]').prop('disabled', true); + var form = $('#importLabelbaseModal form'); + var importTypeField = $('#id_import_type'); + var passphraseFieldGroup = $('#id_passphrase').closest('.form-group'); + + function togglePassphraseField() { + if (importTypeField.val() === 'samourai') { + passphraseFieldGroup.show(); + $('#id_passphrase').prop('disabled', false); + $('#samourai-warning').show(); + } else { + passphraseFieldGroup.hide(); + $('#id_passphrase').prop('disabled', true); + $('#id_passphrase').val('') + $('#samourai-warning').hide(); + } + } + + importTypeField.on('change', togglePassphraseField); + + // Initial call to set the correct state on page load + togglePassphraseField(); + + form.on('submit', function () { + // Disable the OK button on form submission + $('#importLabelbaseModal button[type="submit"]').prop('disabled', true); + }); }); -}); + {% endaddtoblock %} diff --git a/django/templates/cloud.html b/django/templates/cloud.html new file mode 100644 index 0000000..347533c --- /dev/null +++ b/django/templates/cloud.html @@ -0,0 +1,51 @@ +{% extends "_base.html" %} +{% load i18n %} + +{% block title %}Interoperability{% endblock %} +{% block nav_home %}active{% endblock %} + +{% block content %} +
    +

    Lbelbase

    +

    All your labels in one place.

    +
    + + + +
    + + +
    +
    +
    + ... +
    +

    Cloud

    + +

    "Use your labels here, use your labels there – Dave doesn't care."

    +

    + Be like Dave with Labelbase, your ultimate cloud-based platform for managing, merging, and synchronizing wallet labels across all your devices and wallet applications. + +Labelbase seamlessly synchronizes your labels across various wallets and systems, ensuring you have consistent and organized data wherever you go. Simplify your financial management and stay in sync with Labelbase. +

    + + +

    +

    + Get started +

    +
    +
    +
    +
    + +
    + +{% endblock %} diff --git a/django/templates/donate.html b/django/templates/donate.html index 3056ac5..dc6a32d 100644 --- a/django/templates/donate.html +++ b/django/templates/donate.html @@ -7,22 +7,106 @@ {% block content %} -
    -

    Support Labelbase: Make a difference with your donation

    -
    -

    Every contribution helps us build a better Bitcoin labeling experience.

    -

    -Choose the amount you'd like to donate to Labelbase. -

    -Your generosity ensures that our project continues to thrive and evolve, directly supporting our mission to make Bitcoin transactions more organized and transparent. -

    -Thank you for your support! + +

    +

    Support Labelbase: Keep it Going

    + +

    + Every contribution helps us continue building a better Bitcoin labeling experience. +

    +

    + Choose the amount you'd like to donate to Labelbase. +

    +

    + As a free and open-source software, your generosity is crucial to our mission. Your support ensures our project keeps thriving and evolving, directly enhancing Bitcoin label management. +

    + + + + +
    + + + + + + +
    + + + +
    + +
    + + +


    + Thank you for keeping Labelbase going.

    - - - - +
    + + Seedor powers our BTCPay Server instance, providing secure and private donations. + +
    diff --git a/django/templates/label_edit.html b/django/templates/label_edit.html index 7e4542a..e6ff81b 100644 --- a/django/templates/label_edit.html +++ b/django/templates/label_edit.html @@ -64,10 +64,13 @@