Skip to content

Commit 6896c36

Browse files
authored
add Secp256k1 signature (#54)
1 parent d34c64f commit 6896c36

12 files changed

Lines changed: 323 additions & 6 deletions

File tree

pactus/crypto/address.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class AddressType(Enum):
1616
VALIDATOR = 1
1717
BLS_ACCOUNT = 2
1818
ED25519_ACCOUNT = 3
19+
SECP256K1_ACCOUNT = 4
1920

2021

2122
class Address:

pactus/crypto/bls/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
from .signature import DST, SIGNATURE_TYPE_BLS, Signature
44

55
__all__ = ["DST", "SIGNATURE_TYPE_BLS", "PrivateKey", "PublicKey", "Signature"]
6-

pactus/crypto/bls/bls12_381/consts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
k_final = (p**4 - p**2 + 1) // q
1313

1414
# ciphersuite numbers
15-
_gsuite = (
16-
lambda stype, group, stag: b"BLS_"
15+
_gsuite = lambda stype, group, stag: (
16+
b"BLS_"
1717
+ stype
1818
+ b"_BLS12381G"
1919
+ group

pactus/crypto/ed25519/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
from .signature import SIGNATURE_TYPE_ED25519, Signature
44

55
__all__ = ["SIGNATURE_TYPE_ED25519", "PrivateKey", "PublicKey", "Signature"]
6-
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .private_key import PrivateKey
2+
from .public_key import PublicKey
3+
from .signature import SIGNATURE_TYPE_SECP256K1, Signature
4+
5+
__all__ = ["SIGNATURE_TYPE_SECP256K1", "PrivateKey", "PublicKey", "Signature"]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from __future__ import annotations
2+
3+
import secp256k1
4+
5+
from pactus.crypto.hrp import HRP
6+
from pactus.utils import utils
7+
8+
from .public_key import PublicKey
9+
from .signature import SIGNATURE_TYPE_SECP256K1, Signature
10+
11+
PRIVATE_KEY_SIZE = 32
12+
13+
14+
class PrivateKey:
15+
def __init__(self, scalar: secp256k1.PrivateKey) -> None:
16+
self.scalar = scalar
17+
18+
@classmethod
19+
def from_bytes(cls, buffer: bytes) -> PrivateKey:
20+
sk = secp256k1.PrivateKey()
21+
sk.set_raw_privkey(buffer)
22+
return cls(sk)
23+
24+
@classmethod
25+
def random(cls) -> PrivateKey:
26+
sk = secp256k1.PrivateKey()
27+
return cls(sk)
28+
29+
@classmethod
30+
def from_string(cls, text: str) -> PrivateKey:
31+
hrp, typ, data = utils.decode_to_base256_with_type(text)
32+
33+
if hrp != HRP.PRIVATE_KEY_HRP:
34+
msg = f"Invalid hrp: {hrp}"
35+
raise ValueError(msg)
36+
37+
if typ != SIGNATURE_TYPE_SECP256K1:
38+
msg = f"Invalid Private key type: {typ}"
39+
raise ValueError(msg)
40+
41+
if len(data) != PRIVATE_KEY_SIZE:
42+
msg = "Private key data must be 32 bytes long"
43+
raise ValueError(msg)
44+
45+
sk = secp256k1.PrivateKey()
46+
sk.set_raw_privkey(bytes(data))
47+
return cls(sk)
48+
49+
def raw_bytes(self) -> bytes:
50+
# serialize() returns a hex string, convert to bytes
51+
hex_str = self.scalar.serialize()
52+
return bytes.fromhex(hex_str)
53+
54+
def string(self) -> str:
55+
return utils.encode_from_base256_with_type(
56+
HRP.PRIVATE_KEY_HRP,
57+
SIGNATURE_TYPE_SECP256K1,
58+
self.raw_bytes(),
59+
)
60+
61+
def public_key(self) -> PublicKey:
62+
return PublicKey(self.scalar.pubkey)
63+
64+
def sign(self, msg: bytes) -> Signature:
65+
sig = self.scalar.ecdsa_sign(msg)
66+
sig_compact = self.scalar.ecdsa_serialize_compact(sig)
67+
return Signature(sig_compact)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from __future__ import annotations
2+
3+
import hashlib
4+
5+
import secp256k1
6+
from ripemd.ripemd160 import ripemd160
7+
8+
from pactus.crypto.address import Address, AddressType
9+
from pactus.crypto.hrp import HRP
10+
from pactus.utils import utils
11+
12+
from .signature import SIGNATURE_TYPE_SECP256K1, Signature
13+
14+
PUBLIC_KEY_SIZE = 33 # Compressed public key
15+
16+
17+
class PublicKey:
18+
def __init__(self, pub: secp256k1.PublicKey) -> None:
19+
self.pub = pub
20+
21+
@classmethod
22+
def from_string(cls, text: str) -> PublicKey:
23+
hrp, typ, data = utils.decode_to_base256_with_type(text)
24+
25+
if hrp != HRP.PUBLIC_KEY_HRP:
26+
msg = f"Invalid hrp: {hrp}"
27+
raise ValueError(msg)
28+
29+
if typ != SIGNATURE_TYPE_SECP256K1:
30+
msg = f"Invalid Public key type: {typ}"
31+
raise ValueError(msg)
32+
33+
if len(data) != PUBLIC_KEY_SIZE:
34+
msg = "Public key data must be 33 bytes long"
35+
raise ValueError(msg)
36+
37+
pub_key = secp256k1.PublicKey(bytes(data), raw=True)
38+
39+
return cls(pub_key)
40+
41+
def raw_bytes(self) -> bytes:
42+
return self.pub.serialize(compressed=True)
43+
44+
def string(self) -> str:
45+
return utils.encode_from_base256_with_type(
46+
HRP.PUBLIC_KEY_HRP,
47+
SIGNATURE_TYPE_SECP256K1,
48+
self.raw_bytes(),
49+
)
50+
51+
def account_address(self) -> Address:
52+
blake2b = hashlib.blake2b(digest_size=32)
53+
blake2b.update(self.raw_bytes())
54+
hash_256 = blake2b.digest()
55+
hash_160 = ripemd160(hash_256)
56+
57+
return Address(AddressType.SECP256K1_ACCOUNT, hash_160)
58+
59+
def verify(self, msg: bytes, sig: Signature) -> bool:
60+
try:
61+
sig_compact = sig.raw_bytes()
62+
sig_deserialized = self.pub.ecdsa_deserialize_compact(sig_compact)
63+
return self.pub.ecdsa_verify(msg, sig_deserialized)
64+
65+
# ruff: noqa: BLE001 # unable to fix this issue
66+
except Exception:
67+
return False
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from __future__ import annotations
2+
3+
SIGNATURE_SIZE = 64
4+
SIGNATURE_TYPE_SECP256K1 = 4
5+
6+
7+
class Signature:
8+
def __init__(self, sig: bytes) -> None:
9+
self.sig = sig
10+
11+
@classmethod
12+
def from_string(cls, text: str) -> Signature:
13+
data = bytes.fromhex(text)
14+
15+
if len(data) != SIGNATURE_SIZE:
16+
msg = "Signature data must be 64 bytes long"
17+
raise ValueError(msg)
18+
19+
return cls(data)
20+
21+
def raw_bytes(self) -> bytes:
22+
return self.sig
23+
24+
def string(self) -> str:
25+
return self.sig.hex()

pactus/crypto/sss/sss.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
See the bottom few lines for usage. Tested on Python 2 and 3.
88
"""
9+
910
from __future__ import annotations
1011

1112
import functools
@@ -100,7 +101,6 @@ def make_random_shares(secret: int, minimum: int, shares: int, prime: int) -> li
100101
return [(i, _eval_at(poly, i, prime)) for i in range(1, shares + 1)]
101102

102103

103-
104104
def recover_secret(shares: list[tuple[int, int]], prime: int) -> int:
105105
"""
106106
Recover the secret from share points

pactus/types/amount.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,3 @@ def round(self: float) -> float:
8989
return self - 0.5
9090

9191
return self + 0.5
92-

0 commit comments

Comments
 (0)