/// <summary> /// Derives a 512-bit entropy from the BIP-32 instance based on the given path. /// </summary> /// <exception cref="ArgumentException"/> /// <exception cref="ArgumentNullException"/> /// <exception cref="ObjectDisposedException"/> /// <param name="path">Path to use (all indexes must be hardened)</param> /// <returns>512-bit entropy</returns> public byte[] DeriveEntropy(BIP0032Path path) { if (isDisposed) { throw new ObjectDisposedException(nameof(BIP0085)); } if (path is null) { throw new ArgumentNullException(nameof(path), "Path can not be null."); } if (path.Indexes.Any(x => x < HardenedIndex)) { throw new ArgumentException("All indexes inside the given path must be hardened.", nameof(path)); } // BIP-32 treats paths differently, meaning path is the "path" of the child extended key not the child key. // Since BIP-85 treats paths as the "path" of the child key itself it must be modified here. var path2 = new BIP0032Path(path.Indexes.AsSpan(0, path.Indexes.Length - 1).ToArray()); using PrivateKey key = bip32.GetPrivateKeys(path2, 1, path.Indexes[^ 1])[0];
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 PrivateKey[] GetPrivateKeys(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 (PrvKey is null) { throw new ArgumentNullException(nameof(PrvKey), "Can not get child private keys from extended public key."); } 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"); } // Two quick fixes: if (count == 0) { return(null); } if (step < 1) { step = 1; } // First start deriving the extended keys for each index byte[] prevPrvBa = PrvKey.ToBytes(); BigInteger prevPrvInt = PrvKey.ToBigInt(); byte[] prevPubBa = PubKey.ToByteArray(true); byte[] tempCC = ChainCode; byte[] tempLeft; foreach (var index in path.Indexes) { FastStream stream = new FastStream(33 + 4); if (IsHardendedIndex(index)) { stream.Write((byte)0); stream.Write(prevPrvBa); } else { 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(); } // Let PrivateKey do the additional checks and throw here PrivateKey temp = new PrivateKey((kTemp + prevPrvInt) % N); prevPrvInt = temp.ToBigInt(); prevPrvBa = temp.ToBytes(); prevPubBa = temp.ToPublicKey().ToByteArray(true); } // Then derive the actual keys PrivateKey[] result = new PrivateKey[count]; int i = 0; uint childIndex = startIndex; while (i < count) { try { FastStream stream = new FastStream(33 + 4); if (IsHardendedIndex(childIndex)) { stream.Write((byte)0); stream.Write(prevPrvBa); } else { 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; } PrivateKey temp = new PrivateKey((kTemp + prevPrvInt) % N); result[i++] = temp; } catch (ArgumentOutOfRangeException) { // Only ignore this type that is thrown by the PrivateKey constructor. } finally { childIndex += step; } } return(result); }