Code Monkey home page Code Monkey logo

ecdsa-python's Introduction

A lightweight and fast pure Python ECDSA

Overview

We tried other Python libraries such as python-ecdsa, fast-ecdsa and other less famous ones, but we didn't find anything that suited our needs. The first one was pure Python, but it was too slow. The second one mixed Python and C and it was really fast, but we were unable to use it in our current infrastructure, which required pure Python code.

For this reason, we decided to create something simple, compatible with OpenSSL and fast using elegant math such as Jacobian Coordinates to speed up the ECDSA. Starkbank-ECDSA is fully compatible with Python2 and Python3.

Installation

To install StarkBank`s ECDSA-Python, run:

pip install starkbank-ecdsa

Curves

We currently support secp256k1, but you can add more curves to the project. You just need to use the curve.add() function.

Speed

We ran a test on a MAC Pro i7 2017. The libraries were run 100 times and the averages displayed bellow were obtained:

Library sign verify
python-ecdsa 121.3ms 65.1ms
fast-ecdsa 0.1ms 0.2ms
starkbank-ecdsa 4.1ms 7.8ms

Our pure Python code cannot compete with C based libraries, but it's 6x faster to verify and 23x faster to sign than other pure Python libraries.

Sample Code

How to sign a json message for Stark Bank:

from json import dumps
from ellipticcurve.ecdsa import Ecdsa
from ellipticcurve.privateKey import PrivateKey


# Generate privateKey from PEM string
privateKey = PrivateKey.fromPem("""
    -----BEGIN EC PARAMETERS-----
    BgUrgQQACg==
    -----END EC PARAMETERS-----
    -----BEGIN EC PRIVATE KEY-----
    MHQCAQEEIODvZuS34wFbt0X53+P5EnSj6tMjfVK01dD1dgDH02RzoAcGBSuBBAAK
    oUQDQgAE/nvHu/SQQaos9TUljQsUuKI15Zr5SabPrbwtbfT/408rkVVzq8vAisbB
    RmpeRREXj5aog/Mq8RrdYy75W9q/Ig==
    -----END EC PRIVATE KEY-----
""")

# Create message from json
message = dumps({
    "transfers": [
        {
            "amount": 100000000,
            "taxId": "594.739.480-42",
            "name": "Daenerys Targaryen Stormborn",
            "bankCode": "341",
            "branchCode": "2201",
            "accountNumber": "76543-8",
            "tags": ["daenerys", "targaryen", "transfer-1-external-id"]
        }
    ]
})

signature = Ecdsa.sign(message, privateKey)

# Generate Signature in base64. This result can be sent to Stark Bank in the request header as the Digital-Signature parameter.
print(signature.toBase64())

# To double check if the message matches the signature, do this:
publicKey = privateKey.publicKey()

print(Ecdsa.verify(message, signature, publicKey))

Simple use:

from ellipticcurve.ecdsa import Ecdsa
from ellipticcurve.privateKey import PrivateKey


# Generate new Keys
privateKey = PrivateKey()
publicKey = privateKey.publicKey()

message = "My test message"

# Generate Signature
signature = Ecdsa.sign(message, privateKey)

# To verify if the signature is valid
print(Ecdsa.verify(message, signature, publicKey))

How to add more curves:

from ellipticcurve import curve, PrivateKey, PublicKey

newCurve = curve.CurveFp(
    name="frp256v1",
    A=0xf1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c00,
    B=0xee353fca5428a9300d4aba754a44c00fdfec0c9ae4b1a1803075ed967b7bb73f,
    P=0xf1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c03,
    N=0xf1fd178c0b3ad58f10126de8ce42435b53dc67e140d2bf941ffdd459c6d655e1,
    Gx=0xb6b3d4c356c139eb31183d4749d423958c27d2dcaf98b70164c97a2dd98f5cff,
    Gy=0x6142e0f7c8b204911f9271f0f3ecef8c2701c307e8e4c9e183115a1554062cfb,
    oid=[1, 2, 250, 1, 223, 101, 256, 1]
)

curve.add(newCurve)

publicKeyPem = """-----BEGIN PUBLIC KEY-----
MFswFQYHKoZIzj0CAQYKKoF6AYFfZYIAAQNCAATeEFFYiQL+HmDYTf+QDmvQmWGD
dRJPqLj11do8okvkSxq2lwB6Ct4aITMlCyg3f1msafc/ROSN/Vgj69bDhZK6
-----END PUBLIC KEY-----"""

publicKey = PublicKey.fromPem(publicKeyPem)

print(publicKey.toPem())

How to generate compressed public key:

from ellipticcurve import PrivateKey, PublicKey

privateKey = PrivateKey()
publicKey = privateKey.publicKey()
compressedPublicKey = publicKey.toCompressed()

print(compressedPublicKey)

How to recover a compressed public key:

from ellipticcurve import PrivateKey, PublicKey

compressedPublicKey = "0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2"
publicKey = PublicKey.fromCompressed(compressedPublicKey)

print(publicKey.toPem())

OpenSSL

This library is compatible with OpenSSL, so you can use it to generate keys:

openssl ecparam -name secp256k1 -genkey -out privateKey.pem
openssl ec -in privateKey.pem -pubout -out publicKey.pem

Create a message.txt file and sign it:

openssl dgst -sha256 -sign privateKey.pem -out signatureDer.txt message.txt

To verify, do this:

from ellipticcurve.ecdsa import Ecdsa
from ellipticcurve.signature import Signature
from ellipticcurve.publicKey import PublicKey
from ellipticcurve.utils.file import File


publicKeyPem = File.read("publicKey.pem")
signatureDer = File.read("signatureDer.txt", "rb")
message = File.read("message.txt")

publicKey = PublicKey.fromPem(publicKeyPem)
signature = Signature.fromDer(signatureDer)

print(Ecdsa.verify(message, signature, publicKey))

You can also verify it on terminal:

openssl dgst -sha256 -verify publicKey.pem -signature signatureDer.txt message.txt

NOTE: If you want to create a Digital Signature to use with Stark Bank, you need to convert the binary signature to base64.

openssl base64 -in signatureDer.txt -out signatureBase64.txt

You can do the same with this library:

from ellipticcurve.signature import Signature
from ellipticcurve.utils.file import File


signatureDer = File.read("signatureDer.txt", "rb")

signature = Signature.fromDer(signatureDer)

print(signature.toBase64())

Run unit tests

python3 -m unittest discover
python2 -m unittest discover

ecdsa-python's People

Contributors

aekoroglu avatar cdottori-stark avatar matheuscferraz avatar onuratakan avatar pibara avatar rcmstark avatar xavier-stark avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ecdsa-python's Issues

Random number generator used by default is Not Secure

It seems the random number generator used by default when generating a private key is not secure:
When you call:
>privateKey = PrivateKey()
It does
randint(1, curve.N - 1)
While "randint" is imported from "random" module.
Instead random should use " random.SystemRandom"

Make Ecdsa.verify() more flexible

Issue:
Having the conversion to "byte string", the hashing plus conversion to integer inside the Ecdsa.verify() function is highly unflexible and can be performing badly.
There are multiple reasons why you want to do this outside Ecdsa.verify().

One example is huge data that you want to hash in parts and not to keep completely in memory.
With the current implementation of Ecdsa.verify() this is not possible.
Another example would be verifying the ECDSA signature multiple times with different public keys, then all the conversions are done multiple times which is wasting performance.

Suggestion:
Create a new function called Ecdsa.verifyNumber() that directly takes an integer value "numberMessage" as a parameter def verifyNumber(cls, numberMessage, signature, publicKey), which skips all these conversions (typically for "prehashed" values).
Additionally enhance the existing Ecdsa.verify() function to handle different variable types of the message parameter.

Ecdsa.sign() could profit from the same changes.

Code (not tested, but shall be compatible with Python 3 and 2.7)

## Python 2 future-compatible workarounds: (see: http://python-future.org/compatible_idioms.html)
## interpret long as int, support int.from_bytes()
from builtins import int
## support bytes()
from builtins import bytes

...
    @classmethod
    def verifyNumber(cls, numberMessage, signature, publicKey):
        curve = publicKey.curve
        sigR = signature.r
        sigS = signature.s
        inv = Math.inv(sigS, curve.N)
        u1 = Math.multiply(curve.G, n=(numberMessage * inv) % curve.N, A=curve.A, P=curve.P, N=curve.N)
        u2 = Math.multiply(publicKey.point, n=(sigR * inv) % curve.N, A=curve.A, P=curve.P, N=curve.N)
        add = Math.add(u1, u2, P=curve.P, A=curve.A)
        return sigR == add.x

    @classmethod
    def verify(cls, message, signature, publicKey, hashfunc=sha256):
        if isinstance(message, int):
            numberMessage = message
        elif isinstance(message, bytes) \
        or isinstance(message, bytearray):
            if not hashfunc is None:
                hashMessage = hashfunc(message).digest()
            else:
                hashMessage = message
            # TODO: numberMessage = int.from_bytes(hashMessage, byteorder="big")
            numberMessage = BinaryAscii.numberFromString(hashMessage)
        else:
            hashMessage = hashfunc(toBytes(message)).digest()
            # TODO: numberMessage = int.from_bytes(hashMessage, byteorder="big")
            numberMessage = BinaryAscii.numberFromString(hashMessage)
        #
        return verifyNumber(numberMessage, signature, publicKey)

An toAddress function for PublicKey would be convenient

The ecdsa-python library is curently used in the aioflureedb, where a user of aioflureedb who has a private key, will also need to explicitly use the address of the key in order to do any signed operations.

So, just an idea, that maybe if it is possible, it would be amazing if PublicKey in ecdsa-python could get a toAddress(...) method, so code could simply do:

from ellipticcurve import privateKey, BlockChain

private_key = privateKey.PrivateKey.fromString(privkey)
address = private_key.publicKey().toAddress(Blockchain.FLUREEDB)

or if more in line with the library design:

from base58 import b58encode
from ellipticcurve import privateKey, BlockChain

private_key = privateKey.PrivateKey.fromString(privkey)
address = b58encode(private_key.publicKey().toAddress(BlockChain.FLUREEDB))

PublicKey toString and fromString lack support for compressed public keys

Public keys are often encoded using compressed public key format (see image). In compressed public keys, instead 0f [0x04 X Y], the public key is encoded as [0x02 X] for even values of Y and as [0x03 X] for odd values of Y.

The toString functionality is trivial enough to implement in the client through the available API, but the fromString functionality it seems really needs something to be implemented in the library.

Python <-> Java interface

Hi,

Thanks for this wonderful lib.

I am trying to use this to sign and verify in Java, using your "Ecdsa-java" but have trouble sending the signature from Python to Java. If you could kindly provide an example to illustrate signing in Python and verifying in Java that would be really great.

Many thanks,
Vaidya.

toString encoding issues

Hello,

are you aware of any encoding issues using the publicKey's toString function?

I just took your sample from the page and printed the publicKey using it's toString function and I am not getting anything that I would know how to use further. Can you please help?

þ{Ç»ô�Aª,õ5%���¸¢5å�ùI¦Ï­¼-môÿãO+�Us«ËÀ�ÆÁFj^E����¨�ó*ñ�Ýc.ù[Ú¿"

from json import dumps
from ellipticcurve.ecdsa import Ecdsa
from ellipticcurve.privateKey import PrivateKey

privateKey = PrivateKey.fromPem("""
    -----BEGIN EC PARAMETERS-----
    BgUrgQQACg==
    -----END EC PARAMETERS-----
    -----BEGIN EC PRIVATE KEY-----
    MHQCAQEEIODvZuS34wFbt0X53+P5EnSj6tMjfVK01dD1dgDH02RzoAcGBSuBBAAK
    oUQDQgAE/nvHu/SQQaos9TUljQsUuKI15Zr5SabPrbwtbfT/408rkVVzq8vAisbB
    RmpeRREXj5aog/Mq8RrdYy75W9q/Ig==
    -----END EC PRIVATE KEY-----
""")
message = dumps({
    "transfers": [
        {
            "amount": 100000000,
            "taxId": "594.739.480-42",
            "name": "Daenerys Targaryen Stormborn",
            "bankCode": "341",
            "branchCode": "2201",
            "accountNumber": "76543-8",
            "tags": ["daenerys", "targaryen", "transfer-1-external-id"]
        }
    ]
})
signature = Ecdsa.sign(message, privateKey)
print(signature.toBase64())
publicKey = privateKey.publicKey()

print(publicKey.toString())

print(Ecdsa.verify(message, signature, publicKey))

MEUCIQDhaCXUL/PScSj8dgASpKhmJybSDBTaP2GN8wgKAoYgggIgVXSxRXQqDigq6DbjoHrZx/tZ4glwdMNRyDqt1na2030=
þ{Ç»ô�Aª,õ5%���¸¢5å�ùI¦Ï­¼-môÿãO+�Us«ËÀ�ÆÁFj^E����¨�ó*ñ�Ýc.ù[Ú¿"
True

Compatibility issue with Python3

Hello,

I'm running into issues when trying to verify a signature when using python3. I'm running the following test:

from ellipticcurve.ecdsa import Ecdsa
from ellipticcurve.signature import Signature
from ellipticcurve.publicKey import PublicKey

public_key = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA=='
signature= 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0='
message = '1588788367{"event":"test_event","category":"example_payload","message_id":"message_id"}'

key = PublicKey.fromPem(publicKey)
decoded_signature = Signature.fromBase64(signature)
Ecdsa.verify(payload, decoded_signature, key) # returns True in python2 and False in python3

I would expect the call to Ecdsa.verify to return True, which is does in python2, but it returns False in python3. This same key, signature, and payload has been tested and verified successfully using the starkbank nodejs and php ecdsa packages.

verify() unstable results for certain custom defined curves

I noticed when defining custom elliptic curve in some cases the verify() doesn't always return TRUE even for valid signatures.

To fix that I had to change these lines in verify() method like this:

add = Math.add(u1, u2, P=curve.P, A=curve.A)
return sigR == add.x

to:

add = Math.add(u1, u2, P=curve.P, A=curve.A)
modX = add.x % curve.N
return sigR == modX

After this fix the verify() results were stable on every valid signature.

No option to recover public key from signature.

While the recovery info can be encoded in the signatures and parsing of signatures with recovery info is supported, it currently isn't actually possible to verify a signature with recovery info without still suplying a public key first.

Either the verify function should work without an explicit public key, or the signature should allow the public key to be extracted from it so that public key could be handed to the ecdsa verify.

(This issue is related to #37 in that the functionality I need to implement in this project has recovery info in signatures AND a public key that is compressed. If either of these issues were solved validation would be possible.)

ecdsa point addition

Hello,

Can I use ecdsa point addition or multiplication using this library?
Thanks!

Cant read file to sign

I having an issue with "encoding" - Im using your sign function

[ERROR] AttributeError: 'bytes' object has no attribute 'encode'
File "/var/task/ecdsaSignFile.py", line 21, in signFile
signature = Ecdsa.sign(fileData, privateKey)
File "/opt/python/lib/python3.8/site-packages/ellipticcurve/ecdsa.py", line 13, in sign
byteMessage = hashfunc(toBytes(message)).digest()
File "/opt/python/lib/python3.8/site-packages/ellipticcurve/utils/compatibility.py", line 15, in toBytes
return string.encode(encoding)

This is using your File.read from utilities
I have also tried standard python file open
#fileData = open(fileToSign, 'rb')
fileData = File.read(fileToSign, "rb")
This is running on Amazon Linux which I believe uses UTF8
The file is an AES128 encrypted file

Any suggestions please

Pyproject.toml & wheel over setup.py

when installing this package through CI I see the following warning:

DEPRECATION: starkbank-ecdsa is being installed using the legacy 'setup.py install' method, because it does not have a 'pyproject.toml' and the 'wheel' package is not installed. pip 23.1 will enforce this behaviour change. A possible replacement is to enable the '--use-pep517' option. Discussion can be found at pypa/pip#8559

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.