// TODO: change Sign*() methods accessibility to internal or add additinal checks (eg. input.legnth == 32) /// <summary> /// Creates a signature using ECSDSA based on BIP-340. /// </summary> /// <param name="hash">Hash(m) to use for signing</param> /// <param name="key">Private key bytes (must be padded to 32 bytes)</param> /// <returns>Signature</returns> public Signature SignSchnorr(byte[] hash, byte[] key) { BigInteger seckey = key.ToBigInt(true, true); EllipticCurvePoint pubkPoint = MultiplyChecked(seckey, curve.G); if (!IsSquare(pubkPoint.Y)) { seckey = curve.N - seckey; // The internal ComputeTaggedHash_*() methods assume inputs are 32 byte so it needs to be padded here // both hash and key are already 32 byte since the caller is PrivateKey.Sign() method byte[] temp = new byte[32]; byte[] secBa = seckey.ToByteArray(true, true); Buffer.BlockCopy(secBa, 0, temp, 32 - secBa.Length, secBa.Length); key = temp; } using Sha256 sha = new Sha256(); byte[] kBa = sha.ComputeTaggedHash_BIPSchnorrDerive(key, hash); BigInteger k = kBa.ToBigInt(true, true) % curve.N; if (k == 0) { // This branch will only happen if k was 0 or N (reduced to 0) which is nearly impossible! uint count = 1; byte[] extraEntropy = new byte[32]; do { // TODO: investigate what is the appropriate approach in this special case. // A very similar approach to Sign() method is used here with the extra entropy but // instead of RFC-6979 TaggedHash is used with a different "tag" and 3x 32-byte inputs extraEntropy[0] = (byte)count; extraEntropy[1] = (byte)(count >> 8); extraEntropy[2] = (byte)(count >> 16); extraEntropy[3] = (byte)(count >> 24); count++; kBa = sha.ComputeTaggedHash("BIPSchnorrDeriveExtraEntropy", key, hash, extraEntropy); k = kBa.ToBigInt(true, true) % curve.N; } while (k == 0); } EllipticCurvePoint R = MultiplyChecked(k, curve.G); if (!IsSquare(R.Y)) { k = curve.N - k; } BigInteger e = ComputeSchnorrE(R.X.ToByteArray(true, true), pubkPoint, hash); BigInteger s = (k + (e * seckey)) % curve.N; return(new Signature(R.X, s)); }