5

While informing myself on the inner-working of Monero, I stumbled on Curve25519.

The CryptoNote White Paper states:

l: a prime order of the base point; l = 2^252 + 27742317777372353535851937790883648493;
[...]
private ec-key is a standard elliptic curve private key: a number a ∈ [1, l − 1];

   Another Stack Exchange answer states the following:

All you need protocol-wise is a private spend key and a private view key, where a private key is just some number smaller than l where l is a prime order of the EC curve basepoint l=2^252 + 27742317777372353535851937790883648493. Anything bigger than that will get wrapped around ie a and a+l are equivalent.

From my understanding this means that a private key a has the same derived public key as private key a+l.

I've however been unable to reproduce the said "wrapping".

Using a very random a=0x1111111111111111111111111111111111111111111111111111111111111111 I calculated a+l=0x2111111111111111111111111111111125f00aefb408ade76923742b6e06e4fe.

I then tried to use a and a+l on llcoins as Hexadecimal Seed, as Private Spend Key or as Private View Key. They never generated the same results.

Does the wrapping occurring on a ,when greater or equal to l, occur at another step? Does it only occur sometimes?

jww
  • 103
  • 3
Maxithi
  • 577
  • 2
  • 15

1 Answers1

5

It will get wrapped around because it's later passed through sc_reduce32 function which performs mod l operation on the input.

See below example using silent Matt's big int library which is loaded with llcoins. Easiest to just open the developer tab and type stuff into JS console.

To calculate 2^252 + 27742317777372353535851937790883648493:

l = JSBigInt(2).pow(252).add(JSBigInt.parse("27742317777372353535851937790883648493",10));

To print out the number in base16:

l.toString(16);
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"

That's the l. If you swap byes and feed it as seed to llcoins, you should get 0x0 as seed. Try inputting EDD3F55C1A631258D69CF7A2DEF9DE1400000000000000000000000000000010 (little endian) as seed and you'll get 0000000000000000000000000000000000000000000000000000000000000000 as private key.

Now, we can also do the addition from your example:

a = JSBigInt.parse("0x1111111111111111111111111111111111111111111111111111111111111111");
BigInteger {_d: Array(11), _s: 1}
c = l.add(a);
BigInteger {_d: Array(11), _s: 1}
c.toString(16);
"2111111111111111111111111111111125F00AEFB408ADE76923742B6E06E4FE"

Performing mod l will give you:

c.remainder(l).toString(16);
"1111111111111111111111111111110FC3217326E19743AB8FEADF6B41B3D24"

observe how you don't get the value of a because a itself is bigger than l:

a.remainder(l).toString(16);
"1111111111111111111111111111110FC3217326E19743AB8FEADF6B41B3D24"

so adding l to this will give you the original number:

a.remainder(l).add(l).toString(16);
"1111111111111111111111111111111111111111111111111111111111111111"

If you swap endian of c and feed it as seed to llcoins, you'll get the same remainder but with little endian byte order:

seed=FEE4066E2B742369E7AD08B4EF0AF02511111111111111111111111111111121

privSpend=243d1bb4f6adfeb83a74196e321732fc10111111111111111111111111111101

JollyMort
  • 20,004
  • 3
  • 49
  • 105