コード例 #1
0
        public unsafe void Init_OuterPad_Bitcoinseed_Test()
        {
            int extraLen = 5;

            byte[] extraEndBa = GetRandomBytes(extraLen);
            int    dataLen    = 128 + extraLen;

            byte[] data = new byte[dataLen];
            ((Span <byte>)data).Fill(0x5c);
            Buffer.BlockCopy(extraEndBa, 0, data, 128, extraLen);
            // XOR "Bitcoin seed" with initial bytes
            byte[] xor = Encoding.UTF8.GetBytes("Bitcoin seed");
            for (int i = 0; i < xor.Length; i++)
            {
                data[i] ^= xor[i];
            }

            byte[] expected = ComputeSingleSha(data);

            using Sha512Fo sha = new Sha512Fo();
            fixed(byte *dPt = &data[0])
            fixed(ulong *hPt = &sha.hashState[0], wPt = &sha.w[0])
            {
                sha.Init_OuterPad_Bitcoinseed(hPt);
                sha.CompressData(dPt + 128, extraLen, dataLen, hPt, wPt);
                byte[] actual = sha.GetBytes(hPt);

                Assert.Equal(expected, actual);
            }
        }
コード例 #2
0
        public unsafe bool SetBip32(ulong *bigBuffer, ICompareService comparer)
        {
            ulong *hPt    = bigBuffer;
            ulong *wPt    = hPt + Sha512Fo.HashStateSize;
            ulong *seedPt = wPt + Sha512Fo.WorkingVectorSize;
            ulong *iPt    = seedPt + Sha512Fo.HashStateSize;
            ulong *oPt    = iPt + Sha512Fo.WorkingVectorSize;

            // *** BIP32 ***
            // Set from entropy/seed by computing HMAC(data=seed, key="Bitcoin seed")

            // Final result is SHA512(outer_pad | SHA512(inner_pad | data)) where data is 64-byte seed
            // 1. Compute SHA512(inner_pad | data)
            Sha512Fo.Init_InnerPad_Bitcoinseed(hPt);
            *(Block64 *)wPt = *(Block64 *)seedPt;
            // from wPt[8] to wPt[15] didn't change
            Sha512Fo.Compress192SecondBlock(hPt, wPt);

            // 2. Compute SHA512(outer_pad | hash)
            *(Block64 *)wPt = *(Block64 *)hPt; // ** Copy hashState before changing it **
                                               // from wPt[8] to wPt[15] didn't change
            Sha512Fo.Init_OuterPad_Bitcoinseed(hPt);
            Sha512Fo.Compress192SecondBlock(hPt, wPt);
            // Master key is set. PrivateKey= first 32-bytes of hPt and ChainCode is second 32-bytes

            // Each child is derived by computing HMAC(data=(hardened? 0|prvKey : pubkey) | index, key=ChainCode)
            // ChainCode is the second 32-byte half of the hash. Set pad items that never change here:
            // TODO: this part can be set by the caller outside its loop
            iPt[4]  = 0x3636363636363636U;
            iPt[5]  = 0x3636363636363636U;
            iPt[6]  = 0x3636363636363636U;
            iPt[7]  = 0x3636363636363636U;
            iPt[8]  = 0x3636363636363636U;
            iPt[9]  = 0x3636363636363636U;
            iPt[10] = 0x3636363636363636U;
            iPt[11] = 0x3636363636363636U;
            iPt[12] = 0x3636363636363636U;
            iPt[13] = 0x3636363636363636U;
            iPt[14] = 0x3636363636363636U;
            iPt[15] = 0x3636363636363636U;

            oPt[4]  = 0x5c5c5c5c5c5c5c5cU;
            oPt[5]  = 0x5c5c5c5c5c5c5c5cU;
            oPt[6]  = 0x5c5c5c5c5c5c5c5cU;
            oPt[7]  = 0x5c5c5c5c5c5c5c5cU;
            oPt[8]  = 0x5c5c5c5c5c5c5c5cU;
            oPt[9]  = 0x5c5c5c5c5c5c5c5cU;
            oPt[10] = 0x5c5c5c5c5c5c5c5cU;
            oPt[11] = 0x5c5c5c5c5c5c5c5cU;
            oPt[12] = 0x5c5c5c5c5c5c5c5cU;
            oPt[13] = 0x5c5c5c5c5c5c5c5cU;
            oPt[14] = 0x5c5c5c5c5c5c5c5cU;
            oPt[15] = 0x5c5c5c5c5c5c5c5cU;

            Scalar sclrParent = new(hPt, out int overflow);

            if (overflow != 0)
            {
                return(false);
            }

            foreach (uint index in path.Indexes)
            {
                if ((index & 0x80000000) != 0) // IsHardened
                {
                    // First _byte_ is zero
                    // private-key is the first 32 bytes (4 items) of hPt (total 33 bytes)
                    // 4 bytes index + SHA padding are also added
                    wPt[0] = (ulong)sclrParent.b7 << 24 | (ulong)sclrParent.b6 >> 8;
                    wPt[1] = (ulong)sclrParent.b6 << 56 | (ulong)sclrParent.b5 << 24 | (ulong)sclrParent.b4 >> 8;
                    wPt[2] = (ulong)sclrParent.b4 << 56 | (ulong)sclrParent.b3 << 24 | (ulong)sclrParent.b2 >> 8;
                    wPt[3] = (ulong)sclrParent.b2 << 56 | (ulong)sclrParent.b1 << 24 | (ulong)sclrParent.b0 >> 8;
                    wPt[4] = (ulong)sclrParent.b0 << 56 |
                             (ulong)index << 24 |
                             0b00000000_00000000_00000000_00000000_00000000_10000000_00000000_00000000UL;
                }
                else
                {
                    Span <byte> pubkeyBytes = comparer.Calc.GetPubkey(sclrParent, true);
                    fixed(byte *pubXPt = &pubkeyBytes[0])
                    {
                        wPt[0] = (ulong)pubXPt[0] << 56 |
                                 (ulong)pubXPt[1] << 48 |
                                 (ulong)pubXPt[2] << 40 |
                                 (ulong)pubXPt[3] << 32 |
                                 (ulong)pubXPt[4] << 24 |
                                 (ulong)pubXPt[5] << 16 |
                                 (ulong)pubXPt[6] << 8 |
                                 pubXPt[7];
                        wPt[1] = (ulong)pubXPt[8] << 56 |
                                 (ulong)pubXPt[9] << 48 |
                                 (ulong)pubXPt[10] << 40 |
                                 (ulong)pubXPt[11] << 32 |
                                 (ulong)pubXPt[12] << 24 |
                                 (ulong)pubXPt[13] << 16 |
                                 (ulong)pubXPt[14] << 8 |
                                 pubXPt[15];
                        wPt[2] = (ulong)pubXPt[16] << 56 |
                                 (ulong)pubXPt[17] << 48 |
                                 (ulong)pubXPt[18] << 40 |
                                 (ulong)pubXPt[19] << 32 |
                                 (ulong)pubXPt[20] << 24 |
                                 (ulong)pubXPt[21] << 16 |
                                 (ulong)pubXPt[22] << 8 |
                                 pubXPt[23];
                        wPt[3] = (ulong)pubXPt[24] << 56 |
                                 (ulong)pubXPt[25] << 48 |
                                 (ulong)pubXPt[26] << 40 |
                                 (ulong)pubXPt[27] << 32 |
                                 (ulong)pubXPt[28] << 24 |
                                 (ulong)pubXPt[29] << 16 |
                                 (ulong)pubXPt[30] << 8 |
                                 pubXPt[31];
                        wPt[4] = (ulong)pubXPt[32] << 56 |
                                 (ulong)index << 24 |
                                 0b00000000_00000000_00000000_00000000_00000000_10000000_00000000_00000000UL;
                    }
                }

                wPt[5]  = 0;
                wPt[6]  = 0;
                wPt[7]  = 0;
                wPt[8]  = 0;
                wPt[9]  = 0;
                wPt[10] = 0;
                wPt[11] = 0;
                wPt[12] = 0;
                wPt[13] = 0;
                wPt[14] = 0;
                wPt[15] = 1320; // (1+32+4 + 128)*8

                // Final result is SHA512(outer_pad | SHA512(inner_pad | 37_byte_data))
                // 1. Compute SHA512(inner_pad | 37_byte_data)
                // Set pads to be used as working vectors (key is ChainCode that is the second 32 bytes of SHA512
                iPt[0] = 0x3636363636363636U ^ hPt[4];
                iPt[1] = 0x3636363636363636U ^ hPt[5];
                iPt[2] = 0x3636363636363636U ^ hPt[6];
                iPt[3] = 0x3636363636363636U ^ hPt[7];

                oPt[0] = 0x5c5c5c5c5c5c5c5cU ^ hPt[4];
                oPt[1] = 0x5c5c5c5c5c5c5c5cU ^ hPt[5];
                oPt[2] = 0x5c5c5c5c5c5c5c5cU ^ hPt[6];
                oPt[3] = 0x5c5c5c5c5c5c5c5cU ^ hPt[7];

                Sha512Fo.Init(hPt);
                Sha512Fo.SetW(iPt);
                Sha512Fo.CompressBlockWithWSet(hPt, iPt);
                Sha512Fo.Compress165SecondBlock(hPt, wPt);

                // 2. Compute SHA512(outer_pad | hash)
                *(Block64 *)wPt = *(Block64 *)hPt;
                wPt[8]          = 0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000UL;
                wPt[9]          = 0;
                wPt[10]         = 0;
                wPt[11]         = 0;
                wPt[12]         = 0;
                wPt[13]         = 0;
                wPt[14]         = 0;
                wPt[15]         = 1536; // (128+64)*8

                Sha512Fo.Init(hPt);
                Sha512Fo.SetW(oPt);
                Sha512Fo.CompressBlockWithWSet(hPt, oPt);
                Sha512Fo.Compress192SecondBlock(hPt, wPt);

                // New private key is (parentPrvKey + int(hPt)) % order
                sclrParent = sclrParent.Add(new Scalar(hPt, out _), out _);
            }

            // Child extended key (private key + chianCode) should be set by adding the index to the end of the Path
            // and have been computed already
            hPt[0] = (ulong)sclrParent.b7 << 32 | sclrParent.b6;
            hPt[1] = (ulong)sclrParent.b5 << 32 | sclrParent.b4;
            hPt[2] = (ulong)sclrParent.b3 << 32 | sclrParent.b2;
            hPt[3] = (ulong)sclrParent.b1 << 32 | sclrParent.b0;

            return(comparer.Compare(hPt));
        }
コード例 #3
0
        public unsafe bool SetBip32(Sha512Fo sha, byte *mnPt, int mnLen, ulong *iPt, ulong *oPt)
        {
            // The process is: PBKDF2(password=UTF8(mnemonic), salt=UTF8("mnemonic+passphrase") -> BIP32 seed
            //                 BIP32 -> HMACSHA(data=seed, key=MasterKeyHashKey) -> HMACSHA(data=key|index, key=ChainCode)
            // All HMACSHAs are using 512 variant

            // *** PBKDF2 ***
            // dkLen/HmacLen=1 => only 1 block => no loop needed
            // Salt is the "mnemonic+passPhrase" + blockNumber(=1) => fixed and set during precomputing
            ulong[] resultOfF = new ulong[8];
            ulong[] uTemp     = new ulong[80];

            ulong[] iPadHashStateTemp = new ulong[8];
            ulong[] oPadHashStateTemp = new ulong[8];

            ulong parkey0, parkey1, parkey2, parkey3, carry;

            fixed(byte *dPt = &pbkdf2Salt[0])
            fixed(ulong *hPt = &sha.hashState[0], wPt = &sha.w[0], seedPt = &resultOfF[0], uPt = &uTemp[0],
                  ihPt       = &iPadHashStateTemp[0], ohPt = &oPadHashStateTemp[0])
            {
                // Setting values in uTemp that never change
                uPt[8]  = 0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000UL;
                uPt[9]  = 0;
                uPt[10] = 0;
                uPt[11] = 0;
                uPt[12] = 0;
                uPt[13] = 0;
                uPt[14] = 0;
                uPt[15] = 1536;


                // Set HMAC key ie. set pads (used as working vectors)
                if (mnLen > Sha512Fo.BlockByteSize)
                {
                    // Key bytes must be hashed first
                    sha.Init(hPt);
                    sha.CompressData(mnPt, mnLen, mnLen, hPt, wPt);
                    // Set pads to be used as working vectors
                    iPt[0]  = 0x3636363636363636U ^ hPt[0];
                    iPt[1]  = 0x3636363636363636U ^ hPt[1];
                    iPt[2]  = 0x3636363636363636U ^ hPt[2];
                    iPt[3]  = 0x3636363636363636U ^ hPt[3];
                    iPt[4]  = 0x3636363636363636U ^ hPt[4];
                    iPt[5]  = 0x3636363636363636U ^ hPt[5];
                    iPt[6]  = 0x3636363636363636U ^ hPt[6];
                    iPt[7]  = 0x3636363636363636U ^ hPt[7];
                    iPt[8]  = 0x3636363636363636U;
                    iPt[9]  = 0x3636363636363636U;
                    iPt[10] = 0x3636363636363636U;
                    iPt[11] = 0x3636363636363636U;
                    iPt[12] = 0x3636363636363636U;
                    iPt[13] = 0x3636363636363636U;
                    iPt[14] = 0x3636363636363636U;
                    iPt[15] = 0x3636363636363636U;

                    oPt[0]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[0];
                    oPt[1]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[1];
                    oPt[2]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[2];
                    oPt[3]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[3];
                    oPt[4]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[4];
                    oPt[5]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[5];
                    oPt[6]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[6];
                    oPt[7]  = 0x5c5c5c5c5c5c5c5cU ^ hPt[7];
                    oPt[8]  = 0x5c5c5c5c5c5c5c5cU;
                    oPt[9]  = 0x5c5c5c5c5c5c5c5cU;
                    oPt[10] = 0x5c5c5c5c5c5c5c5cU;
                    oPt[11] = 0x5c5c5c5c5c5c5c5cU;
                    oPt[12] = 0x5c5c5c5c5c5c5c5cU;
                    oPt[13] = 0x5c5c5c5c5c5c5c5cU;
                    oPt[14] = 0x5c5c5c5c5c5c5c5cU;
                    oPt[15] = 0x5c5c5c5c5c5c5c5cU;
                }
                else
                {
                    byte[] temp = new byte[Sha512Fo.BlockByteSize];
                    fixed(byte *tPt = &temp[0])
                    {
                        Buffer.MemoryCopy(mnPt, tPt, Sha512Fo.BlockByteSize, mnLen);
                        for (int i = 0, j = 0; i < 16; i++, j += 8)
                        {
                            ulong val =
                                ((ulong)tPt[j] << 56) |
                                ((ulong)tPt[j + 1] << 48) |
                                ((ulong)tPt[j + 2] << 40) |
                                ((ulong)tPt[j + 3] << 32) |
                                ((ulong)tPt[j + 4] << 24) |
                                ((ulong)tPt[j + 5] << 16) |
                                ((ulong)tPt[j + 6] << 8) |
                                tPt[j + 7];

                            iPt[i] = 0x3636363636363636U ^ val;
                            oPt[i] = 0x5c5c5c5c5c5c5c5cU ^ val;
                        }
                    }
                }

                // F()
                // compute u1 = hmac.ComputeHash(data=pbkdf2Salt);

                // Final result is SHA512(outer_pad | SHA512(inner_pad | data)) where data is pbkdf2Salt
                // 1. Compute SHA512(inner_pad | data)
                sha.Init(hPt);
                sha.CompressBlock(hPt, iPt);
                // Make a copy of hashState of inner-pad to be used in the loop below (explaination in the loop)
                *(Block64 *)ihPt = *(Block64 *)hPt;
                // Data length is unknown and an initial block of 128 bytes was already compressed
                sha.CompressData(dPt, pbkdf2Salt.Length, pbkdf2Salt.Length + 128, hPt, wPt);
                // 2. Compute SHA512(outer_pad | hash)
                *(Block64 *)wPt = *(Block64 *)hPt;
                wPt[8]          = 0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000UL;
                wPt[9]          = 0;
                wPt[10]         = 0;
                wPt[11]         = 0;
                wPt[12]         = 0;
                wPt[13]         = 0;
                wPt[14]         = 0;
                wPt[15]         = 1536; // oPad.Length(=128) + hashState.Lengh(=64) = 192 byte *8 = 1,536 bit

                sha.Init(hPt);
                sha.CompressBlock(hPt, oPt);
                // Make a copy of hashState of outer-pad to be used in the loop below (explaination in the loop)
                *(Block64 *)ohPt = *(Block64 *)hPt;
                sha.Compress192SecondBlock(hPt, wPt);

                // Copy u1 to result of F() to be XOR'ed with each result on iterations, and result of F() is the seed
                *(Block64 *)seedPt = *(Block64 *)hPt;

                // Compute u2 to u(c-1) where c is iteration and each u is the HMAC of previous u
                for (int j = 1; j < 2048; j++)
                {
                    // Each u is calculated by computing HMAC(previous_u) where previous_u is 64 bytes hPt
                    // Start by making a copy of hPt so Init() can be called
                    *(Block64 *)uPt = *(Block64 *)hPt;

                    // Final result is SHA512(outer_pad | SHA512(inner_pad | 64_byte_data))
                    // 1. Compute SHA512(inner_pad | 64_byte_data)
                    // 2. Compute SHA512(outer_pad | hash)
                    //    Since pads don't change and each step is Init() then Compress(pad) the hashState is always the same
                    //    after these 2 steps and is already computed and stored in temp arrays above
                    //    by doing this 2*2047=4094 SHA512 block compressions are skipped

                    // Replace: sha.Init(hPt); sha.CompressBlockWithWSet(hPt, iPt); with line below:
                    *(Block64 *)hPt = *(Block64 *)ihPt;
                    sha.Compress192SecondBlock(hPt, uPt);

                    // 2. Compute SHA512(outer_pad | hash)
                    *(Block64 *)wPt = *(Block64 *)hPt;
                    // The rest of wPt is set above and is unchanged

                    // Replace: sha.Init(hPt); sha.CompressBlock(hPt, oPt); with line below:
                    *(Block64 *)hPt = *(Block64 *)ohPt;
                    sha.Compress192SecondBlock(hPt, wPt);

                    // result of F() is XOR sum of all u arrays
                    if (Avx2.IsSupported) // AVX512 :(
                    {
                        Vector256 <ulong> part1 = Avx2.Xor(Avx2.LoadVector256(seedPt), Avx2.LoadVector256(hPt));
                        Vector256 <ulong> part2 = Avx2.Xor(Avx2.LoadVector256(seedPt + 4), Avx2.LoadVector256(hPt + 4));

                        Avx2.Store(seedPt, part1);
                        Avx2.Store(seedPt + 4, part2);
                    }
                    else
                    {
                        seedPt[0] ^= hPt[0];
                        seedPt[1] ^= hPt[1];
                        seedPt[2] ^= hPt[2];
                        seedPt[3] ^= hPt[3];
                        seedPt[4] ^= hPt[4];
                        seedPt[5] ^= hPt[5];
                        seedPt[6] ^= hPt[6];
                        seedPt[7] ^= hPt[7];
                    }
                }


                // *** BIP32 ***
                // Set from entropy/seed by computing HMAC(data=seed, key="Bitcoin seed")

                // Final result is SHA512(outer_pad | SHA512(inner_pad | data)) where data is 64-byte seed
                // 1. Compute SHA512(inner_pad | data)
                sha.Init_InnerPad_Bitcoinseed(hPt);
                *(Block64 *)wPt = *(Block64 *)seedPt;
                // from wPt[8] to wPt[15] didn't change
                sha.Compress192SecondBlock(hPt, wPt);

                // 2. Compute SHA512(outer_pad | hash)
                *(Block64 *)wPt = *(Block64 *)hPt; // ** Copy hashState before changing it **
                // from wPt[8] to wPt[15] didn't change
                sha.Init_OuterPad_Bitcoinseed(hPt);
                sha.Compress192SecondBlock(hPt, wPt);
                // Master key is set. PrivateKey= first 32-bytes of hPt and ChainCode is second 32-bytes

                // Each child is derived by computing HMAC(data=(hardened? 0|prvKey : pubkey) | index, key=ChainCode)
                // ChainCode is the second 32-byte half of the hash. Set pad items that never change here:
                iPt[4]  = 0x3636363636363636U;
                iPt[5]  = 0x3636363636363636U;
                iPt[6]  = 0x3636363636363636U;
                iPt[7]  = 0x3636363636363636U;
                iPt[8]  = 0x3636363636363636U;
                iPt[9]  = 0x3636363636363636U;
                iPt[10] = 0x3636363636363636U;
                iPt[11] = 0x3636363636363636U;
                iPt[12] = 0x3636363636363636U;
                iPt[13] = 0x3636363636363636U;
                iPt[14] = 0x3636363636363636U;
                iPt[15] = 0x3636363636363636U;

                oPt[4]  = 0x5c5c5c5c5c5c5c5cU;
                oPt[5]  = 0x5c5c5c5c5c5c5c5cU;
                oPt[6]  = 0x5c5c5c5c5c5c5c5cU;
                oPt[7]  = 0x5c5c5c5c5c5c5c5cU;
                oPt[8]  = 0x5c5c5c5c5c5c5c5cU;
                oPt[9]  = 0x5c5c5c5c5c5c5c5cU;
                oPt[10] = 0x5c5c5c5c5c5c5c5cU;
                oPt[11] = 0x5c5c5c5c5c5c5c5cU;
                oPt[12] = 0x5c5c5c5c5c5c5c5cU;
                oPt[13] = 0x5c5c5c5c5c5c5c5cU;
                oPt[14] = 0x5c5c5c5c5c5c5c5cU;
                oPt[15] = 0x5c5c5c5c5c5c5c5cU;

                uPt[5]  = 0;
                uPt[6]  = 0;
                uPt[7]  = 0;
                uPt[8]  = 0;
                uPt[9]  = 0;
                uPt[10] = 0;
                uPt[11] = 0;
                uPt[12] = 0;
                uPt[13] = 0;
                uPt[14] = 0;
                uPt[15] = 1320; // (1+32+4 + 128)*8

                BigInteger kParent = new BigInteger(sha.GetFirst32Bytes(hPt), true, true);

                if (kParent == 0 || kParent >= order)
                {
                    return(false);
                }
                parkey0 = hPt[3];
                parkey1 = hPt[2];
                parkey2 = hPt[1];
                parkey3 = hPt[0];

                foreach (var index in path.Indexes)
                {
                    if ((index & 0x80000000) != 0) // IsHardened
                    {
                        // First _byte_ is zero
                        // private-key is the first 32 bytes (4 items) of hPt (total 33 bytes)
                        // 4 bytes index + SHA padding are also added
                        uPt[0] = parkey3 >> 8;
                        uPt[1] = parkey3 << 56 | parkey2 >> 8;
                        uPt[2] = parkey2 << 56 | parkey1 >> 8;
                        uPt[3] = parkey1 << 56 | parkey0 >> 8;
                        uPt[4] = parkey0 << 56 |
                                 (ulong)index << 24 |
                                 0b00000000_00000000_00000000_00000000_00000000_10000000_00000000_00000000UL;
                    }
                    else
                    {
                        var    point  = calc.MultiplyByG(kParent);
                        byte[] xBytes = point.X.ToByteArray(true, true).PadLeft(32);
                        fixed(byte *pubXPt = &xBytes[0])
                        {
                            uPt[0] = (point.Y.IsEven ? 0x0200000000000000UL : 0x0300000000000000UL) |
                                     (ulong)pubXPt[0] << 48 |
                                     (ulong)pubXPt[1] << 40 |
                                     (ulong)pubXPt[2] << 32 |
                                     (ulong)pubXPt[3] << 24 |
                                     (ulong)pubXPt[4] << 16 |
                                     (ulong)pubXPt[5] << 8 |
                                     pubXPt[6];
                            uPt[1] = (ulong)pubXPt[7] << 56 |
                                     (ulong)pubXPt[8] << 48 |
                                     (ulong)pubXPt[9] << 40 |
                                     (ulong)pubXPt[10] << 32 |
                                     (ulong)pubXPt[11] << 24 |
                                     (ulong)pubXPt[12] << 16 |
                                     (ulong)pubXPt[13] << 8 |
                                     pubXPt[14];
                            uPt[2] = (ulong)pubXPt[15] << 56 |
                                     (ulong)pubXPt[16] << 48 |
                                     (ulong)pubXPt[17] << 40 |
                                     (ulong)pubXPt[18] << 32 |
                                     (ulong)pubXPt[19] << 24 |
                                     (ulong)pubXPt[20] << 16 |
                                     (ulong)pubXPt[21] << 8 |
                                     pubXPt[22];
                            uPt[3] = (ulong)pubXPt[23] << 56 |
                                     (ulong)pubXPt[24] << 48 |
                                     (ulong)pubXPt[25] << 40 |
                                     (ulong)pubXPt[26] << 32 |
                                     (ulong)pubXPt[27] << 24 |
                                     (ulong)pubXPt[28] << 16 |
                                     (ulong)pubXPt[29] << 8 |
                                     pubXPt[30];
                            uPt[4] = (ulong)pubXPt[31] << 56 |
                                     (ulong)index << 24 |
                                     0b00000000_00000000_00000000_00000000_00000000_10000000_00000000_00000000UL;
                        }
                    }


                    // Final result is SHA512(outer_pad | SHA512(inner_pad | 37_byte_data))
                    // 1. Compute SHA512(inner_pad | 37_byte_data)
                    // Set pads to be used as working vectors (key is ChainCode that is the second 32 bytes of SHA512
                    iPt[0] = 0x3636363636363636U ^ hPt[4];
                    iPt[1] = 0x3636363636363636U ^ hPt[5];
                    iPt[2] = 0x3636363636363636U ^ hPt[6];
                    iPt[3] = 0x3636363636363636U ^ hPt[7];

                    oPt[0] = 0x5c5c5c5c5c5c5c5cU ^ hPt[4];
                    oPt[1] = 0x5c5c5c5c5c5c5c5cU ^ hPt[5];
                    oPt[2] = 0x5c5c5c5c5c5c5c5cU ^ hPt[6];
                    oPt[3] = 0x5c5c5c5c5c5c5c5cU ^ hPt[7];

                    sha.Init(hPt);
                    sha.CompressBlock(hPt, iPt);
                    sha.Compress165SecondBlock(hPt, uPt);

                    // 2. Compute SHA512(outer_pad | hash)
                    *(Block64 *)wPt = *(Block64 *)hPt;

                    // from wPt[8] to wPt[15] didn't change
                    sha.Init(hPt);
                    sha.CompressBlock(hPt, oPt);
                    sha.Compress192SecondBlock(hPt, wPt);

                    // New private key is (parentPrvKey + int(hPt)) % order
                    // TODO: this is a bottleneck and needs to be replaced by a ModularUInt256 instance
                    kParent = (kParent + new BigInteger(sha.GetFirst32Bytes(hPt), true, true)) % order;

                    ulong toAdd = hPt[3];
                    parkey0 += toAdd;
                    if (parkey0 < toAdd)
                    {
                        parkey1++;
                    }

                    toAdd    = hPt[2];
                    parkey1 += toAdd;
                    if (parkey1 < toAdd)
                    {
                        parkey2++;
                    }

                    toAdd    = hPt[1];
                    parkey2 += toAdd;
                    if (parkey2 < toAdd)
                    {
                        parkey3++;
                    }

                    toAdd    = hPt[0];
                    parkey3 += toAdd;
                    if (parkey3 < toAdd)
                    {
                        carry = 1;
                    }
                    else
                    {
                        carry = 0;
                    }

                    bool bigger = false;
                    if (carry == 1)
                    {
                        bigger = true;
                    }
                    else if (parkey3 == N3)
                    {
                        if (parkey2 > N2)
                        {
                            bigger = true;
                        }
                        else if (parkey2 == N2)
                        {
                            if (parkey1 > N1)
                            {
                                bigger = true;
                            }
                            else if (parkey1 == N1)
                            {
                                if (parkey0 >= N0)
                                {
                                    bigger = true;
                                }
                            }
                        }
                    }

                    if (bigger)
                    {
                        if (parkey0 < N0)
                        {
                            parkey1--;
                        }
                        parkey0 -= N0;

                        if (parkey1 < N1)
                        {
                            parkey2--;
                        }
                        parkey1 -= N1;

                        if (parkey2 < N2)
                        {
                            parkey3--;
                        }
                        parkey2 -= N2;

                        parkey3 -= N3;
                    }
                }

                // Child extended key (private key + chianCode) should be set by adding the index to the end of the Path
                // and have been computed already
                hPt[0] = parkey3;
                hPt[1] = parkey2;
                hPt[2] = parkey1;
                hPt[3] = parkey0;

                return(comparer.Compare(sha.GetFirst32Bytes(hPt)));
            }
        }