From e1045826ba14e3bb6b3c8fe0d7f773605ce36ce8 Mon Sep 17 00:00:00 2001 From: haihp02 Date: Thu, 14 Nov 2024 01:52:58 +0700 Subject: [PATCH 1/6] verify validators 's signature before giving them credit --- services/image_generation_service.py | 25 ++++++++++++++++++++++++- utils/data_types.py | 3 ++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/services/image_generation_service.py b/services/image_generation_service.py index 28b4e21..19ca4e1 100644 --- a/services/image_generation_service.py +++ b/services/image_generation_service.py @@ -21,6 +21,8 @@ from fastapi.middleware.cors import CORSMiddleware from transformers import AutoTokenizer +REQUEST_EXPIRY_LIMIT_SECONDS = 5 + # Define a list of allowed origins (domains) allowed_origins = [ "http://localhost:3000", # Change this to the domain you want to allow @@ -118,9 +120,30 @@ def check_auth(self, key: str) -> None: async def get_credentials( self, request: Request, validator_info: ValidatorInfo ) -> Dict: + # Verify validator by its signature and timestamp + try: + # Check if the request is within the REQUEST_EXPIRY_LIMIT_SECONDS window + received_time_ns = int(validator_info.nonce) + current_time_ns = time.time_ns() + time_difference_seconds = (current_time_ns - received_time_ns) / 1e9 # Convert nanoseconds to seconds + + if time_difference_seconds > REQUEST_EXPIRY_LIMIT_SECONDS: + raise HTTPException(status_code=400, detail="Request expired") + + # Proceed with signature verification + validator_ss58_address = self.metagraph.hotkeys[validator_info.uid] + message = f"{validator_info.postfix}{validator_ss58_address}{validator_info.nonce}" + keypair = bt.Keypair(ss58_address=validator_ss58_address) + is_verified = keypair.verify(message, validator_info.signature) + except (ValueError, KeyError): + is_verified = False + + if not is_verified: + raise HTTPException(status_code=400, detail="Cannot verify validator") + client_ip = request.headers.get('X-Real-Ip') or request.client.host uid = validator_info.uid - hotkey = self.metagraph.hotkeys[uid] + hotkey = validator_ss58_address postfix = validator_info.postfix if not postfix: diff --git a/utils/data_types.py b/utils/data_types.py index 43c04ef..0f98427 100644 --- a/utils/data_types.py +++ b/utils/data_types.py @@ -40,8 +40,9 @@ class ImageToImage(BaseModel): class ValidatorInfo(BaseModel): postfix: str uid: int + signature: str + nonce: str all_uid_info: dict = {} - sha: str = "" class UserSigninInfo(BaseModel): email: str From 8fb9eac3d1f1c7a8c3919d210fed6c33a7b45f03 Mon Sep 17 00:00:00 2001 From: haihp02 Date: Thu, 14 Nov 2024 16:56:03 +0700 Subject: [PATCH 2/6] add verification description to README --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 320e63e..964e06c 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,21 @@ pip install -r requirements.txt ```bash uvicorn app:app.app --reload ``` + +## Credential Retrieval Validation Steps + +The `get_credentials` process is designed to verify the identity and authenticity of a validator's request. This involves checking the request's timestamp and verifying its digital signature. + +1. **Timestamp Expiry Check** + - Ensure the `nonce` (timestamp) in the request is within an acceptable timeframe (`REQUEST_EXPIRY_LIMIT_SECONDS`). + - If the request exceeds this time limit, it’s considered expired and is rejected with an error. + +2. **Signature Verification** + - Construct a message using `validator_info.postfix`, the validator’s unique address, and the `nonce`. + - Use the validator's public key to verify that the `signature` provided matches this constructed message. + - If the signature is invalid, the request is marked unverified and is rejected. + +3. **Error Handling** + - If any issues arise—such as an expired request, invalid signature, or missing information—the process returns an error indicating the specific failure reason. + +This streamlined validation logic ensures that only timely, authentic requests from verified validators are accepted. From 65ba103aa059ccdecf4c020828177f6bcea4bf91 Mon Sep 17 00:00:00 2001 From: haihp02 Date: Thu, 14 Nov 2024 21:02:58 +0700 Subject: [PATCH 3/6] IndexError when uid not found --- services/image_generation_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/image_generation_service.py b/services/image_generation_service.py index 19ca4e1..78dd667 100644 --- a/services/image_generation_service.py +++ b/services/image_generation_service.py @@ -135,7 +135,7 @@ async def get_credentials( message = f"{validator_info.postfix}{validator_ss58_address}{validator_info.nonce}" keypair = bt.Keypair(ss58_address=validator_ss58_address) is_verified = keypair.verify(message, validator_info.signature) - except (ValueError, KeyError): + except (ValueError, KeyError, IndexError): is_verified = False if not is_verified: From 1d40013a8918ee3a4d873e687623206738e57fdb Mon Sep 17 00:00:00 2001 From: haihp02 Date: Fri, 6 Dec 2024 17:38:25 +0700 Subject: [PATCH 4/6] backward compatible --- services/image_generation_service.py | 41 ++++++++++++++++------------ utils/data_types.py | 5 ++-- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/services/image_generation_service.py b/services/image_generation_service.py index 78dd667..6d6aa76 100644 --- a/services/image_generation_service.py +++ b/services/image_generation_service.py @@ -35,7 +35,7 @@ def __init__(self, dbhandler, auth_service): self.dbhandler = dbhandler self.auth_service = auth_service self.subtensor = bt.subtensor("finney") - self.metagraph = self.subtensor.metagraph(23) + self.metagraph: bt.metagraph = self.subtensor.metagraph(23) self.available_validators = self.dbhandler.get_available_validators() self.filter_validators() @@ -120,23 +120,28 @@ def check_auth(self, key: str) -> None: async def get_credentials( self, request: Request, validator_info: ValidatorInfo ) -> Dict: - # Verify validator by its signature and timestamp - try: - # Check if the request is within the REQUEST_EXPIRY_LIMIT_SECONDS window - received_time_ns = int(validator_info.nonce) - current_time_ns = time.time_ns() - time_difference_seconds = (current_time_ns - received_time_ns) / 1e9 # Convert nanoseconds to seconds - - if time_difference_seconds > REQUEST_EXPIRY_LIMIT_SECONDS: - raise HTTPException(status_code=400, detail="Request expired") - - # Proceed with signature verification - validator_ss58_address = self.metagraph.hotkeys[validator_info.uid] - message = f"{validator_info.postfix}{validator_ss58_address}{validator_info.nonce}" - keypair = bt.Keypair(ss58_address=validator_ss58_address) - is_verified = keypair.verify(message, validator_info.signature) - except (ValueError, KeyError, IndexError): - is_verified = False + # Check if validator signed the Request, if not, bypass validating + # TODO: remove this logic once all validators updated to latest version + if not validator_info.signature: + is_verified = True + else: + # Verify validator by its signature and timestamp + try: + # Check if the request is within the REQUEST_EXPIRY_LIMIT_SECONDS window + received_time_ns = int(validator_info.nonce) + current_time_ns = time.time_ns() + time_difference_seconds = (current_time_ns - received_time_ns) / 1e9 # Convert nanoseconds to seconds + + if time_difference_seconds > REQUEST_EXPIRY_LIMIT_SECONDS: + raise HTTPException(status_code=400, detail="Request expired") + + # Proceed with signature verification + validator_ss58_address = self.metagraph.hotkeys[validator_info.uid] + message = f"{validator_info.postfix}{validator_ss58_address}{validator_info.nonce}" + keypair = bt.Keypair(ss58_address=validator_ss58_address) + is_verified = keypair.verify(message, validator_info.signature) + except (ValueError, KeyError, IndexError): + is_verified = False if not is_verified: raise HTTPException(status_code=400, detail="Cannot verify validator") diff --git a/utils/data_types.py b/utils/data_types.py index 0f98427..18b6e8f 100644 --- a/utils/data_types.py +++ b/utils/data_types.py @@ -40,8 +40,9 @@ class ImageToImage(BaseModel): class ValidatorInfo(BaseModel): postfix: str uid: int - signature: str - nonce: str + # TODO: remove default None once all validators updated to latest version + signature: str = None + nonce: str = None all_uid_info: dict = {} class UserSigninInfo(BaseModel): From 5f5dbbd591a365b34a8adf71f07cccbe91d9607d Mon Sep 17 00:00:00 2001 From: haihp02 Date: Sat, 7 Dec 2024 00:48:25 +0700 Subject: [PATCH 5/6] bug fix --- services/image_generation_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/image_generation_service.py b/services/image_generation_service.py index 6d6aa76..0cd4aaf 100644 --- a/services/image_generation_service.py +++ b/services/image_generation_service.py @@ -124,6 +124,7 @@ async def get_credentials( # TODO: remove this logic once all validators updated to latest version if not validator_info.signature: is_verified = True + validator_ss58_address = self.metagraph.hotkeys[validator_info.uid] else: # Verify validator by its signature and timestamp try: From 4110318d7ef0057a6a456c90cbcadf314a49d208 Mon Sep 17 00:00:00 2001 From: vietbeu <44457232+vietbeu@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:02:52 +0700 Subject: [PATCH 6/6] Update REQUEST_EXPIRY_LIMIT_SECONDS --- services/image_generation_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/image_generation_service.py b/services/image_generation_service.py index 0cd4aaf..f57535c 100644 --- a/services/image_generation_service.py +++ b/services/image_generation_service.py @@ -21,7 +21,7 @@ from fastapi.middleware.cors import CORSMiddleware from transformers import AutoTokenizer -REQUEST_EXPIRY_LIMIT_SECONDS = 5 +REQUEST_EXPIRY_LIMIT_SECONDS = 15 # Define a list of allowed origins (domains) allowed_origins = [