mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
Implement unlock private squeak (#1910)
* Add method stub for unlock private squeak in controller class * Add methods to unlock private squeak in controller * Remove todo comments * Add test for create and decrypt private squeak in core module * Update handler method for make squeak to support recipient profile id * Add itest for make private squeak and unlock content as author
This commit is contained in:
parent
75d0b0634d
commit
20d651df8e
13 changed files with 198 additions and 21 deletions
|
|
@ -95,6 +95,29 @@ def contact_profile_id(admin_stub, random_name, public_key):
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def recipient_signing_profile_id(admin_stub, random_recipient_name):
|
||||
# Create a new signing profile
|
||||
profile_id = create_signing_profile(admin_stub, random_recipient_name)
|
||||
yield profile_id
|
||||
# Delete the profile
|
||||
delete_profile(admin_stub, profile_id)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def recipient_contact_profile_id(admin_stub, random_recipient_name, public_key):
|
||||
# Create a new contact profile
|
||||
contact_profile_id = create_contact_profile(
|
||||
admin_stub, random_recipient_name, public_key)
|
||||
yield contact_profile_id
|
||||
# Delete the profile
|
||||
admin_stub.DeleteSqueakProfile(
|
||||
squeak_admin_pb2.DeleteSqueakProfileRequest(
|
||||
profile_id=contact_profile_id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def saved_squeak_hash(admin_stub, signing_profile_id):
|
||||
# Create a new squeak using the new profile
|
||||
|
|
@ -138,6 +161,11 @@ def random_name():
|
|||
yield "random_name_{}".format(uuid.uuid1())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def random_recipient_name():
|
||||
yield "random_recipient_name_{}".format(uuid.uuid1())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def random_image():
|
||||
yield os.urandom(567)
|
||||
|
|
|
|||
|
|
@ -267,6 +267,32 @@ def test_make_reply_squeak(
|
|||
]
|
||||
|
||||
|
||||
def test_make_private_squeak(admin_stub, signing_profile_id, recipient_contact_profile_id):
|
||||
make_squeak_content = "This is a private squeak!"
|
||||
make_squeak_hash = make_squeak(
|
||||
admin_stub,
|
||||
signing_profile_id,
|
||||
make_squeak_content,
|
||||
recipient_profile_id=recipient_contact_profile_id,
|
||||
)
|
||||
assert len(make_squeak_hash) == 32 * 2
|
||||
|
||||
# Get the squeak display item
|
||||
get_squeak_display_entry = get_squeak_display(
|
||||
admin_stub, make_squeak_hash)
|
||||
assert get_squeak_display_entry.squeak_hash == make_squeak_hash
|
||||
assert (
|
||||
get_squeak_display_entry.content_str == "This is a private squeak!"
|
||||
)
|
||||
assert get_squeak_display_entry.is_author_known
|
||||
assert get_squeak_display_entry.HasField("author")
|
||||
assert len(
|
||||
get_squeak_display_entry.author.profile_image) > 0
|
||||
assert not get_squeak_display_entry.is_reply
|
||||
assert not bool(get_squeak_display_entry.reply_to)
|
||||
assert len(get_squeak_display_entry.secret_key_hex) == 32 * 2
|
||||
|
||||
|
||||
def test_make_signing_profile(admin_stub):
|
||||
# Create a new signing profile
|
||||
profile_name = "test_signing_profile_name"
|
||||
|
|
|
|||
|
|
@ -356,12 +356,14 @@ def get_default_peer_port(node_stub):
|
|||
return get_default_peer_port_response.port
|
||||
|
||||
|
||||
def make_squeak(node_stub, profile_id, squeak_content, reply_to_hash=None):
|
||||
def make_squeak(node_stub, profile_id, squeak_content, reply_to_hash=None, recipient_profile_id=None):
|
||||
make_squeak_response = node_stub.MakeSqueak(
|
||||
squeak_admin_pb2.MakeSqueakRequest(
|
||||
profile_id=profile_id,
|
||||
content=squeak_content,
|
||||
replyto=reply_to_hash,
|
||||
has_recipient=(recipient_profile_id is not None),
|
||||
recipient_profile_id=recipient_profile_id,
|
||||
)
|
||||
)
|
||||
return make_squeak_response.squeak_hash
|
||||
|
|
|
|||
|
|
@ -539,6 +539,12 @@ message MakeSqueakRequest {
|
|||
|
||||
/// The replyto hash
|
||||
string replyto = 3;
|
||||
|
||||
/// Has a recipient
|
||||
bool has_recipient = 4;
|
||||
|
||||
/// The profile id of the recipient if squeak is private
|
||||
int32 recipient_profile_id = 5;
|
||||
}
|
||||
|
||||
message MakeSqueakReply {
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ grpcio==1.39.0
|
|||
grpcio-tools==1.39.0
|
||||
importlib_resources==1.4.0
|
||||
pytest==6.2.5
|
||||
squeaklib==0.10.2
|
||||
squeaklib==0.10.3
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ python-bitcoinlib==0.11.0
|
|||
pyzmq==22.3.0
|
||||
requests==2.26.0
|
||||
SQLAlchemy==1.4.25
|
||||
squeaklib==0.10.2
|
||||
squeaklib==0.10.3
|
||||
typed-config==0.2.5
|
||||
|
|
|
|||
|
|
@ -292,12 +292,18 @@ class SqueakAdminServerHandler(object):
|
|||
replyto_hash_str = request.replyto
|
||||
replyto_hash = bytes.fromhex(
|
||||
replyto_hash_str) if replyto_hash_str else None
|
||||
has_recipient = request.has_recipient
|
||||
recipient_profile_id = request.recipient_profile_id if has_recipient else None
|
||||
logger.info("Handle make squeak profile with id: {}".format(profile_id))
|
||||
inserted_squeak_hash = self.squeak_controller.make_squeak(
|
||||
profile_id, content_str, replyto_hash
|
||||
profile_id,
|
||||
content_str,
|
||||
replyto_hash,
|
||||
recipient_profile_id,
|
||||
)
|
||||
inserted_squeak_hash_str = optional_squeak_hash_to_hex(
|
||||
inserted_squeak_hash)
|
||||
inserted_squeak_hash,
|
||||
)
|
||||
return squeak_admin_pb2.MakeSqueakReply(
|
||||
squeak_hash=inserted_squeak_hash_str,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class SqueakCore:
|
|||
signing_profile: SqueakProfile,
|
||||
content_str: str,
|
||||
replyto_hash: Optional[bytes] = None,
|
||||
recipient_profile: Optional[SqueakProfile] = None,
|
||||
) -> Tuple[CSqueak, bytes]:
|
||||
"""Create a new squeak.
|
||||
|
||||
|
|
@ -94,7 +95,8 @@ class SqueakCore:
|
|||
content_str,
|
||||
block_height,
|
||||
block_hash,
|
||||
replyto_hash,
|
||||
replyto_hash=replyto_hash,
|
||||
recipient_public_key=recipient_profile.public_key if recipient_profile else None,
|
||||
)
|
||||
return squeak, secret_key
|
||||
|
||||
|
|
@ -131,21 +133,38 @@ class SqueakCore:
|
|||
raise Exception("Block hash incorrect.")
|
||||
return block_info.block_header
|
||||
|
||||
def get_decrypted_content(self, squeak: CSqueak, secret_key: bytes) -> str:
|
||||
def get_decrypted_content(
|
||||
self,
|
||||
squeak: CSqueak,
|
||||
secret_key: bytes,
|
||||
author_profile: Optional[SqueakProfile] = None,
|
||||
recipient_profile: Optional[SqueakProfile] = None,
|
||||
) -> str:
|
||||
"""Checks if the secret key is valid for the given squeak and returns
|
||||
the decrypted content.
|
||||
|
||||
Args:
|
||||
squeak: The squeak to be validated.
|
||||
secret_key: The secret key.
|
||||
author_profile: The profile of the author.
|
||||
recipient_profile: The profile of the recipient.
|
||||
|
||||
Returns:
|
||||
bytes: the decrypted content
|
||||
str: the decrypted content as a string.
|
||||
|
||||
Raises:
|
||||
Exception: If the secret key is not valid.
|
||||
"""
|
||||
return get_decrypted_content(squeak, secret_key)
|
||||
if author_profile and author_profile.private_key is None:
|
||||
raise Exception("Author profile does not have private key.")
|
||||
if recipient_profile and recipient_profile.private_key is None:
|
||||
raise Exception("Recipient profile does not have private key.")
|
||||
return get_decrypted_content(
|
||||
squeak,
|
||||
secret_key,
|
||||
authorPrivKey=author_profile.private_key if author_profile else None,
|
||||
recipientPrivKey=recipient_profile.private_key if recipient_profile else None,
|
||||
)
|
||||
|
||||
def get_best_block_height(self) -> int:
|
||||
"""Get the current height of the latest block in the blockchain.
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ def make_squeak_with_block(
|
|||
block_height: The height of the latest block in the bitcoin blockchain.
|
||||
block_hahs: The hash of the latest block in the bitcoin blockchain.
|
||||
replyto_hash: The hash of the squeak to which this one is replying.
|
||||
recipient_public_key: The public key of the recipient of a private squeak.
|
||||
|
||||
Returns:
|
||||
Tuple[CSqueak, bytes]: the squeak that was created together
|
||||
|
|
@ -102,7 +103,12 @@ def check_squeak(squeak: CSqueak) -> None:
|
|||
|
||||
|
||||
# TODO: return bytes (encoded utf-8 content)
|
||||
def get_decrypted_content(squeak: CSqueak, secret_key: bytes) -> str:
|
||||
def get_decrypted_content(
|
||||
squeak: CSqueak,
|
||||
secret_key: bytes,
|
||||
authorPrivKey: Optional[SqueakPrivateKey] = None,
|
||||
recipientPrivKey: Optional[SqueakPrivateKey] = None,
|
||||
) -> str:
|
||||
"""Checks if the secret key is valid for the given squeak and returns
|
||||
the decrypted content.
|
||||
|
||||
|
|
@ -116,7 +122,11 @@ def get_decrypted_content(squeak: CSqueak, secret_key: bytes) -> str:
|
|||
Raises:
|
||||
Exception: If the secret key is not valid.
|
||||
"""
|
||||
return squeak.GetDecryptedContentStr(secret_key)
|
||||
return squeak.GetDecryptedContentStr(
|
||||
secret_key,
|
||||
authorPrivKey=authorPrivKey,
|
||||
recipientPrivKey=recipientPrivKey,
|
||||
)
|
||||
|
||||
|
||||
def get_payment_point_of_secret_key(secret_key: bytes) -> bytes:
|
||||
|
|
|
|||
|
|
@ -80,21 +80,40 @@ class SqueakController:
|
|||
self.node_settings = node_settings
|
||||
self.config = config
|
||||
|
||||
def make_squeak(self, profile_id: int, content_str: str, replyto_hash: Optional[bytes]) -> Optional[bytes]:
|
||||
def make_squeak(
|
||||
self,
|
||||
profile_id: int,
|
||||
content_str: str,
|
||||
replyto_hash: Optional[bytes],
|
||||
recipient_profile_id: Optional[int],
|
||||
) -> Optional[bytes]:
|
||||
squeak_profile = self.squeak_store.get_squeak_profile(profile_id)
|
||||
if squeak_profile is None:
|
||||
raise Exception("Profile with id {} not found.".format(
|
||||
profile_id,
|
||||
))
|
||||
if recipient_profile_id:
|
||||
recipient_profile = self.squeak_store.get_squeak_profile(
|
||||
recipient_profile_id)
|
||||
if recipient_profile is None:
|
||||
raise Exception("Recipient profile with id {} not found.".format(
|
||||
recipient_profile_id,
|
||||
))
|
||||
squeak, secret_key = self.squeak_core.make_squeak(
|
||||
squeak_profile,
|
||||
content_str,
|
||||
replyto_hash,
|
||||
recipient_profile=recipient_profile if recipient_profile_id else None,
|
||||
)
|
||||
inserted_squeak_hash = self.squeak_store.save_squeak(squeak)
|
||||
if inserted_squeak_hash is None:
|
||||
raise Exception("Failed to save squeak.")
|
||||
self.squeak_store.save_secret_key(inserted_squeak_hash, secret_key)
|
||||
if squeak.is_private_message:
|
||||
self.squeak_store.unlock_squeak(
|
||||
inserted_squeak_hash,
|
||||
author_profile_id=profile_id,
|
||||
)
|
||||
return inserted_squeak_hash
|
||||
|
||||
def pay_offer(self, received_offer_id: int) -> int:
|
||||
|
|
@ -117,6 +136,18 @@ class SqueakController:
|
|||
)
|
||||
return sent_payment_id
|
||||
|
||||
def unlock_private_squeak_as_recipient(self, squeak_hash: bytes, recipient_profile_id: int):
|
||||
self.squeak_store.unlock_squeak(
|
||||
squeak_hash,
|
||||
recipient_profile_id=recipient_profile_id,
|
||||
)
|
||||
|
||||
def unlock_private_squeak_as_author(self, squeak_hash: bytes, author_profile_id: int):
|
||||
self.squeak_store.unlock_squeak(
|
||||
squeak_hash,
|
||||
author_profile_id=author_profile_id,
|
||||
)
|
||||
|
||||
def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]:
|
||||
return self.squeak_store.get_squeak(squeak_hash)
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,12 @@ class SqueakStore:
|
|||
if not squeak.is_private_message:
|
||||
self.unlock_squeak(squeak_hash)
|
||||
|
||||
def unlock_squeak(self, squeak_hash: bytes, recipient_profile_id: Optional[int] = None):
|
||||
def unlock_squeak(
|
||||
self,
|
||||
squeak_hash: bytes,
|
||||
author_profile_id: Optional[int] = None,
|
||||
recipient_profile_id: Optional[int] = None,
|
||||
):
|
||||
squeak = self.squeak_db.get_squeak(squeak_hash)
|
||||
secret_key = self.squeak_db.get_squeak_secret_key(squeak_hash)
|
||||
if squeak is None:
|
||||
|
|
@ -138,17 +143,20 @@ class SqueakStore:
|
|||
recipient_profile_id)
|
||||
if recipient_profile is None:
|
||||
raise Exception("Recipient profile does not exist.")
|
||||
recipient_private_key = recipient_profile.private_key
|
||||
if recipient_private_key is None:
|
||||
raise Exception("Recipient profile must own the private key.")
|
||||
# TODO: remove this log line later.
|
||||
logger.info("Use private key here: {}".format(
|
||||
recipient_private_key,
|
||||
))
|
||||
decrypted_content = self.squeak_core.get_decrypted_content(
|
||||
squeak,
|
||||
secret_key,
|
||||
# TODO: use recipient private key here.
|
||||
recipient_profile=recipient_profile,
|
||||
)
|
||||
elif author_profile_id:
|
||||
author_profile = self.squeak_db.get_profile(
|
||||
author_profile_id)
|
||||
if author_profile is None:
|
||||
raise Exception("Author profile does not exist.")
|
||||
decrypted_content = self.squeak_core.get_decrypted_content(
|
||||
squeak,
|
||||
secret_key,
|
||||
author_profile=author_profile,
|
||||
)
|
||||
else:
|
||||
decrypted_content = self.squeak_core.get_decrypted_content(
|
||||
|
|
|
|||
|
|
@ -241,6 +241,11 @@ def recipient_contact_profile_name():
|
|||
yield "recipient_contact_profile_name"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def recipient_signing_profile_name():
|
||||
yield "recipient_signing_profile_name"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def signing_profile(signing_profile_name, private_key):
|
||||
yield gen_signing_profile(
|
||||
|
|
@ -265,6 +270,14 @@ def recipient_contact_profile(recipient_contact_profile_name, recipient_public_k
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def recipient_signing_profile(recipient_signing_profile_name, recipient_private_key):
|
||||
yield gen_signing_profile(
|
||||
recipient_signing_profile_name,
|
||||
recipient_private_key,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def squeak_entry_locked(
|
||||
squeak,
|
||||
|
|
|
|||
|
|
@ -316,6 +316,34 @@ def test_make_squeak_with_contact_profile(
|
|||
assert "Can't make squeak with a contact profile." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_make_private_squeak(
|
||||
squeak_core,
|
||||
signing_profile,
|
||||
squeak_content,
|
||||
block_header,
|
||||
recipient_signing_profile,
|
||||
recipient_contact_profile,
|
||||
):
|
||||
created_squeak, created_secret_key = squeak_core.make_squeak(
|
||||
signing_profile,
|
||||
squeak_content,
|
||||
recipient_profile=recipient_contact_profile,
|
||||
)
|
||||
recipient_decrypted_content = squeak_core.get_decrypted_content(
|
||||
created_squeak,
|
||||
created_secret_key,
|
||||
recipient_profile=recipient_signing_profile,
|
||||
)
|
||||
author_decrypted_content = squeak_core.get_decrypted_content(
|
||||
created_squeak,
|
||||
created_secret_key,
|
||||
author_profile=signing_profile,
|
||||
)
|
||||
|
||||
assert recipient_decrypted_content == squeak_content
|
||||
assert author_decrypted_content == squeak_content
|
||||
|
||||
|
||||
def test_get_block_header(
|
||||
squeak_core,
|
||||
squeak,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue