mirror of
https://github.com/varun-r-mallya/py-libp2p.git
synced 2026-02-12 16:10:57 +00:00
Merge pull request #291 from ralexstokes/add-ed25519-support
Adds support for verifying ed25519 signatures, for secio
This commit is contained in:
70
libp2p/crypto/ed25519.py
Normal file
70
libp2p/crypto/ed25519.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
from Crypto.Hash import SHA256
|
||||||
|
from nacl.exceptions import BadSignatureError
|
||||||
|
from nacl.public import PrivateKey as PrivateKeyImpl
|
||||||
|
from nacl.public import PublicKey as PublicKeyImpl
|
||||||
|
from nacl.signing import SigningKey, VerifyKey
|
||||||
|
import nacl.utils as utils
|
||||||
|
|
||||||
|
from libp2p.crypto.keys import KeyPair, KeyType, PrivateKey, PublicKey
|
||||||
|
|
||||||
|
|
||||||
|
class Ed25519PublicKey(PublicKey):
|
||||||
|
def __init__(self, impl: PublicKeyImpl) -> None:
|
||||||
|
self.impl = impl
|
||||||
|
|
||||||
|
def to_bytes(self) -> bytes:
|
||||||
|
return bytes(self.impl)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, key_bytes: bytes) -> "Ed25519PublicKey":
|
||||||
|
return cls(PublicKeyImpl(key_bytes))
|
||||||
|
|
||||||
|
def get_type(self) -> KeyType:
|
||||||
|
return KeyType.Ed25519
|
||||||
|
|
||||||
|
def verify(self, data: bytes, signature: bytes) -> bool:
|
||||||
|
verify_key = VerifyKey(self.to_bytes())
|
||||||
|
h = SHA256.new(data)
|
||||||
|
try:
|
||||||
|
verify_key.verify(h, signature)
|
||||||
|
except BadSignatureError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Ed25519PrivateKey(PrivateKey):
|
||||||
|
def __init__(self, impl: PrivateKeyImpl) -> None:
|
||||||
|
self.impl = impl
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new(cls, seed: bytes = None) -> "Ed25519PrivateKey":
|
||||||
|
if not seed:
|
||||||
|
seed = utils.random()
|
||||||
|
|
||||||
|
private_key_impl = PrivateKeyImpl.from_seed(seed)
|
||||||
|
return cls(private_key_impl)
|
||||||
|
|
||||||
|
def to_bytes(self) -> bytes:
|
||||||
|
return bytes(self.impl)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, data: bytes) -> "Ed25519PrivateKey":
|
||||||
|
impl = PrivateKeyImpl(data)
|
||||||
|
return cls(impl)
|
||||||
|
|
||||||
|
def get_type(self) -> KeyType:
|
||||||
|
return KeyType.Ed25519
|
||||||
|
|
||||||
|
def sign(self, data: bytes) -> bytes:
|
||||||
|
h = SHA256.new(data)
|
||||||
|
signing_key = SigningKey(self.to_bytes())
|
||||||
|
return signing_key.sign(h)
|
||||||
|
|
||||||
|
def get_public_key(self) -> PublicKey:
|
||||||
|
return Ed25519PublicKey(self.impl.public_key)
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_key_pair(seed: bytes = None) -> KeyPair:
|
||||||
|
private_key = Ed25519PrivateKey.new(seed)
|
||||||
|
public_key = private_key.get_public_key()
|
||||||
|
return KeyPair(private_key, public_key)
|
||||||
14
libp2p/crypto/exceptions.py
Normal file
14
libp2p/crypto/exceptions.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from libp2p.exceptions import BaseLibp2pError
|
||||||
|
|
||||||
|
|
||||||
|
class CryptographyError(BaseLibp2pError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MissingDeserializerError(CryptographyError):
|
||||||
|
"""
|
||||||
|
Raise if the requested deserialization routine is missing for
|
||||||
|
some type of cryptographic key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
from libp2p.crypto.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
||||||
|
from libp2p.crypto.exceptions import MissingDeserializerError
|
||||||
from libp2p.crypto.keys import KeyType, PrivateKey, PublicKey
|
from libp2p.crypto.keys import KeyType, PrivateKey, PublicKey
|
||||||
from libp2p.crypto.rsa import RSAPublicKey
|
from libp2p.crypto.rsa import RSAPublicKey
|
||||||
from libp2p.crypto.secp256k1 import Secp256k1PrivateKey, Secp256k1PublicKey
|
from libp2p.crypto.secp256k1 import Secp256k1PrivateKey, Secp256k1PublicKey
|
||||||
@ -5,20 +7,28 @@ from libp2p.crypto.secp256k1 import Secp256k1PrivateKey, Secp256k1PublicKey
|
|||||||
key_type_to_public_key_deserializer = {
|
key_type_to_public_key_deserializer = {
|
||||||
KeyType.Secp256k1.value: Secp256k1PublicKey.from_bytes,
|
KeyType.Secp256k1.value: Secp256k1PublicKey.from_bytes,
|
||||||
KeyType.RSA.value: RSAPublicKey.from_bytes,
|
KeyType.RSA.value: RSAPublicKey.from_bytes,
|
||||||
|
KeyType.Ed25519.value: Ed25519PublicKey.from_bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
key_type_to_private_key_deserializer = {
|
key_type_to_private_key_deserializer = {
|
||||||
KeyType.Secp256k1.value: Secp256k1PrivateKey.from_bytes
|
KeyType.Secp256k1.value: Secp256k1PrivateKey.from_bytes,
|
||||||
|
KeyType.Ed25519.value: Ed25519PrivateKey.from_bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def deserialize_public_key(data: bytes) -> PublicKey:
|
def deserialize_public_key(data: bytes) -> PublicKey:
|
||||||
f = PublicKey.deserialize_from_protobuf(data)
|
f = PublicKey.deserialize_from_protobuf(data)
|
||||||
deserializer = key_type_to_public_key_deserializer[f.key_type]
|
try:
|
||||||
|
deserializer = key_type_to_public_key_deserializer[f.key_type]
|
||||||
|
except KeyError:
|
||||||
|
raise MissingDeserializerError({"key_type": f.key_type, "key": "public_key"})
|
||||||
return deserializer(f.data)
|
return deserializer(f.data)
|
||||||
|
|
||||||
|
|
||||||
def deserialize_private_key(data: bytes) -> PrivateKey:
|
def deserialize_private_key(data: bytes) -> PrivateKey:
|
||||||
f = PrivateKey.deserialize_from_protobuf(data)
|
f = PrivateKey.deserialize_from_protobuf(data)
|
||||||
deserializer = key_type_to_private_key_deserializer[f.key_type]
|
try:
|
||||||
|
deserializer = key_type_to_private_key_deserializer[f.key_type]
|
||||||
|
except KeyError:
|
||||||
|
raise MissingDeserializerError({"key_type": f.key_type, "key": "private_key"})
|
||||||
return deserializer(f.data)
|
return deserializer(f.data)
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
from .keys import PublicKey
|
|
||||||
from .pb import crypto_pb2 as protobuf
|
|
||||||
from .rsa import RSAPublicKey
|
|
||||||
from .secp256k1 import Secp256k1PublicKey
|
|
||||||
|
|
||||||
|
|
||||||
def pubkey_from_protobuf(pubkey_pb: protobuf.PublicKey) -> PublicKey:
|
|
||||||
if pubkey_pb.key_type == protobuf.RSA:
|
|
||||||
return RSAPublicKey.from_bytes(pubkey_pb.data)
|
|
||||||
# TODO: Test against secp256k1 keys
|
|
||||||
elif pubkey_pb.key_type == protobuf.Secp256k1:
|
|
||||||
return Secp256k1PublicKey.from_bytes(pubkey_pb.data)
|
|
||||||
# TODO: Support `Ed25519` and `ECDSA` in the future?
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
f"unsupported key_type={pubkey_pb.key_type}, data={pubkey_pb.data!r}"
|
|
||||||
)
|
|
||||||
@ -2,7 +2,7 @@ from typing import Optional
|
|||||||
|
|
||||||
from libp2p.crypto.keys import PrivateKey, PublicKey
|
from libp2p.crypto.keys import PrivateKey, PublicKey
|
||||||
from libp2p.crypto.pb import crypto_pb2
|
from libp2p.crypto.pb import crypto_pb2
|
||||||
from libp2p.crypto.utils import pubkey_from_protobuf
|
from libp2p.crypto.serialization import deserialize_public_key
|
||||||
from libp2p.io.abc import ReadWriteCloser
|
from libp2p.io.abc import ReadWriteCloser
|
||||||
from libp2p.network.connection.exceptions import RawConnError
|
from libp2p.network.connection.exceptions import RawConnError
|
||||||
from libp2p.network.connection.raw_connection_interface import IRawConnection
|
from libp2p.network.connection.raw_connection_interface import IRawConnection
|
||||||
@ -75,7 +75,9 @@ class InsecureSession(BaseSession):
|
|||||||
|
|
||||||
# Verify if the given `pubkey` matches the given `peer_id`
|
# Verify if the given `pubkey` matches the given `peer_id`
|
||||||
try:
|
try:
|
||||||
received_pubkey = pubkey_from_protobuf(remote_msg.pubkey)
|
received_pubkey = deserialize_public_key(
|
||||||
|
remote_msg.pubkey.SerializeToString()
|
||||||
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise HandshakeFailure(
|
raise HandshakeFailure(
|
||||||
f"unknown `key_type` of remote_msg.pubkey={remote_msg.pubkey}"
|
f"unknown `key_type` of remote_msg.pubkey={remote_msg.pubkey}"
|
||||||
|
|||||||
1
setup.py
1
setup.py
@ -41,6 +41,7 @@ setuptools.setup(
|
|||||||
"protobuf==3.9.0",
|
"protobuf==3.9.0",
|
||||||
"coincurve>=10.0.0,<11.0.0",
|
"coincurve>=10.0.0,<11.0.0",
|
||||||
"fastecdsa==1.7.4",
|
"fastecdsa==1.7.4",
|
||||||
|
"pynacl==1.3.0",
|
||||||
],
|
],
|
||||||
extras_require=extras_require,
|
extras_require=extras_require,
|
||||||
packages=setuptools.find_packages(exclude=["tests", "tests.*"]),
|
packages=setuptools.find_packages(exclude=["tests", "tests.*"]),
|
||||||
|
|||||||
22
tests/crypto/test_ed25519.py
Normal file
22
tests/crypto/test_ed25519.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from libp2p.crypto.ed25519 import create_new_key_pair
|
||||||
|
from libp2p.crypto.serialization import deserialize_private_key, deserialize_public_key
|
||||||
|
|
||||||
|
|
||||||
|
def test_public_key_serialize_deserialize_round_trip():
|
||||||
|
key_pair = create_new_key_pair()
|
||||||
|
public_key = key_pair.public_key
|
||||||
|
|
||||||
|
public_key_bytes = public_key.serialize()
|
||||||
|
another_public_key = deserialize_public_key(public_key_bytes)
|
||||||
|
|
||||||
|
assert public_key == another_public_key
|
||||||
|
|
||||||
|
|
||||||
|
def test_private_key_serialize_deserialize_round_trip():
|
||||||
|
key_pair = create_new_key_pair()
|
||||||
|
private_key = key_pair.private_key
|
||||||
|
|
||||||
|
private_key_bytes = private_key.serialize()
|
||||||
|
another_private_key = deserialize_private_key(private_key_bytes)
|
||||||
|
|
||||||
|
assert private_key == another_private_key
|
||||||
Reference in New Issue
Block a user