Attribute error in point addition finite field programming bitcoin Jimmy Song

Hi there,

I’m reading the book ‘Programming bitcoin : Learn How to Program Bitcoin from Scratch’ by Jimmy Song. I’ve just finished chapters 1 and 2 about finite fields and elliptic curves. I understand all the math and all the exercises. Since all the answers are in the book, reproducing the code so far was easy.
I’m now combining chapter 2 (that produced a class ‘FieldElement’ ) and 3 (that produced a class ‘Point’)
Goal is to define two points P1 and P2 and then do point addition on a preset Field with order prime.
The code I have works fine… exept for the case that P1 = P2 and the y-coordinate is not zero.
(I’m checking my answers with the table of point additions on Elliptic Curves over Finite Fields and it works fine)

Here’s the error:

Here’s my code:

class FieldElement:
    def __init__(self, num, prime):
        if num >= prime or num < 0:
            error = 'num {} is not part of field 0 to {}'.format(num, prime - 1)
            raise ValueError(error)
        self.num = num
        self.prime = prime

    def __repr__(self):
        return 'FieldElement_{}({})'.format(self.prime, self.num)

    def __eq__(self, other):
        if other is None:
            return False
        return self.num == other.num and self.prime == other.prime

    def __ne__(self, other):
        return not (self == other)

    def __add__(self, other):
        if self.prime != other.prime:
            raise TypeError('You cannot add two numbers in different fields!')
        num = (self.num + other.num) % self.prime
        # return self.__class__(num, self.prime)
        return self.__class__(num, self.prime)

    def __sub__(self, other):
        if self.prime != other.prime:
            raise TypeError('You cannot subtract two numbers in different fields!')
        num = (self.num - other.num) % self.prime
        return self.__class__(num, self.prime)

    def __mul__(self, other):
        if self.prime != other.prime:
            raise TypeError('You cannot multiply two numbers in different fields!')
        num = (self.num * other.num) % self.prime
        return self.__class__(num, self.prime)

    def __pow__(self, exponent):
        if exponent < 0:
            exponent = prime - 1 + exponent
        num = (pow(self.num, exponent, self.prime))
        return self.__class__(num, self.prime)

    def __truediv__(self, other):
        if self.prime != other.prime:
            raise TypeError('You cannot divide two numbers in different fields!')
        num = self.num * pow(other.num, self.prime - 2, self.prime) % self.prime
        return self.__class__(num, self.prime)

class Point:
    def __init__(self, x, y, a, b):
        self.a = a
        self.b = b
        self.x = x
        self.y = y
        #Check if you're dealing with the point of infinity:
        if self.x is None and self.y is None:
            return
        #If not, than act as normal:
        if self.y**2 != self.x**3 + a * x + b:
            raise ValueError('({}, {}) is not on the curve'.format(x, y))

    def __add__(self, other):
        if other.a != other.a or other.b != other.b:
            raise TypeError('Points {}, {} are not on the same curves!'.format(self, other))
        #Check for point of infinity:
        if self.x is None:
            return other
        if other.x is None:
            return self
        # Check on inverse additieve property
        # That means the x of P1 en P2 is equal, but y differs so that means a vertical line
        # If so, return the point of infinity:
        if self.x == other.x and self.y != other.y:
            return self.__class__(None, None, self.a, self.b)
        # P1 does not equal P2
        if self.x != other.x:
            h = (other.y - self.y) / (other.x - self.x)
            x = h ** 2 - self.x - other.x
            y = h * (self.x - x) - self.y  # so NOT self.x - other.x here, because we're dealing with x3 here
            return self.__class__(x, y, self.a, self.b)
        # P1 = P2 and y-coordinate is not zero
        if self == other and self.y != 0:
            h = (3 * self.x ** 2 + self.a) / (2 * self.y)
            x = h ** 2 - 2 * self.x
            y = h * (self.x - x) - self.y
            return self.__class__(x, y, self.a, self.b)
        #P1 = P2 and y-coordinate is zero. Return the point of infinity
        if self == other and self.y == 0:
            return self.__class__(None, None, self.a, self.b)

    def __repr__(self):
        return "The point addition gives: {}, {}".format(self.x, self.y)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y \
            and self.a == other.a and self.b == other.b

    def __ne__(self, other):
        return not (self == other)

prime = 103
a = FieldElement(0, prime)
b = FieldElement(7, prime)
x1 = FieldElement(1, prime)
y1 = FieldElement(76, prime)
x2 = FieldElement(1, prime)
y2 = FieldElement(76, prime)
p1 = Point(x1, y1, a, b)
p2 = Point(x2, y2, a, b)

print(p1+p2)

The only thing I did different than in the book is adding

and self.y != 0

to the first line of the situation that P1 = P1 and y-coordinate is not zero (in the add()-method of the Point class) because the denominator cannot be zero.

The errortype is relatively simple, but I just don’t see what I did wrong.
I’m overlooking something, but what?

Any help is appreciated, thanks in advance.

The problem is that the inequality operator (!=), as defined by FieldElement.__ne__ is only defined for comparisons between FieldElement objects (and None). In the line you added (self.y != 0), you are trying to compare a FieldElement with an int. This is an unsupported operation using the current implementation of FieldElement.__ne__.

I am unfamiliar with the math behind this, but what properties would a FinateElement instance representing a zero-valued y-coordinate have? Those properties are what you should check for in Point.__add__.

2 Likes

If you plan to continue learning Python and write longer code I recommend you later looking into type annotations. They allow you to discover this kind of mistakes immediately.

One of the most used type checkers for Python is mypy:
https://mypy.readthedocs.io/en/stable/getting_started.html

2 Likes

Thanks for your quick reply. I also have the _ ne () methode defined in the Point class and, according to the book, that code is fine.
And furthermore, I also use that operator != in the same _ _ add
_() method below the commentline
‘# P1 does not equal P2’

And I’m still confused why all the point additions work well, except this situation. You’re right that the answer must lie in how I setup the code with the operator != and using the int ‘0’, bit I don’t know yet how to solve that…

I’ve tried to replace
self.y ! = 0
with:
y1.num ! = 0
… because y1.num gives me the value of the y-coordinate of P1.
But that didn’t work. I get a diffrerent error: “unsupported operand type(s) for *: ‘int’ and ‘FieldElement’”
…for the codeline: h = (3 * self.x ** 2 + self.a) / (2 * self.y)

Also strange, because I also use that operator in the if-statement just above this one. And for P1 does not equal P2, the code works fine.

btw: when leaving out the code and self.y != 0
… I get the same error: “unsupported operand type(s) for *: ‘int’ and ‘FieldElement’”

btw2: I checked the errata-page of the book, but with no result: Errata - O'Reilly Media

Point.__ne__ is never used in Point.__add__, so its implementation does not matter for the current issue.

To extend on this, Point.__add__ does a bunch of checks on the a, b, x, and y properties of the two Points to be added. These checks make comparisons of these properties between the two Points which are being added. Since a, b, x, and y are (as far as I can tell) always FieldElement instances, it is the comparison methods of FieldElement (__eq__ and __ne__) which get called, not the comparison methods of Point.

Where you compare two FieldElement objects, which is fine.

This is due to the early return in the case where P1 != P2. The problematic comparison is never reached.

2 Likes

I tried to replace if self == other by if self.x == other.x and self.y == other.y
(leaving the ...and self.y !=0 out for now to try to deduce the problem).

Thus the build of this line is exactly the same as the first line in the if-statement above (where P1 is not p2): if self.x != other.x
Because, as you said, there I compare two FieldElement objects, which is fine.
When I test for two different x-values for p1 and p2, that if-statement works fine: giving me back the right point addition.
But when I test for the same x- and y-values (the code under the comment '#P1=P2), I still get the same error: “TypeError: unsupported operand type(s) for *: ‘int’ and ‘FieldElement’”.

This line is the problem now, right?

h = (3 * self.x**2 + self.a) / (2 * self.y)

Here, you are trying to multiply an int (3) with a FieldElement (self.x ** 2). First, python checks if int.__mul__ knows how to handle FieldElement objects. Of course it doesn’t, since it’s something you wrote yourself.

Next, python checks if FieldElement.__rmul__ exists (since python has no way of knowing whether multiplication of FieldElement is commutative it can’t just use __mul__). FieldElement.__rmul__ is not defined, so python tells you

TypeError: unsupported operand type(s) for *: 'int' and 'FieldElement'

(Also note that this is NOT the same error as above; that was AttributeError, this is TypeError.)

What you need to do to fix this is to implement logic in FieldElement.__mul__ for each and every type of object that it should be multipliable with. Something like this:

class FieldElement:
    <...>
    def __mul__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError("You cannot multiply two numbers in different fields!")
            num = (self.num * other.num) % self.prime
            return self.__class__(num, self.prime)
        elif isinstance(other, int):
            # Note that I have no idea if this math is correct, it's just an example.
            return self.__class__((other * self.num) % self.prime, self.prime)
        elif <...>:
        else:
            raise TypeError

Then you need to do the same for __rmul__ (if multiplication of FieldElement is commutative it can just call __mul__).

You will have to do the same for all the dunder methods (__eq__, __add__, etc.).

I would also like to reiterate what @vbrozik said above: Use type hinting and check your code with mypy. There’s a learning curve, but it will effectively let you eliminate this type of bug in the development stage so that it never reaches runtime.

1 Like

Thank you for your reply. It’s late in the evening here in NL and I’ve been working on this all day, so I 'll look into your info asap tomorrow or this weekend. For now: I just solved it two minutes ago like this:

Instead of:

h = (3 * self.x**2 + self.a) /(2 * self.y)
            x = h ** 2 - 2 * self.x
            y = h * (self.x - x) - self.y

… I replaced all the int’s like this:

        if self.x == other.x and self.y == other.y:
            h = ((self.x * self.x) + (self.x * self.x) + (self.x * self.x) + self.a) / (self.y + self.y)
            x = h ** 2 - (self.x + self.x)
            y = h * (self.x - x) - self.y
            return self.__class__(x, y, self.a, self.b)

And it works!!! Thanks to you for putting me on the right track.

I respectfully disagree with @vbrozik (Václav).

While you are learning Python, it is better not to worry about type checkers. That just adds an extra thing to learn (type annotations), a new tool to run (mypy), and the extra burden of trying to get the type annotations correct.

It is better to learn Python and duck-typing first.

I am sure that static type checking and mypy has a place in Python projects with millions of lines of code and unnecessarily complicated class hierarchies, but for simple code, static typing adds little benefit to Python’s dynamic typing.

As a beginner, you would be much better off to learn good unit testing and doctesting practices, including test driven development (TDD). TDD would have found and solved these problems while you were still fighting to get the type annotation syntax correct :slight_smile:

3 Likes

Steven, I partially agree with you :slight_smile:

Certainly at the beginning you should not worry about type checking (but you still need to keep the types in your mind). That is why I wrote that I recommend looking into type annotations later. I think that suitability of type annotations for you depends on your previous experience and maybe on your personality.

When I started learning Python, I knew C already. Learning type annotations was a big relief for me :slight_smile: I see them as part of documentation and as a big aid during the program writing. I do not use very complex type annotations (so parts of my code are not type-checked).

I started to learn testing relatively long time after type annotations. I think learning testing is much more complex. I still have to learn a lot to be able to write functions which are testable without great complications. I also need to change my code so frequently that updating the tests can sometimes be more time consuming than the function design and coding. :frowning:

2 Likes

Tests are certainly a far more powerful tool than type annotations when it comes to finding bugs. It’s just that learning to write (good) tests is, in my opinion, far more difficult than learning type annotations. And in the case of the bugs discussed in this topic, type checking would have sufficed.

If I may, needing to change your tests whenever you change your code may indicate that you are testing implementation rather than behavior. If that’s intentional, forget I mentioned it. I find that restricting myself to only (mostly) testing the public interfaces of my applications lead to better tests and less test churn when making small changes to the code.

No, it is not like that. I know that in unit-testing I have to treat functions as black-boxes. My problem is with the application design proces. During writing an application I have to change parts of the design multiple times - like:

  • redesigning data structures
  • adding / removing arguments to functions
  • expanding functionality, removing redundant functionality

I hope this is just because of my lacking experience and I will learn to make a better design in the first phases of writing a code.

Writing tests for the higher levels of the application (i.e. closer to the public interface) is almost impossible at this moment. There are too many variables - configuration files, CLI arguments, external database, network communication, time…


Richard, I am sorry, it looks like we hijacked your topic. If there is a desire in continuing discussing the testing, we can split it to a separate topic :slight_smile:

1 Like

Realizing that what I did to solve it (by just replacing the int’s) was just a workaround and that your suggestion is how it should be done, I took the time to look at your info and code-suggestion.
It was completely clear to me, so I adjusted the methods and it now works fine.

Thanks again for taking the time to look into this and provide me with the information to solve this.

Here’s the good working code:

class FieldElement:
    def __init__(self, num, prime):
        if num >= prime or num < 0:
            error = 'num {} is not part of field 0 to {}'.format(num, prime - 1)
            raise ValueError(error)
        self.num = num
        self.prime = prime

    def __repr__(self):
        return 'FieldElement_{}({})'.format(self.prime, self.num)

    def __eq__(self, other):
        if isinstance(other, type(self)):
            if other is None:
                return False
            return self.num == other.num and self.prime == other.prime
        elif isinstance(other, int):
            return self.num == other
        else:
            raise TypeError

    def __ne__(self, other):
        if isinstance(other, type(self)):
            return not (self == other)
        elif isinstance(other, int):
            return not (self.num == other)

    def __add__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot add two points in different fields!')
            num = (self.num + other.num) % self.prime
        elif isinstance(other, int):
            num = (other + self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(num, self.prime)

    def __sub__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot subtract two numbers in different fields!')
            num = (self.num - other.num) % self.prime
        elif isinstance(other, int):
            num = (other - self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(num, self.prime)

    def __mul__(self, andere):
        if isinstance(andere, type(self)):
            if self.prime != andere.prime:
                raise TypeError('You cannot multiply two numbers in different fields!')
            getal = (self.num * andere.num) % self.prime
        elif isinstance(andere, int):
            getal = (andere * self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(getal, self.prime)

    def __rmul__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot multiply two numbers in different fields!')
            num = (self.num * other.num) % self.prime
        elif isinstance(other, int):
            num = (other * self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(num, self.prime)

    def __pow__(self, exponent):
        if exponent < 0:
            exponent = prime - 1 + exponent
        num = (pow(self.num, exponent, self.prime))
        return self.__class__(num, self.prime)

    def __truediv__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot divide two points in different fields')
            num = self.num * pow(other.num, self.prime - 2, self.prime) % self.prime
        elif isinstance(other, int):
            num = self.num * pow(other, self.prime - 2, self.prime) % self.prime
        else:
            raise TypeError
        return self.__class__(num, self.prime)

class Point:
    def __init__(self, x, y, a, b):
        self.a = a
        self.b = b
        self.x = x
        self.y = y
        #Check if you're dealing with the point of infinity:
        if self.x is None and self.y is None:
            return
        #If not, than act as normal:
        if self.y**2 != self.x**3 + a * x + b:
            raise ValueError('({}, {}) is not on the curve'.format(x, y))

    def __add__(self, other):
        if self.a != other.a or self.b != other.b:
            raise TypeError('Points {}, {} are not on the same curves!'.format(self, other))
        #Check for point of infinity:
        if self.x is None:
            return other
        if other.x is None:
            return self
        # Check on inverse additieve property
        # That means the x of P1 en P2 is equal, but y differs so that means a vertical line
        # If so, return the point of infinity:
        if self.x == other.x and self.y != other.y:
            return self.__class__(None, None, self.a, self.b)

        # P1 does not equal P2
        if self.x != other.x:
            h = (other.y - self.y) / (other.x - self.x)
            x = h ** 2 - self.x - other.x
            y = h * (self.x - x) - self.y  # so NOT self.x - other.x here, because we're dealing with x3 here
            return self.__class__(x, y, self.a, self.b)

        # P1 = P2 and y-coordinate is zero. Return the point of infinity
        if self == other and self.y == 0:
            return self.__class__(None, None, self.a, self.b)

        # P1 = P2
        if self == other:
            h = (3 * self.x**2 + self.a) /(2 * self.y)
            x = h ** 2 - 2 * self.x
            y = h * (self.x - x) - self.y
            return self.__class__(x, y, self.a, self.b)

    def __repr__(self):
        return "The point addition gives: {}, {}".format(self.x, self.y)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y \
            and self.a == other.a and self.b == other.b

    def __ne__(self, other):
        return not (self == other)

prime = 223
a = FieldElement(0, prime)
b = FieldElement(7, prime)
x1 = FieldElement(0, prime)
y1 = FieldElement(26, prime)
x2 = FieldElement(0, prime)
y2 = FieldElement(197, prime)
#x2 = FieldElement(8, prime)
#y2 = FieldElement(2, prime)
p1 = Point(x1, y1, a, b)
p2 = Point(x2, y2, a, b)

print(p1+p2)

No problem!

Learning…

1 Like

Following up on the right code of point addition I posted last week, I made some extensions to the code so it now:
1- does multiple point addition, thus handling point multiplication
2- plots the result in a graph.

How it works:

  1. goto Elliptic Curves over Finite Fields and choose f.i. a=0, b=7 and r (prime) = 223 Click ‘draw’
  2. Click f.i. on the point 170, 142 and, below the graph, look at the table with the point details. Remember the subgroup and just have a short look at the elements of the subgroup.
  3. Run the code below, choosing the right parameters below the remark-line “#set the parameters” (is done in the code below for the example above)
  4. The point additions are shown and the graph is plotted. The point additions can be checked in the table with the point details: they are the elements of the subgroup.

So if you want to graph point multiplication of an elliptic curve in an infinite field of order prime, here’s the code:

#This code does point multiplication in finite fields
# and plots the graph.
# It is done bij repeatingly do point ADDITION of the same point.

# importing the required module to draw the graph
import matplotlib.pyplot as plt

class FieldElement:
    def __init__(self, num, prime):
        if num >= prime or num < 0:
            error = 'num {} is not part of field 0 to {}'.format(num, prime - 1)
            raise ValueError(error)
        self.num = num
        self.prime = prime

    # __repr__modified: now only returns x an y value of number
    def __repr__(self):
        #old code: return 'FieldElement_{}({})'.format(self.prime, self.num)
        return str(self.num)

    def __eq__(self, other):
        if isinstance(other, type(self)):
            if other is None:
                return False
            return self.num == other.num and self.prime == other.prime
        elif isinstance(other, int):
            return self.num == other
        else:
            raise TypeError

    def __ne__(self, other):
        if isinstance(other, type(self)):
            return not (self == other)
        elif isinstance(other, int):
            return not (self.num == other)

    def __add__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot add two points in different fields!')
            num = (self.num + other.num) % self.prime
        elif isinstance(other, int):
            num = (other + self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(num, self.prime)

    def __sub__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot subtract two numbers in different fields!')
            num = (self.num - other.num) % self.prime
        elif isinstance(other, int):
            num = (other - self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(num, self.prime)

    def __mul__(self, andere):
        if isinstance(andere, type(self)):
            if self.prime != andere.prime:
                raise TypeError('You cannot multiply two numbers in different fields!')
            getal = (self.num * andere.num) % self.prime
        elif isinstance(andere, int):
            getal = (andere * self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(getal, self.prime)

    def __rmul__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot multiply two numbers in different fields!')
            num = (self.num * other.num) % self.prime
        elif isinstance(other, int):
            num = (other * self.num) % self.prime
        else:
            raise TypeError

        return self.__class__(num, self.prime)

    def __pow__(self, exponent):
        if exponent < 0:
            exponent = prime - 1 + exponent
        num = (pow(self.num, exponent, self.prime))
        return self.__class__(num, self.prime)

    def __truediv__(self, other):
        if isinstance(other, type(self)):
            if self.prime != other.prime:
                raise TypeError('You cannot divide two points in different fields')
            num = self.num * pow(other.num, self.prime - 2, self.prime) % self.prime
        elif isinstance(other, int):
            num = self.num * pow(other, self.prime - 2, self.prime) % self.prime
        else:
            raise TypeError
        return self.__class__(num, self.prime)

class Point:
    def __init__(self, x, y, a, b):
        self.a = a
        self.b = b
        self.x = x
        self.y = y
        #Check if you're dealing with the point of infinity:
        if self.x is None and self.y is None:
            return
        #If not, than act as normal:
        if self.y**2 != self.x**3 + a * x + b:
            raise ValueError('({}, {}) is not on the curve'.format(x, y))

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y \
            and self.a == other.a and self.b == other.b

    def __ne__(self, other):
        return not (self == other)

    def __add__(self, other):
        if self.a != other.a or self.b != other.b:
            raise TypeError('Points {}, {} are not on the same curves!'.format(self, other))
        #Check for point of infinity:
        if self.x is None:
            return other
        if other.x is None:
            return self
        # Check on inverse additieve property
        # That means the x of P1 en P2 is equal, but y differs so that means a vertical line
        # If so, return the point of infinity:
        if self.x == other.x and self.y != other.y:
            return self.__class__(None, None, self.a, self.b)

        # P1 does not equal P2
        if self.x != other.x:
            h = (other.y - self.y) / (other.x - self.x)
            x = h ** 2 - self.x - other.x
            y = h * (self.x - x) - self.y  # so NOT self.x - other.x here, because we're dealing with x3 here
            return self.__class__(x, y, self.a, self.b)

        # P1 = P2 and y-coordinate is zero. Return the point of infinity
        if self == other and self.y == 0:
            return self.__class__(None, None, self.a, self.b)

        # P1 = P2
        if self == other:
            h = (3 * self.x**2 + self.a) /(2 * self.y)
            x = h ** 2 - 2 * self.x
            y = h * (self.x - x) - self.y
            return self.__class__(x, y, self.a, self.b)
    #__repr__method changed so it kan handle multiple additions
    def __repr__(self):
        global x3, y3, a, b, a3, b3
        try:
            x3 = getattr(self.x, 'num')
        except:
            print("point of infinity reached")
            return
        x3 = getattr(self.x, 'num')
        y3 = getattr(self.y, 'num')
        a3 = getattr(self.a, 'num')
        b3 = getattr(self.b, 'num')
        return x3, y3, a3, b3

#set the parameters
prime = 223
    #set the x- and y values of first point. These remains the same during the loop
x_value = 170
y_value = 142
    # look up the subgroup number on https://www.graui.de/code/elliptic2/
    # That is the 'order of subgroup' number in the Point Detail Table
    #Put that number here:
subgroup = 42

    #The second point is changes after every addition.
    # Here, the initial value is set.
x2 = FieldElement(x_value, prime)
y2 = FieldElement(y_value, prime)
    #create lists for the x- and y-values so we can plot them later,
    #And assign the first point to the first list-item
x_as = [x_value]
y_as = [y_value]

#loop through all the additions within the subgroup
counter = 1
while counter < subgroup-1:
    #set the values for a and b for the curve y^2 = x^3 + ax + b
    #For the btc-curve, a = 0 and b = 7, resulting in y^2 = x^3 + 7
    # parameters 0 and 7 are filled in below, in the num attrib
    a = FieldElement(0, prime)
    b = FieldElement(7, prime)

    #the first point addition adds identical points. So initially: p1=p2
    x1 = FieldElement(x_value, prime)
    y1 = FieldElement(y_value, prime)

        #define the objects p1 and p2
    p1 = Point(x1, y1, a, b)
    p2 = Point(x2, y2, a, b)

    #do point addition in finite field and show result
    p3 = (p1+p2)
    print(p3.__repr__())

    #now the values/attribs of p3 become the new p2 for the next addition
    p2 = p3
    x2 = FieldElement(x3, prime)
    y2 = FieldElement(y3, prime)

    # add the new x- and y values to the list
    x_as.append(x3)
    y_as.append(y3)

    counter+=1

# naming the x axis
plt.xlabel('x - axis')
# naming the y axis
plt.ylabel('y - axis')

# giving a title to my graph
plt.title('Point multiplication in finite field of order ' + str(prime))

# show only the points( not a line ) with scatter and show the graph
plt.scatter(x_as,y_as)
plt.show()

To show coordinates with the points, just add this code just above the plt.scatter code:

for i, j in zip(x_as, y_as):
   plt.text(i, j+0.5, '({}, {})'.format(i, j))

Or, to show the index numbers with the points, instead add this code:

counter2=1
for i, j in zip(x_as, y_as):

    plt.text(i+0.5, j+0.5,  '{}'.format(counter2))
    counter2+=1