diff --git a/nilai-api/pyproject.toml b/nilai-api/pyproject.toml index 3611ccfa..a87147e9 100644 --- a/nilai-api/pyproject.toml +++ b/nilai-api/pyproject.toml @@ -5,8 +5,7 @@ description = "Add your description here" readme = "README.md" authors = [ { name = "José Cabrero-Holgueras", email = "jose.cabrero@nillion.com" }, - { name = "Manuel Santos", email = "manuel.santos@nillion.com" }, - { name = "Dimitris Mouris", email = "dimitris@nillion.com" } + { name = "Baptiste Lefort", email = "baptiste.lefort@nillion.com" } ] requires-python = ">=3.12" dependencies = [ @@ -31,13 +30,12 @@ dependencies = [ "verifier", "web3>=7.8.0", "click>=8.1.8", - "nuc-helpers", "nuc>=0.1.0", "pyyaml>=6.0.1", "trafilatura>=1.7.0", "secretvaults", "e2b-code-interpreter>=1.0.3", - "nilauth-credit-middleware>=0.1.1", + "nilauth-credit-middleware>=0.1.0", ] @@ -47,7 +45,6 @@ build-backend = "hatchling.build" [tool.uv.sources] nilai-common = { workspace = true } -nuc-helpers = { workspace = true } # TODO: Remove this once the secretvaults package is released with the fix secretvaults = { git = "https://github.com/jcabrero/secretvaults-py", rev = "main" } diff --git a/nilai-api/src/nilai_api/auth/__init__.py b/nilai-api/src/nilai_api/auth/__init__.py index 9252b76a..2e7cd6f7 100644 --- a/nilai-api/src/nilai_api/auth/__init__.py +++ b/nilai-api/src/nilai_api/auth/__init__.py @@ -8,7 +8,7 @@ from nilai_api.auth.strategies import AuthenticationStrategy from nuc.validate import ValidationException -from nuc_helpers.usage import UsageLimitError +from nilai_api.auth.nuc_helpers.usage import UsageLimitError from nilai_api.auth.common import ( AuthenticationInfo, diff --git a/nilai-api/src/nilai_api/auth/common.py b/nilai-api/src/nilai_api/auth/common.py index 793aed2a..24fdea29 100644 --- a/nilai-api/src/nilai_api/auth/common.py +++ b/nilai-api/src/nilai_api/auth/common.py @@ -2,8 +2,8 @@ from typing import Optional from fastapi import HTTPException, status from nilai_api.db.users import UserData -from nuc_helpers.usage import TokenRateLimits, TokenRateLimit -from nuc_helpers.nildb_document import PromptDocument +from nilai_api.auth.nuc_helpers.usage import TokenRateLimits, TokenRateLimit +from nilai_api.auth.nuc_helpers.nildb_document import PromptDocument class AuthenticationError(HTTPException): diff --git a/nilai-api/src/nilai_api/auth/jwt.py b/nilai-api/src/nilai_api/auth/jwt.py deleted file mode 100644 index b0e796b8..00000000 --- a/nilai-api/src/nilai_api/auth/jwt.py +++ /dev/null @@ -1,156 +0,0 @@ -import json -import ecdsa -from pydantic import BaseModel -from base64 import urlsafe_b64decode, urlsafe_b64encode -from hashlib import sha256 - -import time -from web3 import Web3 -from hexbytes import HexBytes - -from eth_account.messages import encode_defunct - - -class JWTAuthResult(BaseModel): - pub_key: str - user_address: str - - -def to_base64_url(data: object) -> str: - # Convert the object to a JSON string - json_string = json.dumps(data, separators=(",", ":")) - - # Encode the string to bytes using ASCII and convert to base64 - base64_bytes = urlsafe_b64encode(json_string.encode("ascii")) - - # Return the base64-encoded string - return base64_bytes.decode("utf-8") - - -def sorted_object(obj: dict | list) -> dict | list: - if not isinstance(obj, (dict, list)): - return obj - if isinstance(obj, list): - return [sorted_object(item) for item in obj] - - sorted_keys = sorted(obj.keys()) - result = {} - for key in sorted_keys: - result[key] = sorted_object(obj[key]) - - return result - - -def sorted_json_string(obj: dict | list) -> str: - return json.dumps(sorted_object(obj), separators=(",", ":")) - - -def escape_characters(input: str) -> str: - amp = "&" - lt = "<" - gt = ">" - return input.replace(amp, "\\u0026").replace(lt, "\\u003c").replace(gt, "\\u003e") - - -def serialize_sign_doc(sign_doc: dict) -> bytes: - serialized = escape_characters(sorted_json_string(sign_doc)) - return serialized.encode("utf-8") - - -def keplr_validate( - message: str, header: dict, payload: dict, signature: bytes -) -> JWTAuthResult: - # Validate the algorithm - if header["alg"] != "ES256": - raise ValueError("Unsupported algorithm") - - # Check expiration - if payload.get("exp") and payload["exp"] < int(time.time()): - raise ValueError("Token has expired") - - signature_payload = to_base64_url({"message": message}) - - sign_doc = { - "chain_id": "", - "account_number": "0", - "sequence": "0", - "fee": {"gas": "0", "amount": []}, - "msgs": [ - { - "type": "sign/MsgSignData", - "value": {"signer": payload["user_address"], "data": signature_payload}, - } - ], - "memo": "", - } - - serialized_sign_doc = serialize_sign_doc(sign_doc) - - public_key = ecdsa.VerifyingKey.from_string( - bytes.fromhex(payload["pub_key"]), curve=ecdsa.SECP256k1, hashfunc=sha256 - ) - - public_key.verify( - signature, - serialized_sign_doc, - ) - - pub_key = payload.get("pub_key") - user_address = payload.get("user_address") - if not pub_key or not user_address: - raise ValueError("Invalid payload, missing pub_key or user_address") - return JWTAuthResult(pub_key=pub_key, user_address=user_address) - - -def metamask_validate( - message: str, header: dict, payload: dict, signature: bytes -) -> JWTAuthResult: - # Validate the algorithm - if header["alg"] != "ES256K": - raise ValueError("Unsupported algorithm") - # Check expiration - if payload.get("exp") and payload["exp"] < int(time.time()): - raise ValueError("Token has expired") - w3 = Web3(Web3.HTTPProvider("")) - signable_message = encode_defunct(text=message) - address = w3.eth.account.recover_message( - signable_message, signature=HexBytes("0x" + signature.hex()) - ) - - if address.lower() != payload.get("user_address"): - raise ValueError("Invalid signature") - - pub_key = payload.get("pub_key") - user_address = payload.get("user_address") - if not pub_key or not user_address: - raise ValueError("Invalid payload, missing pub_key or user_address") - - return JWTAuthResult(pub_key=pub_key, user_address=user_address) - - -def extract_fields(jwt: str) -> tuple[str, dict, dict, bytes]: - # Split and decode JWT components - header_b64, payload_b64, signature_b64 = jwt.split(".") - if not all([header_b64, payload_b64, signature_b64]): - raise ValueError("Invalid JWT format") - - # header = json.loads(urlsafe_b64decode(header_b64 + '=' * (-len(header_b64) % 4))) - # payload = json.loads(urlsafe_b64decode(payload_b64 + '=' * (-len(payload_b64) % 4))) - # signature = urlsafe_b64decode(signature_b64 + '=' * (-len(signature_b64) % 4)) - header = json.loads(urlsafe_b64decode(header_b64)) - payload = json.loads(urlsafe_b64decode(payload_b64)) - signature = urlsafe_b64decode(signature_b64) - - return f"{header_b64}.{payload_b64}", header, payload, signature - - -def validate_jwt(jwt: str) -> JWTAuthResult: - message, header, payload, signature = extract_fields(jwt) - - match header.get("wallet"): - case "Keplr": - return keplr_validate(message, header, payload, signature) - case "Metamask": - return metamask_validate(message, header, payload, signature) - case _: - raise ValueError("Unsupported wallet") diff --git a/nilai-api/src/nilai_api/auth/nuc.py b/nilai-api/src/nilai_api/auth/nuc.py index cf1c2864..e9f1a9e3 100644 --- a/nilai-api/src/nilai_api/auth/nuc.py +++ b/nilai-api/src/nilai_api/auth/nuc.py @@ -9,8 +9,8 @@ from nilai_common.logger import setup_logger -from nuc_helpers.usage import TokenRateLimits -from nuc_helpers.nildb_document import PromptDocument +from nilai_api.auth.nuc_helpers.usage import TokenRateLimits +from nilai_api.auth.nuc_helpers.nildb_document import PromptDocument logger = setup_logger(__name__) diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/__init__.py b/nilai-api/src/nilai_api/auth/nuc_helpers/__init__.py similarity index 66% rename from nilai-auth/nuc-helpers/src/nuc_helpers/__init__.py rename to nilai-api/src/nilai_api/auth/nuc_helpers/__init__.py index 88c75cea..a95c662a 100644 --- a/nilai-auth/nuc-helpers/src/nuc_helpers/__init__.py +++ b/nilai-api/src/nilai_api/auth/nuc_helpers/__init__.py @@ -1,7 +1,4 @@ -from nuc_helpers.helpers import ( - RootToken, - DelegationToken, - InvocationToken, +from nilai_api.auth.nuc_helpers.helpers import ( get_wallet_and_private_key, pay_for_subscription, get_root_token, @@ -10,11 +7,11 @@ get_nilai_public_key, get_nilauth_public_key, validate_token, - NilAuthPublicKey, - NilAuthPrivateKey, - NilchainPrivateKey, ) +from cosmpy.crypto.keypairs import PrivateKey as NilchainPrivateKey +from secp256k1 import PublicKey as NilAuthPublicKey, PrivateKey as NilAuthPrivateKey +from nilai_api.auth.nuc_helpers.types import RootToken, DelegationToken, InvocationToken __all__ = [ "RootToken", diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/helpers.py b/nilai-api/src/nilai_api/auth/nuc_helpers/helpers.py similarity index 98% rename from nilai-auth/nuc-helpers/src/nuc_helpers/helpers.py rename to nilai-api/src/nilai_api/auth/nuc_helpers/helpers.py index cd631c2d..63600853 100644 --- a/nilai-auth/nuc-helpers/src/nuc_helpers/helpers.py +++ b/nilai-api/src/nilai_api/auth/nuc_helpers/helpers.py @@ -5,7 +5,12 @@ import httpx # Importing the types -from nuc_helpers.types import RootToken, DelegationToken, InvocationToken, ChainId +from nilai_api.auth.nuc_helpers.types import ( + RootToken, + DelegationToken, + InvocationToken, + ChainId, +) # Importing the secp256k1 library dependencies from secp256k1 import PrivateKey as NilAuthPrivateKey, PublicKey as NilAuthPublicKey diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/main.py b/nilai-api/src/nilai_api/auth/nuc_helpers/main.py similarity index 99% rename from nilai-auth/nuc-helpers/src/nuc_helpers/main.py rename to nilai-api/src/nilai_api/auth/nuc_helpers/main.py index 928d61fb..5981395f 100644 --- a/nilai-auth/nuc-helpers/src/nuc_helpers/main.py +++ b/nilai-api/src/nilai_api/auth/nuc_helpers/main.py @@ -1,4 +1,4 @@ -from nuc_helpers import ( +from nilai_api.auth.nuc_helpers import ( get_wallet_and_private_key, pay_for_subscription, get_root_token, diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/nildb_document.py b/nilai-api/src/nilai_api/auth/nuc_helpers/nildb_document.py similarity index 100% rename from nilai-auth/nuc-helpers/src/nuc_helpers/nildb_document.py rename to nilai-api/src/nilai_api/auth/nuc_helpers/nildb_document.py diff --git a/nilai-auth/nilai-auth-client/src/nilai_auth_client/py.typed b/nilai-api/src/nilai_api/auth/nuc_helpers/py.typed similarity index 100% rename from nilai-auth/nilai-auth-client/src/nilai_auth_client/py.typed rename to nilai-api/src/nilai_api/auth/nuc_helpers/py.typed diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/types.py b/nilai-api/src/nilai_api/auth/nuc_helpers/types.py similarity index 100% rename from nilai-auth/nuc-helpers/src/nuc_helpers/types.py rename to nilai-api/src/nilai_api/auth/nuc_helpers/types.py diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/usage.py b/nilai-api/src/nilai_api/auth/nuc_helpers/usage.py similarity index 100% rename from nilai-auth/nuc-helpers/src/nuc_helpers/usage.py rename to nilai-api/src/nilai_api/auth/nuc_helpers/usage.py diff --git a/nilai-api/src/nilai_api/auth/strategies.py b/nilai-api/src/nilai_api/auth/strategies.py index 0c64cce6..9917ee39 100644 --- a/nilai-api/src/nilai_api/auth/strategies.py +++ b/nilai-api/src/nilai_api/auth/strategies.py @@ -2,7 +2,6 @@ from datetime import datetime, timezone from nilai_api.db.users import UserManager, UserModel, UserData -from nilai_api.auth.jwt import validate_jwt from nilai_api.auth.nuc import ( validate_nuc, get_token_rate_limit, @@ -81,32 +80,6 @@ async def api_key_strategy(api_key: str) -> AuthenticationInfo: raise AuthenticationError("Missing or invalid API key") -@allow_token(CONFIG.docs.token) -async def jwt_strategy(jwt_creds: str) -> AuthenticationInfo: - result = validate_jwt(jwt_creds) - user_model: Optional[UserModel] = await UserManager.check_api_key( - result.user_address - ) - if user_model: - return AuthenticationInfo( - user=UserData.from_sqlalchemy(user_model), - token_rate_limit=None, - prompt_document=None, - ) - else: - user_model = UserModel( - userid=result.user_address, - name=result.pub_key, - apikey=result.user_address, - ) - await UserManager.insert_user_model(user_model) - return AuthenticationInfo( - user=UserData.from_sqlalchemy(user_model), - token_rate_limit=None, - prompt_document=None, - ) - - @allow_token(CONFIG.docs.token) async def nuc_strategy(nuc_token) -> AuthenticationInfo: """ @@ -139,7 +112,6 @@ async def nuc_strategy(nuc_token) -> AuthenticationInfo: class AuthenticationStrategy(Enum): API_KEY = (api_key_strategy, "API Key") - JWT = (jwt_strategy, "JWT") NUC = (nuc_strategy, "NUC") async def __call__(self, *args, **kwargs) -> AuthenticationInfo: diff --git a/nilai-auth/README.md b/nilai-auth/README.md deleted file mode 100644 index 296f3b76..00000000 --- a/nilai-auth/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Example: nilAuth services. - -# nilAuth Services - -This repository contains two main services: - -## nilai-auth-server - -This server acts as a delegation authority for Nillion User Compute (NUC) tokens, specifically for interacting with the Nilai API. It handles obtaining a root NUC token from a configured NilAuth instance, managing subscriptions on the Nillion Chain, and delegating compute capabilities to end-user public keys. See `nilai-auth/nilai-auth-server/README.md` for more details. - -## nilai-auth-client - -This client demonstrates the end-to-end process of authenticating with the Nilai API using Nillion User Compute (NUC) tokens obtained via the Nilai Auth Server. See `nilai-auth/nilai-auth-client/README.md` for more details. diff --git a/nilai-auth/nilai-auth-client/README.md b/nilai-auth/nilai-auth-client/README.md deleted file mode 100644 index 157ba8aa..00000000 --- a/nilai-auth/nilai-auth-client/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Nilai Auth Client - -This client demonstrates the end-to-end process of authenticating with the Nilai API using Nillion User Compute (NUC) tokens obtained via the Nilai Auth Server. - -## Functionality - -1. **Key Generation:** Generates a new secp256k1 private/public key pair for the user. -2. **Request Delegation:** Sends the user's public key (base64 encoded) to the Nilai Auth Server (`/v1/delegate/` endpoint) to request a delegated NUC token. -3. **Token Validation:** Validates the received delegated token against the public key of the NilAuth instance (acting as the root issuer). -4. **Nilai Public Key Retrieval:** Fetches the public key of the target Nilai API instance (`/v1/public_key` endpoint). -5. **Invocation Token Creation:** Creates an invocation NUC token by: - * Extending the previously obtained delegated token. - * Setting the audience to the Nilai API's public key. - * Signing the invocation token with the user's private key. -6. **Invocation Token Validation:** Validates the created invocation token, ensuring it's correctly targeted at the Nilai API. -7. **API Call:** Uses the `openai` library (configured with the Nilai API base URL) to make a chat completion request. - * The invocation token is passed as the `api_key` in the request header. - * The Nilai API verifies this token before processing the request. -8. **Prints Response:** Outputs the response received from the Nilai API. - -## Prerequisites - -* A running **Nilai Auth Server** (default: `localhost:8100`). -* A running **Nilai API** instance (default: `localhost:8080`). -* A running **NilAuth** node (default: `localhost:30921`). - -## Running the Client - -```bash -cd nilai-auth/nilai-auth-client -# Make sure dependencies are installed (e.g., using uv or pip) -python src/nilai_auth_client/main.py -``` - -## Configuration - -Endpoints for the dependent services are currently hardcoded in `main.py`: - -* `SERVICE_ENDPOINT`: Nilai Auth Server (`localhost:8100`) -* `NILAI_ENDPOINT`: Nilai API (`localhost:8080`) -* `NILAUTH_ENDPOINT`: NilAuth Node (`localhost:30921`) - -These could be made configurable via environment variables or command-line arguments if needed. diff --git a/nilai-auth/nilai-auth-client/examples/tutorial.ipynb b/nilai-auth/nilai-auth-client/examples/tutorial.ipynb deleted file mode 100644 index 1bbf4ca6..00000000 --- a/nilai-auth/nilai-auth-client/examples/tutorial.ipynb +++ /dev/null @@ -1,472 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## NUC Token Delegation and Subscription Management Tutorial\n", - "\n", - "This notebook demonstrates the process of interacting with the NilAuth service to manage subscriptions and prepare for NUC token operations.\n", - "\n", - "**Steps:**\n", - "\n", - "1. **Import Libraries:** Import necessary classes and functions from `nuc`, `cosmpy`, `secp256k1`, and standard Python libraries.\n", - "2. **Load Keys:** Define functions to load or generate the Nilchain wallet (`cosmpy`) private key and the NilAuth (`secp256k1`) private key. These keys are currently hardcoded for demonstration purposes. **Note:** Hardcoding keys is insecure for production environments.\n", - "3. **Initialize Keys and Wallet:** Call the functions to get the `NilAuthPrivateKey` (`builder_private_key`) and the `cosmpy` wallet and keypair. Print the wallet address.\n", - "4. **Configure Nilchain Connection:** Set up the `NetworkConfig` for connecting to the Nillion devnet.\n", - "5. **Connect to Ledger:** Create a `LedgerClient` instance using the network configuration.\n", - "6. **Query Balance:** Check and print the `unil` balance of the initialized wallet on the Nillion Chain.\n", - "7. **Initialize NilAuth Client:** Create a `NilauthClient` instance, connecting to the local NilAuth service endpoint.\n", - "8. **Initialize Payer:** Create a `Payer` object, configuring it with the Nilchain wallet, chain details, and gRPC endpoint. This object will be used to pay for transactions like subscriptions.\n", - "9. **Check Subscription Status:** Use the `NilauthClient` and the builder's private key to check the current subscription status associated with the key.\n", - "10. **Pay for Subscription (if necessary):**\n", - " * If the key is not currently subscribed, use the `nilauth_client.pay_subscription` method along with the `Payer` object to pay for a new subscription on the Nillion Chain.\n", - " * If already subscribed, print the time remaining until expiration and renewal availability." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nilchain Private Key (bytes): l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=\n", - "Nilchain Public Key (bytes): \n", - "Paying for wallet: nillion1mqukqr7d4s3eqhcxwctu7yypm560etp2dghpy6\n", - "Wallet balance: 999999996000000 unil\n", - "[>] Creating nilauth client\n", - "[>] Creating payer\n", - "IS SUBSCRIBED: False\n", - "[>] Paying for subscription\n" - ] - } - ], - "source": [ - "# %% Import necessary libraries\n", - "from nuc.payer import Payer\n", - "from nuc.builder import NucTokenBuilder\n", - "from nuc.nilauth import NilauthClient, BlindModule\n", - "from nuc.envelope import NucTokenEnvelope\n", - "from nuc.token import Command, Did, InvocationBody, DelegationBody\n", - "from nuc.validate import (\n", - " NucTokenValidator,\n", - " ValidationParameters,\n", - " InvocationRequirement,\n", - " ValidationException,\n", - ")\n", - "from cosmpy.crypto.keypairs import PrivateKey as NilchainPrivateKey\n", - "from cosmpy.aerial.wallet import LocalWallet\n", - "from cosmpy.aerial.client import LedgerClient, NetworkConfig\n", - "from secp256k1 import PrivateKey as NilAuthPrivateKey\n", - "import base64\n", - "import datetime\n", - "\n", - "\n", - "# %% Define functions to load keys (replace with secure loading in production)\n", - "def get_wallet():\n", - " \"\"\"Loads the Nilchain wallet private key and creates a wallet object.\"\"\"\n", - " # IMPORTANT: Hardcoding private keys is insecure. Use environment variables or a secrets manager.\n", - " keypair = NilchainPrivateKey(\"l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=\")\n", - " print(f\"Nilchain Private Key (bytes): {keypair.private_key}\")\n", - " print(f\"Nilchain Public Key (bytes): {keypair.public_key}\")\n", - " wallet = LocalWallet(\n", - " keypair, prefix=\"nillion\"\n", - " ) # Nillion uses the 'nillion' address prefix\n", - " return wallet, keypair\n", - "\n", - "\n", - "def get_private_key():\n", - " \"\"\"Loads the NilAuth private key used for signing NUC tokens.\"\"\"\n", - " # IMPORTANT: Hardcoding private keys is insecure. Use environment variables or a secrets manager.\n", - " private_key = NilAuthPrivateKey(\n", - " base64.b64decode(\"l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=\")\n", - " )\n", - " return private_key\n", - "\n", - "\n", - "# %% Initialize keys and wallet\n", - "# This key will be used to sign NUC tokens later (e.g., the root token from NilAuth or delegated tokens)\n", - "builder_private_key = get_private_key()\n", - "\n", - "# This wallet is used for interacting with the Nillion Chain (e.g., paying subscriptions)\n", - "wallet, keypair = get_wallet()\n", - "address = wallet.address()\n", - "print(f\"Paying for wallet: {address}\")\n", - "\n", - "# %% Configure and connect to Nillion Chain\n", - "cfg = NetworkConfig(\n", - " chain_id=\"nillion-chain-devnet\",\n", - " url=\"grpc+http://localhost:26649\", # Nillion Chain gRPC endpoint\n", - " fee_minimum_gas_price=1,\n", - " fee_denomination=\"unil\", # The currency used for fees\n", - " staking_denomination=\"unil\", # The currency used for staking\n", - ")\n", - "ledger_client = LedgerClient(cfg)\n", - "\n", - "# %% Query wallet balance\n", - "balances = ledger_client.query_bank_balance(address, \"unil\")\n", - "print(f\"Wallet balance: {balances} unil\")\n", - "\n", - "# %% Initialize NilAuth Client and Payer\n", - "print(\"[>] Creating nilauth client\")\n", - "# Connect to the NilAuth service which issues root NUC tokens\n", - "nilauth_client = NilauthClient(\"http://localhost:30921\") # NilAuth service endpoint\n", - "\n", - "print(\"[>] Creating payer\")\n", - "# The Payer object bundles wallet details needed to pay for chain transactions\n", - "payer = Payer(\n", - " wallet_private_key=keypair,\n", - " chain_id=\"nillion-chain-devnet\",\n", - " grpc_endpoint=\"http://localhost:26649\", # Nillion Chain gRPC endpoint for the payer\n", - " gas_limit=1000000000000, # Gas limit for transactions\n", - ")\n", - "\n", - "# %% Check and manage NilAuth subscription\n", - "# Check if the builder_private_key is associated with an active subscription\n", - "subscription_details = nilauth_client.subscription_status(\n", - " builder_private_key.pubkey, BlindModule.NILAI\n", - ")\n", - "print(f\"IS SUBSCRIBED: {subscription_details.subscribed}\")\n", - "\n", - "# If not subscribed, pay for one\n", - "if not subscription_details.subscribed:\n", - " print(\"[>] Paying for subscription\")\n", - " nilauth_client.pay_subscription(\n", - " pubkey=builder_private_key.pubkey, # The key to associate the subscription with\n", - " payer=payer, # The payer object to execute the transaction\n", - " blind_module=BlindModule.NILAI,\n", - " )\n", - "else:\n", - " # If already subscribed, print details\n", - " print(\"[>] Subscription is already paid for\")\n", - " now = datetime.datetime.now(datetime.timezone.utc)\n", - " print(f\"EXPIRES IN: {subscription_details.details.expires_at - now}\")\n", - " # Note: Renewal might only be possible within a certain window before expiry\n", - " print(f\"CAN BE RENEWED IN: {subscription_details.details.renewable_at - now}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Requesting and Preparing NUC Tokens\n", - "\n", - "This section focuses on obtaining the initial NUC token (the \"root\" token) from NilAuth and preparing for delegation by generating a new key pair.\n", - "\n", - "**Steps:**\n", - "\n", - "1. **Request Root Token:** Use the `nilauth_client` (initialized earlier) and the `builder_private_key` (which has an active subscription) to request a root NUC token from the NilAuth service. This token grants the initial set of permissions associated with the `builder_private_key`.\n", - "2. **Print Root Token:** Display the raw, encoded string representation of the obtained root token.\n", - "3. **Display Builder Keys:** Print the raw private key bytes and the hex-encoded public key of the `builder_private_key` for reference. This is the key that *owns* the root token.\n", - "4. **Generate Delegated Key Pair:** Create a completely *new* `NilAuthPrivateKey` instance. This key pair (`delegated_key` and its corresponding public key) will represent the entity *receiving* delegated permissions from the root token.\n", - "5. **Display Delegated Keys:** Print the raw private key bytes and the hex-encoded public key of the newly generated `delegated_key`.\n", - "6. **Parse Root Token:** Convert the raw `root_token` string into a structured `NucTokenEnvelope` object using `NucTokenEnvelope.parse()`. This allows programmatic access to the token's claims and structure.\n", - "7. **Print Parsed Envelope:** Display the `NucTokenEnvelope` object, showing its parsed structure." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Root Token (raw string): eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6bmlsOjAzNTIwZTcwYmQ5N2E1ZmE2ZDcwYzYxNGQ1MGVlNDdiZjQ0NWFlMGIwOTQxYTFkNjFkZGQ1YWZhMDIyYjk3YWIxNCIsImF1ZCI6ImRpZDpuaWw6MDMwOTIzZjJlNzEyMGM1MGU0MjkwNWI4NTdkZGQyOTQ3ZjZlY2NlZDZiYjAyYWFiNjRlNjNiMjhlOWUyZTA2ZDEwIiwic3ViIjoiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCJleHAiOjE3NDk3MjgxNDksImNtZCI6Ii9uaWwvYWkiLCJwb2wiOltdLCJub25jZSI6IjYzNDc1YzkyZjE3ZTZlMjgwMWRkZGNlYzFjYjcwNmFlIiwicHJmIjpbXX0.oKd_heCtzZr6sh-q8fqZOXL3rsxvy1gROugUMIEefRJXyBhtSA4YWrK9xHQlprCHIF0dlWSGN_y68D3Fi1OU4g\n", - "Builder Private Key (bytes): 97f49889fceed88a9cdddb16a161d13f6a12307c2b39163f3c3c397c3c2d2434\n", - "Builder Public Key (hex): 030923f2e7120c50e42905b857ddd2947f6ecced6bb02aab64e63b28e9e2e06d10\n", - "Delegated Private Key (bytes): a3c69fe94746509d4b44d213b582e72f3e891568cd8004725ada12d4b139db8a\n", - "Delegated Public Key (hex): 03dda3f7bba93edddf6659660e69f14653cdb8c56b8a7253a22d914ac3cfffc6aa\n", - "Root Token Envelope (parsed object): \n" - ] - } - ], - "source": [ - "# %% Request Root Token from NilAuth\n", - "# Use the key associated with the subscription to request the base NUC token\n", - "root_token = nilauth_client.request_token(\n", - " key=builder_private_key, blind_module=BlindModule.NILAI\n", - ")\n", - "print(f\"Root Token (raw string): {root_token}\")\n", - "\n", - "# %% Display Builder Key Details (Owner of Root Token)\n", - "print(f\"Builder Private Key (bytes): {builder_private_key.serialize()}\")\n", - "print(f\"Builder Public Key (hex): {builder_private_key.pubkey.serialize().hex()}\")\n", - "\n", - "# %% Generate a New Key Pair for Delegation Target\n", - "# This key pair will be the recipient of the delegated permissions\n", - "delegated_key = NilAuthPrivateKey()\n", - "print(f\"Delegated Private Key (bytes): {delegated_key.serialize()}\")\n", - "print(f\"Delegated Public Key (hex): {delegated_key.pubkey.serialize().hex()}\")\n", - "\n", - "# %% Parse the Root Token String into an Object\n", - "# Parsing allows easier access to token attributes and structure\n", - "root_token_envelope = NucTokenEnvelope.parse(root_token)\n", - "print(f\"Root Token Envelope (parsed object): {root_token_envelope}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating a Delegated NUC Token\n", - "\n", - "Now that we have the root token and a key pair for the intended recipient, we create a new NUC token that delegates specific permissions from the root token holder (`builder_private_key`) to the recipient (`delegated_key`).\n", - "\n", - "**Steps:**\n", - "\n", - "1. **Initialize Builder:** Start building a new token using `NucTokenBuilder.extending()`, passing the previously parsed `root_token_envelope`. This signifies that the new token derives its authority from the root token.\n", - "2. **Set Body (Delegation):** Specify the token's body using `.body()`. Here, `DelegationBody(policies=[])` indicates this is a delegation token. Policies could further restrict the delegation, but none are added in this example.\n", - "3. **Set Audience:** Define the recipient of this delegated token using `.audience()`. We pass a `Did` (Decentralized Identifier) object created from the *public key* of the `delegated_key` generated in the previous step. This means only the holder of `delegated_key`'s private key can use this token effectively for further actions (like creating an invocation).\n", - "4. **Specify Command:** Grant permission to execute a specific command using `.command()`. Here, `Command([\"nil\", \"ai\", \"generate\"])` authorizes the audience (the holder of `delegated_key`) to perform the `nil ai generate` action. Multiple commands could be listed.\n", - "5. **Build and Sign:** Finalize the token creation and sign it using `.build()`. Crucially, the signing key here is `builder_private_key` – the private key corresponding to the issuer of the *root* token, proving its authority to delegate.\n", - "6. **Print Delegated Token:** Display the raw, encoded string representation of the newly created delegated token.\n", - "7. **Parse Delegated Token:** Convert the raw `delegated_token` string into a structured `NucTokenEnvelope` object.\n", - "8. **Print Parsed Envelope:** Display the `delegated_token_envelope` object to show its structure, including the specified audience and command." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Delegation Token (raw string): eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiAiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCAiYXVkIjogImRpZDpuaWw6MDNkZGEzZjdiYmE5M2VkZGRmNjY1OTY2MGU2OWYxNDY1M2NkYjhjNTZiOGE3MjUzYTIyZDkxNGFjM2NmZmZjNmFhIiwgInN1YiI6ICJkaWQ6bmlsOjAzMDkyM2YyZTcxMjBjNTBlNDI5MDViODU3ZGRkMjk0N2Y2ZWNjZWQ2YmIwMmFhYjY0ZTYzYjI4ZTllMmUwNmQxMCIsICJjbWQiOiAiL25pbC9haS9nZW5lcmF0ZSIsICJwb2wiOiBbXSwgIm5vbmNlIjogIjE4OTg4ODcxMjk2YTRmN2NkNTliYzgyNGNhNWY2NDc4IiwgInByZiI6IFsiNmRkNTlhYmU4Y2ZiMTJmYmQ1MzFiODdkMmIxYmIwYzY1N2NjMGNjMjgyYTIyMzAzMjk0MWE1ZWU2YmYzMzVhOSJdfQ.WfukyFvrLOQIs7sAYkrkg2BnhJKSLTYJGlPxxHl8nHg6s92_eyOZaKcXAgTlL59YL98FciIGkxpCRIxc6wFVUA/eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6bmlsOjAzNTIwZTcwYmQ5N2E1ZmE2ZDcwYzYxNGQ1MGVlNDdiZjQ0NWFlMGIwOTQxYTFkNjFkZGQ1YWZhMDIyYjk3YWIxNCIsImF1ZCI6ImRpZDpuaWw6MDMwOTIzZjJlNzEyMGM1MGU0MjkwNWI4NTdkZGQyOTQ3ZjZlY2NlZDZiYjAyYWFiNjRlNjNiMjhlOWUyZTA2ZDEwIiwic3ViIjoiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCJleHAiOjE3NDk3MjgxNDksImNtZCI6Ii9uaWwvYWkiLCJwb2wiOltdLCJub25jZSI6IjYzNDc1YzkyZjE3ZTZlMjgwMWRkZGNlYzFjYjcwNmFlIiwicHJmIjpbXX0.oKd_heCtzZr6sh-q8fqZOXL3rsxvy1gROugUMIEefRJXyBhtSA4YWrK9xHQlprCHIF0dlWSGN_y68D3Fi1OU4g\n", - "Delegated Token Envelope (parsed object): \n" - ] - } - ], - "source": [ - "# %% Create the Delegated Token\n", - "# Use the NucTokenBuilder to create a new token based on the root token\n", - "delegated_token = (\n", - " NucTokenBuilder.extending(\n", - " root_token_envelope\n", - " ) # Start from the root token's authority\n", - " .body(\n", - " DelegationBody(policies=[])\n", - " ) # Mark as a delegation token (no specific policies here)\n", - " .audience(\n", - " Did(delegated_key.pubkey.serialize())\n", - " ) # Set the recipient to the delegated public key\n", - " .command(\n", - " Command([\"nil\", \"ai\", \"generate\"])\n", - " ) # Authorize the 'nil ai generate' command\n", - " .build(\n", - " builder_private_key\n", - " ) # Sign the delegation using the *root* token's private key\n", - ")\n", - "\n", - "# Print the resulting delegated token string\n", - "print(f\"Delegation Token (raw string): {delegated_token}\")\n", - "\n", - "# %% Parse the Delegated Token String into an Object\n", - "delegated_token_envelope = NucTokenEnvelope.parse(delegated_token)\n", - "\n", - "# Print the parsed object to see its structure\n", - "print(f\"Delegated Token Envelope (parsed object): {delegated_token_envelope}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating an Invocation NUC Token\n", - "\n", - "This final step creates the token that will actually be sent to the target service (e.g., Nilai API) to authorize a specific action. This is called an \"invocation\" token. It uses the permissions granted by the `delegated_token` and targets a specific service endpoint.\n", - "\n", - "**Steps:**\n", - "\n", - "1. **Generate Placeholder Target Key:** Create a *new* `NilAuthPrivateKey` instance (`nilai_public_key`). **WARNING:** In a real application, you would **fetch the actual public key of the service you want to call** (e.g., from a discovery endpoint like `/v1/public_key` on the Nilai API) instead of generating a new one here. This generated key acts as a placeholder for the target service's identity in this example.\n", - "2. **Display Placeholder Keys:** Print the details of this placeholder key pair.\n", - "3. **Display Delegated Token:** Re-print the parsed `delegated_token_envelope` for context.\n", - "4. **Initialize Invocation Builder:** Start building the invocation token using `NucTokenBuilder.extending()`, passing the `delegated_token_envelope`. This signifies the invocation derives its authority from the permissions granted in the delegation token.\n", - "5. **Set Body (Invocation):** Specify the token's body using `.body()`. `InvocationBody(args={})` marks this as an invocation token. The `args` dictionary could contain specific parameters for the command being invoked, but it's empty here.\n", - "6. **Set Audience (Target Service):** Define the intended recipient service using `.audience()`. We pass a `Did` created from the public key of the **placeholder** `nilai_public_key`. **Critically, in a real scenario, this MUST be the actual public key of the target service.** This ensures the token is only valid for that specific service instance.\n", - "7. **Build and Sign:** Finalize the token creation and sign it using `.build()`. The signing key is `delegated_key` – the private key that *received* the permissions in the previous delegation step. This proves the caller is authorized by the delegation.\n", - "8. **Print Invocation Token:** Display the raw, encoded string representation of the invocation token. This is the token you would typically send as an API key or Bearer token to the target service." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Placeholder Target Private Key (bytes): 1366a4fb211fedaf9f35ed507caa5c1c69e7c12e05aac59004ee8af82c28f353\n", - "Placeholder Target Public Key (hex): 03557a9b7632c332967c9e49ef04b4eeee65f238a58e05123afd67c6440643ec45\n", - "Delegated Token Envelope (used for invocation): \n", - "Invocation Token (raw string): eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiAiZGlkOm5pbDowM2RkYTNmN2JiYTkzZWRkZGY2NjU5NjYwZTY5ZjE0NjUzY2RiOGM1NmI4YTcyNTNhMjJkOTE0YWMzY2ZmZmM2YWEiLCAiYXVkIjogImRpZDpuaWw6MDM1NTdhOWI3NjMyYzMzMjk2N2M5ZTQ5ZWYwNGI0ZWVlZTY1ZjIzOGE1OGUwNTEyM2FmZDY3YzY0NDA2NDNlYzQ1IiwgInN1YiI6ICJkaWQ6bmlsOjAzMDkyM2YyZTcxMjBjNTBlNDI5MDViODU3ZGRkMjk0N2Y2ZWNjZWQ2YmIwMmFhYjY0ZTYzYjI4ZTllMmUwNmQxMCIsICJjbWQiOiAiL25pbC9haS9nZW5lcmF0ZSIsICJhcmdzIjoge30sICJub25jZSI6ICJlY2I5ZmRlMDE1MDQyZTVlOWQ4YTAwNWIwN2EyMTUwOCIsICJwcmYiOiBbIjU1OTVhYmM3MjY4NTYzYjVjMWVkOWFjMzFmZmYwYmZkMzc0ZGY1ODE1YjI2OWZiOGUxMDg3OTllNzhkMWE1MDkiXX0.Z61s5nYqi_EJVWfl_VNHhw16oLELABuP1hhe4NIMr8JFGBk7fyvuToCFaWKGx6aBGR8wrLcLcC1qctZWkvVOlQ/eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiAiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCAiYXVkIjogImRpZDpuaWw6MDNkZGEzZjdiYmE5M2VkZGRmNjY1OTY2MGU2OWYxNDY1M2NkYjhjNTZiOGE3MjUzYTIyZDkxNGFjM2NmZmZjNmFhIiwgInN1YiI6ICJkaWQ6bmlsOjAzMDkyM2YyZTcxMjBjNTBlNDI5MDViODU3ZGRkMjk0N2Y2ZWNjZWQ2YmIwMmFhYjY0ZTYzYjI4ZTllMmUwNmQxMCIsICJjbWQiOiAiL25pbC9haS9nZW5lcmF0ZSIsICJwb2wiOiBbXSwgIm5vbmNlIjogIjE4OTg4ODcxMjk2YTRmN2NkNTliYzgyNGNhNWY2NDc4IiwgInByZiI6IFsiNmRkNTlhYmU4Y2ZiMTJmYmQ1MzFiODdkMmIxYmIwYzY1N2NjMGNjMjgyYTIyMzAzMjk0MWE1ZWU2YmYzMzVhOSJdfQ.WfukyFvrLOQIs7sAYkrkg2BnhJKSLTYJGlPxxHl8nHg6s92_eyOZaKcXAgTlL59YL98FciIGkxpCRIxc6wFVUA/eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6bmlsOjAzNTIwZTcwYmQ5N2E1ZmE2ZDcwYzYxNGQ1MGVlNDdiZjQ0NWFlMGIwOTQxYTFkNjFkZGQ1YWZhMDIyYjk3YWIxNCIsImF1ZCI6ImRpZDpuaWw6MDMwOTIzZjJlNzEyMGM1MGU0MjkwNWI4NTdkZGQyOTQ3ZjZlY2NlZDZiYjAyYWFiNjRlNjNiMjhlOWUyZTA2ZDEwIiwic3ViIjoiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCJleHAiOjE3NDk3MjgxNDksImNtZCI6Ii9uaWwvYWkiLCJwb2wiOltdLCJub25jZSI6IjYzNDc1YzkyZjE3ZTZlMjgwMWRkZGNlYzFjYjcwNmFlIiwicHJmIjpbXX0.oKd_heCtzZr6sh-q8fqZOXL3rsxvy1gROugUMIEefRJXyBhtSA4YWrK9xHQlprCHIF0dlWSGN_y68D3Fi1OU4g\n", - "--------------------------------\n" - ] - } - ], - "source": [ - "# %% Generate Placeholder Target Key (Replace with actual service key retrieval in practice)\n", - "# WARNING: This creates a random key. In a real scenario, fetch the target service's public key.\n", - "nilai_public_key = NilAuthPrivateKey() # Placeholder for the target service's key\n", - "\n", - "# Display the placeholder key details\n", - "print(f\"Placeholder Target Private Key (bytes): {nilai_public_key.serialize()}\")\n", - "print(\n", - " f\"Placeholder Target Public Key (hex): {nilai_public_key.pubkey.serialize().hex()}\"\n", - ")\n", - "\n", - "# Display the delegation token again for context\n", - "print(f\"Delegated Token Envelope (used for invocation): {delegated_token_envelope}\")\n", - "\n", - "# %% Create the Invocation Token\n", - "# Use the NucTokenBuilder to create the token that calls the service\n", - "invocation = (\n", - " NucTokenBuilder.extending(\n", - " delegated_token_envelope\n", - " ) # Start from the delegated token's authority\n", - " .body(\n", - " InvocationBody(args={})\n", - " ) # Mark as an invocation token (no specific args here)\n", - " .audience(\n", - " Did(nilai_public_key.pubkey.serialize())\n", - " ) # Set the target service (using placeholder key here)\n", - " .build(delegated_key) # Sign with the *delegated* private key\n", - ")\n", - "\n", - "# Print the resulting invocation token string (this would be sent to the service)\n", - "print(f\"Invocation Token (raw string): {invocation}\")\n", - "print(\"--------------------------------\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Validating the NUC Token Chain\n", - "\n", - "After creating the root, delegated, and invocation tokens, it's crucial to validate them to ensure they are correctly formed, properly signed, and that the chain of delegation is intact. Validation typically checks signatures, expiration times (if set), audience restrictions, and command permissions against a trusted root issuer (in this case, the NilAuth service).\n", - "\n", - "**Steps:**\n", - "\n", - "1. **Get NilAuth Public Key:** Retrieve the public key of the NilAuth service itself using `nilauth_client.about().public_key.serialize()`. This key acts as the ultimate trust anchor for validating the token chain, as NilAuth issued the root token. Wrap it in a `Did` object.\n", - "2. **Print NilAuth Public Key:** Display the retrieved NilAuth public key `Did`.\n", - "3. **Parse Invocation Token:** Convert the raw `invocation` token string into a structured `NucTokenEnvelope` object.\n", - "4. **Print Parsed Invocation Envelope:** Display the parsed invocation token object.\n", - "5. **Print Proof Count:** Show the number of proofs (signatures) attached to the invocation envelope. An invocation token derived from a delegated token, which itself derived from a root token, should have multiple proofs forming a chain back to the root issuer.\n", - "6. **Initialize Validator:** Create instances of `NucTokenValidator`. The validator needs a list of trusted root public keys. Here, we only trust the `nilauth_public_key`.\n", - "7. **Validate Delegated Token:** Call `validator.validate()` on the `delegated_token_envelope`. This checks if it's correctly signed by the `builder_private_key` (whose authority ultimately comes from NilAuth) and if its structure is valid. (Note: The root token validation is commented out but would follow the same principle).\n", - "8. **Prepare Invocation Validation Parameters:** Create `ValidationParameters` specifically for the invocation token.\n", - " * Set `token_requirements` to an `InvocationRequirement`.\n", - " * Crucially, set the `audience` within the `InvocationRequirement` to the **expected audience** (the placeholder `nilai_public_key.pubkey`'s `Did` in this example, but should be the actual target service's `Did` in practice). This tells the validator to specifically check if the token was intended for this recipient.\n", - "9. **Validate Invocation Token:** Call `validator.validate()` on the `invocation_envelope` using the specific `validation_parameters`. This checks:\n", - " * The signature (must be signed by `delegated_key`).\n", - " * The chain of proofs back to the trusted `nilauth_public_key`.\n", - " * The audience matches the one specified in `validation_parameters`.\n", - " * Expiration, command permissions (implicitly checked based on delegation chain)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nilauth Public Key (Trust Anchor): did:nil:03520e70bd97a5fa6d70c614d50ee47bf445ae0b0941a1d61ddd5afa022b97ab14\n", - "Invocation Envelope (parsed object): \n", - "Invocation Envelope Token Proofs Count: 2\n", - "Validating Delegated Token Envelope...\n", - "Delegated Token is Valid.\n", - "Validating Invocation Envelope...\n", - "Invocation Token is Valid (including audience check).\n" - ] - } - ], - "source": [ - "# %% Get the Public Key of the Root Issuer (NilAuth)\n", - "# The NilAuth service's public key is the ultimate trust anchor\n", - "nilauth_public_key = Did(nilauth_client.about().public_key.serialize())\n", - "print(f\"Nilauth Public Key (Trust Anchor): {nilauth_public_key}\")\n", - "\n", - "# %% Parse the Invocation Token String into an Object\n", - "invocation_envelope = NucTokenEnvelope.parse(invocation)\n", - "print(f\"Invocation Envelope (parsed object): {invocation_envelope}\")\n", - "\n", - "# An invocation token derived from a delegated token should have multiple proofs (signatures)\n", - "print(f\"Invocation Envelope Token Proofs Count: {len(invocation_envelope.proofs)}\")\n", - "\n", - "# %% Validate the Tokens\n", - "# Initialize the validator with the trusted root public key(s)\n", - "validator = NucTokenValidator([nilauth_public_key])\n", - "\n", - "# --- Root Token Validation (Optional - Commented Out) ---\n", - "# print(\"Validating Root Token Envelope...\")\n", - "# try:\n", - "# validator.validate(root_token_envelope)\n", - "# print(\"Root Token is Valid.\")\n", - "# except ValidationException as e:\n", - "# print(f\"Root Token Validation Failed: {e}\")\n", - "\n", - "# --- Delegated Token Validation ---\n", - "print(\"Validating Delegated Token Envelope...\")\n", - "try:\n", - " # Basic validation checks structure and signature relative to the root\n", - " validator.validate(delegated_token_envelope, {})\n", - " print(\"Delegated Token is Valid.\")\n", - "except ValidationException as e:\n", - " print(f\"Delegated Token Validation Failed: {e}\")\n", - "\n", - "# --- Invocation Token Validation ---\n", - "print(\"Validating Invocation Envelope...\")\n", - "try:\n", - " # Prepare specific parameters for invocation validation\n", - " default_parameters = ValidationParameters.default()\n", - " # Tell the validator to check if the audience matches our (placeholder) target service key\n", - " default_parameters.token_requirements = InvocationRequirement(\n", - " audience=Did(\n", - " nilai_public_key.pubkey.serialize()\n", - " ) # Use actual service key DID here\n", - " )\n", - " validation_parameters = default_parameters\n", - "\n", - " # Validate the invocation token against the root and check specific requirements\n", - " validator.validate(invocation_envelope, validation_parameters)\n", - " print(\"Invocation Token is Valid (including audience check).\")\n", - "except ValidationException as e:\n", - " print(f\"Invocation Token Validation Failed: {e}\")\n", - "except Exception as e:\n", - " print(f\"An unexpected error occurred during invocation validation: {e}\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/nilai-auth/nilai-auth-client/pyproject.toml b/nilai-auth/nilai-auth-client/pyproject.toml deleted file mode 100644 index e22369ba..00000000 --- a/nilai-auth/nilai-auth-client/pyproject.toml +++ /dev/null @@ -1,20 +0,0 @@ -[project] -name = "nilai-auth-client" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -authors = [ - { name = "José Cabrero-Holgueras", email = "jose.cabrero@nillion.com" } -] -requires-python = ">=3.12" -dependencies = [ - "nuc-helpers", - "openai>=1.70.0", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.uv.sources] -nuc-helpers = { workspace = true } diff --git a/nilai-auth/nilai-auth-client/src/nilai_auth_client/__init__.py b/nilai-auth/nilai-auth-client/src/nilai_auth_client/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nilai-auth/nilai-auth-client/src/nilai_auth_client/main.py b/nilai-auth/nilai-auth-client/src/nilai_auth_client/main.py deleted file mode 100644 index 705a493a..00000000 --- a/nilai-auth/nilai-auth-client/src/nilai_auth_client/main.py +++ /dev/null @@ -1,102 +0,0 @@ -# Do an HTTP request to the nilai-auth-server -import httpx -from secp256k1 import PrivateKey as NilAuthPrivateKey -from nuc.validate import ValidationParameters, InvocationRequirement - -import base64 - -from nuc.token import Did - -import openai - -from nuc_helpers import ( - DelegationToken, - InvocationToken, - get_nilai_public_key, - get_invocation_token, - validate_token, -) - -SERVICE_ENDPOINT = "localhost:8100" -NILAI_ENDPOINT = "localhost:8080" -NILAUTH_ENDPOINT = "localhost:30921" - - -def retrieve_delegation_token(b64_public_key: str) -> DelegationToken: - """ - Get a delegation token for the given public key - - Args: - b64_public_key: The base64 encoded public key - - Returns: - delegation_token: The delegation token - """ - response = httpx.post( - f"http://{SERVICE_ENDPOINT}/v1/delegate/", - json={"user_public_key": b64_public_key}, - ) - return DelegationToken(**response.json()) - - -def main(): - """ - Main function - """ - # Create a user private key and public key - user_private_key = NilAuthPrivateKey() - user_public_key = user_private_key.pubkey - - if user_public_key is None: - raise Exception("Failed to get public key") - - b64_public_key = base64.b64encode(user_public_key.serialize()).decode("utf-8") - - delegation_token = retrieve_delegation_token(b64_public_key) - - validate_token( - f"http://{NILAUTH_ENDPOINT}", - delegation_token.token, - ValidationParameters.default(), - ) - nilai_public_key = get_nilai_public_key(f"http://{NILAI_ENDPOINT}") - if nilai_public_key is None: - raise Exception("Failed to get nilai public key") - - invocation_token: InvocationToken = get_invocation_token( - delegation_token, - nilai_public_key, - user_private_key, - ) - - default_validation_parameters = ValidationParameters.default() - default_validation_parameters.token_requirements = InvocationRequirement( - audience=Did(nilai_public_key.serialize()) - ) - - validate_token( - f"http://{NILAUTH_ENDPOINT}", - invocation_token.token, - default_validation_parameters, - ) - client = openai.OpenAI( - base_url=f"http://{NILAI_ENDPOINT}/v1", api_key=invocation_token.token - ) - - response = client.chat.completions.create( - model="meta-llama/Llama-3.2-1B-Instruct", - messages=[ - { - "role": "system", - "content": "You are a helpful assistant that provides accurate and concise information.", - }, - {"role": "user", "content": "What is the capital of France?"}, - ], - temperature=0.2, - max_tokens=100, - ) - print(response) - - -if __name__ == "__main__": - main() diff --git a/nilai-auth/nilai-auth-server/README.md b/nilai-auth/nilai-auth-server/README.md deleted file mode 100644 index 4ef0bd72..00000000 --- a/nilai-auth/nilai-auth-server/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Nilai Auth Server - -This server acts as a delegation authority for Nillion User Compute (NUC) tokens, specifically for interacting with the Nilai API. It handles obtaining a root NUC token from a configured NilAuth instance, managing subscriptions on the Nillion Chain, and delegating compute capabilities to end-user public keys. - -## Functionality - -1. **Wallet Initialization:** On startup (or first request), it initializes a Nilchain wallet using a hardcoded private key (for development purposes). -2. **NilAuth Client:** Connects to a NilAuth instance specified in `NILAUTH_TRUSTED_ROOT_ISSUERS`. -3. **Subscription Management:** Checks the Nilchain subscription status associated with its wallet. If not subscribed, it pays for the subscription using its wallet. -4. **Root Token Retrieval:** Obtains a root NUC token from the NilAuth instance. -5. **Delegation Endpoint (`/v1/delegate/`):** - * Accepts a POST request containing the end-user's public key (`user_public_key`). - * Validates the subscription and root token. - * Creates a new NUC token, extending the root token's capabilities. - * Sets the audience of the new token to the provided user public key. - * Authorizes the `nil ai generate` command. - * Signs the new token with its private key. - * Returns the delegated NUC token to the user. - -## Prerequisites - -* A running NilAuth instance accessible at the URL(s) defined in the `NILAUTH_TRUSTED_ROOT_ISSUERS` environment variable (or configured within `nilai_auth_server/config.py`). -* A running Nillion Chain node accessible via gRPC (currently hardcoded to `http://localhost:26649`). -* The server's wallet must have sufficient `unil` tokens to pay for NilAuth subscriptions if needed. - -## Running the Server - -Use a ASGI server like Uvicorn: - -```bash -cd nilai-auth/nilai-auth-server -uv run python3 src/nilai_auth_server/app.py -``` - -## Configuration - -* **Private Key:** Currently hardcoded within `app.py`. **This should be replaced with a secure key management solution for production.** -* **Nilchain gRPC Endpoint:** Hardcoded to `http://localhost:26649` in `app.py`. Consider making this configurable. -* **NilAuth Trusted Issuers:** Configured via `NILAUTH_TRUSTED_ROOT_ISSUERS` in `config.py`. - -## API - -### POST `/v1/delegate/` - -* **Request Body:** - ```json - { - "user_public_key": "string (base64 encoded secp256k1 public key)" - } - ``` -* **Response Body:** - ```json - { - "token": "string (NUC token envelope)" - } - ``` diff --git a/nilai-auth/nilai-auth-server/gunicorn.conf.py b/nilai-auth/nilai-auth-server/gunicorn.conf.py deleted file mode 100644 index 494c3c5d..00000000 --- a/nilai-auth/nilai-auth-server/gunicorn.conf.py +++ /dev/null @@ -1,16 +0,0 @@ -# gunicorn.config.py - -# Bind to address and port -bind = ["0.0.0.0:8080"] - -# Set the number of workers (2) -workers = 1 - -# Set the number of threads per worker (16) -threads = 1 - -# Set the timeout (120 seconds) -timeout = 120 - -# Set the worker class to UvicornWorker for async handling -worker_class = "uvicorn.workers.UvicornWorker" diff --git a/nilai-auth/nilai-auth-server/pyproject.toml b/nilai-auth/nilai-auth-server/pyproject.toml deleted file mode 100644 index 95a622cd..00000000 --- a/nilai-auth/nilai-auth-server/pyproject.toml +++ /dev/null @@ -1,22 +0,0 @@ -[project] -name = "nilai-auth-server" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -authors = [ - { name = "José Cabrero-Holgueras", email = "jose.cabrero@nillion.com" } -] -requires-python = ">=3.12" -dependencies = [ - "fastapi[standard]>=0.115.5", - "gunicorn>=23.0.0", - "nuc-helpers", - "uvicorn>=0.34.0", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.uv.sources] -nuc-helpers = { workspace = true } diff --git a/nilai-auth/nilai-auth-server/src/nilai_auth_server/__init__.py b/nilai-auth/nilai-auth-server/src/nilai_auth_server/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nilai-auth/nilai-auth-server/src/nilai_auth_server/app.py b/nilai-auth/nilai-auth-server/src/nilai_auth_server/app.py deleted file mode 100644 index 558e32eb..00000000 --- a/nilai-auth/nilai-auth-server/src/nilai_auth_server/app.py +++ /dev/null @@ -1,71 +0,0 @@ -from fastapi import FastAPI -from nuc.nilauth import NilauthClient -from pydantic import BaseModel -from secp256k1 import PublicKey as NilAuthPublicKey -import base64 -from nilai_auth_server.config import NILAUTH_TRUSTED_ROOT_ISSUER - -from nuc_helpers import ( - RootToken, - DelegationToken, - pay_for_subscription, - get_wallet_and_private_key, - get_root_token, - get_delegation_token, -) - -app = FastAPI() - -PRIVATE_KEY = "l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=" # This is an example private key with funds for testing devnet, and should not be used in production -NILCHAIN_GRPC = "localhost:26649" - - -class DelegateRequest(BaseModel): - user_public_key: str - - -@app.post("/v1/delegate/") -def delegate(request: DelegateRequest) -> DelegationToken: - """ - Delegate the root token to the delegated key - - Args: - request: The request body - """ - - server_wallet, server_keypair, server_private_key = get_wallet_and_private_key( - PRIVATE_KEY - ) - nilauth_client = NilauthClient(f"http://{NILAUTH_TRUSTED_ROOT_ISSUER}") - - if not server_private_key.pubkey: - raise Exception("Failed to get public key") - - # Pay for the subscription - pay_for_subscription( - nilauth_client, - server_wallet, - server_keypair, - server_private_key.pubkey, - f"http://{NILCHAIN_GRPC}", - ) - - # Create a root token - root_token: RootToken = get_root_token(nilauth_client, server_private_key) - - user_public_key = NilAuthPublicKey( - base64.b64decode(request.user_public_key), raw=True - ) - - delegation_token: DelegationToken = get_delegation_token( - root_token, - server_private_key, - user_public_key, - ) - return delegation_token - - -if __name__ == "__main__": - import uvicorn - - uvicorn.run(app, host="0.0.0.0", port=8100) diff --git a/nilai-auth/nilai-auth-server/src/nilai_auth_server/config.py b/nilai-auth/nilai-auth-server/src/nilai_auth_server/config.py deleted file mode 100644 index 4109136a..00000000 --- a/nilai-auth/nilai-auth-server/src/nilai_auth_server/config.py +++ /dev/null @@ -1,4 +0,0 @@ -from dotenv import load_dotenv - -load_dotenv() -NILAUTH_TRUSTED_ROOT_ISSUER = "localhost:30921" diff --git a/nilai-auth/nilai-auth-server/src/nilai_auth_server/py.typed b/nilai-auth/nilai-auth-server/src/nilai_auth_server/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/nilai-auth/nuc-helpers/README.md b/nilai-auth/nuc-helpers/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/nilai-auth/nuc-helpers/pyproject.toml b/nilai-auth/nuc-helpers/pyproject.toml deleted file mode 100644 index 1a38b274..00000000 --- a/nilai-auth/nuc-helpers/pyproject.toml +++ /dev/null @@ -1,22 +0,0 @@ -[project] -name = "nuc-helpers" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -authors = [ - { name = "José Cabrero-Holgueras", email = "jose.cabrero@nillion.com" } -] -requires-python = ">=3.12" -dependencies = [ - "cosmpy==0.9.2", - "pydantic>=2.11.2", - "secp256k1>=0.14.0", - "httpx>=0.28.1", - "nuc>=0.1.0", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.uv.sources] diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/py.typed b/nilai-auth/nuc-helpers/src/nuc_helpers/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/pyproject.toml b/pyproject.toml index 059d6735..324e573a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,16 +4,14 @@ version = "0.1.0" description = "" authors = [ { name = "José Cabrero-Holgueras", email = "jose.cabrero@nillion.com" }, - { name = "Manuel Santos", email = "manuel.santos@nillion.com" }, - { name = "Dimitris Mouris", email = "dimitris@nillion.com" } + { name = "Baptiste Lefort", email = "baptiste.lefort@nillion.com" } ] readme = "README.md" requires-python = ">=3.12" dependencies = [ "nilai-api", "nilai-common", - "nilai-models", - "nuc-helpers", + "nilai-models" ] [dependency-groups] @@ -39,13 +37,12 @@ build-backend = "setuptools.build_meta" find = { include = ["nilai"] } [tool.uv.workspace] -members = ["nilai-models", "nilai-api", "packages/nilai-common", "nilai-auth/nilai-auth-server", "nilai-auth/nilai-auth-client", "nilai-auth/nuc-helpers"] +members = ["nilai-models", "nilai-api", "packages/nilai-common"] [tool.uv.sources] nilai-common = { workspace = true } nilai-api = { workspace = true } nilai-models = { workspace = true } -nuc-helpers = { workspace = true } [tool.pyright] exclude = ["**/.venv", "**/.venv/**"] diff --git a/tests/e2e/nuc.py b/tests/e2e/nuc.py index 9259baf6..2c2366a9 100644 --- a/tests/e2e/nuc.py +++ b/tests/e2e/nuc.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta, timezone -from nuc_helpers import ( +from nilai_api.auth.nuc_helpers import ( get_wallet_and_private_key, pay_for_subscription, get_root_token, diff --git a/tests/unit/nilai_api/auth/test_jwt.py b/tests/unit/nilai_api/auth/test_jwt.py deleted file mode 100644 index a1c89268..00000000 --- a/tests/unit/nilai_api/auth/test_jwt.py +++ /dev/null @@ -1,125 +0,0 @@ -import pytest -from base64 import urlsafe_b64encode -import json -from ...nilai_api.auth import ( - keplr_jwt_valid_forever, - keplr_jwt_expired, - metamask_jwt_valid_forever, - metamask_jwt_expired, - keplr_jwt_invalid_sig, - metamask_jwt_invalid_sig, -) -from nilai_api.auth.jwt import ( - to_base64_url, - sorted_object, - sorted_json_string, - escape_characters, - serialize_sign_doc, - extract_fields, - keplr_validate, - metamask_validate, - validate_jwt, -) - - -def test_to_base64_url(): - data = {"key": "value"} - result = to_base64_url(data) - expected = urlsafe_b64encode( - json.dumps(data, separators=(",", ":")).encode("ascii") - ).decode("utf-8") - assert result == expected - - -def test_sorted_object(): - obj = {"b": 2, "a": 1, "c": [3, 2, 1]} - result = sorted_object(obj) - expected = {"a": 1, "b": 2, "c": [3, 2, 1]} - assert result == expected, result - - -def test_sorted_json_string(): - obj = {"b": 2, "a": 1} - result = sorted_json_string(obj) - expected = json.dumps({"a": 1, "b": 2}, separators=(",", ":")) - assert result == expected - - -def test_escape_characters(): - input_str = "&<>" - result = escape_characters(input_str) - expected = "\\u0026\\u003c\\u003e" - assert result == expected - - -def test_serialize_sign_doc(): - sign_doc = {"key": "value"} - result = serialize_sign_doc(sign_doc) - expected = escape_characters(sorted_json_string(sign_doc)).encode("utf-8") - assert result == expected - - -def test_validate_keplr_valid(): - result = validate_jwt(keplr_jwt_valid_forever) - assert result is not None, result - - -def test_validate_metamask_valid(): - result = validate_jwt(metamask_jwt_valid_forever) - assert result is not None, result - - -def test_validate_keplr_expired(): - with pytest.raises(ValueError): - _ = validate_jwt(keplr_jwt_expired) - - -def test_validate_metamask_expired(): - with pytest.raises(ValueError): - _ = validate_jwt(metamask_jwt_expired) - - -def test_validate_keplr_invalid(): - with pytest.raises(ValueError): - _ = validate_jwt(keplr_jwt_invalid_sig) - - -def test_validate_metamask_invalid(): - with pytest.raises(ValueError): - _ = validate_jwt(metamask_jwt_invalid_sig) - - -def test_keplr_validate(): - message, header, payload, signature = extract_fields(keplr_jwt_valid_forever) - result = keplr_validate(message, header, payload, signature) - assert result is not None, result - - -def test_keplr_validate_invalid(): - message, header, payload, signature = extract_fields(keplr_jwt_invalid_sig) - with pytest.raises(ValueError): - keplr_validate(message, header, payload, signature) - - -def test_metamask_validate(): - message, header, payload, signature = extract_fields(metamask_jwt_valid_forever) - result = metamask_validate(message, header, payload, signature) - assert result is not None, result - - -def test_metamask_validate_invalid(): - message, header, payload, signature = extract_fields(metamask_jwt_invalid_sig) - with pytest.raises(ValueError): - metamask_validate(message, header, payload, signature) - - -def test_keplr_validate_expired(): - message, header, payload, signature = extract_fields(keplr_jwt_expired) - with pytest.raises(ValueError): - keplr_validate(message, header, payload, signature) - - -def test_metamask_validate_expired(): - message, header, payload, signature = extract_fields(metamask_jwt_expired) - with pytest.raises(ValueError): - metamask_validate(message, header, payload, signature) diff --git a/tests/unit/nilai_api/auth/test_strategies.py b/tests/unit/nilai_api/auth/test_strategies.py index ed8e2a1e..0c169f53 100644 --- a/tests/unit/nilai_api/auth/test_strategies.py +++ b/tests/unit/nilai_api/auth/test_strategies.py @@ -3,7 +3,7 @@ from datetime import datetime, timezone, timedelta from fastapi import HTTPException -from nilai_api.auth.strategies import api_key_strategy, jwt_strategy, nuc_strategy +from nilai_api.auth.strategies import api_key_strategy, nuc_strategy from nilai_api.auth.common import AuthenticationInfo, PromptDocument from nilai_api.db.users import RateLimits, UserModel @@ -59,50 +59,6 @@ async def test_api_key_strategy_invalid_key(self): assert exc_info.value.status_code == 401 assert "Missing or invalid API key" in str(exc_info.value.detail) - @pytest.mark.asyncio - async def test_jwt_strategy_existing_user(self, mock_user_model): - """Test JWT authentication with existing user""" - with ( - patch("nilai_api.auth.strategies.validate_jwt") as mock_validate, - patch("nilai_api.auth.strategies.UserManager.check_api_key") as mock_check, - ): - mock_jwt_result = MagicMock() - mock_jwt_result.user_address = "test-address" - mock_jwt_result.pub_key = "test-pub-key" - mock_validate.return_value = mock_jwt_result - mock_check.return_value = mock_user_model - - result = await jwt_strategy("jwt-token") - - assert isinstance(result, AuthenticationInfo) - assert result.user.name == "Test User" - assert result.token_rate_limit is None - assert result.prompt_document is None - - @pytest.mark.asyncio - async def test_jwt_strategy_new_user(self): - """Test JWT authentication creating new user""" - with ( - patch("nilai_api.auth.strategies.validate_jwt") as mock_validate, - patch("nilai_api.auth.strategies.UserManager.check_api_key") as mock_check, - patch( - "nilai_api.auth.strategies.UserManager.insert_user_model" - ) as mock_insert, - ): - mock_jwt_result = MagicMock() - mock_jwt_result.user_address = "new-user-address" - mock_jwt_result.pub_key = "new-user-pub-key" - mock_validate.return_value = mock_jwt_result - mock_check.return_value = None - mock_insert.return_value = None - - result = await jwt_strategy("jwt-token") - - assert isinstance(result, AuthenticationInfo) - assert result.token_rate_limit is None - assert result.prompt_document is None - mock_insert.assert_called_once() - @pytest.mark.asyncio async def test_nuc_strategy_existing_user_with_prompt_document( self, mock_user_model, mock_prompt_document @@ -135,7 +91,7 @@ async def test_nuc_strategy_existing_user_with_prompt_document( @pytest.mark.asyncio async def test_nuc_strategy_new_user_with_token_limits(self, mock_prompt_document): """Test NUC authentication creating new user with token limits""" - from nuc_helpers.usage import TokenRateLimits, TokenRateLimit + from nilai_api.auth.nuc_helpers.usage import TokenRateLimits, TokenRateLimit mock_token_limits = TokenRateLimits( limits=[ @@ -264,21 +220,6 @@ async def test_all_strategies_return_authentication_info_with_prompt_document_fi assert hasattr(result, "prompt_document") assert result.prompt_document is None - # Test JWT strategy - with ( - patch("nilai_api.auth.strategies.validate_jwt") as mock_validate, - patch("nilai_api.auth.strategies.UserManager.check_api_key") as mock_check, - ): - mock_jwt_result = MagicMock() - mock_jwt_result.user_address = "test-address" - mock_jwt_result.pub_key = "test-pub-key" - mock_validate.return_value = mock_jwt_result - mock_check.return_value = mock_user_model - - result = await jwt_strategy("jwt-token") - assert hasattr(result, "prompt_document") - assert result.prompt_document is None - # Test NUC strategy with ( patch("nilai_api.auth.strategies.validate_nuc") as mock_validate, diff --git a/tests/unit/nuc_helpers/test_nildb_document.py b/tests/unit/nuc_helpers/test_nildb_document.py index 860f7b8d..4f75b465 100644 --- a/tests/unit/nuc_helpers/test_nildb_document.py +++ b/tests/unit/nuc_helpers/test_nildb_document.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import patch, MagicMock from nuc.token import Did -from nuc_helpers.nildb_document import PromptDocument +from nilai_api.auth.nuc_helpers.nildb_document import PromptDocument from ..nuc_helpers import DummyDecodedNucToken, DummyNucTokenEnvelope diff --git a/tests/unit/nuc_helpers/test_usage.py b/tests/unit/nuc_helpers/test_usage.py index 445b3286..e60d653e 100644 --- a/tests/unit/nuc_helpers/test_usage.py +++ b/tests/unit/nuc_helpers/test_usage.py @@ -1,6 +1,10 @@ import unittest from unittest.mock import patch -from nuc_helpers.usage import TokenRateLimits, UsageLimitError, UsageLimitKind +from nilai_api.auth.nuc_helpers.usage import ( + TokenRateLimits, + UsageLimitError, + UsageLimitKind, +) from ..nuc_helpers import DummyDecodedNucToken, DummyNucTokenEnvelope from datetime import datetime, timedelta, timezone diff --git a/uv.lock b/uv.lock index b6827114..7d5ccb35 100644 --- a/uv.lock +++ b/uv.lock @@ -6,11 +6,8 @@ requires-python = ">=3.12" members = [ "nilai", "nilai-api", - "nilai-auth-client", - "nilai-auth-server", "nilai-common", "nilai-models", - "nuc-helpers", ] [[package]] @@ -616,6 +613,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, ] +[[package]] +name = "dockerfile-parse" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/df/929ee0b5d2c8bd8d713c45e71b94ab57c7e11e322130724d54f469b2cd48/dockerfile-parse-2.0.1.tar.gz", hash = "sha256:3184ccdc513221983e503ac00e1aa504a2aa8f84e5de673c46b0b6eee99ec7bc", size = 24556, upload-time = "2023-07-18T13:36:07.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/6c/79cd5bc1b880d8c1a9a5550aa8dacd57353fa3bb2457227e1fb47383eb49/dockerfile_parse-2.0.1-py2.py3-none-any.whl", hash = "sha256:bdffd126d2eb26acf1066acb54cb2e336682e1d72b974a40894fac76a4df17f6", size = 14845, upload-time = "2023-07-18T13:36:06.052Z" }, +] + +[[package]] +name = "e2b" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "dockerfile-parse" }, + { name = "httpcore" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "python-dateutil" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/98/d91bcc565bc32d9ce1b109a93f59056e7822e2202e34c7a800bfb4b44d3f/e2b-2.2.5.tar.gz", hash = "sha256:b07bdbc81ab1b58d96ffb3dab7ab7311b259454927eaa6eb785c52a9a469f1f2", size = 91127, upload-time = "2025-10-09T20:55:30.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/04/20380adc9207f053480f521fba6e5de16b5a8e83c9688b9f12b64f5f9c50/e2b-2.2.5-py3-none-any.whl", hash = "sha256:1c5dfc0e0a04c2cd5d4e0a715869523f95ea9effe934e7dbd887b1404bd3ec2b", size = 167419, upload-time = "2025-10-09T20:55:28.159Z" }, +] + +[[package]] +name = "e2b-code-interpreter" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "e2b" }, + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/d0/116900ce07bef1cf2068a0c6da6df4eebcb78b22c037ccb3360613ac1d47/e2b_code_interpreter-2.1.1.tar.gz", hash = "sha256:a51af42d30cbeca158168cee11e362254467b508a0ab578760248f0e97779dd8", size = 10114, upload-time = "2025-10-08T00:18:19.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/c0/774e3e37ba818e27496d2a483a2d679a56b6427562f6e94af862eefc9a9f/e2b_code_interpreter-2.1.1-py3-none-any.whl", hash = "sha256:f7d561ae0bc6f0ae755d09afaa636d3ba935af1b11e6e31d5dbd5988da5a4101", size = 12983, upload-time = "2025-10-08T00:18:17.424Z" }, +] + [[package]] name = "ecdsa" version = "0.19.1" @@ -1470,7 +1510,6 @@ dependencies = [ { name = "nilai-api" }, { name = "nilai-common" }, { name = "nilai-models" }, - { name = "nuc-helpers" }, ] [package.dev-dependencies] @@ -1493,7 +1532,6 @@ requires-dist = [ { name = "nilai-api", editable = "nilai-api" }, { name = "nilai-common", editable = "packages/nilai-common" }, { name = "nilai-models", editable = "nilai-models" }, - { name = "nuc-helpers", editable = "nilai-auth/nuc-helpers" }, ] [package.metadata.requires-dev] @@ -1522,6 +1560,7 @@ dependencies = [ { name = "authlib" }, { name = "click" }, { name = "cryptography" }, + { name = "e2b-code-interpreter" }, { name = "fastapi", extra = ["standard"] }, { name = "greenlet" }, { name = "gunicorn" }, @@ -1530,7 +1569,6 @@ dependencies = [ { name = "nilauth-credit-middleware" }, { name = "nilrag" }, { name = "nuc" }, - { name = "nuc-helpers" }, { name = "openai" }, { name = "pg8000" }, { name = "prometheus-fastapi-instrumentator" }, @@ -1553,6 +1591,7 @@ requires-dist = [ { name = "authlib", specifier = ">=1.4.1" }, { name = "click", specifier = ">=8.1.8" }, { name = "cryptography", specifier = ">=43.0.1" }, + { name = "e2b-code-interpreter", specifier = ">=1.0.3" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.115.5" }, { name = "greenlet", specifier = ">=3.1.1" }, { name = "gunicorn", specifier = ">=23.0.0" }, @@ -1561,7 +1600,6 @@ requires-dist = [ { name = "nilauth-credit-middleware", specifier = ">=0.1.0" }, { name = "nilrag", specifier = ">=0.1.11" }, { name = "nuc", specifier = ">=0.1.0" }, - { name = "nuc-helpers", editable = "nilai-auth/nuc-helpers" }, { name = "openai", specifier = ">=1.59.9" }, { name = "pg8000", specifier = ">=1.31.2" }, { name = "prometheus-fastapi-instrumentator", specifier = ">=7.0.2" }, @@ -1576,40 +1614,6 @@ requires-dist = [ { name = "web3", specifier = ">=7.8.0" }, ] -[[package]] -name = "nilai-auth-client" -version = "0.1.0" -source = { editable = "nilai-auth/nilai-auth-client" } -dependencies = [ - { name = "nuc-helpers" }, - { name = "openai" }, -] - -[package.metadata] -requires-dist = [ - { name = "nuc-helpers", editable = "nilai-auth/nuc-helpers" }, - { name = "openai", specifier = ">=1.70.0" }, -] - -[[package]] -name = "nilai-auth-server" -version = "0.1.0" -source = { editable = "nilai-auth/nilai-auth-server" } -dependencies = [ - { name = "fastapi", extra = ["standard"] }, - { name = "gunicorn" }, - { name = "nuc-helpers" }, - { name = "uvicorn" }, -] - -[package.metadata] -requires-dist = [ - { name = "fastapi", extras = ["standard"], specifier = ">=0.115.5" }, - { name = "gunicorn", specifier = ">=23.0.0" }, - { name = "nuc-helpers", editable = "nilai-auth/nuc-helpers" }, - { name = "uvicorn", specifier = ">=0.34.0" }, -] - [[package]] name = "nilai-common" version = "0.1.0" @@ -1713,27 +1717,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/ba/a99b12ee5132976d974fe65f9dbeaaafe4183a8558859c72bd271f87e25c/nuc-0.1.0-py3-none-any.whl", hash = "sha256:6845133866f2d41592be74ca2a41295d09d7a6d89886a5a1181dceefd4fe5a65", size = 22513, upload-time = "2025-07-01T14:46:54.685Z" }, ] -[[package]] -name = "nuc-helpers" -version = "0.1.0" -source = { editable = "nilai-auth/nuc-helpers" } -dependencies = [ - { name = "cosmpy" }, - { name = "httpx" }, - { name = "nuc" }, - { name = "pydantic" }, - { name = "secp256k1" }, -] - -[package.metadata] -requires-dist = [ - { name = "cosmpy", specifier = "==0.9.2" }, - { name = "httpx", specifier = ">=0.28.1" }, - { name = "nuc", specifier = ">=0.1.0" }, - { name = "pydantic", specifier = ">=2.11.2" }, - { name = "secp256k1", specifier = ">=0.14.0" }, -] - [[package]] name = "numpy" version = "1.26.4"