/// <summary> /// Checks to see if the given point is on the used elliptic curve. /// </summary> /// <exception cref="ArgumentException"/> /// <param name="point">Point to check</param> public void CheckOnCurve(EllipticCurvePoint point) { if (!curve.IsOnCurve(point)) { throw new ArgumentException("The given point is not on the given curve.", nameof(point)); } }
/// <summary> /// Adds two <see cref="EllipticCurvePoint"/>s without verifying if they are on curve and returns the result. /// </summary> /// <param name="point1">First point to add</param> /// <param name="point2">Second point to add</param> /// <returns>Result of point addition</returns> internal EllipticCurvePoint AddChecked(EllipticCurvePoint point1, EllipticCurvePoint point2) { if (point1 == EllipticCurvePoint.InfinityPoint) { return(point2); } if (point2 == EllipticCurvePoint.InfinityPoint) { return(point1); } BigInteger m; if (point1.X == point2.X) { if (point1.Y != point2.Y) // (x,y) + (x,−y) = O { return(EllipticCurvePoint.InfinityPoint); } // Point double or (x,y) + (x,y) m = ((3 * point1.X * point1.X) + curve.A) * (2 * point1.Y).ModInverse(curve.P); // Note that since points are on a group with a prime (mod p) all of them do have multiplicative inverses. } else // point1 != point2. (x1,y1) + (x2,y2) { m = (point1.Y - point2.Y) * (point1.X - point2.X).ModInverse(curve.P); } BigInteger x3 = ((m * m) - point1.X - point2.X).Mod(curve.P); BigInteger y3 = (m * (point1.X - x3) - point1.Y).Mod(curve.P); return(new EllipticCurvePoint(x3, y3)); }
/// <summary> /// Adds two <see cref="EllipticCurvePoint"/>s after verifying if they are on curve and returns the result. /// </summary> /// <exception cref="ArgumentException"/> /// <param name="point1">First point to add</param> /// <param name="point2">Second point to add</param> /// <returns>Result of point addition</returns> public EllipticCurvePoint Add(EllipticCurvePoint point1, EllipticCurvePoint point2) { CheckOnCurve(point1); CheckOnCurve(point2); return(AddChecked(point1, point2)); }
/// <summary> /// Returns if a given <see cref="EllipticCurvePoint"/> is on this curve. /// </summary> /// <param name="point">The <see cref="EllipticCurvePoint"/> to check.</param> /// <returns>True if the point is on curve, false if otherwise.</returns> public bool IsOnCurve(EllipticCurvePoint point) { if (point == EllipticCurvePoint.InfinityPoint) { return(true); } // Big*Big is faster than Pow(Big,2). Only true for 2 though. BigInteger rem = ((point.Y * point.Y) - BigInteger.Pow(point.X, 3) - B) % P; return(rem == 0); }
// 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)); }
/// <summary> /// After checking if the point was on curve it returtns the result of multiplying the point with the given integer. /// Also the integer will be changed if it is bigger than <see cref="IECurveFp.N"/>. /// </summary> /// <exception cref="ArgumentException"/> /// <param name="k">The integer to multiply the point with</param> /// <param name="point">The <see cref="EllipticCurvePoint"/></param> /// <returns>Result of multiplication</returns> public EllipticCurvePoint Multiply(BigInteger k, EllipticCurvePoint point) { CheckOnCurve(point); if (k % curve.N == 0 || point == EllipticCurvePoint.InfinityPoint) { return(EllipticCurvePoint.InfinityPoint); } k %= curve.N; return((k < 0) ? MultiplyChecked(-k, PointNegChecked(point)) : MultiplyChecked(k, point)); }
/// <summary> /// Verifies if the given signature is a valid ECSDSA signature based on BIP-340. /// </summary> /// <param name="hash">Hash(m) used in signing</param> /// <param name="sig">Signature</param> /// <param name="pubPoint">The public key's <see cref="EllipticCurvePoint"/></param> /// <returns>True if the signature was valid, otherwise false.</returns> public bool VerifySchnorr(byte[] hash, Signature sig, EllipticCurvePoint pubPoint) { // Validity of hash and sig and pubPoint must have been checked by the caller already if (sig.R >= curve.P || sig.S >= curve.N) { return(false); } BigInteger e = ComputeSchnorrE(sig.R.ToByteArray(true, true), pubPoint, hash); EllipticCurvePoint R = AddChecked(MultiplyChecked(sig.S, curve.G), MultiplyChecked(curve.N - e, pubPoint)); return(IsSquare(R.Y) && R.X == sig.R); }
private BigInteger ComputeSchnorrE(byte[] rba, EllipticCurvePoint P, byte[] hash) { // Compute "tagged hash": // tagHash = Sha256(tagstring) // msg = R.X | P.X | hash // return sha256(tagHash | tagHash | msg) using Sha256 sha = new Sha256(); byte[] pba = P.X.ToByteArray(true, true); // TODO: change all these BigIntegers to ModUint256 type so that it doesn't need length checks,...! byte[] pubBa = new byte[32]; Buffer.BlockCopy(pba, 0, pubBa, 32 - pba.Length, pba.Length); BigInteger e = sha.ComputeTaggedHash_BIPSchnorr(rba, pubBa, hash).ToBigInt(true, true) % curve.N; return(e); }
/// <summary> /// Recovers all possible public keys (up to 4) from the given ECDSA signature based on Standards for Efficient Cryptography /// (SEC 1: Elliptic Curve Cryptography) 4.1.6 Public Key Recovery Operation (page 47). /// Return value indicates success. /// </summary> /// <param name="hash">Hash(m) used in signing</param> /// <param name="sig">Signature</param> /// <param name="results">Recovered public keys (empty if no public key could be recovered)</param> /// <returns>True if any public key was found, otherwise false.</returns> public bool TryRecoverPublicKeys(byte[] hash, Signature sig, out EllipticCurvePoint[] results) { List <EllipticCurvePoint> temp = new List <EllipticCurvePoint>(curve.H * 4); for (int j = 0; j <= curve.H; j++) { BigInteger x = sig.R + (j * curve.N); if (!TryFindY(x, 2, out BigInteger y)) { continue; } EllipticCurvePoint R = new EllipticCurvePoint(x, y); if (!curve.IsOnCurve(R)) { continue; } BigInteger e = hash.ToBigInt(true, true); for (int k = 1; k <= 2; k++) { // Q = r^−1(sR − eG). EllipticCurvePoint Q = MultiplyChecked(sig.R.ModInverse(curve.N), AddChecked(MultiplyChecked(sig.S, R), PointNegChecked(MultiplyChecked(e, curve.G))) ); if (curve.IsOnCurve(Q)) { if (!temp.Contains(Q)) { // TODO: we are missing step 1.6.2 (verify if this pubkey + signature is valid) temp.Add(Q); } } R = PointNegChecked(R); } } results = temp.ToArray(); return(results.Length != 0); }
/// <summary> /// Returtns the result of multiplying the point with the given integer. /// Assumes point is on curve and k>0 and <<see cref="IECurveFp.N"/>. /// </summary> /// <param name="k">The integer to multiply the point with</param> /// <param name="point">The <see cref="EllipticCurvePoint"/></param> /// <returns>Result of multiplication</returns> internal EllipticCurvePoint MultiplyChecked(BigInteger k, EllipticCurvePoint point) { EllipticCurvePoint result = EllipticCurvePoint.InfinityPoint; EllipticCurvePoint addend = point; while (k != 0) { if ((k & 1) == 1) { result = AddChecked(result, addend); } addend = AddChecked(addend, addend); k >>= 1; } return(result); }
// Note: in all the following methods, we assume inputs are valid hence skip checkking. // It is because all these methods are used internally by other classes that perform the checks. // for example Sign() is used by PrivateKey so the keyBytes and hash are both valid /// <summary> /// Creates a signature using ECDSA based on Standards for Efficient Cryptography (SEC 1: Elliptic Curve Cryptography) /// section 4.1.3 Signing Operation (page 44). /// Return value indicates success. /// </summary> /// <param name="hash">Hash(m) to use for signing</param> /// <param name="key">Private key bytes (must be padded to 32 bytes)</param> /// <param name="k"> /// The ephemeral elliptic curve key used for signing /// (k should be smaller than <see cref="IECurveFp.N"/>, it is always smaller if <see cref="Rfc6979"/> is used). /// </param> /// <param name="lowS">If true s values bigger than <see cref="IECurveFp.N"/>/2 will be converted.</param> /// <param name="lowR">If true the process fails if R.X had its highest bit set</param> /// <param name="sig">Signature (null if process fails)</param> /// <returns>True if successful, otherwise false.</returns> public bool TrySign(byte[] hash, byte[] key, BigInteger k, bool lowS, bool lowR, out Signature sig) { // TODO: research what happens if e value is zero. does it reveal private key? // https://bitcointalk.org/index.php?topic=260595.msg4928224#msg4928224 BigInteger e = hash.ToBigInt(true, true); EllipticCurvePoint rp = MultiplyChecked(k, curve.G); byte v = (byte)(((rp.X > curve.N) ? 2 : 0) | (rp.Y.IsEven ? 0 : 1)); BigInteger r = rp.X % curve.N; // Note about r: // R.X is at most 32 bytes, if the highest bit is set then in DER encoding 0 is appended to indicate // it is a positive integer. // Here if low r is requested, we only check the length and reject cases where R.X is 32 bytes and needs the // additional 0 and if R.X was smaller than 32 bytes it passes even if the higest bit was set. // In other words 0<31 bytes> or 0<30 byte>,... are accepted // TODO: when BigInteger is replaced by ModularUInt256 in the future, this should be replaced by a simple // test of the highest bit instead of ToByteArray and length check. if (r == 0 || (lowR && r.ToByteArray(isBigEndian: true).Length > 32)) { sig = null; return(false); } BigInteger s = k.ModInverse(curve.N) * (e + (r * key.ToBigInt(true, true))) % curve.N; if (s == 0) { sig = null; return(false); } if (lowS && s > curve.N / 2) { v ^= 1; s = curve.N - s; } sig = new Signature(r, s, v); return(true); }
/// <summary> /// Converts the given byte array to an <see cref="EllipticCurvePoint"/>. Return value indicates success. /// </summary> /// <param name="bytes">Byte sequence to use (must be 33 or 65 bytes and start with 2/3 or 4)</param> /// <param name="result">Resulting point (<see cref="EllipticCurvePoint.InfinityPoint"/> if fails)</param> /// <returns>True if the conversion is successful, otherwise false.</returns> public bool TryGetPoint(byte[] bytes, out EllipticCurvePoint result) { if (bytes == null) { return(false); } else if (bytes.Length == 33 && (bytes[0] == 2 || bytes[0] == 3)) { byte[] xBa = bytes.SubArray(1, 32); BigInteger x = xBa.ToBigInt(true, true); if (!TryFindY(x, bytes[0], out BigInteger y)) { return(false); } result = new EllipticCurvePoint(x, y); return(curve.IsOnCurve(result)); } else if (bytes.Length == 65 && (bytes[0] == 4 || bytes[0] == 6 || bytes[0] == 7)) { byte[] xBa = bytes.SubArray(1, 32); byte[] yBa = bytes.SubArray(33, 32); BigInteger x = xBa.ToBigInt(true, true); BigInteger y = yBa.ToBigInt(true, true); // Hybrid form: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf if ((bytes[0] == 6 && !y.IsEven) || (bytes[0] == 7 && y.IsEven)) { return(false); } result = new EllipticCurvePoint(x, y); return(curve.IsOnCurve(result)); } else { return(false); } }
/// <summary> /// Verifies if the given signature is a valid ECDSA signature based on Standards for Efficient Cryptography /// (SEC 1: Elliptic Curve Cryptography) 4.1.4 Verifying Operation (page 46). /// </summary> /// <param name="hash">Hash(m) used in signing</param> /// <param name="sig">Signature</param> /// <param name="pubPoint">The public key's <see cref="EllipticCurvePoint"/></param> /// <param name="lowS">If true s values bigger than <see cref="IECurveFp.N"/>/2 are rejected.</param> /// <returns>True if the signature was valid, otherwise false.</returns> public bool Verify(byte[] hash, Signature sig, EllipticCurvePoint pubPoint, bool lowS = true) { if (sig.R == 0 || sig.R > curve.N || sig.S == 0 || sig.S > curve.N || (lowS && sig.S > curve.N / 2)) { return(false); } BigInteger e = hash.ToBigInt(true, true); BigInteger invMod = sig.S.ModInverse(curve.N); BigInteger u1 = e * invMod % curve.N; BigInteger u2 = sig.R * invMod % curve.N; EllipticCurvePoint Rxy = AddChecked(MultiplyChecked(u1, curve.G), MultiplyChecked(u2, pubPoint)); if (Rxy.X == 0 && Rxy.Y == 0) { return(false); } BigInteger v = Rxy.X % curve.N; return(v == sig.R); }
/// <summary> /// Given a point (x,y) returns -point (x,-y) without checking if the point is on curve. /// </summary> /// <param name="point">Point to negate</param> /// <returns>The negative point</returns> internal EllipticCurvePoint PointNegChecked(EllipticCurvePoint point) { return((point == EllipticCurvePoint.InfinityPoint) ? point : new EllipticCurvePoint(point.X, (-point.Y).Mod(curve.P))); }
/// <summary> /// Given a point (x,y) returns -point (x,-y) if the coin was on curve. /// </summary> /// <exception cref="ArgumentException"/> /// <param name="point">Point to negate</param> /// <returns>The negative point</returns> public EllipticCurvePoint PointNeg(EllipticCurvePoint point) { CheckOnCurve(point); return(PointNegChecked(point)); }