Implement DHT record encryption

This commit is contained in:
neequ57 2025-08-24 05:21:04 +00:00 committed by Christien Rioux
parent 848da0ae4e
commit 285d98a185
84 changed files with 2353 additions and 1077 deletions

View file

@ -11,7 +11,7 @@ from veilid.types import ValueSeqNum, VeilidJSONEncoder
##################################################################
BOGUS_KEY = veilid.RecordKey.from_value(
veilid.CryptoKind.CRYPTO_KIND_VLD0, veilid.BareRecordKey.from_bytes(b' '))
veilid.CryptoKind.CRYPTO_KIND_VLD0, veilid.BareRecordKey.from_parts(veilid.BareOpaqueRecordKey.from_bytes(b' '), None))
@pytest.mark.asyncio
@ -105,10 +105,11 @@ async def test_set_get_dht_value_with_owner(api_connection: veilid.VeilidAPI):
async with cs:
owner = await cs.generate_key_pair()
record_key = await rc.get_dht_record_key(veilid.DHTSchema.dflt(2), owner = owner.key())
rec = await rc.create_dht_record(kind, veilid.DHTSchema.dflt(2), owner=owner)
record_key = await rc.get_dht_record_key(veilid.DHTSchema.dflt(2), owner = owner.key(), encryption_key=rec.key.encryption_key())
assert rec.key == record_key
assert rec.owner == owner.key()
assert rec.owner_secret is not None

View file

@ -58,7 +58,7 @@ class RoutingContext(ABC):
@abstractmethod
async def get_dht_record_key(
self, schema: types.DHTSchema, owner: types.PublicKey) -> types.RecordKey:
self, schema: types.DHTSchema, owner: types.PublicKey, encryption_key: Optional[types.SharedSecret]) -> types.RecordKey:
pass
@abstractmethod

View file

@ -654,9 +654,11 @@ class _JsonRoutingContext(RoutingContext):
async def get_dht_record_key(
self, schema: DHTSchema, owner: PublicKey) -> RecordKey:
self, schema: DHTSchema, owner: PublicKey, encryption_key: Optional[SharedSecret]) -> RecordKey:
assert isinstance(schema, DHTSchema)
assert isinstance(owner, PublicKey)
if encryption_key is not None:
assert isinstance(encryption_key, SharedSecret)
return raise_api_result(
await self.api.send_ndjson_request(
@ -666,6 +668,7 @@ class _JsonRoutingContext(RoutingContext):
rc_op=RoutingContextOperation.GET_DHT_RECORD_KEY,
schema=schema,
owner=owner,
encryption_key=encryption_key,
)
)

View file

@ -1306,6 +1306,12 @@
{
"type": "object",
"properties": {
"encryption_key": {
"type": [
"string",
"null"
]
},
"owner": {
"type": "string"
},

View file

@ -8,6 +8,7 @@ from .types import (
Timestamp,
TimestampDuration,
PublicKey,
RecordKey,
ValueData,
ValueSubkey,
VeilidLogLevel,
@ -563,12 +564,12 @@ class VeilidRouteChange:
class VeilidValueChange:
key: PublicKey
key: RecordKey
subkeys: list[tuple[ValueSubkey, ValueSubkey]]
count: int
value: Optional[ValueData]
def __init__(self, key: PublicKey, subkeys: list[tuple[ValueSubkey, ValueSubkey]], count: int, value: Optional[ValueData]):
def __init__(self, key: RecordKey, subkeys: list[tuple[ValueSubkey, ValueSubkey]], count: int, value: Optional[ValueData]):
self.key = key
self.subkeys = subkeys
self.count = count
@ -578,7 +579,7 @@ class VeilidValueChange:
def from_json(cls, j: dict) -> Self:
"""JSON object hook"""
return cls(
PublicKey(j["key"]),
RecordKey(j["key"]),
[(p[0], p[1]) for p in j["subkeys"]],
j["count"],
None if j["value"] is None else ValueData.from_json(j["value"]),

View file

@ -124,9 +124,6 @@ class EncodedString(str):
assert isinstance(b, bytes)
return cls(urlsafe_b64encode_no_pad(b))
class BareRecordKey(EncodedString):
pass
class BarePublicKey(EncodedString):
pass
@ -145,7 +142,6 @@ class BareSignature(EncodedString):
class Nonce(EncodedString):
pass
class BareRouteId(EncodedString):
pass
@ -155,6 +151,27 @@ class BareNodeId(EncodedString):
class BareMemberId(EncodedString):
pass
class BareOpaqueRecordKey(EncodedString):
pass
class BareRecordKey(str):
@classmethod
def from_parts(cls, key: BareOpaqueRecordKey, encryption_key: Optional[BareSharedSecret]) -> Self:
assert isinstance(key, BareOpaqueRecordKey)
if encryption_key is not None:
assert isinstance(encryption_key, BareSharedSecret)
return cls(f"{key}:{encryption_key}")
return cls(f"{key}")
def key(self) -> BareOpaqueRecordKey:
parts = self.split(":", 1)
return BareOpaqueRecordKey(parts[0])
def encryption_key(self) -> Optional[BareSharedSecret]:
parts = self.split(":", 1)
if len(parts) == 2:
return BareSharedSecret(self.split(":", 1)[1])
return None
class BareKeyPair(str):
@classmethod
@ -180,6 +197,15 @@ class CryptoTyped(str):
raise ValueError("Not CryptoTyped")
return self[5:]
class SharedSecret(CryptoTyped):
@classmethod
def from_value(cls, kind: CryptoKind, value: BareSharedSecret) -> Self:
assert isinstance(kind, CryptoKind)
assert isinstance(value, BareSharedSecret)
return cls(f"{kind}:{value}")
def value(self) -> BareSharedSecret:
return BareSharedSecret(self._value())
class RecordKey(CryptoTyped):
@classmethod
@ -191,15 +217,9 @@ class RecordKey(CryptoTyped):
def value(self) -> BareRecordKey:
return BareRecordKey(self._value())
class SharedSecret(CryptoTyped):
@classmethod
def from_value(cls, kind: CryptoKind, value: BareSharedSecret) -> Self:
assert isinstance(kind, CryptoKind)
assert isinstance(value, BareSharedSecret)
return cls(f"{kind}:{value}")
def value(self) -> BareSharedSecret:
return BareSharedSecret(self._value())
def encryption_key(self) -> Optional[SharedSecret]:
ek = self.value().encryption_key()
return None if ek == None else SharedSecret.from_value(self.kind(), ek)
class HashDigest(CryptoTyped):
@classmethod