4

I am trying to implement the function in the following section: Per-commitment Secret Requirements.

generate_from_seed(seed, I):
    P = seed
    for B in 47 down to 0:
        if B set in I:
            flip(B) in P
            P = SHA256(P)
    return P

Where "flip(B)" alternates the B'th least significant bit in the value P.

According to this definition, if we have seed=0x0101010101010101010101010101010101010101010101010101010101010101 and I=1, I would expect the result to be

>>> from hashlib import sha256
>>> from binascii import hexlify

>>> hexlify(sha256(int(("00000001"*31)+"00000000",2).to_bytes(length=32,byteorder="big")).digest())
b'79356295f56e69998b9140cb77c63d3d80c93874259793a38d1dbd8678809ca9'

Because the flip function is executed once, setting the 0th LSB (the rightmost bit) to 0.

Instead the result is (test vectors):

>>> hexlify(sha256(int("00000000"+("00000001"*31),2).to_bytes(length=32,byteorder="big")).digest())
b'915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c'

And looking at one implementation, it is clear that people are implementing this using:

output[lp / 8] ^= (1 << (lp % 8));

Which seems to me as wrong, since it is changing the LSB of the byte which, if lp is small, would be more significant, and therefore the "most significant" according to my interpretation. But inside the byte, it is changing a bit that could be, if we are working in big-endian mode, indexed in the opposite direction. Which doesn't follow from the spec, since it only talks about bits.

While I could use little-endianness in this example, and it would fix this specific test, it wouldn't work on the other test vectors, so I don't consider that a proper fix, and that wouldn't make sense since the spec says nothing about using little-endianness.

Somebody please help me understand the definition of "least significant bit" such that these test vectors make sense. It seems to me, that it requires me to consider the existence of bytes, which doesn't follow from my understanding of LSB.

Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196

2 Answers2

-1

This works on all five tests of generate_from_seed:

            byte[] p = new byte[32];
            for (int i = 0; i < 32; ++i)
                p[i] = seed[i];
            for (int i = 47; i >= 0; --i)
            {
                int byteNumber =  i / 8;
                int bitNumber = (i % 8);
                byte mask = (byte) (1 << bitNumber);

                // access index in little endian order !!!

                if ((index[5 - byteNumber] & mask) == mask)
                {
                    p[byteNumber] ^= mask;
                    p = Utils.SHA256Hash(p);
                }
            }
            return p;
hxsquid
  • 19
  • 4
  • I am not asking for a program that passes the test, I am asking for an explanation of how the tests can fit with the pseudocode. Also, I don't see what your solution contributes since it is more complicated than the one in ptarmigan. – Janus Troelsen Jul 18 '18 at 09:09
-1

It was a spec bug, it didn't mention that this used little-endian: https://github.com/lightningnetwork/lightning-rfc/pull/779

Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196