diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c33ebbe0..1e8cce94 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -59,7 +59,7 @@ services: - nilauth nilauth: - image: public.ecr.aws/k5d9x2g2/nilauth:latest + image: public.ecr.aws/k5d9x2g2/nilauth:ff0d9198d1b8819527bc036a58f875c4046b6f21 depends_on: - nilauth-postgres - nilchain diff --git a/docker/nilauth/config.yaml b/docker/nilauth/config.yaml index fe42c669..01819219 100644 --- a/docker/nilauth/config.yaml +++ b/docker/nilauth/config.yaml @@ -13,7 +13,9 @@ payments: subscriptions: renewal_threshold_seconds: 1000 length_seconds: 120 - dollar_cost: 1 + dollar_cost: + nilai: 1 + nildb: 1 token_price: base_url: http://token-price-api/api/v3/simple/price diff --git a/nilai-api/pyproject.toml b/nilai-api/pyproject.toml index 866bfa76..63333b1f 100644 --- a/nilai-api/pyproject.toml +++ b/nilai-api/pyproject.toml @@ -31,8 +31,8 @@ dependencies = [ "verifier", "web3>=7.8.0", "click>=8.1.8", - "nuc", "nuc-helpers", + "nuc", ] @@ -42,5 +42,5 @@ build-backend = "hatchling.build" [tool.uv.sources] nilai-common = { workspace = true } -nuc = { git = "https://github.com/NillionNetwork/nuc-py.git" } nuc-helpers = { workspace = true } +nuc = { git = "https://github.com/NillionNetwork/nuc-py.git", tag = "4922b5e9354e611cc31322d681eb29da05be584e" } diff --git a/nilai-api/src/nilai_api/auth/nuc.py b/nilai-api/src/nilai_api/auth/nuc.py index cf1cc4eb..0a300d19 100644 --- a/nilai-api/src/nilai_api/auth/nuc.py +++ b/nilai-api/src/nilai_api/auth/nuc.py @@ -3,7 +3,7 @@ from nuc.validate import NucTokenValidator, ValidationParameters, InvocationRequirement from nuc.envelope import NucTokenEnvelope from nuc.nilauth import NilauthClient -from nuc.token import Did, NucToken +from nuc.token import Did, NucToken, Command from functools import lru_cache from nilai_api.config import NILAUTH_TRUSTED_ROOT_ISSUERS from nilai_api.state import state @@ -16,6 +16,9 @@ logger = setup_logger(__name__) +NILAI_BASE_COMMAND: Command = Command.parse("/nil/ai") + + @lru_cache(maxsize=1) def get_validator() -> NucTokenValidator: """ @@ -52,6 +55,14 @@ def get_validation_parameters() -> ValidationParameters: return default_parameters +def check_is_nilai_subcommand(nuc_token_envelope: NucTokenEnvelope) -> bool: + """ + Check if the NUC token is a Nilai subcommand + """ + command: Command = nuc_token_envelope.token.token.command + return command.is_attenuation_of(NILAI_BASE_COMMAND) + + def validate_nuc(nuc_token: str) -> Tuple[str, str]: """ Validate a NUC token @@ -66,7 +77,15 @@ def validate_nuc(nuc_token: str) -> Tuple[str, str]: logger.info(f"Validating NUC token: {nuc_token_envelope.token.token}") logger.info(f"Validation parameters: {get_validation_parameters()}") logger.info(f"Public key: {state.public_key.serialize()}") - get_validator().validate(nuc_token_envelope, get_validation_parameters()) + if not check_is_nilai_subcommand(nuc_token_envelope): + logger.error( + f"NUC token namespace is not a /nil/ai attenuation: {nuc_token_envelope.token.token.command}" + ) + raise AuthenticationError("NUC token namespace is not a /nil/ai attenuation") + + get_validator().validate( + nuc_token_envelope, context={}, parameters=get_validation_parameters() + ) token: NucToken = nuc_token_envelope.token.token # Validate the diff --git a/nilai-auth/nilai-auth-client/examples/tutorial.ipynb b/nilai-auth/nilai-auth-client/examples/tutorial.ipynb index 05a24818..1bbf4ca6 100644 --- a/nilai-auth/nilai-auth-client/examples/tutorial.ipynb +++ b/nilai-auth/nilai-auth-client/examples/tutorial.ipynb @@ -33,16 +33,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=\n", - "\n", + "Nilchain Private Key (bytes): l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=\n", + "Nilchain Public Key (bytes): \n", "Paying for wallet: nillion1mqukqr7d4s3eqhcxwctu7yypm560etp2dghpy6\n", - "Wallet balance: 999999989000000 unil\n", + "Wallet balance: 999999996000000 unil\n", "[>] Creating nilauth client\n", "[>] Creating payer\n", - "IS SUBSCRIBED: True\n", - "[>] Subscription is already paid for\n", - "EXPIRES IN: 0:01:01.197447\n", - "CAN BE RENEWED IN: -1 day, 23:44:21.197434\n" + "IS SUBSCRIBED: False\n", + "[>] Paying for subscription\n" ] } ], @@ -50,7 +48,7 @@ "# %% Import necessary libraries\n", "from nuc.payer import Payer\n", "from nuc.builder import NucTokenBuilder\n", - "from nuc.nilauth import NilauthClient\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", @@ -128,15 +126,18 @@ "\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(builder_private_key)\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", - " key=builder_private_key, # The key to associate the subscription with\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", @@ -168,26 +169,28 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Root Token: eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6bmlsOjAzNTIwZTcwYmQ5N2E1ZmE2ZDcwYzYxNGQ1MGVlNDdiZjQ0NWFlMGIwOTQxYTFkNjFkZGQ1YWZhMDIyYjk3YWIxNCIsImF1ZCI6ImRpZDpuaWw6MDMwOTIzZjJlNzEyMGM1MGU0MjkwNWI4NTdkZGQyOTQ3ZjZlY2NlZDZiYjAyYWFiNjRlNjNiMjhlOWUyZTA2ZDEwIiwic3ViIjoiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCJleHAiOjE3NDU1MDg2OTcsImNtZCI6Ii9uaWwiLCJwb2wiOltdLCJub25jZSI6ImMxNDE0MTIwYjg5ODk3ZTdlM2YyN2ZiZGE0OWEyZDAwIiwicHJmIjpbXX0.3csz4DdLahcYmc6vbwE-HdweGF6TCFUiatoO6AAssSJNUR0VGiRHmCSU3rlmdYAHPHEPcLWNBBLo-e9bMOQXUg\n", - "Builder Private Key: 97f49889fceed88a9cdddb16a161d13f6a12307c2b39163f3c3c397c3c2d2434\n", - "Builder Public Key: 030923f2e7120c50e42905b857ddd2947f6ecced6bb02aab64e63b28e9e2e06d10\n", - "Delegated Private Key: 5586c1abd910c869517a5c1a733c19b3513fef5f700e2c8624e867b620e981ca\n", - "Delegated Public Key: 03f65bb3be4bd7752e9d680ac315025c5252e9e36836217fe9bdb4b8514f04e8d9\n", - "Root Token Envelope: \n" + "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(key=builder_private_key)\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", @@ -228,15 +231,15 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Delegation Token: eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiAiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCAiYXVkIjogImRpZDpuaWw6MDNmNjViYjNiZTRiZDc3NTJlOWQ2ODBhYzMxNTAyNWM1MjUyZTllMzY4MzYyMTdmZTliZGI0Yjg1MTRmMDRlOGQ5IiwgInN1YiI6ICJkaWQ6bmlsOjAzMDkyM2YyZTcxMjBjNTBlNDI5MDViODU3ZGRkMjk0N2Y2ZWNjZWQ2YmIwMmFhYjY0ZTYzYjI4ZTllMmUwNmQxMCIsICJjbWQiOiAiL25pbC9haS9nZW5lcmF0ZSIsICJwb2wiOiBbXSwgIm5vbmNlIjogImZlNTE1ZDA0MGY3MWEyYmY0NzdlYjc2MzBjOWE5YjViIiwgInByZiI6IFsiYWU3ZmY3NTkxYjcxMjQ1MjY3Nzg0NTBmMDZlZDlkYjlkYTU2YmIwZjRmOTIzZmI1YTUzMzMyNWViYWU5ZmQ5MSJdfQ.CguMqBWX0YX2rErcpiHX4PvExo6kiEmnE3QOJMPZ1KU_iiQD1p6kzjY5YRHHT_mWVjgQVNsVR2B9swr7Zk63mw/eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6bmlsOjAzNTIwZTcwYmQ5N2E1ZmE2ZDcwYzYxNGQ1MGVlNDdiZjQ0NWFlMGIwOTQxYTFkNjFkZGQ1YWZhMDIyYjk3YWIxNCIsImF1ZCI6ImRpZDpuaWw6MDMwOTIzZjJlNzEyMGM1MGU0MjkwNWI4NTdkZGQyOTQ3ZjZlY2NlZDZiYjAyYWFiNjRlNjNiMjhlOWUyZTA2ZDEwIiwic3ViIjoiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCJleHAiOjE3NDU1MDg2OTcsImNtZCI6Ii9uaWwiLCJwb2wiOltdLCJub25jZSI6ImMxNDE0MTIwYjg5ODk3ZTdlM2YyN2ZiZGE0OWEyZDAwIiwicHJmIjpbXX0.3csz4DdLahcYmc6vbwE-HdweGF6TCFUiatoO6AAssSJNUR0VGiRHmCSU3rlmdYAHPHEPcLWNBBLo-e9bMOQXUg\n", - "Delegated Token Envelope: \n" + "Delegation Token (raw string): eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiAiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCAiYXVkIjogImRpZDpuaWw6MDNkZGEzZjdiYmE5M2VkZGRmNjY1OTY2MGU2OWYxNDY1M2NkYjhjNTZiOGE3MjUzYTIyZDkxNGFjM2NmZmZjNmFhIiwgInN1YiI6ICJkaWQ6bmlsOjAzMDkyM2YyZTcxMjBjNTBlNDI5MDViODU3ZGRkMjk0N2Y2ZWNjZWQ2YmIwMmFhYjY0ZTYzYjI4ZTllMmUwNmQxMCIsICJjbWQiOiAiL25pbC9haS9nZW5lcmF0ZSIsICJwb2wiOiBbXSwgIm5vbmNlIjogIjE4OTg4ODcxMjk2YTRmN2NkNTliYzgyNGNhNWY2NDc4IiwgInByZiI6IFsiNmRkNTlhYmU4Y2ZiMTJmYmQ1MzFiODdkMmIxYmIwYzY1N2NjMGNjMjgyYTIyMzAzMjk0MWE1ZWU2YmYzMzVhOSJdfQ.WfukyFvrLOQIs7sAYkrkg2BnhJKSLTYJGlPxxHl8nHg6s92_eyOZaKcXAgTlL59YL98FciIGkxpCRIxc6wFVUA/eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6bmlsOjAzNTIwZTcwYmQ5N2E1ZmE2ZDcwYzYxNGQ1MGVlNDdiZjQ0NWFlMGIwOTQxYTFkNjFkZGQ1YWZhMDIyYjk3YWIxNCIsImF1ZCI6ImRpZDpuaWw6MDMwOTIzZjJlNzEyMGM1MGU0MjkwNWI4NTdkZGQyOTQ3ZjZlY2NlZDZiYjAyYWFiNjRlNjNiMjhlOWUyZTA2ZDEwIiwic3ViIjoiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCJleHAiOjE3NDk3MjgxNDksImNtZCI6Ii9uaWwvYWkiLCJwb2wiOltdLCJub25jZSI6IjYzNDc1YzkyZjE3ZTZlMjgwMWRkZGNlYzFjYjcwNmFlIiwicHJmIjpbXX0.oKd_heCtzZr6sh-q8fqZOXL3rsxvy1gROugUMIEefRJXyBhtSA4YWrK9xHQlprCHIF0dlWSGN_y68D3Fi1OU4g\n", + "Delegated Token Envelope (parsed object): \n" ] } ], @@ -293,17 +296,17 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "New Private Key: 6288b98e03a595f54788137b0a9353d334b363af30c71d469ae8368a2257ab0e\n", - "Delegated Key: 02e24769ba1fc9aa1dd819ca12cc8179b5417e3bebf9c9c9e5880342d48246f420\n", - "Delegated Token Envelope: \n", - "Invocation: eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiAiZGlkOm5pbDowM2Y2NWJiM2JlNGJkNzc1MmU5ZDY4MGFjMzE1MDI1YzUyNTJlOWUzNjgzNjIxN2ZlOWJkYjRiODUxNGYwNGU4ZDkiLCAiYXVkIjogImRpZDpuaWw6MDJlMjQ3NjliYTFmYzlhYTFkZDgxOWNhMTJjYzgxNzliNTQxN2UzYmViZjljOWM5ZTU4ODAzNDJkNDgyNDZmNDIwIiwgInN1YiI6ICJkaWQ6bmlsOjAzMDkyM2YyZTcxMjBjNTBlNDI5MDViODU3ZGRkMjk0N2Y2ZWNjZWQ2YmIwMmFhYjY0ZTYzYjI4ZTllMmUwNmQxMCIsICJjbWQiOiAiL25pbC9haS9nZW5lcmF0ZSIsICJhcmdzIjoge30sICJub25jZSI6ICI1ZmRkODk1NjE4MWM1YTZiZjRlNDVhMzBlYzc1YmE0NSIsICJwcmYiOiBbIjBjYWY3MWU3ZGVjMWEwNjc3Y2M0ZWNhYjJhYzY5Nzk1MWRjZDBjZDc4MTM4MDA4MzVkZjY2MmE2ODQzNTNjYzAiXX0.2D5Z7JcodjWyPcRqPx2OIn2Marnk-46XCJAlYIBzdTZCPQSs99gfWslQdoaC84pWYqiaoTvYuogqTDTdrtXaeA/eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiAiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCAiYXVkIjogImRpZDpuaWw6MDNmNjViYjNiZTRiZDc3NTJlOWQ2ODBhYzMxNTAyNWM1MjUyZTllMzY4MzYyMTdmZTliZGI0Yjg1MTRmMDRlOGQ5IiwgInN1YiI6ICJkaWQ6bmlsOjAzMDkyM2YyZTcxMjBjNTBlNDI5MDViODU3ZGRkMjk0N2Y2ZWNjZWQ2YmIwMmFhYjY0ZTYzYjI4ZTllMmUwNmQxMCIsICJjbWQiOiAiL25pbC9haS9nZW5lcmF0ZSIsICJwb2wiOiBbXSwgIm5vbmNlIjogImZlNTE1ZDA0MGY3MWEyYmY0NzdlYjc2MzBjOWE5YjViIiwgInByZiI6IFsiYWU3ZmY3NTkxYjcxMjQ1MjY3Nzg0NTBmMDZlZDlkYjlkYTU2YmIwZjRmOTIzZmI1YTUzMzMyNWViYWU5ZmQ5MSJdfQ.CguMqBWX0YX2rErcpiHX4PvExo6kiEmnE3QOJMPZ1KU_iiQD1p6kzjY5YRHHT_mWVjgQVNsVR2B9swr7Zk63mw/eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6bmlsOjAzNTIwZTcwYmQ5N2E1ZmE2ZDcwYzYxNGQ1MGVlNDdiZjQ0NWFlMGIwOTQxYTFkNjFkZGQ1YWZhMDIyYjk3YWIxNCIsImF1ZCI6ImRpZDpuaWw6MDMwOTIzZjJlNzEyMGM1MGU0MjkwNWI4NTdkZGQyOTQ3ZjZlY2NlZDZiYjAyYWFiNjRlNjNiMjhlOWUyZTA2ZDEwIiwic3ViIjoiZGlkOm5pbDowMzA5MjNmMmU3MTIwYzUwZTQyOTA1Yjg1N2RkZDI5NDdmNmVjY2VkNmJiMDJhYWI2NGU2M2IyOGU5ZTJlMDZkMTAiLCJleHAiOjE3NDU1MDg2OTcsImNtZCI6Ii9uaWwiLCJwb2wiOltdLCJub25jZSI6ImMxNDE0MTIwYjg5ODk3ZTdlM2YyN2ZiZGE0OWEyZDAwIiwicHJmIjpbXX0.3csz4DdLahcYmc6vbwE-HdweGF6TCFUiatoO6AAssSJNUR0VGiRHmCSU3rlmdYAHPHEPcLWNBBLo-e9bMOQXUg\n", + "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" ] } @@ -371,24 +374,20 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Nilauth Public Key: did:nil:03520e70bd97a5fa6d70c614d50ee47bf445ae0b0941a1d61ddd5afa022b97ab14\n", - "Invocation Envelope: \n", - "Invocation Envelope Token Proofs: 2\n", - "Validating Root Token Envelope\n", - "Validating Delegated Token Envelope\n", - "None\n", - "Validating Invocation Envelope\n", - "InvocationRequirement(audience=Did(public_key=b'\\x02\\xe2Gi\\xba\\x1f\\xc9\\xaa\\x1d\\xd8\\x19\\xca\\x12\\xcc\\x81y\\xb5A~;\\xeb\\xf9\\xc9\\xc9\\xe5\\x88\\x03B\\xd4\\x82F\\xf4 '))\n", - "{\"iss\": \"did:nil:03f65bb3be4bd7752e9d680ac315025c5252e9e36836217fe9bdb4b8514f04e8d9\", \"aud\": \"did:nil:02e24769ba1fc9aa1dd819ca12cc8179b5417e3bebf9c9c9e5880342d48246f420\", \"sub\": \"did:nil:030923f2e7120c50e42905b857ddd2947f6ecced6bb02aab64e63b28e9e2e06d10\", \"cmd\": \"/nil/ai/generate\", \"args\": {}, \"nonce\": \"5fdd8956181c5a6bf4e45a30ec75ba45\", \"prf\": [\"0caf71e7dec1a0677cc4ecab2ac697951dcd0cd7813800835df662a684353cc0\"]}\n", - "Expected audience: did:nil:02e24769ba1fc9aa1dd819ca12cc8179b5417e3bebf9c9c9e5880342d48246f420\n", - "Token audience: did:nil:02e24769ba1fc9aa1dd819ca12cc8179b5417e3bebf9c9c9e5880342d48246f420\n" + "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" ] } ], @@ -421,7 +420,7 @@ "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", + " 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", 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 index 419cb1cc..558e32eb 100644 --- a/nilai-auth/nilai-auth-server/src/nilai_auth_server/app.py +++ b/nilai-auth/nilai-auth-server/src/nilai_auth_server/app.py @@ -38,12 +38,15 @@ def delegate(request: DelegateRequest) -> DelegationToken: ) 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, + server_private_key.pubkey, f"http://{NILCHAIN_GRPC}", ) diff --git a/nilai-auth/nuc-helpers/pyproject.toml b/nilai-auth/nuc-helpers/pyproject.toml index 347e9272..54b97570 100644 --- a/nilai-auth/nuc-helpers/pyproject.toml +++ b/nilai-auth/nuc-helpers/pyproject.toml @@ -9,10 +9,10 @@ authors = [ requires-python = ">=3.12" dependencies = [ "cosmpy==0.9.2", - "nuc", "pydantic>=2.11.2", "secp256k1>=0.14.0", "httpx>=0.28.1", + "nuc", ] [build-system] @@ -20,4 +20,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.uv.sources] -nuc = { git = "https://github.com/NillionNetwork/nuc-py.git" } +nuc = { git = "https://github.com/NillionNetwork/nuc-py.git", tag = "4922b5e9354e611cc31322d681eb29da05be584e" } diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/helpers.py b/nilai-auth/nuc-helpers/src/nuc_helpers/helpers.py index 2e366043..9af37535 100644 --- a/nilai-auth/nuc-helpers/src/nuc_helpers/helpers.py +++ b/nilai-auth/nuc-helpers/src/nuc_helpers/helpers.py @@ -1,12 +1,11 @@ import base64 import datetime import logging -from functools import lru_cache from typing import Tuple import httpx -# Importing the pydantic library dependencies -from pydantic import BaseModel +# Importing the types +from nuc_helpers.types import RootToken, DelegationToken, InvocationToken, ChainId # Importing the secp256k1 library dependencies from secp256k1 import PrivateKey as NilAuthPrivateKey, PublicKey as NilAuthPublicKey @@ -14,7 +13,7 @@ # Importing the nuc library dependencies from nuc.payer import Payer from nuc.builder import NucTokenBuilder -from nuc.nilauth import NilauthClient +from nuc.nilauth import NilauthClient, BlindModule from nuc.envelope import NucTokenEnvelope from nuc.token import Command, Did, InvocationBody from nuc.validate import NucTokenValidator, ValidationParameters @@ -26,36 +25,42 @@ logger = logging.getLogger(__name__) -## Pydantic models - - -class RootToken(BaseModel): - token: str +def get_wallet_and_private_key_from_mnemonic( + mnemonic: str, +) -> Tuple[LocalWallet, NilchainPrivateKey, NilAuthPrivateKey]: + """ + Get the wallet and private key from a mnemonic -class DelegationToken(BaseModel): - token: str + Args: + mnemonic: The mnemonic to use for the wallet + Returns: + wallet: The wallet of the user to use for payments on the nilchain + keypair: The keypair of the wallet + private_key: The private key of the keypair to use for nilauth + """ -class InvocationToken(BaseModel): - token: str + wallet = LocalWallet.from_mnemonic(mnemonic, prefix="nillion") + keypair = NilchainPrivateKey(base64.b64decode(wallet.signer().private_key)) + private_key = NilAuthPrivateKey(base64.b64decode(keypair.private_key)) + return wallet, keypair, private_key ## Helpers -@lru_cache(maxsize=1) def get_wallet_and_private_key( private_key_bytes: str | bytes | None = None, ) -> Tuple[LocalWallet, NilchainPrivateKey, NilAuthPrivateKey]: """ - Get the wallet and private key + Get the wallet and private key from a private key bytes Args: private_key_bytes: The private key bytes to use for the wallet Returns: - wallet: The wallet - keypair: The keypair - private_key: The private key + wallet: The wallet of the user to use for payments on the nilchain + keypair: The keypair of the wallet + private_key: The private key of the keypair to use for nilauth """ keypair = NilchainPrivateKey(private_key_bytes) wallet = LocalWallet(keypair, prefix="nillion") @@ -64,7 +69,9 @@ def get_wallet_and_private_key( def get_root_token( - nilauth_client: NilauthClient, private_key: NilAuthPrivateKey + nilauth_client: NilauthClient, + private_key: NilAuthPrivateKey, + blind_module: BlindModule = BlindModule.NILAI, ) -> RootToken: """ Get the root token from nilauth @@ -77,24 +84,31 @@ def get_root_token( The root token """ ## Getting the root token from nilauth - root_token: str = nilauth_client.request_token(key=private_key) + root_token: str = nilauth_client.request_token( + key=private_key, blind_module=blind_module + ) return RootToken(token=root_token) -def get_unil_balance(address: Address, grpc_endpoint: str) -> int: +def get_unil_balance( + address: Address, + grpc_endpoint: str, + chain_id: ChainId = ChainId.NILLION_CHAIN_DEVNET, +) -> int: """ Get the UNIL balance of the user Args: address: The address of the user grpc_endpoint: The endpoint of the grpc server + chain_id: The chain id of the nilchain (default is devnet) Returns: The balance of the user in UNIL """ cfg = NetworkConfig( - chain_id="nillion-chain-devnet", + chain_id=chain_id.value, url="grpc+" + grpc_endpoint, fee_minimum_gas_price=1, fee_denomination="unil", @@ -109,8 +123,10 @@ def pay_for_subscription( nilauth_client: NilauthClient, wallet: LocalWallet, keypair: NilchainPrivateKey, - private_key: NilAuthPrivateKey, + public_key: NilAuthPublicKey, grpc_endpoint: str, + blind_module: BlindModule = BlindModule.NILAI, + chain_id: ChainId = ChainId.NILLION_CHAIN_DEVNET, ) -> None: """ Pay for the subscription using the Nilchain keypair if the user is not subscribed @@ -120,23 +136,18 @@ def pay_for_subscription( keypair: The Nilchain keypair private_key: The NilAuth private key of the user grpc_endpoint: The endpoint of the grpc server + chain_id: The chain id of the nilchain (default is devnet) """ - if ( - get_unil_balance(wallet.address(), grpc_endpoint=grpc_endpoint) - < nilauth_client.subscription_cost() - ): - raise RuntimeError("User does not have enough UNIL to pay for the subscription") - payer = Payer( wallet_private_key=keypair, - chain_id="nillion-chain-devnet", + chain_id=chain_id.value, grpc_endpoint=grpc_endpoint, gas_limit=1000000000000, ) # Pretty print the subscription details - subscription_details = nilauth_client.subscription_status(private_key) + subscription_details = nilauth_client.subscription_status(public_key, blind_module) logger.info(f"IS SUBSCRIBED: {subscription_details.subscribed}") if not subscription_details or subscription_details.subscribed is None: raise RuntimeError( @@ -144,10 +155,17 @@ def pay_for_subscription( ) if not subscription_details.subscribed: + if get_unil_balance( + wallet.address(), grpc_endpoint=grpc_endpoint + ) < nilauth_client.subscription_cost(blind_module=blind_module): + raise RuntimeError( + "User does not have enough UNIL to pay for the subscription" + ) logger.info("[>] Paying for subscription") nilauth_client.pay_subscription( - key=private_key, + pubkey=public_key, payer=payer, + blind_module=blind_module, ) else: logger.info("[>] Subscription is already paid for") @@ -268,4 +286,4 @@ def validate_token( token_envelope = NucTokenEnvelope.parse(token) validator = NucTokenValidator([get_nilauth_public_key(nilauth_url)]) - validator.validate(token_envelope, validation_parameters) + validator.validate(token_envelope, context={}, parameters=validation_parameters) diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/main.py b/nilai-auth/nuc-helpers/src/nuc_helpers/main.py index b6833f7e..928d61fb 100644 --- a/nilai-auth/nuc-helpers/src/nuc_helpers/main.py +++ b/nilai-auth/nuc-helpers/src/nuc_helpers/main.py @@ -31,11 +31,14 @@ def b2b2b2c_test(): nilauth_client = NilauthClient(f"http://{NILAUTH_ENDPOINT}") # Pay for the subscription + if not server_private_key.pubkey: + raise Exception("Failed to get public key") + pay_for_subscription( nilauth_client, server_wallet, server_keypair, - server_private_key, + server_private_key.pubkey, f"http://{NILCHAIN_GRPC}", ) @@ -111,11 +114,14 @@ def b2b2c_test(): nilauth_client = NilauthClient(f"http://{NILAUTH_ENDPOINT}") # Pay for the subscription + if not server_private_key.pubkey: + raise Exception("Failed to get public key") + pay_for_subscription( nilauth_client, server_wallet, server_keypair, - server_private_key, + server_private_key.pubkey, f"http://{NILCHAIN_GRPC}", ) @@ -173,11 +179,14 @@ def b2c_test(): nilauth_client = NilauthClient(f"http://{NILAUTH_ENDPOINT}") # Pay for the subscription + if not server_private_key.pubkey: + raise Exception("Failed to get public key") + pay_for_subscription( nilauth_client, server_wallet, server_keypair, - server_private_key, + server_private_key.pubkey, f"http://{NILCHAIN_GRPC}", ) diff --git a/nilai-auth/nuc-helpers/src/nuc_helpers/types.py b/nilai-auth/nuc-helpers/src/nuc_helpers/types.py new file mode 100644 index 00000000..d8ad7793 --- /dev/null +++ b/nilai-auth/nuc-helpers/src/nuc_helpers/types.py @@ -0,0 +1,36 @@ +from pydantic import BaseModel +from enum import StrEnum + + +## Pydantic models +class TokenType(StrEnum): + ROOT = "root" + DELEGATION = "delegation" + INVOCATION = "invocation" + PRIVATE_KEY = "private_key" + + +class ChainId(StrEnum): + NILLION_CHAIN_MAINNET = "nillion-1" + NILLION_CHAIN_TESTNET = "nillion-chain-testnet-1" + NILLION_CHAIN_DEVNET = "nillion-chain-devnet" + + +class PrivateKey(BaseModel): + type: TokenType = TokenType.PRIVATE_KEY + token: str + + +class RootToken(BaseModel): + type: TokenType = TokenType.ROOT + token: str + + +class DelegationToken(BaseModel): + type: TokenType = TokenType.DELEGATION + token: str + + +class InvocationToken(BaseModel): + type: TokenType = TokenType.INVOCATION + token: str diff --git a/tests/e2e/nuc.py b/tests/e2e/nuc.py index 27cf6f30..491c4fcf 100644 --- a/tests/e2e/nuc.py +++ b/tests/e2e/nuc.py @@ -13,110 +13,119 @@ get_delegation_token, DelegationToken, ) -from nuc.nilauth import NilauthClient +from nuc.nilauth import NilauthClient, BlindModule from nuc.token import Did from nuc.validate import ValidationParameters, InvocationRequirement -def get_nuc_token() -> InvocationToken: - # Services must be running for this to work - PRIVATE_KEY = "l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=" # This is an example private key with funds for testing devnet, and should not be used in production +def get_nuc_token( + usage_limit: int | None = None, + expires_at: datetime | None = None, + blind_module: BlindModule = BlindModule.NILAI, + create_delegation: bool = False, + create_invalid_delegation: bool = False, +) -> InvocationToken: + """ + Unified function to get NUC tokens with various configurations. + + Args: + usage_limit: Optional usage limit for delegation tokens + expires_at: Optional expiration time for delegation tokens + blind_module: Optional blind module to use for the token + create_delegation: Whether to create a delegation token (for rate limiting) + create_invalid_delegation: Whether to create an invalid delegation chain (for testing) + + Returns: + InvocationToken: The generated token + """ + # Constants + PRIVATE_KEY = "l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=" # Example private key for testing devnet NILAI_ENDPOINT = "localhost:8080" NILAUTH_ENDPOINT = "localhost:30921" NILCHAIN_GRPC = "localhost:26649" - # Server private key + # Setup server private key and client server_wallet, server_keypair, server_private_key = get_wallet_and_private_key( PRIVATE_KEY ) nilauth_client = NilauthClient(f"http://{NILAUTH_ENDPOINT}") - # Pay for the subscription + if not server_private_key.pubkey: + raise Exception("Failed to get public key") + + # Pay for subscription pay_for_subscription( nilauth_client, server_wallet, server_keypair, - server_private_key, + server_private_key.pubkey, f"http://{NILCHAIN_GRPC}", + blind_module=blind_module, ) - # Create a root token - root_token: RootToken = get_root_token(nilauth_client, server_private_key) - - nilai_public_key: NilAuthPublicKey = get_nilai_public_key( - f"http://{NILAI_ENDPOINT}" - ) - invocation_token: InvocationToken = get_invocation_token( - root_token, - nilai_public_key, - server_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, - ) - - return invocation_token - - -def get_rate_limited_nuc_token(rate_limit: int = 3) -> InvocationToken: - # Services must be running for this to work - PRIVATE_KEY = "l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=" # This is an example private key with funds for testing devnet, and should not be used in production - NILAI_ENDPOINT = "localhost:8080" - NILAUTH_ENDPOINT = "localhost:30921" - NILCHAIN_GRPC = "localhost:26649" - - # Server private key - server_wallet, server_keypair, server_private_key = get_wallet_and_private_key( - PRIVATE_KEY - ) - nilauth_client = NilauthClient(f"http://{NILAUTH_ENDPOINT}") - - # Pay for the subscription - pay_for_subscription( + # Create root token + root_token: RootToken = get_root_token( nilauth_client, - server_wallet, - server_keypair, server_private_key, - f"http://{NILCHAIN_GRPC}", + blind_module=blind_module, ) - # Create a root token - root_token: RootToken = get_root_token(nilauth_client, server_private_key) - + # Get Nilai public key nilai_public_key: NilAuthPublicKey = get_nilai_public_key( f"http://{NILAI_ENDPOINT}" ) - # 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(public_key.serialize()).decode("utf-8") - - delegation_token: DelegationToken = get_delegation_token( - root_token, - server_private_key, - user_public_key, - usage_limit=3, - expires_at=datetime.now(timezone.utc) + timedelta(minutes=5), - ) - - invocation_token: InvocationToken = get_invocation_token( - delegation_token, - nilai_public_key, - user_private_key, - ) - + # Handle delegation token creation if requested + if create_delegation or create_invalid_delegation: + # Create 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 user public key") + + # Set default values for delegation + delegation_usage_limit = usage_limit if usage_limit is not None else 3 + delegation_expires_at = ( + expires_at + if expires_at is not None + else datetime.now(timezone.utc) + timedelta(minutes=5) + ) + + # Create delegation token + delegation_token: DelegationToken = get_delegation_token( + root_token, + server_private_key, + user_public_key, + usage_limit=delegation_usage_limit, + expires_at=delegation_expires_at, + ) + + # Create invalid delegation chain if requested (for testing) + if create_invalid_delegation: + delegation_token = get_delegation_token( + delegation_token, + user_private_key, + user_public_key, + usage_limit=5, + expires_at=datetime.now(timezone.utc) + timedelta(minutes=5), + ) + + # Create invocation token from delegation + invocation_token: InvocationToken = get_invocation_token( + delegation_token, + nilai_public_key, + user_private_key, + ) + else: + # Create invocation token directly from root token + invocation_token: InvocationToken = get_invocation_token( + root_token, + nilai_public_key, + server_private_key, + ) + + # Validate the token default_validation_parameters = ValidationParameters.default() default_validation_parameters.token_requirements = InvocationRequirement( audience=Did(nilai_public_key.serialize()) @@ -131,74 +140,27 @@ def get_rate_limited_nuc_token(rate_limit: int = 3) -> InvocationToken: return invocation_token -def get_invalid_rate_limited_nuc_token() -> InvocationToken: - # Services must be running for this to work - PRIVATE_KEY = "l/SYifzu2Iqc3dsWoWHRP2oSMHwrORY/PDw5fDwtJDQ=" # This is an example private key with funds for testing devnet, and should not be used in production - NILAI_ENDPOINT = "localhost:8080" - NILAUTH_ENDPOINT = "localhost:30921" - NILCHAIN_GRPC = "localhost:26649" - - # Server private key - server_wallet, server_keypair, server_private_key = get_wallet_and_private_key( - PRIVATE_KEY - ) - nilauth_client = NilauthClient(f"http://{NILAUTH_ENDPOINT}") - - # Pay for the subscription - pay_for_subscription( - nilauth_client, - server_wallet, - server_keypair, - server_private_key, - f"http://{NILCHAIN_GRPC}", - ) - - # Create a root token - root_token: RootToken = get_root_token(nilauth_client, server_private_key) - - nilai_public_key: NilAuthPublicKey = get_nilai_public_key( - f"http://{NILAI_ENDPOINT}" +def get_rate_limited_nuc_token(rate_limit: int = 3) -> InvocationToken: + """Convenience function for getting rate-limited tokens.""" + return get_nuc_token( + usage_limit=rate_limit, + expires_at=datetime.now(timezone.utc) + timedelta(minutes=5), + create_delegation=True, ) - # 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(public_key.serialize()).decode("utf-8") - - delegation_token: DelegationToken = get_delegation_token( - root_token, - server_private_key, - user_public_key, +def get_invalid_rate_limited_nuc_token() -> InvocationToken: + """Convenience function for getting invalid rate-limited tokens (for testing).""" + return get_nuc_token( usage_limit=3, expires_at=datetime.now(timezone.utc) + timedelta(minutes=5), + create_delegation=True, + create_invalid_delegation=True, ) - delegation_token: DelegationToken = get_delegation_token( - delegation_token, - user_private_key, - user_public_key, - usage_limit=5, - expires_at=datetime.now(timezone.utc) + timedelta(minutes=5), - ) - - 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, +def get_nildb_nuc_token() -> InvocationToken: + """Convenience function for getting NILDB NUC tokens.""" + return get_nuc_token( + blind_module=BlindModule.NILDB, ) - - return invocation_token diff --git a/tests/e2e/test_http.py b/tests/e2e/test_http.py index 85454fd3..9e105366 100644 --- a/tests/e2e/test_http.py +++ b/tests/e2e/test_http.py @@ -14,6 +14,7 @@ from .nuc import ( get_rate_limited_nuc_token, get_invalid_rate_limited_nuc_token, + get_nildb_nuc_token, ) import httpx import pytest @@ -67,6 +68,22 @@ def invalid_rate_limited_client(): ) +@pytest.fixture +def nildb_client(): + """Create an HTTPX client with default headers""" + invocation_token = get_nildb_nuc_token() + return httpx.Client( + base_url=BASE_URL, + headers={ + "accept": "application/json", + "Content-Type": "application/json", + "Authorization": f"Bearer {invocation_token.token}", + }, + timeout=None, + verify=False, + ) + + @pytest.fixture def nillion_2025_client(): """Create an HTTPX client with default headers""" @@ -568,6 +585,20 @@ def test_invalid_rate_limiting_nucs(invalid_rate_limited_client): ) +@pytest.mark.skipif( + AUTH_STRATEGY != "nuc", reason="NUC rate limiting not used with API key" +) +def test_invalid_nildb_command_nucs(nildb_client): + """Test rate limiting by sending multiple rapid requests""" + # Payload for repeated requests + payload = { + "model": test_models[0], + "messages": [{"role": "user", "content": "What is your name?"}], + } + response = nildb_client.post("/chat/completions", json=payload) + assert response.status_code == 401, "Invalid NILDB command should return 401" + + def test_large_payload_handling(client): """Test handling of large input payloads""" # Create a very large system message diff --git a/tests/e2e/test_openai.py b/tests/e2e/test_openai.py index 7a04e376..48ad1884 100644 --- a/tests/e2e/test_openai.py +++ b/tests/e2e/test_openai.py @@ -17,6 +17,7 @@ from .nuc import ( get_rate_limited_nuc_token, get_invalid_rate_limited_nuc_token, + get_nildb_nuc_token, ) @@ -51,6 +52,13 @@ def invalid_rate_limited_client(): return _create_openai_client(invocation_token.token) +@pytest.fixture +def nildb_client(): + """Create an OpenAI client configured to use the Nilai API with rate limiting""" + invocation_token = get_nildb_nuc_token() + return _create_openai_client(invocation_token.token) + + @pytest.mark.parametrize( "model", test_models, @@ -173,6 +181,39 @@ def test_invalid_rate_limiting_nucs(invalid_rate_limited_client, model): assert forbidden, "No NUC rate limiting detected, when expected" +@pytest.mark.parametrize( + "model", + test_models, +) +@pytest.mark.skipif( + AUTH_STRATEGY != "nuc", reason="NUC rate limiting not used with API key" +) +def test_invalid_nildb_command_nucs(nildb_client, model): + """Test rate limiting by sending multiple rapid requests""" + import openai + + # Send multiple rapid requests + forbidden = False + for _ in range(4): # Adjust number based on expected rate limits + try: + _ = nildb_client.chat.completions.create( + model=model, + 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, + ) + except openai.AuthenticationError: + forbidden = True + + assert forbidden, "No NILDB command detected, when expected" + + @pytest.mark.parametrize( "model", test_models, diff --git a/uv.lock b/uv.lock index bd07cfc7..f91a90ef 100644 --- a/uv.lock +++ b/uv.lock @@ -1387,7 +1387,7 @@ requires-dist = [ { name = "httpx", specifier = ">=0.27.2" }, { name = "nilai-common", editable = "packages/nilai-common" }, { name = "nilrag", specifier = ">=0.1.11" }, - { name = "nuc", git = "https://github.com/NillionNetwork/nuc-py.git" }, + { name = "nuc", git = "https://github.com/NillionNetwork/nuc-py.git?tag=4922b5e9354e611cc31322d681eb29da05be584e" }, { name = "nuc-helpers", editable = "nilai-auth/nuc-helpers" }, { name = "openai", specifier = ">=1.59.9" }, { name = "pg8000", specifier = ">=1.31.2" }, @@ -1511,7 +1511,7 @@ wheels = [ [[package]] name = "nuc" version = "0.0.0a0" -source = { git = "https://github.com/NillionNetwork/nuc-py.git#ff950979d3ebe0c0808453e578fc76c927575687" } +source = { git = "https://github.com/NillionNetwork/nuc-py.git?tag=4922b5e9354e611cc31322d681eb29da05be584e#4922b5e9354e611cc31322d681eb29da05be584e" } dependencies = [ { name = "cosmpy" }, { name = "requests" }, @@ -1534,7 +1534,7 @@ dependencies = [ requires-dist = [ { name = "cosmpy", specifier = "==0.9.2" }, { name = "httpx", specifier = ">=0.28.1" }, - { name = "nuc", git = "https://github.com/NillionNetwork/nuc-py.git" }, + { name = "nuc", git = "https://github.com/NillionNetwork/nuc-py.git?tag=4922b5e9354e611cc31322d681eb29da05be584e" }, { name = "pydantic", specifier = ">=2.11.2" }, { name = "secp256k1", specifier = ">=0.14.0" }, ]