Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge ed25519 dependencies to an optional server feature flag #986

Merged
merged 15 commits into from
Feb 3, 2022
Merged
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
run: |
pip install nox
nox -s pytest
nox -s pytest-speedups -- --cov-append
nox -s pytest-all-features -- --cov-append

mv .coverage .coverage.${{ matrix.os }}.${{ matrix.python-version }}

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ py -3 -m pip install -U hikari

---

## Optional Features

* `hikari[server]` - Install dependencies required to enable Hikari's standard interaction server (RESTBot) functionality.
* `hikari[speedups]` - Detailed in [`hikari[speedups]`](#hikarispeedups).

## Additional resources

You may wish to use a command framework on top of Hikari so that you can start writing a bot quickly without
Expand Down Expand Up @@ -162,8 +167,8 @@ other internal settings in the interpreter.
### `hikari[speedups]`

If you have a C compiler (Microsoft VC++ Redistributable 14.0 or newer, or a modern copy of GCC/G++, Clang, etc), you
can install Hikari using `pip install -U hikari[speedups]`. This will install `aiodns`, `cchardet`, `Brotli`,
`ciso8601` and `ed25519`, which will provide you with a small performance boost.
can install Hikari using `pip install -U hikari[speedups]`. This will install `aiodns`, `cchardet`, `Brotli`, and
`ciso8601` which will provide you with a small performance boost.

### `uvloop`

Expand Down
3 changes: 3 additions & 0 deletions changes/986.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Running the standard interaction server implementation now requires a `hikari[server]` install.

This matches a switch over to PyNacl for the cryptographic payload validation.
38 changes: 27 additions & 11 deletions hikari/impl/interaction_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,22 @@
from hikari.api import special_endpoints
from hikari.interactions import base_interactions
from hikari.internal import data_binding
from hikari.internal import ed25519

if typing.TYPE_CHECKING:
import socket as socket_
import ssl

import aiohttp.typedefs

# This is kept inline as pynacl is an optional dependency.
from nacl import signing
davfsa marked this conversation as resolved.
Show resolved Hide resolved

from hikari.api import entity_factory as entity_factory_api
from hikari.api import rest as rest_api
from hikari.interactions import command_interactions
from hikari.interactions import component_interactions

_InteractionT_co = typing.TypeVar("_InteractionT_co", bound=base_interactions.PartialInteraction, covariant=True)
_ResponseT_co = typing.TypeVar("_ResponseT_co", bound=special_endpoints.InteractionResponseBuilder, covariant=True)
_MessageResponseBuilderT = typing.Union[
special_endpoints.InteractionDeferredBuilder,
special_endpoints.InteractionMessageBuilder,
Expand Down Expand Up @@ -151,9 +152,10 @@ class InteractionServer(interaction_server.InteractionServer):
"_is_closing",
"_listeners",
"_loads",
"_nacl",
"_public_key",
"_rest_client",
"_server",
"_verify",
)

def __init__(
Expand All @@ -165,6 +167,16 @@ def __init__(
rest_client: rest_api.RESTClient,
public_key: typing.Optional[bytes] = None,
) -> None:
# This is kept inline as pynacl is an optional dependency.
try:
import nacl.exceptions
import nacl.signing

except ModuleNotFoundError as exc:
raise RuntimeError(
"You must install the optional `hikari[server]` dependencies to use the default interaction server."
) from exc
FasterSpeeding marked this conversation as resolved.
Show resolved Hide resolved

# Building asyncio.Lock when there isn't a running loop may lead to runtime errors.
self._application_fetch_lock: typing.Optional[asyncio.Lock] = None
# Building asyncio.Event when there isn't a running loop may lead to runtime errors.
Expand All @@ -174,9 +186,10 @@ def __init__(
self._is_closing = False
self._listeners: typing.Dict[typing.Type[base_interactions.PartialInteraction], typing.Any] = {}
self._loads = loads
self._nacl = nacl
davfsa marked this conversation as resolved.
Show resolved Hide resolved
self._rest_client = rest_client
self._server: typing.Optional[aiohttp.web_runner.AppRunner] = None
self._verify = ed25519.build_ed25519_verifier(public_key) if public_key is not None else None
self._public_key = nacl.signing.VerifyKey(public_key) if public_key is not None else None

@property
def is_alive(self) -> bool:
Expand All @@ -189,23 +202,23 @@ def is_alive(self) -> bool:
"""
return self._server is not None

async def _fetch_public_key(self) -> ed25519.VerifierT:
async def _fetch_public_key(self) -> signing.VerifyKey:
if self._application_fetch_lock is None:
self._application_fetch_lock = asyncio.Lock()

application: typing.Union[applications.Application, applications.AuthorizationApplication]
async with self._application_fetch_lock:
if self._verify:
return self._verify
if self._public_key:
return self._public_key

if self._rest_client.token_type == applications.TokenType.BOT:
application = await self._rest_client.fetch_application()

else:
application = (await self._rest_client.fetch_authorization()).application

self._verify = ed25519.build_ed25519_verifier(application.public_key)
return self._verify
self._public_key = self._nacl.signing.VerifyKey(application.public_key)
return self._public_key

async def aiohttp_hook(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
"""Handle an AIOHTTP interaction request.
Expand Down Expand Up @@ -320,9 +333,12 @@ async def on_interaction(self, body: bytes, signature: bytes, timestamp: bytes)
Instructions on how the REST server calling this should respond to
the interaction request.
"""
verify = self._verify or await self._fetch_public_key()
public_key = self._public_key or await self._fetch_public_key()

try:
public_key.verify(timestamp + body, signature)

if not verify(body, signature, timestamp):
except (self._nacl.exceptions.BadSignatureError, ValueError):
_LOGGER.error("Received a request with an invalid signature")
return _Response(_BAD_REQUEST_STATUS, b"Invalid request signature")

Expand Down
109 changes: 0 additions & 109 deletions hikari/internal/ed25519.py

This file was deleted.

11 changes: 10 additions & 1 deletion pipelines/mypy.nox.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@
@nox.session(reuse_venv=True)
def mypy(session: nox.Session) -> None:
"""Perform static type analysis on Python source code."""
session.install("-r", "requirements.txt", "-r", "speedup-requirements.txt", "-r", "dev-requirements.txt")
session.install(
"-r",
"requirements.txt",
"-r",
"speedup-requirements.txt",
"-r",
"server-requirements.txt",
"-r",
"dev-requirements.txt",
)

_generate_stubs(session)

Expand Down
13 changes: 11 additions & 2 deletions pipelines/pytest.nox.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,21 @@ def pytest(session: nox.Session) -> None:


@nox.session(reuse_venv=True)
def pytest_speedups(session: nox.Session) -> None:
def pytest_all_features(session: nox.Session) -> None:
"""Run unit tests and measure code coverage, using speedup modules.

Coverage can be disabled with the `--skip-coverage` flag.
"""
session.install("-r", "requirements.txt", "-r", "speedup-requirements.txt", "-r", "dev-requirements.txt")
session.install(
"-r",
"requirements.txt",
"-r",
"server-requirements.txt",
"-r",
"speedup-requirements.txt",
"-r",
"dev-requirements.txt",
)
_pytest(session, "-OO")


Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ aiohttp~=3.8
attrs~=21.4
colorlog~=6.6
multidict~=6.0
pure25519==0.0.1
1 change: 1 addition & 0 deletions server-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pynacl~=1.5
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def parse_requirements_file(path):
install_requires=parse_requirements_file("requirements.txt"),
extras_require={
"speedups": parse_requirements_file("speedup-requirements.txt"),
"server": parse_requirements_file("server-requirements.txt"),
},
test_suite="tests",
include_package_data=True,
Expand Down
1 change: 0 additions & 1 deletion speedup-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ aiodns~=3.0
cchardet~=2.1
Brotli~=1.0
ciso8601~=2.2
ed25519~=1.5
Loading