1
+ import datetime as dt
1
2
import json
2
3
import logging
4
+ from enum import Enum
3
5
4
6
from aleph_pytezos .crypto .key import Key
5
7
10
12
LOGGER = logging .getLogger (__name__ )
11
13
CHAIN_NAME = "TEZOS"
12
14
15
+ # Default dApp URL for Micheline-style signatures
16
+ DEFAULT_DAPP_URL = "aleph.im"
17
+
18
+
19
+ class TezosSignatureType (str , Enum ):
20
+ RAW = "raw"
21
+ MICHELINE = "micheline"
22
+
23
+
24
+ def timestamp_to_iso_8601 (timestamp : float ) -> str :
25
+ """
26
+ Returns the timestamp formatted to ISO-8601, JS-style.
27
+
28
+ Compared to the regular `isoformat()`, this function only provides precision down
29
+ to milliseconds and prints a "Z" instead of +0000 for UTC.
30
+ This format is typically used by JavaScript applications, like our TS SDK.
31
+
32
+ Example: 2022-09-23T14:41:19.029Z
33
+
34
+ :param timestamp: The timestamp to format.
35
+ :return: The formatted timestamp.
36
+ """
37
+
38
+ return (
39
+ dt .datetime .utcfromtimestamp (timestamp ).isoformat (timespec = "milliseconds" ) + "Z"
40
+ )
41
+
42
+
43
+ def micheline_verification_buffer (
44
+ verification_buffer : bytes ,
45
+ timestamp : float ,
46
+ dapp_url : str ,
47
+ ) -> bytes :
48
+ """
49
+ Computes the verification buffer for Micheline-type signatures.
50
+
51
+ This verification buffer is used when signing data with a Tezos web wallet.
52
+ See https://tezostaquito.io/docs/signing/#generating-a-signature-with-beacon-sdk.
53
+
54
+ :param verification_buffer: The original (non-Tezos) verification buffer for the Aleph message.
55
+ :param timestamp: Timestamp of the message.
56
+ :param dapp_url: The URL of the dApp, for use as part of the verification buffer.
57
+ :return: The verification buffer used for the signature by the web wallet.
58
+ """
59
+
60
+ prefix = b"Tezos Signed Message:"
61
+ timestamp = timestamp_to_iso_8601 (timestamp ).encode ("utf-8" )
62
+
63
+ payload = b" " .join (
64
+ (prefix , dapp_url .encode ("utf-8" ), timestamp , verification_buffer )
65
+ )
66
+ hex_encoded_payload = payload .hex ()
67
+ payload_size = str (len (hex_encoded_payload )).encode ("utf-8" )
68
+
69
+ return b"\x05 " + b"\x01 \x00 " + payload_size + payload
70
+
71
+
72
+ def get_tezos_verification_buffer (
73
+ message : BasePendingMessage , signature_type : TezosSignatureType , dapp_url : str
74
+ ) -> bytes :
75
+ verification_buffer = get_verification_buffer (message )
76
+
77
+ if signature_type == TezosSignatureType .RAW :
78
+ return verification_buffer
79
+ elif signature_type == TezosSignatureType .MICHELINE :
80
+ return micheline_verification_buffer (
81
+ verification_buffer , message .time , dapp_url
82
+ )
83
+
84
+ raise ValueError (f"Unsupported signature type: { signature_type } " )
85
+
13
86
14
87
async def verify_signature (message : BasePendingMessage ) -> bool :
15
88
"""
16
89
Verifies the cryptographic signature of a message signed with a Tezos key.
17
90
"""
18
91
19
- verification_buffer = get_verification_buffer (message )
20
92
try :
21
93
signature_dict = json .loads (message .signature )
22
94
except json .JSONDecodeError :
@@ -30,6 +102,9 @@ async def verify_signature(message: BasePendingMessage) -> bool:
30
102
LOGGER .exception ("'%s' key missing from Tezos signature dictionary." , e .args [0 ])
31
103
return False
32
104
105
+ signature_type = TezosSignatureType (signature_dict .get ("signingType" , "raw" ))
106
+ dapp_url = signature_dict .get ("dAppUrl" , DEFAULT_DAPP_URL )
107
+
33
108
key = Key .from_encoded_key (public_key )
34
109
# Check that the sender ID is equal to the public key hash
35
110
public_key_hash = key .public_key_hash ()
@@ -41,6 +116,10 @@ async def verify_signature(message: BasePendingMessage) -> bool:
41
116
public_key_hash ,
42
117
)
43
118
119
+ verification_buffer = get_tezos_verification_buffer (
120
+ message , signature_type , dapp_url
121
+ )
122
+
44
123
# Check the signature
45
124
try :
46
125
key .verify (signature , verification_buffer )
0 commit comments