Introduced Ticket/Billing-Code Respawn, with dual paths on the ticket prep orchestration, it's helper functions, and the main prep controller. This introduced the functions, which were tested with a demo CLI client and local server, and appears to be stable. Server-side endpoints will be pushed as well. Note: This update couples profile slot ids more closely with which ticket slot id is being used for it, but they are not yet officially tied. In the future, they should be.
This commit is contained in:
parent
1695e0314e
commit
31488f790e
17 changed files with 379 additions and 83 deletions
|
|
@ -1,5 +1,10 @@
|
|||
# Major Change Log:
|
||||
|
||||
# Wipe & Respawn Codes/Tickets
|
||||
### Aug 14, 2026
|
||||
Introduced Ticket/Billing-Code Respawn, with dual paths on the ticket prep orchestration, it's helper functions, and the main prep controller. This introduced the functions, which were tested with a demo CLI client and local server, and appears to be stable. Server-side endpoints will be pushed as well. Note: This update couples profiles more closely with which ticket slot is being used for it, but they are not yet officially tied. In the future, they should be.
|
||||
<br/>
|
||||
|
||||
# Reduce Redundancy
|
||||
### Aug 13, 2026
|
||||
Isolated download file modules to their own module, then had both the install application versions (browsers), and install dependencies both use that same module.
|
||||
|
|
|
|||
|
|
@ -51,9 +51,12 @@ class Constants:
|
|||
HV_PROFILE_CONFIG_HOME: Final[str] = f'{HV_CONFIG_HOME}/profiles'
|
||||
HV_PROFILE_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/profiles'
|
||||
|
||||
# ticketing group:
|
||||
# ── ticketing ─────────────────────────────────────────────
|
||||
HV_TICKETING_CONFIG_HOME: Final[str] = f"{HV_CONFIG_HOME}/ticketing"
|
||||
HV_TICKETING_DATA_HOME: Final[str] = f"{HV_DATA_HOME}/ticket_data"
|
||||
TICKET_TRACKER_PATH = f"{HV_TICKETING_CONFIG_HOME}/ticket_tracker.json"
|
||||
# ── end of ticketing ──────────────────────────────────────
|
||||
|
||||
|
||||
HV_APPLICATION_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/applications'
|
||||
HV_INCIDENT_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/incidents'
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from core.errors.logger import logger
|
|||
from core.errors.exceptions import ServerSideError, NetworkingError
|
||||
|
||||
from core.services.prepare_tickets.ticket_tracker import does_ticket_tracker_exist
|
||||
|
||||
from core.services.prepare_tickets.setup_ticket_tracker import setup_ticket_tracker
|
||||
# from core.services.networking.api_requests.step1_get_or_post import send_data_to_server
|
||||
from core.services.networking.httpx import connect
|
||||
|
||||
|
|
@ -56,6 +56,9 @@ def initiate_payment(
|
|||
if tickets_exist_already:
|
||||
invoice_data_object.add_error_code("already_exists")
|
||||
return invoice_data_object
|
||||
else:
|
||||
made_ticket_tracker = setup_ticket_tracker(how_many_profiles=how_many_profiles)
|
||||
logger.debug(f"Ticket Tracker doesn't exist, so we made it: {made_ticket_tracker} for {how_many_profiles} profiles")
|
||||
|
||||
billing_id = do_we_have_billing_id()
|
||||
if billing_id:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ from core.services.helpers.get_how_many_profiles_were_ordered import (
|
|||
get_how_many_profiles_were_ordered,
|
||||
)
|
||||
from core.observers.BaseObserver import BaseObserver
|
||||
from core.models.Result import Result, ResultError
|
||||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.errors.logger import logger
|
||||
|
||||
# generic
|
||||
from typing import Optional
|
||||
|
||||
"""
|
||||
Goal:
|
||||
|
|
@ -26,24 +33,33 @@ If it doesn't have the public key, then it checks the config file.
|
|||
If it doesn't have a config file, then it uses the temp billing id to get the public key from the server.
|
||||
"""
|
||||
|
||||
|
||||
# Bulk Prep:
|
||||
def prepare_tickets(
|
||||
how_many_profiles: int,
|
||||
ticket_observer: TicketObserver,
|
||||
connection_observer: ConnectionObserver,
|
||||
which_ticket: Optional[int] = None,
|
||||
billing_code: Optional[int] = None
|
||||
) -> dict:
|
||||
|
||||
# these two are for preparation of a single ticket to a profile.
|
||||
# can't have one blank without the other. (profile or ticket)
|
||||
if bool(which_ticket) != bool(billing_code):
|
||||
return {"valid": False, "error_code": "failed_input"}
|
||||
|
||||
# make sure it's a number:
|
||||
if not isinstance(how_many_profiles, int):
|
||||
ticket_observer.notify("failed_input", None)
|
||||
return {"valid": False, "error_code": "failed_input"}
|
||||
|
||||
# make sure that "how_many_profiles" is actually the number of profiles ordered
|
||||
# (which is based on locally saved data from the previous step):
|
||||
how_many_ordered = get_how_many_profiles_were_ordered()
|
||||
if how_many_profiles != how_many_ordered:
|
||||
ticket_observer.notify("failed_input", None)
|
||||
return {"valid": False, "error_code": "failed_input"}
|
||||
# allow single prep:
|
||||
if how_many_profiles != 1:
|
||||
# but if it's not a single ticket, then make sure that "how_many_profiles" is actually the number of profiles ordered
|
||||
# (which is based on locally saved data from the previous step):
|
||||
how_many_ordered = get_how_many_profiles_were_ordered()
|
||||
if how_many_profiles != how_many_ordered:
|
||||
ticket_observer.notify("failed_input", None)
|
||||
return {"valid": False, "error_code": "failed_input"}
|
||||
|
||||
# make sure this guy has a public key to verify against:
|
||||
does_he_have_public_key = make_sure_pub_key_exists(connection_observer)
|
||||
|
|
@ -56,7 +72,11 @@ def prepare_tickets(
|
|||
|
||||
# ok now we have the pre-reqs, let's use this high level orchestrator,
|
||||
prep_results = ticket_prep_orchestrator(
|
||||
how_many_profiles, ticket_observer, connection_observer
|
||||
how_many_profiles=how_many_profiles,
|
||||
ticket_observer=ticket_observer,
|
||||
connection_observer=connection_observer,
|
||||
which_ticket=which_ticket, # only relevant for single ticket prep
|
||||
billing_code=billing_code # only relevant for single ticket prep
|
||||
)
|
||||
|
||||
# rest of this function is evaluating the results:
|
||||
|
|
@ -83,3 +103,85 @@ def prepare_tickets(
|
|||
notification = f"Error with Ticket Preparation or Verification!"
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
return prep_results
|
||||
|
||||
|
||||
def respawn_billing_code_into_ticket(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
which_ticket: int,
|
||||
ticket_observer: TicketObserver,
|
||||
connection_observer: ConnectionObserver
|
||||
) -> bool:
|
||||
"""
|
||||
Converts a valid VPN billing code, into a valid unused ticket.
|
||||
At the cost of the server expiring the billing code immediately.
|
||||
|
||||
This has the effect of "wiping" the billing code, and starting over.
|
||||
The new ticket can be applied to any other profile now.
|
||||
"""
|
||||
# extract billing code:
|
||||
billing_code = profile.subscription.billing_code
|
||||
|
||||
if not billing_code:
|
||||
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="You lack a valid subscription on the profile slot is being respawned")
|
||||
|
||||
if not which_ticket:
|
||||
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Missing which ticket slot is being respawned")
|
||||
|
||||
if not isinstance(which_ticket, int):
|
||||
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Ticket slot must be a number.")
|
||||
|
||||
ticket_result = prepare_tickets(
|
||||
how_many_profiles=1,
|
||||
ticket_observer=ticket_observer,
|
||||
connection_observer=connection_observer,
|
||||
which_ticket=which_ticket,
|
||||
billing_code=billing_code
|
||||
)
|
||||
|
||||
if not isinstance(ticket_result, dict):
|
||||
return Result(valid=False, error_type=ResultError.UNKNOWN, message=f"Error! The prepare_tickets function returned an invalid format of {type(ticket_result)} when it should be a dict. This is being recieved in the respawn_ticket function.")
|
||||
|
||||
valid = ticket_result.get('valid', False)
|
||||
|
||||
if not valid:
|
||||
notification = f"Error in Respawn Prep!"
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
return prep_error_result(ticket_result)
|
||||
|
||||
logger.info("Got a valid result from the respawn's prepare_tickets. Now wiping the profile's subscription.")
|
||||
|
||||
# Now that the billing code has been wiped by the server, wipe it locally:
|
||||
profile.subscription = None
|
||||
profile.save()
|
||||
|
||||
notification = f"Respawn Done for Profile {profile.id}!"
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
return Result(valid=True, message=f"It worked. Respawn Done for Profile {profile.id}! It's completely wiped and you now have a valid ticket at slot {which_ticket}")
|
||||
|
||||
|
||||
def prep_error_result(ticket_result: dict):
|
||||
message = ticket_result.get('message', False)
|
||||
error_code = ticket_result.get('error_code', False)
|
||||
|
||||
if message == "missing_data":
|
||||
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Missing Input Data. This is an error with the introduction functions.")
|
||||
|
||||
elif message == "cant_save":
|
||||
return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Can't save the data. Please check your file permissions and free space.")
|
||||
|
||||
elif message == "verification_failed":
|
||||
how_many_failed = ticket_result.get('how_many_failed', False)
|
||||
return Result(valid=False, error_type=ResultError.ENCRYPTION, message=f"Verification of the encryption failed for {how_many_failed} profiles.")
|
||||
|
||||
elif error_code == "failed":
|
||||
return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="There was an error with setting up ticket tracker or some local filesystem problem.")
|
||||
|
||||
else:
|
||||
if message:
|
||||
error_msg = message
|
||||
elif error_code:
|
||||
error_msg = f"An error occured: {error_code}."
|
||||
else:
|
||||
error_msg = "An unknown error occured."
|
||||
|
||||
return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
# MissingData, FailedToSave, InvalidData, CriticalFailure defined here
|
||||
|
||||
|
||||
from core.models.Result import Result, ResultError
|
||||
from typing import Optional
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class ResultError(Enum):
|
|||
INTERFACE = "interface"
|
||||
TIMEOUT = "timeout"
|
||||
INVALID_API_REPLY = "invalid_api_reply"
|
||||
ENCRYPTION = "encryption"
|
||||
LEAK_ISSUE = "leak_issue"
|
||||
NEED_SYNC = "need_sync"
|
||||
UNKNOWN = "unknown"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from core.services.crypto.TicketCustomer import TicketCustomer
|
||||
from core.errors.logger import logger
|
||||
|
||||
from typing import Optional
|
||||
|
||||
"""
|
||||
We are making both an unblinded and blinded commitment pair.
|
||||
|
|
@ -15,12 +18,14 @@ def make_ONE_commitment_pair(
|
|||
|
||||
# First, make the original unblinded commitment. it's saved inside the profile object:
|
||||
did_unblinded_save = profile_object.make_unblinded_commitment(which_ticket)
|
||||
logger.info(f"did_unblinded_save {did_unblinded_save}")
|
||||
|
||||
# that `profile_object` object is holding the unblinded commitment,
|
||||
# so it can be directly used to blind it (without having to serialize then deserialize it).
|
||||
|
||||
# Then BLIND it, so it can be sent to the billing server:
|
||||
blind_commitment = profile_object.blind_commitment(which_ticket)
|
||||
logger.info(f"blind_commitment is {blind_commitment}")
|
||||
|
||||
# we need to make sure we actually saved the data,
|
||||
# because it's the only way to unblind it later:
|
||||
|
|
@ -33,15 +38,30 @@ def make_ONE_commitment_pair(
|
|||
return blind_commitment
|
||||
|
||||
|
||||
def make_ALL_commitments(how_many_profiles_to_make: int) -> list | None:
|
||||
def make_ALL_commitments(how_many_profiles_to_make: int, which_ticket: Optional[int] = None) -> list | None:
|
||||
# Setup the entire class object of "profile_object" for using all these other functions,
|
||||
profile_object = TicketCustomer()
|
||||
|
||||
# setup loop:
|
||||
which_ticket = 0
|
||||
list_of_all_blinded_data = []
|
||||
failed_to_save = []
|
||||
|
||||
##########################################################
|
||||
# SINGLE TICKET PREP (respawn flow)
|
||||
##########################################################
|
||||
if which_ticket and how_many_profiles_to_make == 1:
|
||||
logger.info("Doing a single ticket prep.")
|
||||
blinded_string = make_ONE_commitment_pair(profile_object, which_ticket)
|
||||
profile_object.reset()
|
||||
list_of_all_blinded_data.append(blinded_string)
|
||||
return list_of_all_blinded_data
|
||||
|
||||
##########################################################
|
||||
# BULK TICKET PREP (regular flow)
|
||||
##########################################################
|
||||
logger.info(f"Doing a regular bulk ticket prep for {how_many_profiles_to_make} tickets")
|
||||
which_ticket = 0
|
||||
|
||||
# loop up to the number of profiles requested:
|
||||
while which_ticket < how_many_profiles_to_make:
|
||||
which_ticket = which_ticket + 1
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from core.services.WebServiceApiService import WebServiceApiService
|
|||
from core.controllers.ConnectionController import ConnectionController
|
||||
from core.observers.ConnectionObserver import ConnectionObserver
|
||||
from core.Constants import Constants
|
||||
from core.errors.logger import logger
|
||||
|
||||
from typing import Union, Optional
|
||||
import base64
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def get_process_id_from_state(current_state: Optional[SystemState]) -> Result:
|
|||
def _shut_down_by_known_process_id(process_id: int) -> Result:
|
||||
graceful_close = singbox.stop(process_id)
|
||||
if not graceful_close.valid:
|
||||
logger.error(f"[{function_name}] Could not gracefully close it. So we'll {KILL_WAIT_TIME} seconds and kill it.")
|
||||
logger.error(f"Could not gracefully close it. So we'll {KILL_WAIT_TIME} seconds and kill it.")
|
||||
|
||||
still_active = pid_tools.is_running(pid=process_id, process_name="sing-box")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from core.models.Result import Result, ResultError
|
||||
from core.utils.run_commands import run_generic_command
|
||||
from core.errors.logger import logger
|
||||
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from core.utils.save_data import save_data
|
|||
from core.errors.logger import logger
|
||||
|
||||
import json
|
||||
|
||||
from typing import Optional
|
||||
|
||||
# save each of the server's signatures replies to disk,
|
||||
def save_ONE_blind_signature(each_blind_signature: dict, which_ticket: int) -> bool:
|
||||
|
|
@ -10,14 +10,15 @@ def save_ONE_blind_signature(each_blind_signature: dict, which_ticket: int) -> b
|
|||
return did_it_save
|
||||
|
||||
|
||||
def save_ALL_blind_sigs(ALL_signed_blind_signatures: list) -> bool:
|
||||
# setup basic counter & flag for the loop below:
|
||||
which_ticket = 0
|
||||
def save_ALL_blind_sigs(ALL_signed_blind_signatures: list, which_ticket: Optional[int] = None) -> bool:
|
||||
|
||||
if which_ticket is None:
|
||||
# setup basic counter & flag for the loop below:
|
||||
which_ticket = 1
|
||||
|
||||
did_they_ALL_save = True
|
||||
|
||||
for each_blind_signature in ALL_signed_blind_signatures:
|
||||
which_ticket = which_ticket + 1
|
||||
|
||||
"""
|
||||
As a list of JSONs, each signature is loaded seperately,
|
||||
so that if the format is off for a single ticket, it doesn't collapse all the tickets.
|
||||
|
|
@ -44,4 +45,8 @@ def save_ALL_blind_sigs(ALL_signed_blind_signatures: list) -> bool:
|
|||
error_msg = f"Unable to save the blind signature for {which_ticket}! Skipping it and moving on.."
|
||||
logger.error(error_msg, exc_info=True)
|
||||
|
||||
finally:
|
||||
which_ticket = which_ticket + 1
|
||||
|
||||
|
||||
return did_they_ALL_save
|
||||
|
|
|
|||
|
|
@ -8,23 +8,39 @@ if TYPE_CHECKING:
|
|||
# from core.services.networking.api_requests.step1_get_or_post import send_data_to_server
|
||||
from core.services.networking.httpx import connect
|
||||
from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType
|
||||
from core.services.networking.api_requests.step5_solve_api_problems import solve_api_problems
|
||||
|
||||
# from core.services.networking.api_requests.step5_solve_api_problems import solve_api_problems
|
||||
from core.services.networking.make_url import make_url
|
||||
from core.services.helpers.get_which_billing_key import get_which_billing_key
|
||||
|
||||
# models & their controllers
|
||||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.models.Subscription import Subscription # not sure if needed
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
|
||||
# utils
|
||||
from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file
|
||||
# constants & errors
|
||||
from core.Constants import Constants
|
||||
from core.errors.exceptions import *
|
||||
from core.errors.exceptions import InvalidData, ServerSideError
|
||||
from core.errors.logger import logger
|
||||
|
||||
from typing import Optional
|
||||
|
||||
def prep_payload(list_of_all_blinded_data: list) -> dict:
|
||||
billing_folder = Constants.HV_TICKETING_CONFIG_HOME
|
||||
billing_path = f"{billing_folder}/billing_choices.json"
|
||||
|
||||
|
||||
# Bulk Group of Tickets:
|
||||
def prep_payload_for_bulk(list_of_all_blinded_data: list) -> dict:
|
||||
"""
|
||||
Purpose:
|
||||
Prepare the POST payload for a BULK group ticket prep
|
||||
|
||||
Rank:
|
||||
Mini-Helper
|
||||
"""
|
||||
# get temp billing id from local storage:
|
||||
billing_folder = Constants.HV_TICKETING_CONFIG_HOME
|
||||
billing_path = f"{billing_folder}/billing_choices.json"
|
||||
temp_billing_code = get_value_from_json_file(billing_path, "temp_billing_code")
|
||||
|
||||
# prep the JSON payload, it doesn't need the public key, since the server can look that up,
|
||||
|
|
@ -36,20 +52,63 @@ def prep_payload(list_of_all_blinded_data: list) -> dict:
|
|||
return payload
|
||||
|
||||
|
||||
# Single Ticket:
|
||||
def prep_single_ticket_payload(billing_code: str, list_of_all_blinded_data: list) -> dict:
|
||||
"""
|
||||
Purpose:
|
||||
Prepare the POST payload for a SINGLE ticket's respawn prep
|
||||
|
||||
Rank:
|
||||
Mini-Helper
|
||||
"""
|
||||
# get ticketing plan data:
|
||||
which_key = get_value_from_json_file(billing_path, "which_key")
|
||||
|
||||
if not which_key or not billing_code:
|
||||
logger.error(f"Missing pre-reqs for sending the blind commitments to the server. profile's billing_code is {billing_code} and which_key is {which_key}")
|
||||
return None
|
||||
|
||||
# prep the JSON payload,
|
||||
payload = {
|
||||
"billing_code": billing_code,
|
||||
"which_key": which_key,
|
||||
"blinded_data": list_of_all_blinded_data,
|
||||
}
|
||||
|
||||
logger.info("We have a valid payload of pre-reqs for sending the blind commitments")
|
||||
return payload
|
||||
|
||||
|
||||
def send_blind_commitments(
|
||||
list_of_all_blinded_data: list,
|
||||
ticket_observer: TicketObserver,
|
||||
connection_observer: ConnectionObserver,
|
||||
billing_code: Optional[int] = None
|
||||
) -> list | None:
|
||||
"""
|
||||
Purpose:
|
||||
1) Oversee payload prep
|
||||
2) Send the data to the server API
|
||||
3) Handle the response
|
||||
|
||||
try:
|
||||
payload = prep_payload(list_of_all_blinded_data)
|
||||
|
||||
# send it:
|
||||
Rank:
|
||||
Coordinator
|
||||
"""
|
||||
# BULK GROUP:
|
||||
if billing_code is None:
|
||||
payload = prep_payload_for_bulk(list_of_all_blinded_data)
|
||||
which_endpoint = "sign"
|
||||
url = make_url(which_endpoint)
|
||||
# api_reply_object = send_data_to_server(payload, url, connection_observer)
|
||||
# SINGLE TICKET:
|
||||
else:
|
||||
payload = prep_single_ticket_payload(billing_code=billing_code, list_of_all_blinded_data=list_of_all_blinded_data)
|
||||
which_endpoint = "respawn"
|
||||
|
||||
if not payload:
|
||||
return False
|
||||
|
||||
# send it:
|
||||
url = make_url(which_endpoint)
|
||||
try:
|
||||
api_reply_object = connect.single_endpoint(
|
||||
method="post",
|
||||
url=url,
|
||||
|
|
@ -63,18 +122,6 @@ def send_blind_commitments(
|
|||
ticket_observer.notify("error", subject=api_reply_object.message)
|
||||
return None
|
||||
|
||||
# if not api_reply_object.valid:
|
||||
# final_error_msg = f"[SEND BLIND COMMITMENTS] First Post request failed {api_reply_object.message}"
|
||||
# logger.error(final_error_msg)
|
||||
# api_reply_object = solve_api_problems(
|
||||
# api_reply_object=api_reply_object,
|
||||
# get_or_post="post",
|
||||
# url=url,
|
||||
# payload=payload,
|
||||
# connection_observer=connection_observer,
|
||||
# client_observer=None
|
||||
# )
|
||||
|
||||
# assuming it worked, extract the data:
|
||||
reply = api_reply_object.data
|
||||
|
||||
|
|
@ -116,6 +163,6 @@ def send_blind_commitments(
|
|||
except ServerSideError as e:
|
||||
logger.error(f"ServerSideError: {e}", exc_info=True)
|
||||
return None
|
||||
except:
|
||||
logger.error(f"Generic Python Error", exc_info=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Generic Python Error {e}", exc_info=True)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -8,27 +8,68 @@ if TYPE_CHECKING:
|
|||
from core.services.crypto.make_commitments import make_ALL_commitments
|
||||
from core.services.prepare_tickets.unblind_all_tickets import unblind_ALL_tickets
|
||||
from core.services.prepare_tickets.setup_ticket_tracker import setup_ticket_tracker
|
||||
from core.services.prepare_tickets import ticket_tracker
|
||||
from core.services.prepare_tickets.send_blind_commitments import send_blind_commitments
|
||||
from core.services.prepare_tickets.validate_blind_signatures import validate_blind_signatures
|
||||
from core.services.prepare_tickets.save_ALL_blind_sigs import save_ALL_blind_sigs
|
||||
from core.utils.basic_operations.does_file_exist import does_file_exist
|
||||
# errors & constants
|
||||
from core.Constants import Constants
|
||||
from core.errors.exceptions import *
|
||||
from core.errors.exceptions import MissingData, CriticalFailure
|
||||
from core.errors.logger import logger
|
||||
|
||||
from typing import Optional
|
||||
|
||||
"""
|
||||
There's two potential paths here,
|
||||
|
||||
Path 1) Bulk preparation.
|
||||
Called by TicketPrepController's prepare_tickets
|
||||
which_ticket and billing_code are None
|
||||
|
||||
Path 2) Single "respawn",
|
||||
TicketPrepController's respawn_billing_code_into_ticket
|
||||
with which_ticket and billing_code. (and how_many_profiles is one)
|
||||
"""
|
||||
|
||||
def ticket_prep_orchestrator(
|
||||
how_many_profiles: int,
|
||||
ticket_observer: TicketObserver,
|
||||
connection_observer: ConnectionObserver,
|
||||
which_ticket: Optional[int] = None,
|
||||
billing_code: Optional[int] = None
|
||||
) -> dict:
|
||||
"""
|
||||
Purpose:
|
||||
Orchestrate the entire ticket preparation operation.
|
||||
|
||||
Rank:
|
||||
Feature's Primary Cross-Module Orchestrator
|
||||
|
||||
Called by:
|
||||
TicketPrepController's prepare_tickets = For Bulk Tickets
|
||||
TicketPrepController's respawn_billing_code_into_ticket = For a Single Ticket
|
||||
|
||||
Returns:
|
||||
Dicts. Shouldn't raise errors on it's own.
|
||||
"""
|
||||
if not billing_code and which_ticket:
|
||||
logger.error("Can't have no billing_code, if you have a ticket to replace")
|
||||
return {
|
||||
"valid": False,
|
||||
"message": "missing_data",
|
||||
}
|
||||
|
||||
try:
|
||||
#####################################################
|
||||
# MAKE BLIND COMMITMENTS
|
||||
#####################################################
|
||||
# make all commitments, but get the blinded ones:
|
||||
list_of_all_blinded_commitments = make_ALL_commitments(how_many_profiles)
|
||||
list_of_all_blinded_commitments = make_ALL_commitments(how_many_profiles, which_ticket)
|
||||
|
||||
# did we actually save them? if not, try again,
|
||||
if list_of_all_blinded_commitments is None:
|
||||
list_of_all_blinded_commitments = make_ALL_commitments(how_many_profiles)
|
||||
list_of_all_blinded_commitments = make_ALL_commitments(how_many_profiles, which_ticket)
|
||||
# still failed to save?!
|
||||
if list_of_all_blinded_commitments is None:
|
||||
return {
|
||||
|
|
@ -36,13 +77,16 @@ def ticket_prep_orchestrator(
|
|||
"message": "cant_save",
|
||||
}
|
||||
|
||||
#####################################################
|
||||
# SEND BLIND TO GET SIGNED
|
||||
#####################################################
|
||||
# assuming we actually saved the unblinding factors,
|
||||
notification = "Sending Blinded Package to the Server.."
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
|
||||
# then send the entire blinded list to the server to sign:
|
||||
blind_signatures = send_blind_commitments(
|
||||
list_of_all_blinded_commitments, ticket_observer, connection_observer
|
||||
list_of_all_blinded_commitments, ticket_observer, connection_observer, billing_code
|
||||
)
|
||||
|
||||
# Recieve signatures:
|
||||
|
|
@ -54,19 +98,23 @@ def ticket_prep_orchestrator(
|
|||
return {
|
||||
"valid": False,
|
||||
"message": f"The server's blind signature was blank or invalid. It said: {blind_signatures}",
|
||||
"error_code": "invalid_reply"
|
||||
}
|
||||
|
||||
else:
|
||||
#####################################################
|
||||
# EVALUATE AND SAVE THE SERVER'S REPLIES
|
||||
#####################################################
|
||||
# regardless of the outcome of the verification, save all blind sigs, just in case, because the user can't get them again,
|
||||
notification = "Saving the Server's Blind Replies"
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
did_they_ALL_save = save_ALL_blind_sigs(blind_signatures)
|
||||
did_they_ALL_save = save_ALL_blind_sigs(blind_signatures, which_ticket)
|
||||
|
||||
# verify the server's blind signatures against the public key,
|
||||
notification = "Evaluating the Server's Blind Replies"
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
failed_validations = validate_blind_signatures(
|
||||
blind_signatures, ticket_observer, connection_observer
|
||||
blind_signatures, ticket_observer, connection_observer, which_ticket
|
||||
)
|
||||
|
||||
logger.debug(f"failed_validations is {failed_validations}")
|
||||
|
|
@ -86,17 +134,42 @@ def ticket_prep_orchestrator(
|
|||
"failed_validations": failed_validations,
|
||||
}
|
||||
|
||||
#####################################################
|
||||
# UNBLIND
|
||||
#####################################################
|
||||
# Unblind the signatures & combine with unblinded commitment:
|
||||
notification = f"Unblinding Signatures & Preparing Tickets..."
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
did_prep_work = unblind_ALL_tickets(
|
||||
blind_signatures, ticket_observer, connection_observer
|
||||
ALL_signed_blind_signatures=blind_signatures,
|
||||
ticket_observer=ticket_observer,
|
||||
connection_observer=connection_observer,
|
||||
which_ticket=which_ticket
|
||||
)
|
||||
|
||||
# make a json to keep track of which tickets are used:
|
||||
setup_tracker = setup_ticket_tracker(how_many_profiles)
|
||||
#####################################################
|
||||
# TICKET TRACKER
|
||||
#####################################################
|
||||
# I'm checking how many profiles here, because if it's multiple tickets then,
|
||||
# we wipe the file if it already exists. But for one ticket, we just update it
|
||||
if how_many_profiles > 1:
|
||||
# make a json to keep track of which tickets are used:
|
||||
setup_tracker = setup_ticket_tracker(how_many_profiles)
|
||||
if not setup_tracker:
|
||||
return {"valid": False, "message": "failed"}
|
||||
else:
|
||||
# for a single ticket, we only want to setup the ticket tracker if it doesn't exist yet:
|
||||
if not does_file_exist(Constants.TICKET_TRACKER_PATH):
|
||||
setup_tracker = setup_ticket_tracker(how_many_profiles)
|
||||
if not setup_tracker:
|
||||
return {"valid": False, "message": "failed"}
|
||||
# assuming ticket tracker exists, let's update it:
|
||||
wiped_sub = ticket_tracker.wipe_one_ticket_sub(which_ticket)
|
||||
|
||||
if did_prep_work and setup_tracker:
|
||||
#####################################################
|
||||
# FINAL EVALUATION
|
||||
#####################################################
|
||||
if did_prep_work:
|
||||
# this means it unblinded, & setup the tracker without erroring out,
|
||||
# but it does NOT mean that verification of the blind sigs worked.
|
||||
return {"valid": True, "message": "worked"}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
from core.utils.basic_operations.write_or_read_from_json import read_entire_json
|
||||
from core.utils.basic_operations.write_or_read_from_json import read_entire_json, update_json
|
||||
from core.errors.exceptions import *
|
||||
from core.errors.logger import logger
|
||||
from core.Constants import Constants
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
# folders:
|
||||
billing_folder = Constants.HV_TICKETING_CONFIG_HOME
|
||||
ticket_tracker_path = f"{billing_folder}/ticket_tracker.json"
|
||||
# billing_folder = Constants.HV_TICKETING_CONFIG_HOME
|
||||
# ticket_tracker_path = f"{billing_folder}/ticket_tracker.json"
|
||||
|
||||
|
||||
def get_all_unused_tickets() -> dict:
|
||||
data = read_entire_json(ticket_tracker_path)
|
||||
data = read_entire_json(Constants.TICKET_TRACKER_PATH)
|
||||
|
||||
if data == False or data == "" or data == None:
|
||||
return {"valid": False, "message": "invalid_data"}
|
||||
|
|
@ -33,7 +34,7 @@ def get_all_unused_tickets() -> dict:
|
|||
|
||||
|
||||
def is_given_ticket_able_to_be_read(which_ticket: int) -> bool:
|
||||
data = read_entire_json(ticket_tracker_path)
|
||||
data = read_entire_json(Constants.TICKET_TRACKER_PATH)
|
||||
|
||||
# the ticket # was originally an int, but needs to be a string to do lookups,
|
||||
which_ticket = str(which_ticket) # type: ignore
|
||||
|
|
@ -46,17 +47,17 @@ def is_given_ticket_able_to_be_read(which_ticket: int) -> bool:
|
|||
|
||||
|
||||
def does_ticket_tracker_exist() -> tuple:
|
||||
if os.path.exists(ticket_tracker_path):
|
||||
return (True, ticket_tracker_path)
|
||||
if os.path.exists(Constants.TICKET_TRACKER_PATH):
|
||||
return (True, Constants.TICKET_TRACKER_PATH)
|
||||
else:
|
||||
return (False, ticket_tracker_path)
|
||||
return (False, Constants.TICKET_TRACKER_PATH)
|
||||
|
||||
|
||||
def get_data_for_a_single_ticket(which_ticket_as_int: int) -> tuple:
|
||||
data = read_entire_json(ticket_tracker_path)
|
||||
data = read_entire_json(Constants.TICKET_TRACKER_PATH)
|
||||
|
||||
if data == False or data == "" or data == None:
|
||||
error_msg = f"Entire file of tickets does not exist. Check the location {ticket_tracker_path}"
|
||||
error_msg = f"Entire file of tickets does not exist. Check the location {Constants.TICKET_TRACKER_PATH}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
print(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
|
@ -72,5 +73,19 @@ def get_data_for_a_single_ticket(which_ticket_as_int: int) -> tuple:
|
|||
return status, location, subscription
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Key '{which_ticket}' does not exist in JSON file {ticket_tracker_path}"
|
||||
f"Key '{which_ticket}' does not exist in JSON file {Constants.TICKET_TRACKER_PATH}"
|
||||
)
|
||||
|
||||
|
||||
def wipe_one_ticket_sub(which_ticket: int) -> bool:
|
||||
wipe_payload = {
|
||||
"status": "unused",
|
||||
"location": None,
|
||||
"subscription": None
|
||||
}
|
||||
|
||||
return update_json(
|
||||
filepath=Constants.TICKET_TRACKER_PATH,
|
||||
key_to_add=which_ticket,
|
||||
value_to_update=wipe_payload
|
||||
)
|
||||
|
|
@ -9,13 +9,14 @@ from core.services.crypto.TicketCustomer import TicketCustomer
|
|||
# utils
|
||||
from core.utils.save_data import save_data
|
||||
# observers
|
||||
from core.observers.BaseObserver import BaseObserver
|
||||
from core.models.Event import Event
|
||||
# from core.observers.BaseObserver import BaseObserver
|
||||
# from core.models.Event import Event
|
||||
# errors
|
||||
from core.errors.exceptions import *
|
||||
from core.errors.exceptions import MissingData, FailedToSave, InvalidData
|
||||
from core.errors.logger import logger
|
||||
# generic
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def prep_ONE_unblinded_ticket(
|
||||
|
|
@ -49,6 +50,7 @@ def unblind_ALL_tickets(
|
|||
ALL_signed_blind_signatures: list,
|
||||
ticket_observer: TicketObserver,
|
||||
connection_observer: ConnectionObserver,
|
||||
which_ticket: Optional[int] = None
|
||||
) -> bool:
|
||||
try:
|
||||
if ALL_signed_blind_signatures == False or ALL_signed_blind_signatures == None:
|
||||
|
|
@ -59,8 +61,16 @@ def unblind_ALL_tickets(
|
|||
# Setup the entire class object of "profile_object" for using all these other functions,
|
||||
profile_object = TicketCustomer()
|
||||
|
||||
# setup basic counter for the loop below:
|
||||
which_ticket = 0
|
||||
# if it's a single ticket, make sure we only have one ticket in the list:
|
||||
if which_ticket is not None:
|
||||
if not isinstance(which_ticket, int):
|
||||
return False
|
||||
quantity_tickets = len(ALL_signed_blind_signatures)
|
||||
if quantity_tickets != 1:
|
||||
return False
|
||||
else:
|
||||
# setup counter for the loop below because it's not an individual ticket:
|
||||
which_ticket = 1
|
||||
|
||||
"""
|
||||
LOOP STARTS:
|
||||
|
|
@ -70,8 +80,6 @@ def unblind_ALL_tickets(
|
|||
Finally, saving the unblinded signature and unblinded commitment together.
|
||||
"""
|
||||
for each_blind_signature in ALL_signed_blind_signatures:
|
||||
which_ticket = which_ticket + 1
|
||||
|
||||
# We type check, instead of just blindly loading the json, because this function used by two different modules with different formats.
|
||||
if isinstance(each_blind_signature, str):
|
||||
blind_signature = json.loads(each_blind_signature)
|
||||
|
|
@ -114,10 +122,12 @@ def unblind_ALL_tickets(
|
|||
exc_info=True,
|
||||
)
|
||||
# continue because this is only one ticket, don't want to ruin the others.
|
||||
which_ticket = which_ticket + 1
|
||||
continue
|
||||
|
||||
notification = f"Finished ticket {which_ticket}'s prep"
|
||||
ticket_observer.notify("preparing", subject=notification)
|
||||
which_ticket = which_ticket + 1
|
||||
|
||||
# finished the loop:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ from core.models.Event import Event
|
|||
from core.Constants import Constants
|
||||
from core.errors.exceptions import *
|
||||
from core.errors.logger import logger
|
||||
import traceback
|
||||
# generic
|
||||
import traceback
|
||||
import json
|
||||
|
||||
from typing import Optional
|
||||
|
||||
def make_sure_we_have_inputs(
|
||||
ALL_signed_blind_signatures: list,
|
||||
|
|
@ -93,6 +93,7 @@ def validate_blind_signatures(
|
|||
ALL_signed_blind_signatures: list,
|
||||
ticket_observer: TicketObserver,
|
||||
connection_observer: ConnectionObserver,
|
||||
which_ticket: Optional[int] = None,
|
||||
):
|
||||
try:
|
||||
# Setup the entire class object of "profile_object" for using all these other functions,
|
||||
|
|
@ -112,7 +113,10 @@ def validate_blind_signatures(
|
|||
# end: confirm inputs
|
||||
|
||||
# setup basic counters, flags, and lists for the loop below:
|
||||
which_ticket = 0
|
||||
|
||||
if which_ticket is None:
|
||||
which_ticket = 1
|
||||
|
||||
did_we_answer_the_key_question_yet = "no"
|
||||
list_of_failed_verifications = []
|
||||
|
||||
|
|
@ -122,8 +126,6 @@ def validate_blind_signatures(
|
|||
Looping through each signature, verifying it against the single public key.
|
||||
"""
|
||||
for each_blind_signature in ALL_signed_blind_signatures:
|
||||
which_ticket = which_ticket + 1
|
||||
|
||||
# prep from server's format:
|
||||
blind_signature = json.loads(each_blind_signature)
|
||||
|
||||
|
|
@ -132,6 +134,9 @@ def validate_blind_signatures(
|
|||
which_ticket, blind_signature, public_key
|
||||
)
|
||||
|
||||
# bump loop for next round:
|
||||
which_ticket = which_ticket + 1
|
||||
|
||||
if validity_data["valid"] == True:
|
||||
# verification went fine:
|
||||
notification = f"Verified {which_ticket}'s blind signature"
|
||||
|
|
|
|||
|
|
@ -8,14 +8,18 @@ from core.services.using_tickets.send_unblinded import send_unblinded_ticket_to_
|
|||
from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType
|
||||
|
||||
from core.utils.basic_operations.write_or_read_from_json import (
|
||||
update_value_in_json_with_two_values,
|
||||
update_value_in_json_with_two_values
|
||||
)
|
||||
from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file
|
||||
from core.errors.exceptions import *
|
||||
from core.errors.logger import logger
|
||||
from core.Constants import Constants
|
||||
|
||||
import traceback
|
||||
|
||||
# prep the ticket tracker:
|
||||
billing_folder = Constants.HV_TICKETING_CONFIG_HOME
|
||||
ticket_tracker_path = f"{billing_folder}/ticket_tracker.json"
|
||||
|
||||
# Coordinates using the ticket anonymously.
|
||||
# To do so, it sends to the server & updates the 'ticker_tracker' JSON with new data:
|
||||
|
|
@ -25,10 +29,6 @@ def use_ticket_orchestrator(
|
|||
connection_observer: ConnectionObserver,
|
||||
) -> dict | ApiResponse:
|
||||
|
||||
# prep the ticket tracker:
|
||||
billing_folder = Constants.HV_TICKETING_CONFIG_HOME
|
||||
ticket_tracker_path = f"{billing_folder}/ticket_tracker.json"
|
||||
|
||||
# send to server:
|
||||
api_reply_object = send_unblinded_ticket_to_server(
|
||||
which_ticket, which_location, connection_observer
|
||||
|
|
@ -122,3 +122,4 @@ def make_sure_sub_saved(
|
|||
billing_code,
|
||||
f"{billing_folder}/emergency_code_for_profile_{which_ticket_as_str}.txt",
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue