nest-plugins-reference 0.1.0__tar.gz → 0.1.1__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.
Files changed (33) hide show
  1. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/PKG-INFO +1 -1
  2. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/__init__.py +1 -1
  3. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/auth/jwt_auth.py +3 -0
  4. nest_plugins_reference-0.1.1/nest_plugins_reference/identity/did_key.py +198 -0
  5. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/payments/prepaid_credits.py +26 -4
  6. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/pyproject.toml +1 -1
  7. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/tests/test_imports.py +1 -1
  8. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/tests/test_plugins.py +41 -0
  9. nest_plugins_reference-0.1.0/nest_plugins_reference/identity/did_key.py +0 -101
  10. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/.gitignore +0 -0
  11. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/README.md +0 -0
  12. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/auth/__init__.py +0 -0
  13. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/comms/__init__.py +0 -0
  14. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/comms/nest_native.py +0 -0
  15. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/coordination/__init__.py +0 -0
  16. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/coordination/contract_net.py +0 -0
  17. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/datafacts/__init__.py +0 -0
  18. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/datafacts/datafacts_v1.py +0 -0
  19. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/identity/__init__.py +0 -0
  20. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/memory/__init__.py +0 -0
  21. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/memory/blackboard.py +0 -0
  22. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/negotiation/__init__.py +0 -0
  23. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/negotiation/alternating_offers.py +0 -0
  24. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/payments/__init__.py +0 -0
  25. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/privacy/__init__.py +0 -0
  26. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/privacy/noop.py +0 -0
  27. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/py.typed +0 -0
  28. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/registry/__init__.py +0 -0
  29. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/registry/in_memory.py +0 -0
  30. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/transport/__init__.py +0 -0
  31. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/transport/in_memory.py +0 -0
  32. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/trust/__init__.py +0 -0
  33. {nest_plugins_reference-0.1.0 → nest_plugins_reference-0.1.1}/nest_plugins_reference/trust/score_average.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nest-plugins-reference
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: NEST reference plugins: default implementations for all 12 layers
5
5
  Project-URL: Homepage, https://github.com/mariagorskikh/nest
6
6
  Project-URL: Repository, https://github.com/mariagorskikh/nest
@@ -1,4 +1,4 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  """NEST reference plugins: default implementations for all 12 layers."""
3
3
 
4
- __version__ = "0.1.0"
4
+ __version__ = "0.1.1"
@@ -85,6 +85,9 @@ class JwtAuth:
85
85
  raise ValueError(msg)
86
86
 
87
87
  data = json.loads(payload_str)
88
+ if data["exp"] < self._now():
89
+ msg = "Token has expired"
90
+ raise ValueError(msg)
88
91
  return AuthContext(
89
92
  subject=AgentId(data["sub"]),
90
93
  scopes=data["scopes"],
@@ -0,0 +1,198 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """DID:key identity plugin — deterministic public-key signatures for simulation.
3
+
4
+ This is intentionally dependency-free and deterministic so traces replay
5
+ byte-for-byte. It is not production cryptography; for real deployments, swap
6
+ to a proper Ed25519 implementation.
7
+
8
+ Example::
9
+
10
+ identity = DidKeyIdentity(AgentId("a1"), seed=b"secret")
11
+ sig = identity.sign(b"payload")
12
+ ok = identity.verify(b"payload", sig, AgentId("a1"))
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import json
19
+ import math
20
+
21
+ from nest_core.types import AgentId, AgentIdentity, Signature
22
+
23
+
24
+ class DidKeyIdentity:
25
+ """Public-verifiable identity for simulation.
26
+
27
+ Example::
28
+
29
+ ident = DidKeyIdentity(AgentId("a1"), seed=b"seed")
30
+ sig = ident.sign(b"hello")
31
+ """
32
+
33
+ def __init__(self, agent_id: AgentId, seed: bytes = b"") -> None:
34
+ self._agent_id = agent_id
35
+ self._seed = seed
36
+ self._public_numbers, self._private_exponent = _derive_keypair(seed, agent_id)
37
+ self._public_key = _encode_public_key(self._public_numbers)
38
+ self._known_keys: dict[AgentId, tuple[int, int]] = {agent_id: self._public_numbers}
39
+
40
+ def register_peer(
41
+ self,
42
+ agent_id: AgentId,
43
+ public_key: bytes,
44
+ private_key: bytes | None = None,
45
+ ) -> None:
46
+ """Register a peer's public key for verification.
47
+
48
+ Example::
49
+
50
+ ident.register_peer(AgentId("a2"), peer_pk)
51
+ """
52
+ if private_key is not None:
53
+ msg = "register_peer accepts public keys only"
54
+ raise ValueError(msg)
55
+ self._known_keys[agent_id] = _decode_public_key(public_key)
56
+
57
+ @property
58
+ def public_key(self) -> bytes:
59
+ """This agent's public key.
60
+
61
+ Example::
62
+
63
+ pk = ident.public_key
64
+ """
65
+ return self._public_key
66
+
67
+ def sign(self, payload: bytes) -> Signature:
68
+ """Sign a payload with this agent's private key.
69
+
70
+ Example::
71
+
72
+ sig = ident.sign(b"data")
73
+ """
74
+ n, _e = self._public_numbers
75
+ digest = _digest_int(payload, n)
76
+ sig_int = pow(digest, self._private_exponent, n)
77
+ size = (n.bit_length() + 7) // 8
78
+ sig_bytes = sig_int.to_bytes(size, "big")
79
+ return Signature(signer=self._agent_id, value=sig_bytes, algorithm="sim-rsa-sha256")
80
+
81
+ def verify(self, payload: bytes, sig: Signature, agent: AgentId) -> bool:
82
+ """Verify a signature from a given agent.
83
+
84
+ Example::
85
+
86
+ ok = ident.verify(b"data", sig, AgentId("a1"))
87
+ """
88
+ if sig.signer != agent:
89
+ return False
90
+ public_numbers = self._known_keys.get(agent)
91
+ if public_numbers is None:
92
+ return False
93
+ n, e = public_numbers
94
+ sig_int = int.from_bytes(sig.value, "big")
95
+ if sig_int >= n:
96
+ return False
97
+ expected = _digest_int(payload, n)
98
+ actual = pow(sig_int, e, n)
99
+ return actual == expected
100
+
101
+ async def resolve(self, agent: AgentId) -> AgentIdentity:
102
+ """Resolve an agent ID to its identity record.
103
+
104
+ Example::
105
+
106
+ info = await ident.resolve(AgentId("a1"))
107
+ """
108
+ public_numbers = self._known_keys.get(agent)
109
+ pk = _encode_public_key(public_numbers) if public_numbers is not None else b""
110
+ return AgentIdentity(
111
+ agent_id=agent,
112
+ public_key=pk,
113
+ method="did:key",
114
+ )
115
+
116
+
117
+ _PUBLIC_EXPONENT = 65537
118
+
119
+
120
+ def _derive_keypair(seed: bytes, agent_id: AgentId) -> tuple[tuple[int, int], int]:
121
+ p = _derive_prime(seed, agent_id, b"p")
122
+ q = _derive_prime(seed, agent_id, b"q")
123
+ counter = 0
124
+ while p == q or math.gcd(_PUBLIC_EXPONENT, (p - 1) * (q - 1)) != 1:
125
+ counter += 1
126
+ q = _derive_prime(seed, agent_id, b"q" + counter.to_bytes(2, "big"))
127
+ n = p * q
128
+ phi = (p - 1) * (q - 1)
129
+ d = pow(_PUBLIC_EXPONENT, -1, phi)
130
+ return (n, _PUBLIC_EXPONENT), d
131
+
132
+
133
+ def _derive_prime(seed: bytes, agent_id: AgentId, label: bytes) -> int:
134
+ counter = 0
135
+ while True:
136
+ material = seed + b":" + str(agent_id).encode() + b":" + label + counter.to_bytes(4, "big")
137
+ digest = hashlib.sha512(material).digest()
138
+ candidate = int.from_bytes(digest[:32], "big")
139
+ candidate |= 1
140
+ candidate |= 1 << 255
141
+ if _is_probable_prime(candidate):
142
+ return candidate
143
+ counter += 1
144
+
145
+
146
+ def _is_probable_prime(n: int) -> bool:
147
+ if n < 2:
148
+ return False
149
+ small_primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37)
150
+ for prime in small_primes:
151
+ if n == prime:
152
+ return True
153
+ if n % prime == 0:
154
+ return False
155
+
156
+ d = n - 1
157
+ s = 0
158
+ while d % 2 == 0:
159
+ s += 1
160
+ d //= 2
161
+
162
+ for base in small_primes:
163
+ if base >= n:
164
+ continue
165
+ x = pow(base, d, n)
166
+ if x in (1, n - 1):
167
+ continue
168
+ for _ in range(s - 1):
169
+ x = pow(x, 2, n)
170
+ if x == n - 1:
171
+ break
172
+ else:
173
+ return False
174
+ return True
175
+
176
+
177
+ def _encode_public_key(public_numbers: tuple[int, int]) -> bytes:
178
+ n, e = public_numbers
179
+ return json.dumps(
180
+ {"alg": "sim-rsa-sha256", "e": e, "n": format(n, "x")},
181
+ sort_keys=True,
182
+ separators=(",", ":"),
183
+ ).encode("ascii")
184
+
185
+
186
+ def _decode_public_key(public_key: bytes) -> tuple[int, int]:
187
+ try:
188
+ data = json.loads(public_key.decode("ascii"))
189
+ if data.get("alg") != "sim-rsa-sha256":
190
+ raise ValueError
191
+ return int(data["n"], 16), int(data["e"])
192
+ except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
193
+ msg = "Invalid public key"
194
+ raise ValueError(msg) from exc
195
+
196
+
197
+ def _digest_int(payload: bytes, modulus: int) -> int:
198
+ return int.from_bytes(hashlib.sha256(payload).digest(), "big") % modulus
@@ -29,10 +29,17 @@ class PrepaidCredits:
29
29
  receipt = await pay.pay(AgentId("a2"), Money(amount=50), PaymentRef("p1"))
30
30
  """
31
31
 
32
- def __init__(self, agent_id: AgentId, initial_balance: int = 1000) -> None:
32
+ def __init__(
33
+ self,
34
+ agent_id: AgentId,
35
+ initial_balance: int = 1000,
36
+ balances: dict[AgentId, int] | None = None,
37
+ payments: dict[PaymentRef, Receipt] | None = None,
38
+ ) -> None:
33
39
  self._agent_id = agent_id
34
- self._balances: dict[AgentId, int] = {agent_id: initial_balance}
35
- self._payments: dict[PaymentRef, Receipt] = {}
40
+ self._balances = balances if balances is not None else {}
41
+ self._balances.setdefault(agent_id, initial_balance)
42
+ self._payments = payments if payments is not None else {}
36
43
 
37
44
  def balance(self, agent: AgentId) -> int:
38
45
  """Check an agent's balance.
@@ -59,6 +66,13 @@ class PrepaidCredits:
59
66
 
60
67
  receipt = await pay.pay(AgentId("a2"), Money(amount=50), PaymentRef("p1"))
61
68
  """
69
+ if amount.amount <= 0:
70
+ msg = f"Payment amount must be positive: {amount.amount}"
71
+ raise ValueError(msg)
72
+ if ref in self._payments:
73
+ msg = f"Duplicate payment reference: {ref}"
74
+ raise ValueError(msg)
75
+
62
76
  payer_balance = self._balances.get(self._agent_id, 0)
63
77
  if payer_balance < amount.amount:
64
78
  msg = f"Insufficient balance: {payer_balance} < {amount.amount}"
@@ -94,6 +108,14 @@ class PrepaidCredits:
94
108
  msg = f"Payment not found: {ref}"
95
109
  raise ValueError(msg)
96
110
 
97
- self._balances[receipt.payee] = self._balances.get(receipt.payee, 0) - receipt.amount.amount
111
+ payee_balance = self._balances.get(receipt.payee, 0)
112
+ if payee_balance < receipt.amount.amount:
113
+ msg = (
114
+ f"Insufficient balance for refund: {receipt.payee} has "
115
+ f"{payee_balance}, needs {receipt.amount.amount}"
116
+ )
117
+ raise ValueError(msg)
118
+
119
+ self._balances[receipt.payee] = payee_balance - receipt.amount.amount
98
120
  self._balances[receipt.payer] = self._balances.get(receipt.payer, 0) + receipt.amount.amount
99
121
  del self._payments[ref]
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  [project]
3
3
  name = "nest-plugins-reference"
4
- version = "0.1.0"
4
+ version = "0.1.1"
5
5
  description = "NEST reference plugins: default implementations for all 12 layers"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.12"
@@ -6,4 +6,4 @@ def test_nest_plugins_reference_imports() -> None:
6
6
  """Importing nest_plugins_reference should succeed and expose a version string."""
7
7
  import nest_plugins_reference
8
8
 
9
- assert nest_plugins_reference.__version__ == "0.1.0"
9
+ assert nest_plugins_reference.__version__ == "0.1.1"
@@ -16,6 +16,7 @@ from nest_core.types import (
16
16
  PaymentRef,
17
17
  PaymentStatus,
18
18
  Query,
19
+ Receipt,
19
20
  ServiceRef,
20
21
  Statement,
21
22
  Task,
@@ -122,6 +123,23 @@ class TestDidKeyIdentity:
122
123
  sig = ident.sign(b"payload")
123
124
  assert not ident.verify(b"wrong", sig, AgentId("a1"))
124
125
 
126
+ def test_verify_peer_with_public_key_only(self) -> None:
127
+ from nest_plugins_reference.identity.did_key import DidKeyIdentity
128
+
129
+ sender = DidKeyIdentity(AgentId("a1"), seed=b"seed")
130
+ verifier = DidKeyIdentity(AgentId("a2"), seed=b"seed")
131
+ verifier.register_peer(AgentId("a1"), sender.public_key)
132
+
133
+ sig = sender.sign(b"payload")
134
+ assert verifier.verify(b"payload", sig, AgentId("a1"))
135
+
136
+ def test_register_peer_rejects_private_key(self) -> None:
137
+ from nest_plugins_reference.identity.did_key import DidKeyIdentity
138
+
139
+ ident = DidKeyIdentity(AgentId("a1"), seed=b"seed")
140
+ with pytest.raises(ValueError, match="public keys only"):
141
+ ident.register_peer(AgentId("a2"), ident.public_key, private_key=b"secret")
142
+
125
143
  @pytest.mark.asyncio
126
144
  async def test_resolve(self) -> None:
127
145
  from nest_plugins_reference.identity.did_key import DidKeyIdentity
@@ -302,6 +320,29 @@ class TestPrepaidCredits:
302
320
  q = await pay.quote(ServiceRef("svc"))
303
321
  assert q.price.amount == 10
304
322
 
323
+ @pytest.mark.asyncio
324
+ async def test_shared_ledger_debits_calling_agent(self) -> None:
325
+ from nest_plugins_reference.payments.prepaid_credits import PrepaidCredits
326
+
327
+ balances = {AgentId("buyer"): 100, AgentId("seller"): 0}
328
+ payments: dict[PaymentRef, Receipt] = {}
329
+ buyer = PrepaidCredits(AgentId("buyer"), balances=balances, payments=payments)
330
+ seller = PrepaidCredits(AgentId("seller"), balances=balances, payments=payments)
331
+
332
+ await buyer.pay(AgentId("seller"), Money(amount=40), PaymentRef("p1"))
333
+
334
+ assert buyer.balance(AgentId("buyer")) == 60
335
+ assert seller.balance(AgentId("seller")) == 40
336
+ assert await seller.verify_payment(PaymentRef("p1")) == PaymentStatus.CONFIRMED
337
+
338
+ @pytest.mark.asyncio
339
+ async def test_rejects_non_positive_payment(self) -> None:
340
+ from nest_plugins_reference.payments.prepaid_credits import PrepaidCredits
341
+
342
+ pay = PrepaidCredits(AgentId("a1"), initial_balance=100)
343
+ with pytest.raises(ValueError, match="positive"):
344
+ await pay.pay(AgentId("a2"), Money(amount=0), PaymentRef("p1"))
345
+
305
346
 
306
347
  # ---------------------------------------------------------------------------
307
348
  # 8. Coordination: contract_net
@@ -1,101 +0,0 @@
1
- # SPDX-License-Identifier: Apache-2.0
2
- """DID:key identity plugin — Ed25519 key-based identity.
3
-
4
- Uses hashlib for deterministic key derivation in simulation (no real crypto
5
- needed for testing). For real deployments, swap to a proper Ed25519 library.
6
-
7
- Example::
8
-
9
- identity = DidKeyIdentity(AgentId("a1"), seed=b"secret")
10
- sig = identity.sign(b"payload")
11
- ok = identity.verify(b"payload", sig, AgentId("a1"))
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import hashlib
17
- import hmac
18
-
19
- from nest_core.types import AgentId, AgentIdentity, Signature
20
-
21
-
22
- class DidKeyIdentity:
23
- """Ed25519-style identity using HMAC-SHA256 for simulation.
24
-
25
- Example::
26
-
27
- ident = DidKeyIdentity(AgentId("a1"), seed=b"seed")
28
- sig = ident.sign(b"hello")
29
- """
30
-
31
- def __init__(self, agent_id: AgentId, seed: bytes = b"") -> None:
32
- self._agent_id = agent_id
33
- self._seed = seed
34
- key_material = hashlib.sha256(seed + agent_id.encode()).digest()
35
- self._private_key = key_material
36
- self._public_key = hashlib.sha256(key_material).digest()
37
- self._known_keys: dict[AgentId, bytes] = {agent_id: self._public_key}
38
- self._private_keys: dict[AgentId, bytes] = {agent_id: self._private_key}
39
-
40
- def register_peer(
41
- self,
42
- agent_id: AgentId,
43
- public_key: bytes,
44
- private_key: bytes | None = None,
45
- ) -> None:
46
- """Register a peer's public key (and optionally private key) for verification.
47
-
48
- Example::
49
-
50
- ident.register_peer(AgentId("a2"), peer_pk)
51
- """
52
- self._known_keys[agent_id] = public_key
53
- if private_key is not None:
54
- self._private_keys[agent_id] = private_key
55
-
56
- @property
57
- def public_key(self) -> bytes:
58
- """This agent's public key.
59
-
60
- Example::
61
-
62
- pk = ident.public_key
63
- """
64
- return self._public_key
65
-
66
- def sign(self, payload: bytes) -> Signature:
67
- """Sign a payload with this agent's private key.
68
-
69
- Example::
70
-
71
- sig = ident.sign(b"data")
72
- """
73
- sig_bytes = hmac.new(self._private_key, payload, hashlib.sha256).digest()
74
- return Signature(signer=self._agent_id, value=sig_bytes, algorithm="hmac-sha256")
75
-
76
- def verify(self, payload: bytes, sig: Signature, agent: AgentId) -> bool:
77
- """Verify a signature from a given agent.
78
-
79
- Example::
80
-
81
- ok = ident.verify(b"data", sig, AgentId("a1"))
82
- """
83
- private_key = self._private_keys.get(agent)
84
- if private_key is None:
85
- return False
86
- expected = hmac.new(private_key, payload, hashlib.sha256).digest()
87
- return hmac.compare_digest(sig.value, expected)
88
-
89
- async def resolve(self, agent: AgentId) -> AgentIdentity:
90
- """Resolve an agent ID to its identity record.
91
-
92
- Example::
93
-
94
- info = await ident.resolve(AgentId("a1"))
95
- """
96
- pk = self._known_keys.get(agent, b"")
97
- return AgentIdentity(
98
- agent_id=agent,
99
- public_key=pk,
100
- method="did:key",
101
- )