koi-net 1.1.0b5__tar.gz → 1.1.0b7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of koi-net might be problematic. Click here for more details.

Files changed (43) hide show
  1. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/PKG-INFO +1 -1
  2. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/pyproject.toml +1 -1
  3. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/error_handler.py +6 -6
  4. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/graph.py +1 -1
  5. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/request_handler.py +1 -1
  6. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/api_models.py +13 -4
  7. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/edge.py +4 -1
  8. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/envelope.py +10 -6
  9. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/errors.py +6 -6
  10. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/event.py +0 -3
  11. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/secure.py +70 -16
  12. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/server.py +2 -1
  13. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/.github/workflows/publish-to-pypi.yml +0 -0
  14. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/.gitignore +0 -0
  15. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/LICENSE +0 -0
  16. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/README.md +0 -0
  17. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/examples/coordinator.py +0 -0
  18. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/examples/partial.py +0 -0
  19. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/requirements.txt +0 -0
  20. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/__init__.py +0 -0
  21. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/config.py +0 -0
  22. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/context.py +0 -0
  23. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/core.py +0 -0
  24. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/default_actions.py +0 -0
  25. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/effector.py +0 -0
  26. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/identity.py +0 -0
  27. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/lifecycle.py +0 -0
  28. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/__init__.py +0 -0
  29. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/behavior.py +0 -0
  30. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/event_queue.py +0 -0
  31. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/resolver.py +0 -0
  32. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/network/response_handler.py +0 -0
  33. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/poller.py +0 -0
  34. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/processor/__init__.py +0 -0
  35. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/processor/default_handlers.py +0 -0
  36. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/processor/handler.py +0 -0
  37. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/processor/interface.py +0 -0
  38. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/processor/knowledge_object.py +0 -0
  39. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/processor/knowledge_pipeline.py +0 -0
  40. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/__init__.py +0 -0
  41. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/consts.py +0 -0
  42. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/protocol/node.py +0 -0
  43. {koi_net-1.1.0b5 → koi_net-1.1.0b7}/src/koi_net/secure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koi-net
3
- Version: 1.1.0b5
3
+ Version: 1.1.0b7
4
4
  Summary: Implementation of KOI-net protocol in Python
5
5
  Project-URL: Homepage, https://github.com/BlockScience/koi-net/
6
6
  Author-email: Luke Miller <luke@block.science>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "koi-net"
7
- version = "1.1.0-beta.5"
7
+ version = "1.1.0-beta.7"
8
8
  description = "Implementation of KOI-net protocol in Python"
9
9
  authors = [
10
10
  {name = "Luke Miller", email = "luke@block.science"}
@@ -1,5 +1,5 @@
1
1
  from logging import getLogger
2
- from koi_net.protocol.errors import ErrorTypes
2
+ from koi_net.protocol.errors import ErrorType
3
3
  from koi_net.protocol.event import EventType
4
4
  from rid_lib.types import KoiNetNode
5
5
  from ..processor.interface import ProcessorInterface
@@ -36,15 +36,15 @@ class ErrorHandler:
36
36
 
37
37
  def handle_protocol_error(
38
38
  self,
39
- error_type: ErrorTypes,
39
+ error_type: ErrorType,
40
40
  node: KoiNetNode
41
41
  ):
42
42
  logger.info(f"Handling protocol error {error_type} for node {node!r}")
43
43
  match error_type:
44
- case ErrorTypes.UnknownNode:
44
+ case ErrorType.UnknownNode:
45
45
  logger.info("Peer doesn't know me, attempting handshake...")
46
46
  self.actor.handshake_with(node)
47
47
 
48
- case ErrorTypes.InvalidKey: ...
49
- case ErrorTypes.InvalidSignature: ...
50
- case ErrorTypes.InvalidTarget: ...
48
+ case ErrorType.InvalidKey: ...
49
+ case ErrorType.InvalidSignature: ...
50
+ case ErrorType.InvalidTarget: ...
@@ -41,7 +41,7 @@ class NetworkGraph:
41
41
  logger.debug(f"Added edge {rid!r} ({edge_profile.source} -> {edge_profile.target})")
42
42
  logger.debug("Done")
43
43
 
44
- def get_edge(self, source: KoiNetNode, target: KoiNetNode,) -> EdgeProfile | None:
44
+ def get_edge(self, source: KoiNetNode, target: KoiNetNode,) -> KoiNetEdge | None:
45
45
  """Returns edge RID given the RIDs of a source and target node."""
46
46
  if (source, target) in self.dg.edges:
47
47
  edge_data = self.dg.get_edge_data(source, target)
@@ -119,7 +119,7 @@ class RequestHandler:
119
119
  )
120
120
 
121
121
  try:
122
- result = httpx.post(url, data=signed_envelope.model_dump_json())
122
+ result = httpx.post(url, data=signed_envelope.model_dump_json(exclude_none=True))
123
123
  except httpx.ConnectError as err:
124
124
  logger.debug("Failed to connect")
125
125
  self.error_handler.handle_connection_error(node)
@@ -1,51 +1,60 @@
1
1
  """Pydantic models for request and response/payload objects in the KOI-net API."""
2
2
 
3
- from pydantic import BaseModel
3
+ from typing import Literal
4
+ from pydantic import BaseModel, Field
4
5
  from rid_lib import RID, RIDType
5
6
  from rid_lib.ext import Bundle, Manifest
6
7
  from .event import Event
7
- from .errors import ErrorTypes
8
+ from .errors import ErrorType
8
9
 
9
10
 
10
11
  # REQUEST MODELS
11
12
 
12
13
  class PollEvents(BaseModel):
13
- rid: RID
14
+ type: Literal["poll_events"] = Field("poll_events")
14
15
  limit: int = 0
15
16
 
16
17
  class FetchRids(BaseModel):
18
+ type: Literal["fetch_rids"] = Field("fetch_rids")
17
19
  rid_types: list[RIDType] = []
18
20
 
19
21
  class FetchManifests(BaseModel):
22
+ type: Literal["fetch_manifests"] = Field("fetch_manifests")
20
23
  rid_types: list[RIDType] = []
21
24
  rids: list[RID] = []
22
25
 
23
26
  class FetchBundles(BaseModel):
27
+ type: Literal["fetch_bundles"] = Field("fetch_bundles")
24
28
  rids: list[RID]
25
29
 
26
30
 
27
31
  # RESPONSE/PAYLOAD MODELS
28
32
 
29
33
  class RidsPayload(BaseModel):
34
+ type: Literal["rids_payload"] = Field("rids_payload")
30
35
  rids: list[RID]
31
36
 
32
37
  class ManifestsPayload(BaseModel):
38
+ type: Literal["manifests_payload"] = Field("manifests_payload")
33
39
  manifests: list[Manifest]
34
40
  not_found: list[RID] = []
35
41
 
36
42
  class BundlesPayload(BaseModel):
43
+ type: Literal["bundles_payload"] = Field("bundles_payload")
37
44
  bundles: list[Bundle]
38
45
  not_found: list[RID] = []
39
46
  deferred: list[RID] = []
40
47
 
41
48
  class EventsPayload(BaseModel):
49
+ type: Literal["events_payload"] = Field("events_payload")
42
50
  events: list[Event]
43
51
 
44
52
 
45
53
  # ERROR MODELS
46
54
 
47
55
  class ErrorResponse(BaseModel):
48
- error: ErrorTypes
56
+ type: Literal["error_response"] = Field("error_response")
57
+ error: ErrorType
49
58
 
50
59
  # TYPES
51
60
 
@@ -2,6 +2,7 @@ from enum import StrEnum
2
2
  from pydantic import BaseModel
3
3
  from rid_lib import RIDType
4
4
  from rid_lib.ext.bundle import Bundle
5
+ from rid_lib.ext.utils import sha256_hash
5
6
  from rid_lib.types import KoiNetEdge, KoiNetNode
6
7
 
7
8
 
@@ -27,7 +28,9 @@ def generate_edge_bundle(
27
28
  rid_types: list[RIDType],
28
29
  edge_type: EdgeType
29
30
  ) -> Bundle:
30
- edge_rid = KoiNetEdge.generate(source, target)
31
+ edge_rid = KoiNetEdge(sha256_hash(
32
+ str(source) + str(target)
33
+ ))
31
34
  edge_profile = EdgeProfile(
32
35
  source=source,
33
36
  target=target,
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from typing import Generic, TypeVar
3
- from pydantic import BaseModel
3
+ from pydantic import BaseModel, ConfigDict
4
4
  from rid_lib.types import KoiNetNode
5
5
 
6
6
  from .secure import PrivateKey, PublicKey
@@ -13,6 +13,8 @@ logger = logging.getLogger(__name__)
13
13
  T = TypeVar("T", bound=RequestModels | ResponseModels)
14
14
 
15
15
  class SignedEnvelope(BaseModel, Generic[T]):
16
+ model_config = ConfigDict(exclude_none=True)
17
+
16
18
  payload: T
17
19
  source_node: KoiNetNode
18
20
  target_node: KoiNetNode
@@ -26,24 +28,26 @@ class SignedEnvelope(BaseModel, Generic[T]):
26
28
  target_node=self.target_node
27
29
  )
28
30
 
29
- logger.debug(f"Verifying envelope: {unsigned_envelope.model_dump_json()}")
30
-
31
+ logger.debug(f"Verifying envelope: {unsigned_envelope.model_dump_json(exclude_none=True)}")
32
+
31
33
  pub_key.verify(
32
34
  self.signature,
33
- unsigned_envelope.model_dump_json().encode()
35
+ unsigned_envelope.model_dump_json(exclude_none=True).encode()
34
36
  )
35
37
 
36
38
  class UnsignedEnvelope(BaseModel, Generic[T]):
39
+ model_config = ConfigDict(exclude_none=True)
40
+
37
41
  payload: T
38
42
  source_node: KoiNetNode
39
43
  target_node: KoiNetNode
40
44
 
41
45
  def sign_with(self, priv_key: PrivateKey) -> SignedEnvelope[T]:
42
- logger.debug(f"Signing envelope: {self.model_dump_json()}")
46
+ logger.debug(f"Signing envelope: {self.model_dump_json(exclude_none=True)}")
43
47
  logger.debug(f"Type: [{type(self.payload)}]")
44
48
 
45
49
  signature = priv_key.sign(
46
- self.model_dump_json().encode()
50
+ self.model_dump_json(exclude_none=True).encode()
47
51
  )
48
52
 
49
53
  return SignedEnvelope(
@@ -1,23 +1,23 @@
1
1
  from enum import StrEnum
2
2
 
3
3
 
4
- class ErrorTypes(StrEnum):
4
+ class ErrorType(StrEnum):
5
5
  UnknownNode = "unknown_node"
6
6
  InvalidKey = "invalid_key"
7
7
  InvalidSignature = "invalid_signature"
8
8
  InvalidTarget = "invalid_target"
9
9
 
10
10
  class ProtocolError(Exception):
11
- error_type: ErrorTypes
11
+ error_type: ErrorType
12
12
 
13
13
  class UnknownNodeError(ProtocolError):
14
- error_type = ErrorTypes.UnknownNode
14
+ error_type = ErrorType.UnknownNode
15
15
 
16
16
  class InvalidKeyError(ProtocolError):
17
- error_type = ErrorTypes.InvalidKey
17
+ error_type = ErrorType.InvalidKey
18
18
 
19
19
  class InvalidSignatureError(ProtocolError):
20
- error_type = ErrorTypes.InvalidSignature
20
+ error_type = ErrorType.InvalidSignature
21
21
 
22
22
  class InvalidTargetError(ProtocolError):
23
- error_type = ErrorTypes.InvalidTarget
23
+ error_type = ErrorType.InvalidTarget
@@ -15,9 +15,6 @@ class Event(BaseModel):
15
15
  manifest: Manifest | None = None
16
16
  contents: dict | None = None
17
17
 
18
- class Config:
19
- exclude_none = True
20
-
21
18
  def __repr__(self):
22
19
  return f"<Event '{self.rid}' event type: '{self.event_type}'>"
23
20
 
@@ -1,22 +1,64 @@
1
1
  import logging
2
- from base64 import urlsafe_b64decode, urlsafe_b64encode
2
+ from base64 import b64decode, b64encode
3
3
  from cryptography.hazmat.primitives import hashes
4
4
  from cryptography.hazmat.primitives.asymmetric import ec
5
5
  from cryptography.hazmat.primitives import serialization
6
6
  from rid_lib.ext.utils import sha256_hash
7
+ from cryptography.hazmat.primitives.asymmetric.utils import (
8
+ decode_dss_signature,
9
+ encode_dss_signature
10
+ )
7
11
 
8
12
  logger = logging.getLogger(__name__)
9
13
 
10
14
 
15
+ def der_to_raw_signature(der_signature: bytes, curve=ec.SECP256R1()) -> bytes:
16
+ """Convert a DER-encoded signature to raw r||s format."""
17
+
18
+ # Decode the DER signature to get r and s
19
+ r, s = decode_dss_signature(der_signature)
20
+
21
+ # Determine byte length based on curve bit size
22
+ byte_length = (curve.key_size + 7) // 8
23
+
24
+ # Convert r and s to big-endian byte arrays of fixed length
25
+ r_bytes = r.to_bytes(byte_length, byteorder='big')
26
+ s_bytes = s.to_bytes(byte_length, byteorder='big')
27
+
28
+ # Concatenate r and s
29
+ return r_bytes + s_bytes
30
+
31
+
32
+ def raw_to_der_signature(raw_signature: bytes, curve=ec.SECP256R1()) -> bytes:
33
+ """Convert a raw r||s signature to DER format."""
34
+
35
+ # Determine byte length based on curve bit size
36
+ byte_length = (curve.key_size + 7) // 8
37
+
38
+ # Split the raw signature into r and s components
39
+ if len(raw_signature) != 2 * byte_length:
40
+ raise ValueError(f"Raw signature must be {2 * byte_length} bytes for {curve.name}")
41
+
42
+ r_bytes = raw_signature[:byte_length]
43
+ s_bytes = raw_signature[byte_length:]
44
+
45
+ # Convert bytes to integers
46
+ r = int.from_bytes(r_bytes, byteorder='big')
47
+ s = int.from_bytes(s_bytes, byteorder='big')
48
+
49
+ # Encode as DER
50
+ return encode_dss_signature(r, s)
51
+
52
+
11
53
  class PrivateKey:
12
54
  priv_key: ec.EllipticCurvePrivateKey
13
55
 
14
56
  def __init__(self, priv_key):
15
57
  self.priv_key = priv_key
16
-
58
+
17
59
  @classmethod
18
60
  def generate(cls):
19
- return cls(priv_key=ec.generate_private_key(ec.SECP192R1()))
61
+ return cls(priv_key=ec.generate_private_key(ec.SECP256R1()))
20
62
 
21
63
  def public_key(self) -> "PublicKey":
22
64
  return PublicKey(self.priv_key.public_key())
@@ -40,12 +82,14 @@ class PrivateKey:
40
82
  def sign(self, message: bytes) -> str:
41
83
  hashed_message = sha256_hash(message.decode())
42
84
 
43
- signature = urlsafe_b64encode(
44
- self.priv_key.sign(
45
- data=message,
46
- signature_algorithm=ec.ECDSA(hashes.SHA256())
47
- )
48
- ).decode()
85
+ der_signature_bytes = self.priv_key.sign(
86
+ data=message,
87
+ signature_algorithm=ec.ECDSA(hashes.SHA256())
88
+ )
89
+
90
+ raw_signature_bytes = der_to_raw_signature(der_signature_bytes)
91
+
92
+ signature = b64encode(raw_signature_bytes).decode()
49
93
 
50
94
  logger.debug(f"Signing message with [{self.public_key().to_der()}]")
51
95
  logger.debug(f"hash: {hashed_message}")
@@ -78,29 +122,39 @@ class PublicKey:
78
122
  def from_der(cls, pub_key_der: str):
79
123
  return cls(
80
124
  pub_key=serialization.load_der_public_key(
81
- data=urlsafe_b64decode(pub_key_der)
125
+ data=b64decode(pub_key_der)
82
126
  )
83
127
  )
84
128
 
85
129
  def to_der(self) -> str:
86
- return urlsafe_b64encode(
130
+ return b64encode(
87
131
  self.pub_key.public_bytes(
88
132
  encoding=serialization.Encoding.DER,
89
133
  format=serialization.PublicFormat.SubjectPublicKeyInfo
90
134
  )
91
135
  ).decode()
92
136
 
137
+
93
138
  def verify(self, signature: str, message: bytes) -> bool:
94
- hashed_message = sha256_hash(message.decode())
139
+ # hashed_message = sha256_hash(message.decode())
140
+
141
+ # print(message.hex())
142
+ # print()
143
+ # print(hashed_message)
144
+ # print()
145
+ # print(message.decode())
95
146
 
96
- logger.debug(f"Verifying message with [{self.to_der()}]")
97
- logger.debug(f"hash: {hashed_message}")
98
- logger.debug(f"signature: {signature}")
147
+ # logger.debug(f"Verifying message with [{self.to_der()}]")
148
+ # logger.debug(f"hash: {hashed_message}")
149
+ # logger.debug(f"signature: {signature}")
150
+
151
+ raw_signature_bytes = b64decode(signature)
152
+ der_signature_bytes = raw_to_der_signature(raw_signature_bytes)
99
153
 
100
154
  # NOTE: throws cryptography.exceptions.InvalidSignature on failure
101
155
 
102
156
  self.pub_key.verify(
103
- signature=urlsafe_b64decode(signature),
157
+ signature=der_signature_bytes,
104
158
  data=message,
105
159
  signature_algorithm=ec.ECDSA(hashes.SHA256())
106
160
  )
@@ -73,7 +73,8 @@ class NodeServer:
73
73
  self.router.add_api_route(
74
74
  path=path,
75
75
  endpoint=self.secure.envelope_handler(func),
76
- methods=["POST"]
76
+ methods=["POST"],
77
+ response_model_exclude_none=True
77
78
  )
78
79
 
79
80
  _add_endpoint(BROADCAST_EVENTS_PATH, self.broadcast_events)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes