Skip to content

Commit fdfd3e2

Browse files
Added extended signing key support for cip8
1 parent 0a95536 commit fdfd3e2

File tree

2 files changed

+84
-5
lines changed

2 files changed

+84
-5
lines changed

pycardano/cip/cip8.py

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

3+
from cbor2 import CBORTag, dumps
4+
35
from cose.algorithms import EdDSA
46
from cose.headers import KID, Algorithm
57
from cose.keys import CoseKey
@@ -10,9 +12,12 @@
1012
from cose.messages import CoseMessage, Sign1Message
1113

1214
from pycardano.address import Address
15+
from pycardano.crypto import BIP32ED25519PublicKey
1316
from pycardano.key import (
1417
PaymentVerificationKey,
1518
SigningKey,
19+
ExtendedSigningKey,
20+
ExtendedVerificationKey,
1621
StakeExtendedSigningKey,
1722
StakeSigningKey,
1823
StakeVerificationKey,
@@ -25,7 +30,7 @@
2530

2631
def sign(
2732
message: str,
28-
signing_key: SigningKey,
33+
signing_key: Union[ExtendedSigningKey, SigningKey],
2934
attach_cose_key: bool = False,
3035
network: Network = Network.MAINNET,
3136
) -> Union[str, dict]:
@@ -45,7 +50,9 @@ def sign(
4550
"""
4651

4752
# derive the verification key
48-
verification_key = VerificationKey.from_signing_key(signing_key)
53+
verification_key = signing_key.to_verification_key()
54+
if isinstance(verification_key, ExtendedVerificationKey):
55+
verification_key = verification_key.to_non_extended()
4956

5057
if isinstance(signing_key, StakeSigningKey) or isinstance(
5158
signing_key, StakeExtendedSigningKey
@@ -85,7 +92,20 @@ def sign(
8592

8693
msg.key = cose_key # attach the key to the message
8794

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

90110
# turn the enocded message into a hex string and remove the first byte
91111
# which is always "d2"
@@ -108,7 +128,9 @@ def sign(
108128

109129

110130
def verify(
111-
signed_message: Union[str, dict], attach_cose_key: Optional[bool] = None
131+
signed_message: Union[str, dict],
132+
attach_cose_key: Optional[bool] = None,
133+
extended=False,
112134
) -> dict:
113135
"""Verify the signature of a COSESign1 message and decode its contents following CIP-0008.
114136
Supports messages signed by browser wallets or `Message.sign()`.
@@ -175,7 +197,16 @@ def verify(
175197
# attach the key to the decoded message
176198
decoded_message.key = cose_key
177199

178-
signature_verified = decoded_message.verify_signature()
200+
if extended:
201+
vk = BIP32ED25519PublicKey(
202+
public_key=verification_key[:32], chain_code=verification_key[32:]
203+
)
204+
vk.verify(
205+
signature=decoded_message.signature, message=decoded_message._sig_structure
206+
)
207+
signature_verified = True
208+
else:
209+
signature_verified = decoded_message.verify_signature()
179210

180211
message = decoded_message.payload.decode("utf-8")
181212

test/pycardano/test_cip8.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
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+
14+
EXTENDED_SK = ExtendedSigningKey.from_json(
15+
"""{
16+
"type": "PaymentExtendedSigningKeyShelley_ed25519_bip32",
17+
"description": "Payment Signing Key",
18+
"cborHex": "5880e8428867ab9cc9304379a3ce0c238a592bd6d2349d2ebaf8a6ed2c6d2974a15ad59c74b6d8fa3edd032c6261a73998b7deafe983b6eeaff8b6fb3fab06bdf8019b693a62bce7a3cad1b9c02d22125767201c65db27484bb67d3cee7df7288d62c099ac0ce4a215355b149fd3114a2a7ef0438f01f8872c4487a61b469e26aae4"
19+
}"""
20+
)
21+
22+
EXTENDED_VK = ExtendedVerificationKey.from_json(
23+
"""{
24+
"type": "PaymentExtendedVerificationKeyShelley_ed25519_bip32",
25+
"description": "Payment Verification Key",
26+
"cborHex": "58409b693a62bce7a3cad1b9c02d22125767201c65db27484bb67d3cee7df7288d62c099ac0ce4a215355b149fd3114a2a7ef0438f01f8872c4487a61b469e26aae4"
27+
}"""
28+
)
29+
30+
1031
SK = PaymentSigningKey.from_json(
1132
"""{
1233
"type": "GenesisUTxOSigningKey_ed25519",
@@ -138,6 +159,33 @@ def test_sign_and_verify():
138159
assert verification["signing_address"].payment_part == VK.hash()
139160

140161

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

0 commit comments

Comments
 (0)