From fdfd3e20b7ee7972d3e1ad02705d0d1e4c86e89f Mon Sep 17 00:00:00 2001 From: Elder Millenial Date: Thu, 5 Oct 2023 21:23:19 -0400 Subject: [PATCH 1/4] Added extended signing key support for cip8 --- pycardano/cip/cip8.py | 41 +++++++++++++++++++++++++++---- test/pycardano/test_cip8.py | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/pycardano/cip/cip8.py b/pycardano/cip/cip8.py index 67536d35..691a2c3c 100644 --- a/pycardano/cip/cip8.py +++ b/pycardano/cip/cip8.py @@ -1,5 +1,7 @@ from typing import Optional, Union +from cbor2 import CBORTag, dumps + from cose.algorithms import EdDSA from cose.headers import KID, Algorithm from cose.keys import CoseKey @@ -10,9 +12,12 @@ from cose.messages import CoseMessage, Sign1Message from pycardano.address import Address +from pycardano.crypto import BIP32ED25519PublicKey from pycardano.key import ( PaymentVerificationKey, SigningKey, + ExtendedSigningKey, + ExtendedVerificationKey, StakeExtendedSigningKey, StakeSigningKey, StakeVerificationKey, @@ -25,7 +30,7 @@ def sign( message: str, - signing_key: SigningKey, + signing_key: Union[ExtendedSigningKey, SigningKey], attach_cose_key: bool = False, network: Network = Network.MAINNET, ) -> Union[str, dict]: @@ -45,7 +50,9 @@ def sign( """ # derive the verification key - verification_key = VerificationKey.from_signing_key(signing_key) + verification_key = signing_key.to_verification_key() + if isinstance(verification_key, ExtendedVerificationKey): + verification_key = verification_key.to_non_extended() if isinstance(signing_key, StakeSigningKey) or isinstance( signing_key, StakeExtendedSigningKey @@ -85,7 +92,20 @@ def sign( msg.key = cose_key # attach the key to the message - encoded = msg.encode() + if isinstance(signing_key, ExtendedSigningKey): + message = [ + msg.phdr_encoded, + msg.uhdr_encoded, + msg.payload, + signing_key.sign(msg._sig_structure), + ] + + encoded = dumps( + CBORTag(msg.cbor_tag, message), default=msg._custom_cbor_encoder + ) + + else: + encoded = msg.encode() # turn the enocded message into a hex string and remove the first byte # which is always "d2" @@ -108,7 +128,9 @@ def sign( def verify( - signed_message: Union[str, dict], attach_cose_key: Optional[bool] = None + signed_message: Union[str, dict], + attach_cose_key: Optional[bool] = None, + extended=False, ) -> dict: """Verify the signature of a COSESign1 message and decode its contents following CIP-0008. Supports messages signed by browser wallets or `Message.sign()`. @@ -175,7 +197,16 @@ def verify( # attach the key to the decoded message decoded_message.key = cose_key - signature_verified = decoded_message.verify_signature() + if extended: + vk = BIP32ED25519PublicKey( + public_key=verification_key[:32], chain_code=verification_key[32:] + ) + vk.verify( + signature=decoded_message.signature, message=decoded_message._sig_structure + ) + signature_verified = True + else: + signature_verified = decoded_message.verify_signature() message = decoded_message.payload.decode("utf-8") diff --git a/test/pycardano/test_cip8.py b/test/pycardano/test_cip8.py index 035d7b10..631715bb 100644 --- a/test/pycardano/test_cip8.py +++ b/test/pycardano/test_cip8.py @@ -1,5 +1,8 @@ from pycardano.cip.cip8 import sign, verify +from pycardano.crypto.bip32 import BIP32ED25519PrivateKey, HDWallet from pycardano.key import ( + ExtendedSigningKey, + ExtendedVerificationKey, PaymentSigningKey, PaymentVerificationKey, StakeSigningKey, @@ -7,6 +10,24 @@ ) from pycardano.network import Network + +EXTENDED_SK = ExtendedSigningKey.from_json( + """{ + "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", + "description": "Payment Signing Key", + "cborHex": "5880e8428867ab9cc9304379a3ce0c238a592bd6d2349d2ebaf8a6ed2c6d2974a15ad59c74b6d8fa3edd032c6261a73998b7deafe983b6eeaff8b6fb3fab06bdf8019b693a62bce7a3cad1b9c02d22125767201c65db27484bb67d3cee7df7288d62c099ac0ce4a215355b149fd3114a2a7ef0438f01f8872c4487a61b469e26aae4" + }""" +) + +EXTENDED_VK = ExtendedVerificationKey.from_json( + """{ + "type": "PaymentExtendedVerificationKeyShelley_ed25519_bip32", + "description": "Payment Verification Key", + "cborHex": "58409b693a62bce7a3cad1b9c02d22125767201c65db27484bb67d3cee7df7288d62c099ac0ce4a215355b149fd3114a2a7ef0438f01f8872c4487a61b469e26aae4" + }""" +) + + SK = PaymentSigningKey.from_json( """{ "type": "GenesisUTxOSigningKey_ed25519", @@ -138,6 +159,33 @@ def test_sign_and_verify(): assert verification["signing_address"].payment_part == VK.hash() +def test_extended_sign_and_verify(): + # try first with no cose key attached + + message = "Pycardano is cool." + signed_message = sign( + message, + signing_key=EXTENDED_SK, + attach_cose_key=False, + network=Network.TESTNET, + ) + + verification = verify(signed_message, extended=True) + assert verification["verified"] + assert verification["message"] == "Pycardano is cool." + assert verification["signing_address"].payment_part == EXTENDED_VK.hash() + + # try again but attach cose key + signed_message = sign( + message, signing_key=EXTENDED_SK, attach_cose_key=True, network=Network.TESTNET + ) + + verification = verify(signed_message) + assert verification["verified"] + assert verification["message"] == "Pycardano is cool." + assert verification["signing_address"].payment_part == EXTENDED_VK.hash() + + def test_sign_and_verify_stake(): # try first with no cose key attached message = "Pycardano is cool." From 01230ac1b99c6b1d7f87d891229376e198f108bb Mon Sep 17 00:00:00 2001 From: Elder Millenial Date: Fri, 6 Oct 2023 09:31:39 -0400 Subject: [PATCH 2/4] Fixed unused imports, flake8 checks pass. --- pycardano/cip/cip8.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pycardano/cip/cip8.py b/pycardano/cip/cip8.py index 691a2c3c..14508632 100644 --- a/pycardano/cip/cip8.py +++ b/pycardano/cip/cip8.py @@ -21,7 +21,6 @@ StakeExtendedSigningKey, StakeSigningKey, StakeVerificationKey, - VerificationKey, ) from pycardano.network import Network From 143e843e3c6386837aa346cc72f2504c89ce66db Mon Sep 17 00:00:00 2001 From: Elder Millenial Date: Wed, 11 Oct 2023 09:12:09 -0400 Subject: [PATCH 3/4] Fixed mypy error for overloaded variable --- pycardano/cip/cip8.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycardano/cip/cip8.py b/pycardano/cip/cip8.py index 14508632..68ff0305 100644 --- a/pycardano/cip/cip8.py +++ b/pycardano/cip/cip8.py @@ -92,7 +92,7 @@ def sign( msg.key = cose_key # attach the key to the message if isinstance(signing_key, ExtendedSigningKey): - message = [ + _message = [ msg.phdr_encoded, msg.uhdr_encoded, msg.payload, @@ -100,7 +100,7 @@ def sign( ] encoded = dumps( - CBORTag(msg.cbor_tag, message), default=msg._custom_cbor_encoder + CBORTag(msg.cbor_tag, _message), default=msg._custom_cbor_encoder ) else: From 049c6bf3ca21a11e1fe9c67142b8b0079326507c Mon Sep 17 00:00:00 2001 From: Elder Millenial Date: Tue, 17 Oct 2023 20:43:27 -0400 Subject: [PATCH 4/4] Remove extraneous parameter for verify --- pycardano/cip/cip8.py | 8 +++----- test/pycardano/test_cip8.py | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pycardano/cip/cip8.py b/pycardano/cip/cip8.py index 68ff0305..951473fb 100644 --- a/pycardano/cip/cip8.py +++ b/pycardano/cip/cip8.py @@ -1,7 +1,6 @@ from typing import Optional, Union from cbor2 import CBORTag, dumps - from cose.algorithms import EdDSA from cose.headers import KID, Algorithm from cose.keys import CoseKey @@ -14,10 +13,10 @@ from pycardano.address import Address from pycardano.crypto import BIP32ED25519PublicKey from pycardano.key import ( - PaymentVerificationKey, - SigningKey, ExtendedSigningKey, ExtendedVerificationKey, + PaymentVerificationKey, + SigningKey, StakeExtendedSigningKey, StakeSigningKey, StakeVerificationKey, @@ -129,7 +128,6 @@ def sign( def verify( signed_message: Union[str, dict], attach_cose_key: Optional[bool] = None, - extended=False, ) -> dict: """Verify the signature of a COSESign1 message and decode its contents following CIP-0008. Supports messages signed by browser wallets or `Message.sign()`. @@ -196,7 +194,7 @@ def verify( # attach the key to the decoded message decoded_message.key = cose_key - if extended: + if len(verification_key) > 32: vk = BIP32ED25519PublicKey( public_key=verification_key[:32], chain_code=verification_key[32:] ) diff --git a/test/pycardano/test_cip8.py b/test/pycardano/test_cip8.py index 631715bb..7ffdedc4 100644 --- a/test/pycardano/test_cip8.py +++ b/test/pycardano/test_cip8.py @@ -10,7 +10,6 @@ ) from pycardano.network import Network - EXTENDED_SK = ExtendedSigningKey.from_json( """{ "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", @@ -170,7 +169,7 @@ def test_extended_sign_and_verify(): network=Network.TESTNET, ) - verification = verify(signed_message, extended=True) + verification = verify(signed_message) assert verification["verified"] assert verification["message"] == "Pycardano is cool." assert verification["signing_address"].payment_part == EXTENDED_VK.hash()