Skip to content

Commit 8a63fc3

Browse files
committed
Refactor utils
1 parent bb4a780 commit 8a63fc3

25 files changed

+418
-436
lines changed

.cspell.jsonc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"words": [
33
"bitcointalk",
4+
"chacha",
45
"Cipolla",
56
"Codacy",
67
"Codecov",
@@ -14,12 +15,17 @@
1415
"helloworld",
1516
"hexdigest",
1617
"hkdf",
18+
"keccak",
1719
"pycryptodome",
20+
"pytest",
1821
"readablize",
1922
"secp",
2023
"urandom",
2124
"xcfl",
2225
"xchacha"
2326
],
24-
"ignorePaths": [".cspell.jsonc", "LICENSE"]
27+
"ignorePaths": [
28+
".cspell.jsonc",
29+
"LICENSE"
30+
]
2531
}

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
# Release Notes
22

3+
## 0.4.4
4+
5+
- Refactor `utils`
6+
- Bump dependencies
7+
- Make `eth-keys` optional
8+
- Revamp documentation
9+
310
## 0.4.1 ~ 0.4.3
411

512
- Bump dependencies
613
- Support Python 3.12, 3.13
14+
- Drop Python 3.8
715

816
## 0.4.0
917

DETAILS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,6 @@ Now we have the shared key, and we can use the `nonce` and `tag` to decrypt. Thi
136136
b'helloworld'
137137
```
138138

139-
> Strictly speaking, `nonce` != `iv`, but this is a little bit off topic, if you are curious, you can check [the comment in `utils/symmetric.py`](./ecies/utils/symmetric.py#L79).
139+
> Strictly speaking, `nonce` != `iv`, but this is a little bit off topic, if you are curious, you can check [the comment in `utils/symmetric.py`](./ecies/utils/symmetric.py#L86).
140140
>
141141
> Warning: it's dangerous to reuse nonce, if you don't know what you are doing, just follow the default setting.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2018-2024 Weiliang Li
3+
Copyright (c) 2018-2025 Weiliang Li
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,66 @@
11
# eciespy
22

33
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/2a11aeb9939244019d2c64bce3ff3c4e)](https://app.codacy.com/gh/ecies/py/dashboard)
4+
[![License](https://img.shields.io/github/license/ecies/py.svg)](https://github.com/ecies/py)
5+
[![PyPI](https://img.shields.io/pypi/v/eciespy.svg)](https://pypi.org/project/eciespy/)
6+
[![PyPI - Downloads](https://img.shields.io/pypi/dm/eciespy)](https://pypistats.org/packages/eciespy)
7+
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/eciespy.svg)](https://pypi.org/project/eciespy/)
48
[![CI](https://img.shields.io/github/actions/workflow/status/ecies/py/ci.yml?branch=master)](https://github.com/ecies/py/actions)
59
[![Codecov](https://img.shields.io/codecov/c/github/ecies/py.svg)](https://codecov.io/gh/ecies/py)
6-
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/eciespy.svg)](https://pypi.org/project/eciespy/)
7-
[![PyPI](https://img.shields.io/pypi/v/eciespy.svg)](https://pypi.org/project/eciespy/)
8-
[![License](https://img.shields.io/github/license/ecies/py.svg)](https://github.com/ecies/py)
910

1011
Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python.
1112

1213
Other language versions:
1314

14-
- [Rust](https://github.com/ecies/rs)
1515
- [TypeScript](https://github.com/ecies/js)
16+
- [Rust](https://github.com/ecies/rs)
1617
- [Golang](https://github.com/ecies/go)
1718
- [WASM](https://github.com/ecies/rs-wasm)
19+
- [Java](https://github.com/ecies/java)
20+
- [Dart](https://github.com/ecies/dart)
1821

19-
You can also check a FastAPI web backend demo [here](https://github.com/ecies/py-demo).
22+
You can also check a web backend demo [here](https://github.com/ecies/py-demo).
2023

2124
## Install
2225

2326
`pip install eciespy`
2427

28+
Or `pip install 'eciespy[eth]'` to use `ecies.utils.generate_eth_key`.
29+
2530
## Quick Start
2631

2732
```python
28-
>>> from ecies.utils import generate_eth_key, generate_key
33+
>>> from ecies.utils import generate_key
2934
>>> from ecies import encrypt, decrypt
30-
>>> eth_k = generate_eth_key()
31-
>>> sk_hex = eth_k.to_hex() # hex string
32-
>>> pk_hex = eth_k.public_key.to_hex() # hex string
33-
>>> data = b'this is a test'
34-
>>> decrypt(sk_hex, encrypt(pk_hex, data))
35-
b'this is a test'
36-
>>> secp_k = generate_key()
37-
>>> sk_bytes = secp_k.secret # bytes
38-
>>> pk_bytes = secp_k.public_key.format(True) # bytes
39-
>>> decrypt(sk_bytes, encrypt(pk_bytes, data))
40-
b'this is a test'
35+
>>> data = 'hello world🌍'.encode()
36+
>>> sk = generate_key()
37+
>>> sk_bytes = sk.secret # bytes
38+
>>> pk_bytes = sk.public_key.format(True) # bytes
39+
>>> decrypt(sk_bytes, encrypt(pk_bytes, data)).decode()
40+
'hello world🌍'
4141
```
4242

4343
Or just use a builtin command `eciespy` in your favorite [command line](#command-line-interface).
4444

4545
## API
4646

47-
### `ecies.encrypt(receiver_pk: Union[str, bytes], msg: bytes) -> bytes`
47+
### `ecies.encrypt(receiver_pk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG) -> bytes`
4848

4949
Parameters:
5050

5151
- **receiver_pk** - Receiver's public key (hex str or bytes)
52-
- **msg** - Data to encrypt
52+
- **data** - Data to encrypt
53+
- **config** - Optional configuration object
5354

5455
Returns: **bytes**
5556

56-
### `ecies.decrypt(receiver_sk: Union[str, bytes], msg: bytes) -> bytes`
57+
### `ecies.decrypt(receiver_sk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG) -> bytes`
5758

5859
Parameters:
5960

6061
- **receiver_sk** - Receiver's private key (hex str or bytes)
61-
- **msg** - Data to decrypt
62+
- **data** - Data to decrypt
63+
- **config** - Optional configuration object
6264

6365
Returns: **bytes**
6466

ecies/__init__.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .config import ECIES_CONFIG, Config
66
from .utils import (
7-
compat_eth_public_key,
7+
bytes2pk,
88
decapsulate,
99
encapsulate,
1010
generate_key,
@@ -18,7 +18,7 @@
1818

1919

2020
def encrypt(
21-
receiver_pk: Union[str, bytes], msg: bytes, config: Config = ECIES_CONFIG
21+
receiver_pk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG
2222
) -> bytes:
2323
"""
2424
Encrypt with receiver's secp256k1 public key
@@ -27,8 +27,10 @@ def encrypt(
2727
----------
2828
receiver_pk: Union[str, bytes]
2929
Receiver's public key (hex str or bytes)
30-
msg: bytes
30+
data: bytes
3131
Data to encrypt
32+
config: Config
33+
Optional configuration object
3234
3335
Returns
3436
-------
@@ -38,20 +40,22 @@ def encrypt(
3840
if isinstance(receiver_pk, str):
3941
pk = hex2pk(receiver_pk)
4042
elif isinstance(receiver_pk, bytes):
41-
pk = PublicKey(compat_eth_public_key(receiver_pk))
43+
pk = bytes2pk(receiver_pk)
4244
else:
4345
raise TypeError("Invalid public key type")
4446

4547
ephemeral_sk = generate_key()
4648
ephemeral_pk = ephemeral_sk.public_key.format(config.is_ephemeral_key_compressed)
4749

48-
sym_key = encapsulate(ephemeral_sk, pk, config)
49-
encrypted = sym_encrypt(sym_key, msg, config)
50+
sym_key = encapsulate(ephemeral_sk, pk, config.is_hkdf_key_compressed)
51+
encrypted = sym_encrypt(
52+
sym_key, data, config.symmetric_algorithm, config.symmetric_nonce_length
53+
)
5054
return ephemeral_pk + encrypted
5155

5256

5357
def decrypt(
54-
receiver_sk: Union[str, bytes], msg: bytes, config: Config = ECIES_CONFIG
58+
receiver_sk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG
5559
) -> bytes:
5660
"""
5761
Decrypt with receiver's secp256k1 private key
@@ -60,8 +64,10 @@ def decrypt(
6064
----------
6165
receiver_sk: Union[str, bytes]
6266
Receiver's private key (hex str or bytes)
63-
msg: bytes
67+
data: bytes
6468
Data to decrypt
69+
config: Config
70+
Optional configuration object
6571
6672
Returns
6773
-------
@@ -76,7 +82,9 @@ def decrypt(
7682
raise TypeError("Invalid secret key type")
7783

7884
key_size = config.ephemeral_key_size
79-
ephemeral_pk, encrypted = PublicKey(msg[0:key_size]), msg[key_size:]
85+
ephemeral_pk, encrypted = PublicKey(data[0:key_size]), data[key_size:]
8086

81-
sym_key = decapsulate(ephemeral_pk, sk, config)
82-
return sym_decrypt(sym_key, encrypted, config)
87+
sym_key = decapsulate(ephemeral_pk, sk, config.is_hkdf_key_compressed)
88+
return sym_decrypt(
89+
sym_key, encrypted, config.symmetric_algorithm, config.symmetric_nonce_length
90+
)

ecies/__main__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import sys
1515

1616
from ecies import decrypt, encrypt
17-
from ecies.utils import generate_eth_key
17+
from ecies.utils import generate_key, get_eth_address, get_eth_public_key
1818

1919
__description__ = "Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python"
2020

@@ -68,11 +68,11 @@ def main():
6868

6969
args = parser.parse_args()
7070
if args.generate:
71-
k = generate_eth_key()
71+
k = generate_key()
7272
sk, pk, addr = (
7373
k.to_hex(),
74-
k.public_key.to_hex(),
75-
k.public_key.to_checksum_address(),
74+
f"0x{get_eth_public_key(k.public_key).hex()}",
75+
get_eth_address(k.public_key),
7676
)
7777
print("Private: {}\nPublic: {}\nAddress: {}".format(sk, pk, addr))
7878
return

ecies/utils/__init__.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
from .elliptic import (
2-
compat_eth_public_key,
3-
decapsulate,
4-
encapsulate,
5-
generate_eth_key,
6-
generate_key,
7-
hex2pk,
8-
hex2sk,
9-
)
10-
from .hex import decode_hex, sha256
1+
from .elliptic import bytes2pk, decapsulate, encapsulate, generate_key, hex2pk, hex2sk
2+
from .eth import generate_eth_key, get_eth_address, get_eth_public_key
3+
from .hash import derive_key, sha256
4+
from .hex import decode_hex
115
from .symmetric import sym_decrypt, sym_encrypt
126

137
__all__ = [
14-
"sha256",
15-
"decode_hex",
168
"sym_encrypt",
179
"sym_decrypt",
1810
"generate_key",
19-
"generate_eth_key",
2011
"hex2sk",
2112
"hex2pk",
13+
"bytes2pk",
2214
"decapsulate",
2315
"encapsulate",
24-
"compat_eth_public_key",
16+
# eth
17+
"generate_eth_key",
18+
"get_eth_address",
19+
"get_eth_public_key",
20+
# hex
21+
"decode_hex",
22+
# hash
23+
"sha256",
24+
"derive_key",
2525
]

ecies/utils/elliptic.py

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from coincurve import PrivateKey, PublicKey
22
from coincurve.utils import get_valid_secret
33

4-
from ..config import ECIES_CONFIG, Config
4+
from .eth import compat_eth_public_key
5+
from .hash import derive_key
56
from .hex import decode_hex
6-
from .symmetric import derive_key
77

88

99
def generate_key() -> PrivateKey:
@@ -20,24 +20,9 @@ def generate_key() -> PrivateKey:
2020
return PrivateKey(get_valid_secret())
2121

2222

23-
def generate_eth_key():
24-
"""
25-
Generate a random `eth_keys.keys.PrivateKey`
26-
27-
Returns
28-
-------
29-
eth_keys.keys.PrivateKey
30-
An ethereum key
31-
32-
"""
33-
from eth_keys import keys
34-
35-
return keys.PrivateKey(get_valid_secret())
36-
37-
3823
def hex2pk(pk_hex: str) -> PublicKey:
3924
"""
40-
Convert ethereum hex to `coincurve.PublicKey`
25+
Convert public key hex to `coincurve.PublicKey`
4126
The hex should be 65 bytes (uncompressed) or 33 bytes (compressed), but ethereum public key has 64 bytes.
4227
`0x04` will be appended if it's an ethereum public key.
4328
@@ -55,10 +40,22 @@ def hex2pk(pk_hex: str) -> PublicKey:
5540
return PublicKey(compat_eth_public_key(decode_hex(pk_hex)))
5641

5742

58-
def compat_eth_public_key(data: bytes):
59-
if len(data) == 64: # eth public key format
60-
data = b"\x04" + data
61-
return data
43+
def bytes2pk(pk_bytes: bytes) -> PublicKey:
44+
"""
45+
Convert public key bytes to `coincurve.PublicKey`
46+
47+
Parameters
48+
----------
49+
pk_bytes: bytes
50+
Public key bytes
51+
52+
Returns
53+
-------
54+
coincurve.PublicKey
55+
A secp256k1 public key
56+
57+
"""
58+
return PublicKey(compat_eth_public_key(pk_bytes))
6259

6360

6461
def hex2sk(sk_hex: str) -> PrivateKey:
@@ -81,9 +78,8 @@ def hex2sk(sk_hex: str) -> PrivateKey:
8178

8279
# private below
8380
def encapsulate(
84-
private_key: PrivateKey, peer_public_key: PublicKey, config: Config = ECIES_CONFIG
81+
private_key: PrivateKey, peer_public_key: PublicKey, is_compressed: bool = False
8582
) -> bytes:
86-
is_compressed = config.is_hkdf_key_compressed
8783
shared_point = peer_public_key.multiply(private_key.secret)
8884
master = private_key.public_key.format(is_compressed) + shared_point.format(
8985
is_compressed
@@ -92,9 +88,8 @@ def encapsulate(
9288

9389

9490
def decapsulate(
95-
public_key: PublicKey, peer_private_key: PrivateKey, config: Config = ECIES_CONFIG
91+
public_key: PublicKey, peer_private_key: PrivateKey, is_compressed: bool = False
9692
) -> bytes:
97-
is_compressed = config.is_hkdf_key_compressed
9893
shared_point = public_key.multiply(peer_private_key.secret)
9994
master = public_key.format(is_compressed) + shared_point.format(is_compressed)
10095
return derive_key(master)

0 commit comments

Comments
 (0)