-
Notifications
You must be signed in to change notification settings - Fork 81
/
main.py
142 lines (115 loc) · 4.58 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""Interface for cryptographic key pairs for use with the XRP Ledger."""
from secrets import token_bytes
from typing import Dict, Optional, Tuple, Type
from typing_extensions import Final
from xrpl.constants import CryptoAlgorithm
from xrpl.core import addresscodec
from xrpl.core.keypairs.crypto_implementation import CryptoImplementation
from xrpl.core.keypairs.ed25519 import ED25519
from xrpl.core.keypairs.ed25519 import PREFIX as ED_PREFIX
from xrpl.core.keypairs.exceptions import XRPLKeypairsException
from xrpl.core.keypairs.helpers import get_account_id
from xrpl.core.keypairs.secp256k1 import SECP256K1
_VERIFICATION_MESSAGE: Final[bytes] = b"This test message should verify."
_ALGORITHM_TO_MODULE_MAP: Final[Dict[CryptoAlgorithm, Type[CryptoImplementation]]] = {
CryptoAlgorithm.ED25519: ED25519,
CryptoAlgorithm.SECP256K1: SECP256K1,
}
def generate_seed(
entropy: Optional[str] = None,
algorithm: CryptoAlgorithm = CryptoAlgorithm.ED25519,
) -> str:
"""
Generate a seed value that cryptographic keys can be derived from.
Args:
entropy: Must be at least addresscodec.SEED_LENGTH bytes long and
will be truncated to that length
algorithm: CryptoAlgorithm to use for seed generation. The default is
:data:`CryptoAlgorithm.ED25519 <xrpl.CryptoAlgorithm.ED25519>`.
Returns:
A seed value that can be used to derive a key pair with the given
cryptographic algorithm.
"""
if entropy is None:
parsed_entropy = token_bytes(addresscodec.SEED_LENGTH)
else:
parsed_entropy = bytes(entropy, "UTF-8")[: addresscodec.SEED_LENGTH]
return addresscodec.encode_seed(parsed_entropy, algorithm)
def derive_keypair(
seed: str, validator: bool = False, algorithm: Optional[CryptoAlgorithm] = None
) -> Tuple[str, str]:
"""
Derive the public and private keys from a given seed value.
Args:
seed: Seed to derive the key pair from. Use
:func:`generate_seed() <xrpl.core.keypairs.generate_seed>` to generate an
appropriate value.
validator: Whether the keypair is a validator keypair.
algorithm: The algorithm used to encode the keys. Inferred from the seed if not
included.
Returns:
A (public key, private key) pair derived from the given seed.
Raises:
XRPLKeypairsException: If the derived keypair did not generate a
verifiable signature.
"""
decoded_seed, algorithm = addresscodec.decode_seed(seed, algorithm)
module = _ALGORITHM_TO_MODULE_MAP[algorithm]
public_key, private_key = module.derive_keypair(decoded_seed, validator)
signature = module.sign(_VERIFICATION_MESSAGE, private_key)
if not module.is_valid_message(_VERIFICATION_MESSAGE, signature, public_key):
raise XRPLKeypairsException(
"Derived keypair did not generate verifiable signature",
)
return public_key, private_key
def derive_classic_address(public_key: str) -> str:
"""
Derive the XRP Ledger classic address for a given public key. See
`Address Derivation
<https://xrpl.org/cryptographic-keys.html#account-id-and-address>`_
for more information.
Args:
public_key: The public key to derive the address from, as hexadecimal.
Returns:
The classic address corresponding to the given public key.
"""
account_id = get_account_id(bytes.fromhex(public_key))
return addresscodec.encode_classic_address(account_id)
def sign(message: bytes, private_key: str) -> str:
"""
Sign a message using a given private key.
Args:
message: The message to sign, as bytes.
private_key: The private key to use to sign the message.
Returns:
Signed message, as hexadecimal.
"""
return (
_get_module_from_key(private_key)
.sign(
message,
private_key,
)
.hex()
.upper()
)
def is_valid_message(message: bytes, signature: bytes, public_key: str) -> bool:
"""
Verifies the signature on a given message.
Args:
message: The message to validate.
signature: The signature of the message.
public_key: The public key to use to verify the message and
signature.
Returns:
Whether the message is valid for the given signature and public key.
"""
return _get_module_from_key(public_key).is_valid_message(
message,
signature,
public_key,
)
def _get_module_from_key(key: str) -> Type[CryptoImplementation]:
if key.startswith(ED_PREFIX):
return ED25519
return SECP256K1