Skip to content

Commit

Permalink
Added extended signing key support for cip8 (#273)
Browse files Browse the repository at this point in the history
* Added extended signing key support for cip8

* Fixed unused imports, flake8 checks pass.

* Fixed mypy error for overloaded variable

* Remove extraneous parameter for verify
  • Loading branch information
theeldermillenial authored Oct 18, 2023
1 parent a6f76ea commit cd975db
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 6 deletions.
40 changes: 34 additions & 6 deletions pycardano/cip/cip8.py
Original file line number Diff line number Diff line change
@@ -1,5 +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
Expand All @@ -10,13 +11,15 @@
from cose.messages import CoseMessage, Sign1Message

from pycardano.address import Address
from pycardano.crypto import BIP32ED25519PublicKey
from pycardano.key import (
ExtendedSigningKey,
ExtendedVerificationKey,
PaymentVerificationKey,
SigningKey,
StakeExtendedSigningKey,
StakeSigningKey,
StakeVerificationKey,
VerificationKey,
)
from pycardano.network import Network

Expand All @@ -25,7 +28,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]:
Expand All @@ -45,7 +48,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
Expand Down Expand Up @@ -85,7 +90,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"
Expand All @@ -108,7 +126,8 @@ 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,
) -> dict:
"""Verify the signature of a COSESign1 message and decode its contents following CIP-0008.
Supports messages signed by browser wallets or `Message.sign()`.
Expand Down Expand Up @@ -175,7 +194,16 @@ def verify(
# attach the key to the decoded message
decoded_message.key = cose_key

signature_verified = decoded_message.verify_signature()
if len(verification_key) > 32:
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")

Expand Down
47 changes: 47 additions & 0 deletions test/pycardano/test_cip8.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
from pycardano.cip.cip8 import sign, verify
from pycardano.crypto.bip32 import BIP32ED25519PrivateKey, HDWallet
from pycardano.key import (
ExtendedSigningKey,
ExtendedVerificationKey,
PaymentSigningKey,
PaymentVerificationKey,
StakeSigningKey,
StakeVerificationKey,
)
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",
Expand Down Expand Up @@ -138,6 +158,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)
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."
Expand Down

0 comments on commit cd975db

Please sign in to comment.