Skip to content

Commit cd975db

Browse files
Added extended signing key support for cip8 (#273)
* Added extended signing key support for cip8 * Fixed unused imports, flake8 checks pass. * Fixed mypy error for overloaded variable * Remove extraneous parameter for verify
1 parent a6f76ea commit cd975db

File tree

2 files changed

+81
-6
lines changed

2 files changed

+81
-6
lines changed

pycardano/cip/cip8.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Optional, Union
22

3+
from cbor2 import CBORTag, dumps
34
from cose.algorithms import EdDSA
45
from cose.headers import KID, Algorithm
56
from cose.keys import CoseKey
@@ -10,13 +11,15 @@
1011
from cose.messages import CoseMessage, Sign1Message
1112

1213
from pycardano.address import Address
14+
from pycardano.crypto import BIP32ED25519PublicKey
1315
from pycardano.key import (
16+
ExtendedSigningKey,
17+
ExtendedVerificationKey,
1418
PaymentVerificationKey,
1519
SigningKey,
1620
StakeExtendedSigningKey,
1721
StakeSigningKey,
1822
StakeVerificationKey,
19-
VerificationKey,
2023
)
2124
from pycardano.network import Network
2225

@@ -25,7 +28,7 @@
2528

2629
def sign(
2730
message: str,
28-
signing_key: SigningKey,
31+
signing_key: Union[ExtendedSigningKey, SigningKey],
2932
attach_cose_key: bool = False,
3033
network: Network = Network.MAINNET,
3134
) -> Union[str, dict]:
@@ -45,7 +48,9 @@ def sign(
4548
"""
4649

4750
# derive the verification key
48-
verification_key = VerificationKey.from_signing_key(signing_key)
51+
verification_key = signing_key.to_verification_key()
52+
if isinstance(verification_key, ExtendedVerificationKey):
53+
verification_key = verification_key.to_non_extended()
4954

5055
if isinstance(signing_key, StakeSigningKey) or isinstance(
5156
signing_key, StakeExtendedSigningKey
@@ -85,7 +90,20 @@ def sign(
8590

8691
msg.key = cose_key # attach the key to the message
8792

88-
encoded = msg.encode()
93+
if isinstance(signing_key, ExtendedSigningKey):
94+
_message = [
95+
msg.phdr_encoded,
96+
msg.uhdr_encoded,
97+
msg.payload,
98+
signing_key.sign(msg._sig_structure),
99+
]
100+
101+
encoded = dumps(
102+
CBORTag(msg.cbor_tag, _message), default=msg._custom_cbor_encoder
103+
)
104+
105+
else:
106+
encoded = msg.encode()
89107

90108
# turn the enocded message into a hex string and remove the first byte
91109
# which is always "d2"
@@ -108,7 +126,8 @@ def sign(
108126

109127

110128
def verify(
111-
signed_message: Union[str, dict], attach_cose_key: Optional[bool] = None
129+
signed_message: Union[str, dict],
130+
attach_cose_key: Optional[bool] = None,
112131
) -> dict:
113132
"""Verify the signature of a COSESign1 message and decode its contents following CIP-0008.
114133
Supports messages signed by browser wallets or `Message.sign()`.
@@ -175,7 +194,16 @@ def verify(
175194
# attach the key to the decoded message
176195
decoded_message.key = cose_key
177196

178-
signature_verified = decoded_message.verify_signature()
197+
if len(verification_key) > 32:
198+
vk = BIP32ED25519PublicKey(
199+
public_key=verification_key[:32], chain_code=verification_key[32:]
200+
)
201+
vk.verify(
202+
signature=decoded_message.signature, message=decoded_message._sig_structure
203+
)
204+
signature_verified = True
205+
else:
206+
signature_verified = decoded_message.verify_signature()
179207

180208
message = decoded_message.payload.decode("utf-8")
181209

test/pycardano/test_cip8.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
from pycardano.cip.cip8 import sign, verify
2+
from pycardano.crypto.bip32 import BIP32ED25519PrivateKey, HDWallet
23
from pycardano.key import (
4+
ExtendedSigningKey,
5+
ExtendedVerificationKey,
36
PaymentSigningKey,
47
PaymentVerificationKey,
58
StakeSigningKey,
69
StakeVerificationKey,
710
)
811
from pycardano.network import Network
912

13+
EXTENDED_SK = ExtendedSigningKey.from_json(
14+
"""{
15+
"type": "PaymentExtendedSigningKeyShelley_ed25519_bip32",
16+
"description": "Payment Signing Key",
17+
"cborHex": "5880e8428867ab9cc9304379a3ce0c238a592bd6d2349d2ebaf8a6ed2c6d2974a15ad59c74b6d8fa3edd032c6261a73998b7deafe983b6eeaff8b6fb3fab06bdf8019b693a62bce7a3cad1b9c02d22125767201c65db27484bb67d3cee7df7288d62c099ac0ce4a215355b149fd3114a2a7ef0438f01f8872c4487a61b469e26aae4"
18+
}"""
19+
)
20+
21+
EXTENDED_VK = ExtendedVerificationKey.from_json(
22+
"""{
23+
"type": "PaymentExtendedVerificationKeyShelley_ed25519_bip32",
24+
"description": "Payment Verification Key",
25+
"cborHex": "58409b693a62bce7a3cad1b9c02d22125767201c65db27484bb67d3cee7df7288d62c099ac0ce4a215355b149fd3114a2a7ef0438f01f8872c4487a61b469e26aae4"
26+
}"""
27+
)
28+
29+
1030
SK = PaymentSigningKey.from_json(
1131
"""{
1232
"type": "GenesisUTxOSigningKey_ed25519",
@@ -138,6 +158,33 @@ def test_sign_and_verify():
138158
assert verification["signing_address"].payment_part == VK.hash()
139159

140160

161+
def test_extended_sign_and_verify():
162+
# try first with no cose key attached
163+
164+
message = "Pycardano is cool."
165+
signed_message = sign(
166+
message,
167+
signing_key=EXTENDED_SK,
168+
attach_cose_key=False,
169+
network=Network.TESTNET,
170+
)
171+
172+
verification = verify(signed_message)
173+
assert verification["verified"]
174+
assert verification["message"] == "Pycardano is cool."
175+
assert verification["signing_address"].payment_part == EXTENDED_VK.hash()
176+
177+
# try again but attach cose key
178+
signed_message = sign(
179+
message, signing_key=EXTENDED_SK, attach_cose_key=True, network=Network.TESTNET
180+
)
181+
182+
verification = verify(signed_message)
183+
assert verification["verified"]
184+
assert verification["message"] == "Pycardano is cool."
185+
assert verification["signing_address"].payment_part == EXTENDED_VK.hash()
186+
187+
141188
def test_sign_and_verify_stake():
142189
# try first with no cose key attached
143190
message = "Pycardano is cool."

0 commit comments

Comments
 (0)