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); } }
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)); }
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))); } }