Replace base58 implementation, add new key tests
authorCryptoManiac <balthazar@yandex.ru>
Tue, 18 Aug 2015 22:54:55 +0000 (01:54 +0300)
committerCryptoManiac <balthazar@yandex.ru>
Tue, 18 Aug 2015 22:54:55 +0000 (01:54 +0300)
Novacoin/AddressTools.cs
Novacoin/CKey.cs
Novacoin/CKeyPair.cs
Novacoin/CPubKey.cs
NovacoinTest/Program.cs

index 9356305..a8d78bb 100644 (file)
@@ -2,10 +2,8 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
 
-using System.Numerics;
+using Org.BouncyCastle.Math;
 
 namespace Novacoin
 {
@@ -29,41 +27,38 @@ namespace Novacoin
 
     public class AddressTools
     {
-        const string strDigits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+        private const string strDigits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+        private static readonly BigInteger _base = BigInteger.ValueOf(58);
 
         /// <summary>
         /// Encode a byte sequence as a base58-encoded string
         /// </summary>
         /// <param name="bytes">Byte sequence</param>
         /// <returns>Encoding result</returns>
-        public static string Base58Encode(byte[] bytes)
+        public static string Base58Encode(byte[] input)
         {
-            string strResult = "";
-
-            int nBytes = bytes.Length;
-            BigInteger arrayToInt = 0;
-            BigInteger encodeSize = strDigits.Length;
-
-            for (int i = 0; i < nBytes; ++i)
+            // TODO: This could be a lot more efficient.
+            var bi = new BigInteger(1, input);
+            var s = new StringBuilder();
+            while (bi.CompareTo(_base) >= 0)
             {
-                arrayToInt = arrayToInt * 256 + bytes[i];
+                var mod = bi.Mod(_base);
+                s.Insert(0, new[] { strDigits[mod.IntValue] });
+                bi = bi.Subtract(mod).Divide(_base);
             }
-            while (arrayToInt > 0)
-            {
-                int rem = (int)(arrayToInt % encodeSize);
-                arrayToInt /= encodeSize;
-                strResult = strDigits[rem] + strResult;
-            }
-
-            // Leading zeroes encoded as base58 zeros
-            for (int i = 0; i < nBytes && bytes[i] == 0; ++i)
+            s.Insert(0, new[] { strDigits[bi.IntValue] });
+            // Convert leading zeros too.
+            foreach (var anInput in input)
             {
-                strResult = strDigits[0] + strResult;
+                if (anInput == 0)
+                    s.Insert(0, new[] { strDigits[0] });
+                else
+                    break;
             }
-
-            return strResult;
+            return s.ToString();
         }
 
+
         /// <summary>
         /// Encode a byte sequence to a base58-encoded string, including checksum
         /// </summary>
@@ -86,32 +81,41 @@ namespace Novacoin
         /// </summary>
         /// <param name="strBase58">Base58 data string</param>
         /// <returns>Byte array</returns>
-        public static IEnumerable<byte> Base58Decode(string strBase58)
+        public static byte[] Base58Decode(string input)
         {
-            // Remove whitespaces
-            strBase58 = Regex.Replace(strBase58, @"s", "");
-
-            BigInteger intData = 0;
-            for (int i = 0; i < strBase58.Length; i++)
+            var bytes = DecodeToBigInteger(input).ToByteArray();
+            // We may have got one more byte than we wanted, if the high bit of the next-to-last byte was not zero. This
+            // is because BigIntegers are represented with twos-compliment notation, thus if the high bit of the last
+            // byte happens to be 1 another 8 zero bits will be added to ensure the number parses as positive. Detect
+            // that case here and chop it off.
+            var stripSignByte = bytes.Length > 1 && bytes[0] == 0 && bytes[1] >= 0x80;
+            // Count the leading zeros, if any.
+            var leadingZeros = 0;
+            for (var i = 0; input[i] == strDigits[0]; i++)
             {
-                int digit = strDigits.IndexOf(strBase58[i]);
+                leadingZeros++;
+            }
+            var tmp = new byte[bytes.Length - (stripSignByte ? 1 : 0) + leadingZeros];
+            Array.Copy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros, tmp.Length - leadingZeros);
+            return tmp;
+        }
 
-                if (digit < 0)
+        public static BigInteger DecodeToBigInteger(string input)
+        {
+            var bi = BigInteger.ValueOf(0);
+            // Work backwards through the string.
+            for (var i = input.Length - 1; i >= 0; i--)
+            {
+                var alphaIndex = strDigits.IndexOf(input[i]);
+                if (alphaIndex == -1)
                 {
-                    throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", strBase58[i], i));
+                    throw new FormatException("Illegal character " + input[i] + " at " + i);
                 }
-
-                intData = intData * 58 + digit;
+                bi = bi.Add(BigInteger.ValueOf(alphaIndex).Multiply(_base.Pow(input.Length - 1 - i)));
             }
-
-            // Leading zero bytes get encoded as leading `1` characters
-            int leadingZeroCount = strBase58.TakeWhile(c => c == '1').Count();
-
-            IEnumerable<byte> leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount);
-            IEnumerable<byte> bytesWithoutLeadingZeros = intData.ToByteArray().Reverse().SkipWhile(b => b == 0);
-
-            return leadingZeros.Concat(bytesWithoutLeadingZeros);
+            return bi;
         }
+
         public static IEnumerable<byte> Base58DecodeCheck(string strBase58Check)
         {
             byte[] rawData = Base58Decode(strBase58Check).ToArray();
index 6f9ce35..3fe695e 100644 (file)
@@ -93,5 +93,15 @@ namespace Novacoin
         {
             get { return _Public.Q.GetEncoded(); }
         }
+
+        /// <summary>
+        /// Is this a compressed public key?
+        /// </summary>
+        /// <returns></returns>
+        public bool IsCompressed
+        {
+            get { return _Public.Q.IsCompressed; }
+        }
+
     }
 }
index 3230344..885529b 100644 (file)
@@ -1,6 +1,6 @@
 \feffusing System.Collections.Generic;
 using System.Linq;
-using System.Text;
+using System;
 
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
@@ -57,6 +57,34 @@ namespace Novacoin
             }
         }
 
+        public CKeyPair(string strBase58)
+        {
+            List<byte> rawBytes = AddressTools.Base58DecodeCheck(strBase58).ToList();
+            rawBytes.RemoveAt(0); // Remove key version byte
+
+            int nSecretLen = rawBytes[0] == 0x00 ? 33 : 32;
+            int nTaggedSecretLen = nSecretLen + 1;
+
+            if (rawBytes.Count > nTaggedSecretLen || rawBytes.Count < nSecretLen)
+            {
+                throw new FormatException("Invalid private key");
+            }
+
+            // Deserialize secret value
+            BigInteger D = new BigInteger(rawBytes.Take(nSecretLen).ToArray());
+
+            // Calculate public key
+            ECPoint Q = curve.G.Multiply(D);
+
+            _Private = new ECPrivateKeyParameters(D, domain);
+            _Public = new ECPublicKeyParameters(Q, domain);
+
+            if (rawBytes.Count == nTaggedSecretLen && rawBytes.Last() == 0x01) // Check compression tag
+            {
+                _Public = Compress(_Public);
+            }
+        }
+
         /// <summary>
         /// Create signature for supplied data
         /// </summary>
@@ -86,13 +114,32 @@ namespace Novacoin
             get { return _Private.D.ToByteArray(); }
         }
 
+        public string ToHex()
+        {
+            List<byte> r = new List<byte>(Secret);
+            
+            if (IsCompressed)
+            {
+                r.Add(0x01);
+            }
+
+            return Interop.ToHex(r);
+        }
+
         public override string ToString()
         {
-            StringBuilder sb = new StringBuilder();
+            List<byte> r = new List<byte>();
+
+            r.Add((byte)(128 + AddrType.PUBKEY_ADDRESS));
 
-            sb.AppendFormat("CKeyPair(Secret={0}, Public={1})", Interop.ToHex(Secret), Interop.ToHex(Public));
+            r.AddRange(Secret);
+
+            if (IsCompressed)
+            {
+                r.Add(0x01);
+            }
 
-            return sb.ToString();
+            return AddressTools.Base58EncodeCheck(r);
         }
     }
 }
index 8ad4c3e..fa4c052 100644 (file)
@@ -30,6 +30,16 @@ namespace Novacoin
         }
 
         /// <summary>
+        /// Init with base58 encoded sequence of bytes
+        /// </summary>
+        /// <param name="strBase58"></param>
+        public CPubKey(string strBase58)
+        {
+            ECPoint pQ = curve.Curve.DecodePoint(AddressTools.Base58DecodeCheck(strBase58).ToArray());
+            _Public = new ECPublicKeyParameters(pQ, domain);
+        }
+
+        /// <summary>
         /// Quick validity test
         /// </summary>
         /// <returns>Validation result</returns>
@@ -38,18 +48,20 @@ namespace Novacoin
             get { return !_Public.Q.IsInfinity; }
         }
 
-        /// <summary>
-        /// Is this a compressed public key?
-        /// </summary>
-        /// <returns></returns>
-        public bool IsCompressed
+        public string ToHex()
         {
-            get { return _Public.Q.IsCompressed; }
+            return Interop.ToHex(Public);
         }
 
         public override string ToString()
         {
-            return Interop.ToHex(Public);
+            List<byte> r = new List<byte>();
+
+            r.Add((byte)(AddrType.PUBKEY_ADDRESS));
+
+            r.AddRange(Public);
+
+            return AddressTools.Base58EncodeCheck(r);
         }
     }
 }
index 058e4ef..a6686e2 100644 (file)
@@ -44,9 +44,18 @@ namespace NovacoinTest
             CKeyPair keyPair1 = new CKeyPair();
             CKeyPair keyPair2 = new CKeyPair(keyPair1.Secret);
             CPubKey pubKey = keyPair2.GetPubKey();
-       
-            Console.WriteLine(keyPair1.ToString());
-            Console.WriteLine("PubKey: {0}", pubKey.ToString());
+
+            string strPrivKeyBase58 = keyPair1.ToString();
+
+            Console.WriteLine("Privkey in Base58: {0}", strPrivKeyBase58);
+            Console.WriteLine("Privkey in Hex: {0}", keyPair1.ToHex());
+
+            CKeyPair keyPair3 = new CKeyPair(strPrivKeyBase58);
+            Console.WriteLine("Privkey base58 deserialization is OK: {0}", keyPair3.GetKeyID().ToString() == keyPair1.GetKeyID().ToString());
+
+            Console.WriteLine("Pubkey in Base58: {0}", pubKey.ToString());
+            Console.WriteLine("Pubkey in Hex: {0}", pubKey.ToHex());
+
             Console.WriteLine("Reinitialization is OK: {0}\n", keyPair1.ToString() == keyPair2.ToString());
 
             /// Address generation test