public bool Compare(byte[] key) { BigInteger kVal = new BigInteger(key, true, true); if (kVal >= curve.N || kVal == 0) { return(false); } EllipticCurvePoint actual = calc.MultiplyByG(kVal); return(actual == point); }
public PublicKey[] GetPublicKeys(BIP0032Path path, uint count, uint startIndex = 0, uint step = 1) { if (isDisposed) { throw new ObjectDisposedException(nameof(BIP0032)); } if (path is null) { throw new ArgumentNullException(nameof(path), "Path can not be null!"); } if (ExtendedKeyDepth == byte.MaxValue || ExtendedKeyDepth + path.Indexes.Length + 1 > byte.MaxValue) { throw new ArgumentOutOfRangeException(nameof(ExtendedKeyDepth), "Can not get children since " + "depth will be bigger than 1 byte"); } PublicKey[] result = new PublicKey[count]; if (!(PrvKey is null)) { PrivateKey[] tempPK = GetPrivateKeys(path, count, startIndex, step); for (int j = 0; j < tempPK.Length; j++) { result[j] = tempPK[j].ToPublicKey(); tempPK[j].Dispose(); } return(result); } // If we are here it means PrivateKey was null bool anyHardened = path.Indexes.Any(x => IsHardendedIndex(x)); if (anyHardened || IsHardendedIndex(startIndex)) { throw new ArgumentException(); } // Two quick fixes: if (count == 0) { return(null); } if (step < 1) { step = 1; } // First start deriving the extended keys for each index BigInteger prevPrvInt = PrvKey.ToBigInt(); byte[] prevPubBa = PubKey.ToByteArray(true); EllipticCurvePoint prevPubPoint = PubKey.ToPoint(); byte[] tempCC = ChainCode; byte[] tempLeft; EllipticCurveCalculator calc = new EllipticCurveCalculator(); foreach (var index in path.Indexes) { // There is no hardened indexes thanks to first check FastStream stream = new FastStream(33 + 4); stream.Write(prevPubBa); stream.WriteBigEndian(index); HmacAndSplitData(stream.ToByteArray(), tempCC, out tempLeft, out tempCC); BigInteger kTemp = tempLeft.ToBigInt(true, true); // Note that we throw an exception here if the values were invalid (highly unlikely) // because it is the extended keys, we can't skip anything here. if (kTemp == 0 || kTemp >= N) { throw new ArgumentException(); } EllipticCurvePoint pt = calc.AddChecked(calc.MultiplyByG(kTemp), prevPubPoint); PublicKey tempPub = new PublicKey(pt); prevPubPoint = tempPub.ToPoint(); prevPubBa = tempPub.ToByteArray(true); } // Then derive the actual keys int i = 0; uint childIndex = startIndex; while (i < count) { // There is no hardened indexes thanks to first check FastStream stream = new FastStream(33 + 4); stream.Write(prevPubBa); stream.WriteBigEndian(childIndex); HmacAndSplitData(stream.ToByteArray(), tempCC, out tempLeft, out _); BigInteger kTemp = tempLeft.ToBigInt(true, true); // Note: we don't throw any exceptions here. We simply ignore invalid values (highly unlikely) // and move on to the next index. The returned value will always be filled with expected number of items. if (kTemp == 0 || kTemp >= N) { continue; } EllipticCurvePoint pt = calc.AddChecked(calc.MultiplyByG(kTemp), prevPubPoint); PublicKey temp = new PublicKey(pt); result[i++] = temp; childIndex += step; } return(result); }
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))); } }
private unsafe bool LoopComp(string key, int missingCount, char missingChar, byte[] expectedHash) { int[] missingIndexes = new int[missingCount]; byte[] ba = new byte[32]; for (int i = 0, j = 0; i < ba.Length; i++) { int hi, lo; if (key[i * 2] == missingChar) { hi = 0; missingIndexes[j++] = i * 2; } else { hi = key[i * 2] - 65; hi = hi + 10 + ((hi >> 31) & 7); } if (key[i * 2 + 1] == '*') { lo = 0; missingIndexes[j++] = i * 2 + 1; } else { lo = key[i * 2 + 1] - 65; lo = lo + 10 + ((lo >> 31) & 7) & 0x0f; } ba[i] = (byte)(lo | hi << 4); } var cartesian = CartesianProduct.Create(Enumerable.Repeat(Enumerable.Range(0, 16), missingCount)); EllipticCurveCalculator calc = new EllipticCurveCalculator(); BigInteger smallVal = new BigInteger(ba, true, true); EllipticCurvePoint smallPub = calc.MultiplyByG(smallVal); Ripemd160Sha256 hash = new Ripemd160Sha256(); Parallel.ForEach(cartesian, (item, loopState) => { Span <byte> temp = new byte[32]; int mis = 0; foreach (int keyItem in item) { int misIndex = missingIndexes[mis]; if (misIndex % 2 == 0) { temp[misIndex / 2] |= (byte)(keyItem << 4); } else { temp[misIndex / 2] |= (byte)keyItem; } mis++; } BigInteger tempVal = new BigInteger(temp, true, true); EllipticCurvePoint tempPub = calc.MultiplyByG(tempVal); EllipticCurvePoint pub = calc.AddChecked(tempPub, smallPub); byte[] toHash = new byte[33]; toHash[0] = pub.Y.IsEven ? (byte)2 : (byte)3; byte[] xBytes = pub.X.ToByteArray(true, true); Buffer.BlockCopy(xBytes, 0, toHash, 33 - xBytes.Length, xBytes.Length); ReadOnlySpan <byte> actual = hash.ComputeHash(toHash); if (actual.SequenceEqual(expectedHash)) { char[] origHex = key.ToCharArray(); int index = 0; foreach (var keyItem in item) { origHex[missingIndexes[index++]] = GetHex(keyItem); } AddQueue($"Found a key: {new string(origHex)}"); loopState.Break(); } }); AddQueue("Failed to find any key."); return(false); }
public bool Compare(BigInteger key) => Compare(calc.MultiplyByG(key));