Pages:
Author

Topic: [C#] Trying to implement EC Multiplication in pure code (Read 427 times)

legendary
Activity: 3472
Merit: 10611
The most popular way to indicate a negative number is with "-". They are doing a memory dump. Is there a way to print and parse the number properly? In DER encoding it is justified, we get a single long hex digit sequence, but when printing a single number it just looks wrong, and consequently increases the cognitive load. One could write in the source code -0xff, and all is good, but AFAIK there's no (easy) way to parse "-0xff" as a BigInteger.
You are thinking as a "human" not a "computer". In computer there is no "-" there are only bits and when we are representing an arbitrary length octet string there has to be a certain way we represent the sign and that is the most significant bit.
0xff is 0b11111111 and since the most significant bit is set this number is negative.
full member
Activity: 206
Merit: 450
This is a C# thing, it adds a leading zero when MSB of the top byte is one, something to do with distinguishing between positive and negative numbers. You have to be careful when parsing hex numbers as well: BigInteger.Parse("ff") = -1.

Overall C# is a language with many such intricacies (or I would say stupid choices).
This is not a C# thing at all. This is a popular way in computers to indicate positive/negative numbers which the BigInteger class also uses. In fact we are using a similar logic in bitcoin signatures that are encoded using DER encoding (use most significant bit of the most significant byte to indicate sign).

The most popular way to indicate a negative number is with "-". They are doing a memory dump. Is there a way to print and parse the number properly? In DER encoding it is justified, we get a single long hex digit sequence, but when printing a single number it just looks wrong, and consequently increases the cognitive load. One could write in the source code -0xff, and all is good, but AFAIK there's no (easy) way to parse "-0xff" as a BigInteger.
legendary
Activity: 3472
Merit: 10611
This is a C# thing, it adds a leading zero when MSB of the top byte is one, something to do with distinguishing between positive and negative numbers. You have to be careful when parsing hex numbers as well: BigInteger.Parse("ff") = -1.

Overall C# is a language with many such intricacies (or I would say stupid choices).
This is not a C# thing at all. This is a popular way in computers to indicate positive/negative numbers which the BigInteger class also uses. In fact we are using a similar logic in bitcoin signatures that are encoded using DER encoding (use most significant bit of the most significant byte to indicate sign).
full member
Activity: 206
Merit: 450
Thank you!

Your code works fine, there's just one thing which slips (?) the process. It adds an additional zero sometimes in front of the x & y and I don't know what's responsible for that.  For example, if I run it with k=1, it returns it properly:
Code:
x: 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
y: 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8

Trying it with k=2, you'll get:
Code:
x: 0C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5
y: 1AE168FEA63DC339A3C58419466CEAEEF7F632653266D0E1236431A950CFE52A

That extra zero (0C60...) shouldn't exist, because obviously, once is hashed with SHA256 it'll give a different result.

This is a C# thing, it adds a leading zero when MSB of the top byte is one, something to do with distinguishing between positive and negative numbers. You have to be careful when parsing hex numbers as well: BigInteger.Parse("ff") = -1.

Overall C# is a language with many such intricacies (or I would say stupid choices).
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
Thank you!

Your code works fine, there's just one thing which slips (?) the process. It adds an additional zero sometimes in front of the x & y and I don't know what's responsible for that.  For example, if I run it with k=1, it returns it properly:
Code:
x: 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
y: 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8

Trying it with k=2, you'll get:
Code:
x: 0C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5
y: 1AE168FEA63DC339A3C58419466CEAEEF7F632653266D0E1236431A950CFE52A

That extra zero (0C60...) shouldn't exist, because obviously, once is hashed with SHA256 it'll give a different result.




Seriously now, I'll say this again; thank you. You wasted your time to write me code? The least I can do is merit you.
full member
Activity: 206
Merit: 450
...
I could swear that ^n means raising to the power of n. But, it turns out that it doesn't. It actually means index from end operator. As for the missing parts, I didn't know them. I was only following the ruby scripts from learnmeabitcoin. Thanks for the time it took you to correct me.


I would say it is Logical exclusive OR operator ^.

Quote
I did make the changes, but still get false result. For k=100 I should be getting:

Test it step by step:
Is ECdouble giving the correct result for G, 2G, O?
Is ECaddition doing it as well: G+G, G+(-G), G+2G, 2G+3G, 3G+4G?
Finally is ECMultiplication correct for small numbers (1-10), for big numbers (n-1), for numbers >=n ?


Your inverse function is still wrong. You could give up and use the simpler (but slower) version:
Code:
        public static BigInteger inverse(BigInteger a, BigInteger m)
        {
            return BigInteger.ModPow(a, m - 2, m);
        }

Looking at your struggle is just painful. Here is working code.
Code:
using System;
using System.Numerics;

public class Program
{
    static BigInteger p = BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007908834671663");
    static BigInteger n = BigInteger.Parse("115792089237316195423570985008687907852837564279074904382605163141518161494337");
    static BigInteger[] zero = {0,0};
    static BigInteger[] g = {
        BigInteger.Parse("55066263022277343669578718895168534326250603453777594175500187360389116729240"),
        BigInteger.Parse("32670510020758816978083085130507043184471273380659243275938904335757337482424")
    };
    public static void Main()
    {
        BigInteger[] g0 = {0,0}, g2 = ECdouble(g), g4 = ECdouble(g2);
        BigInteger[] z = ECMultiplication(7, g);
        //z = ECaddition(z, g);
        //z = ECaddition(z, g2);
        //z = ECaddition(z, g4);
        Console.WriteLine(z[0].ToString());
        Console.WriteLine(z[1].ToString());
        Console.WriteLine(IsOnCurve(z));
        Console.WriteLine(IsOnCurve(zero));
    }

    public static BigInteger inverse(BigInteger a, BigInteger m) { return BigInteger.ModPow(a, m-2, m); }
    public static BigInteger[] ECdouble(BigInteger[] point)
        {
            if (point[1] == 0) return zero;
            BigInteger slope = (3 * BigInteger.ModPow(point[0], 2, p) * inverse((2 * point[1]), p)) % p;
            BigInteger x = (BigInteger.ModPow(slope, 2, p) - (2 * point[0])) % p;
            BigInteger y = (slope * (point[0] - x) - point[1]) % p;
            if (x < 0) x += p;
            if (y < 0) y += p;
            BigInteger[] coord = { x, y };
            return coord;

        }
    public static BigInteger[] ECaddition(BigInteger[] point1, BigInteger[] point2)
        {
            if (point1[1] == 0) return point2;
            if (point2[1] == 0) return point1;
            if (point1[0] == point2[0]) {
                if (point1[1] == point2[1]) return ECdouble(point1);
                return zero;
            }
            BigInteger slope = ((point2[1] - point1[1]) * inverse(point2[0] - point1[0], p)) % p;
            BigInteger x = (BigInteger.ModPow(slope, 2, p) - point1[0] - point2[0]) % p;
            BigInteger y = ((slope * (point1[0] - x)) - point1[1]) % p;
            if (x < 0) x += p;
            if (y < 0) y += p;
            BigInteger[] coord = { x, y };
            return coord;
        }

    public static BigInteger[] ECMultiplication(BigInteger k, BigInteger[] Gpoint)
        {
            BigInteger[] powerOfTwo = Gpoint;
            BigInteger[] result = zero;
            k %= n; if (k < 0) k += n;
            while (k > 0) {
                if ((k&1) == 1) result = ECaddition(result, powerOfTwo);
                k >>= 1;
                powerOfTwo = ECdouble(powerOfTwo);
            }
            return result;
        }
    public static bool IsOnCurve(BigInteger[] point)
        {
            BigInteger x = point[0] % p; if (x<0) x += p;
            BigInteger y = point[1] % p; if (y<0) y += p;
            return BigInteger.ModPow(y, 2, p) == (BigInteger.ModPow(x, 3, p) + 7) % p;
        }
}
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
...
I could swear that ^n means raising to the power of n. But, it turns out that it doesn't. It actually means index from end operator. As for the missing parts, I didn't know them. I was only following the ruby scripts from learnmeabitcoin. Thanks for the time it took you to correct me.

I did make the changes, but still get false result. For k=100 I should be getting:
Code:
048282263212C609D9EA2A6E3E172DE238D8C39CABD5AC1CA10646E23FD5F5150811F8A8098557DFE45E8256E830B60ACE62D613AC2F7B17BED31B6EAFF6E26CAF

But, I get:
Code:
040F19435668D97C96E4AE99B4BC78EB71826D36F9B380E0B462FD3159F5D896642B2B297DED0525941A6BEBB8386979BDEFBC7DFC6707C6871E67983E0807E4523

So, I still have something wrong in my code. Just to catch you; I used n*n instead of Math.Pow(n, 2) just because they're BigInteger and the Math.Pow(n, 2) would require extra additions which would make the code even more complex. Both of them work fine.
full member
Activity: 206
Merit: 450
Marked with red are some grievous mistakes. Blue are some missing parts.
Since you managed to discard any feedback let me make it very clear:
^ 2 is not squaring a number: 2^2 = 0

public BigInteger[] ECdouble(BigInteger[] point)
        {
           if (point[1] == 0) {
                BigInteger[] coord = {0,0};
                return coord;
            }
           BigInteger slope = ((3 * point[0] ^ 2) * inverse((2 * point[1]), p)) % p;
            BigInteger x = (slope ^ 2 - (2 * point[0])) % p;
            BigInteger y = (slope * (point[0] - x) - point[1]) % p;
            BigInteger[] coord = { x, y };
            return coord;

        }


public BigInteger[] ECaddition(BigInteger[] point1, BigInteger[] point2)
        {
            if (point1[0] == point2[0] && point1[1] == point2[1])
            {
                return ECdouble(point1);
            }
           if (point1[0] == point2[0])
            {
                BigInteger[] coord = {0,0};
                return coord;
            }

           BigInteger slope = ((point1[1] - point2[1]) * inverse(point1[0] - point2[0], p)) % p;
            BigInteger x = (slope ^ 2 - point1[0] - point2[0]) % p;
            BigInteger y = ((slope * (point1[0] - x)) - point1[1]) % p;
            BigInteger[] coord = { x, y };
            return coord;
        }


member
Activity: 873
Merit: 22
$$P2P BTC BRUTE.JOIN NOW ! https://uclck.me/SQPJk
More about modern and fast point addiation. I think this will be more good then simple add or doubling points.

Try implement in yours code https://paulmillr.com/posts/noble-secp256k1-fast-ecc/#unsafe-multiplication-for-key-recovery
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
Tuesday July 27th 2021 and I still face it.

My modular inverse function should be working fine.
Code:
public BigInteger inverse(BigInteger a, BigInteger m)
        {
            BigInteger m_orig = m;
            BigInteger prevy = 0;
            BigInteger y = 1;
            BigInteger q;
            BigInteger oldy = 0;
            BigInteger olda = 0;
            if (a < 0)
            {
                a = a % m;
            }
            while (a > 1)
            {
                q = m / a;
                oldy = y;
                y = prevy - q * y;
                prevy = oldy;
                olda = a;
                a = m % a;
                m = olda;
            }
            return y % m_orig;
        }

I rechecked the ECdouble & ECaddition and made a few corrections:
Code:
public BigInteger[] ECdouble(BigInteger[] point)
        {
            BigInteger slope = ((3 * point[0] ^ 2) * inverse((2 * point[1]), p)) % p;
            BigInteger x = (slope ^ 2 - (2 * point[0])) % p;
            BigInteger y = (slope * (point[0] - x) - point[1]) % p;
            BigInteger[] coord = { x, y };
            return coord;

        }

Code:
public BigInteger[] ECaddition(BigInteger[] point1, BigInteger[] point2)
        {
            if (point1[0] == point2[0] && point1[1] == point2[1])
            {
                return ECdouble(point1);
            }

            BigInteger slope = ((point1[1] - point2[1]) * inverse(point1[0] - point2[0], p)) % p;
            BigInteger x = (slope ^ 2 - point1[0] - point2[0]) % p;
            BigInteger y = ((slope * (point1[0] - x)) - point1[1]) % p;
            BigInteger[] coord = { x, y };
            return coord;
        }

The ECmultiplication was completely wrong and I'm curious why nobody noticed it. Not sure if it's now correct, but:
Code:
public BigInteger[] ECMultiplication(BigInteger k, BigInteger[] Gpoint)
        {
            BigInteger[] current = Gpoint;

            //private key to binary
            string binary = String.Join(String.Empty,
              privatekey.Select(
                c => Convert.ToString(Convert.ToInt32(c.ToString(), 16), 2).PadLeft(4, '0')
              )
            );

            // ignoring the first binary character
            binary = binary.Substring(1);
           
            for (int i=0; i            {
                current = ECdouble(current);
                if (binary[i] == '1')
                {
                    current = ECaddition(current, Gpoint);
                }
                   
               
            }
           
            return current;
        }

And finally, for k = 100 I should be getting:
Code:
048282263212C609D9EA2A6E3E172DE238D8C39CABD5AC1CA10646E23FD5F5150811F8A8098557DFE45E8256E830B60ACE62D613AC2F7B17BED31B6EAFF6E26CAF

But, my code has another opinion:
Code:
047D285CA13DEC25E44F435B6876601CC9042F8787AD8B7DCC1F6588FF50D5327FF21E17C179DFC3A565E3ECCD0EEF92D9A39D6B23FAB1F8093A72E3468C9A2A335




[My C# code]
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
Alright, so back to this thread five weeks later. Coding Enthusiast, your code works great; I'm just continuing this, because I now want to complete it straightly in my writing without using any external libraries. I've edited the modular inverse function, due to the assignments' parallelism as said by HCP. (I've also corrected the a>0 too)

modinv should be working properly:
Code:
public BigInteger modinv(BigInteger a, BigInteger m)
        {
            BigInteger prevy = 0;
            BigInteger y = 1;
            BigInteger q;
            BigInteger oldy;
            BigInteger olda;
            if (a < 0)
            {
                a = a % m;
            }
            while (a > 1)
            {
                q = m / a;
                oldy = y;
                y = prevy - q * y;
                prevy = oldy;
                olda = a;
                a = m % a;
                m = olda;
            }
            return y;
        }

If I haven't made any other mistakes in EC addition, multiplication and doubling, then it must be on the curve's variables:

Code:
string privatekey = "5"; // this is the private key in hex
BigInteger p = BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007908834671663");
BigInteger[] g =
{
        BigInteger.Parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", NumberStyles.AllowHexSpecifier),
        BigInteger.Parse("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", NumberStyles.AllowHexSpecifier)
};

This is what I run:
Code:
BigInteger k = BigInteger.Parse(privatekey, NumberStyles.AllowHexSpecifier);
BigInteger[] point = ECMultiplication(k, g);
string x = point[0].ToString("X");
string y = point[1].ToString("X");
string public_key_uncompressed = "04" + x + y;
ECDSApublic.Text = public_key_uncompressed;

And that's what I get:
Code:
0441721458CC97441B6C43006E2AE8050D55F8A200A22E067BA1D4F6C4E846B27AF5D0F2E457F91F826EC0412BEA2A13BADD81D5DB59009620EA2E56C927D6ED521

While I should be getting:
Code:
042F8BDE4D1A07209355B4A7250A5C5128E88B84BDDC619AB7CBA8D569B240EFE4D8AC222636E5E3D6D4DBA9DDA6C9C426F788271BAB0D6840DCA87D3AA6AC62D6
HCP
legendary
Activity: 2086
Merit: 4363
I turned this:
Quote
# Modular Inverse - Ruby doesn't have a built-in function for finding modular inverses, so here's one using the extended Euclidean algorithm.
def modinv(a, m = $p)
  a = a % m if a < 0 # make sure a is positive
  prevy, y = 0, 1
  while a > 1
    q = m / a
    y, prevy = prevy - q * y, y
    a, m = m % a, a
  end
  return y
end

Into this:
Quote
public BigInteger modinv(BigInteger a, BigInteger m)
        {
            BigInteger prevy = 0;
            BigInteger y = 1;
            BigInteger q;
            if(a > 0)
            {
                a = a % m;
            }
            while(a > 1)
            {
                q = m / a;
                y = prevy - q * y;
                prevy = y;
                a = m % a;
                m = a;
            }
            return y;
        }

Assuming these code snippets have all been copy/pasted here and not typed by hand... are you sure that your "a>0" check is correct? Huh Seems like it should be "a<0" as per the Ruby example.


Also, the your lines:
Code:
                y = prevy - q * y;
                prevy = y;
                a = m % a;
                m = a;

Won't work... because in Ruby the assignments/evaluation in the following code:
Code:
    y, prevy = prevy - q * y, y
    a, m = m % a, a
happens in parallel... so you'll need to use some temp variables to hold the "old" value of y and a...

ie.
Code:
                oldy = y
                y = prevy - q * y;
                prevy = oldy;
                olda = a
                a = m % a;
                m = olda;

Otherwise your final values of prevy and m will be the wrong values.
legendary
Activity: 1042
Merit: 2805
Bitcoin and C♯ Enthusiast
I don't know how to turn this PublicKey to string. Obviously, ToPublicKey().ToString() didn't work.
Assuming you are using the PublicKey class provided here you have to call the ToByteArray() method and then encode the bytes however you want. byte[] has a special extension method to convert to hexadecimal called ToBase16().

BTW if you want to get public key of a private key it is best to use the PrivateKey class directly since it makes sure you have a valid key.
Code:
var key = new PrivateKey(rng_or_wif_or_bytes_or_int);
string compPubHex = key.ToPublicKey().ToByteArray(true).ToBase16();
key.Dispose();

Also, how do I get rid of Autarkysoft.Bitcoin.Cryptography.Asymmetric.EllipticCurve each time I want to include a part from EllipticCurveCalculator to my main c# script? I'm imported lots of usings, but it seems that it always needs the namespace:
I'm not sure what you are asking here. Each .cs file has to have the using directives before you can use the types in that namespace. This is not something you can get rid of.
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
Private key to public key code would be MultiplyByG(BigInteger k) method.
Thank you. I'm now trying it, because it seems simple. I've installed Bitcoin.net and copied this code:
Code:
        private readonly Autarkysoft.Bitcoin.Cryptography.Asymmetric.EllipticCurve.EllipticCurveCalculator calc = new Autarkysoft.Bitcoin.Cryptography.Asymmetric.EllipticCurve.EllipticCurveCalculator();

        public PublicKey ToPublicKey()
        {
            return new PublicKey(calc.MultiplyByG(BigInteger.Parse("1")));
        }

I don't know how to turn this PublicKey to string. Obviously, ToPublicKey().ToString() didn't work. Also, how do I get rid of Autarkysoft.Bitcoin.Cryptography.Asymmetric.EllipticCurve each time I want to include a part from EllipticCurveCalculator to my main c# script? I'm imported lots of usings, but it seems that it always needs the namespace:
Code:
using Autarkysoft.Bitcoin.Cryptography.Arithmetic;
using Autarkysoft.Bitcoin.Cryptography.Asymmetric.KeyPairs;
using Autarkysoft.Bitcoin.Cryptography.Hashing;
using Autarkysoft.Bitcoin.Blockchain.Scripts;
using Autarkysoft.Bitcoin.Blockchain.Transactions;
using Autarkysoft.Bitcoin.Cryptography.Asymmetric.EllipticCurve;

Unless this is just for educational purposes you shouldn't do it.
It's not exactly educational purposes. I surely do this for exercise, but I just feel that I have to write it on a standalone c# script for one of my projects I have in mind.
legendary
Activity: 1568
Merit: 6660
bitcoincleanup.com / bitmixlist.org
Into this:
Code:
 public BigInteger[] ECMultiplication(BigInteger k, BigInteger[] Gpoint)
        {
            BigInteger[] current = g;
            string binary = String.Join(String.Empty,
              privatekey.Select(
                c => Convert.ToString(Convert.ToInt32(c.ToString(), 16), 2).PadLeft(4, '0')
              )
            );

            // ignoring the first binary character
            binary = binary.Substring(1);
            current = ECdouble(current);
            if (binary[0] == '1') {
                current = ECaddition(current, Gpoint);
            }
            return current;
        }

You're representing the points as an array which is unnecessary. You just need two variables to represent k and another two passed as reference for the return value.

Also why are you converting the BigInteger to a string and back again? It will cause a lot of unnecessary overhead and is probably the reason why you get a different result. You should be able to do this with just pure math, right?
legendary
Activity: 1042
Merit: 2805
Bitcoin and C♯ Enthusiast
but I am not sure that I understand these terms. I do have understood theoretically how ECC works, but in its maths, it seems impossible to make any sense.
Unless this is just for educational purposes you shouldn't do it. Use a library like libsecp256k1 with a wrapper in C#.

That being said, it'd really satisfy me if there's an already written code (just like in learnmeabitcoin) in C#.
Bitcoin.Net has an EllipticCurve namespace which is a very simple implementation of ECC.
Private key to public key code would be MultiplyByG(BigInteger k) method.
Keep in mind that this is minimally tested using NIST Cryptographic Algorithm Validation Program since I plan to completely replace it before releasing version 1.0.

If you want to see a more efficient implementation based on libsec256k1 check out the ECC namespace in FinderOuter.
copper member
Activity: 909
Merit: 2301
Follow this article: https://www.coindesk.com/math-behind-bitcoin

First, implement it using small numbers. After that, increase the numbers and make sure there is no brute force (like checking every number to find modulo inverse). Understanding the code is the most important part, so starting with small numbers should help.
newbie
Activity: 25
Merit: 1
maybe you should use a library.

For that particular function? For modinv only?

I use this one for modular multiplicative inverse.

Code:
int modInverse(int a, int n) 
{
    int i = n, v = 0, d = 1;
    while (a>0) {
        int t = i/a, x = a;
        a = i % x;
        i = x;
        x = d;
        d = v - t*x;
        v = x;
    }
    v %= n;
    if (v<0) v = (v+n)%n;
    return v;
}

On the other hand you would save your time using a library like Bouncy Castle for all your work on ecc calculation  Cry
You can find good source code to learn like Casascius one - https://github.com/casascius/Bitcoin-Address-Utility

full member
Activity: 206
Merit: 450
maybe you should use a library.

For that particular function? For modinv only?

Looks like for all. One more mistake is using ^2 for square. In C# (and C, C++, etc.) this is XOR with 2.

Do you really know C#? When translating code you should first understand what it does, both original and translated. Anyway, this must be a good learning experience for you. Maybe check what it does step by step and consult manuals more.

newbie
Activity: 1
Merit: 0
I'm working in something like at the university. Try to check if c# doesn't round any result.
Pages:
Jump to: