From: CryptoManiac Date: Tue, 18 Aug 2015 22:54:55 +0000 (+0300) Subject: Replace base58 implementation, add new key tests X-Git-Url: https://git.novaco.in/?p=NovacoinLibrary.git;a=commitdiff_plain;h=e03dceab99c6b3db1c55aa8faf7d2a45ef791f52 Replace base58 implementation, add new key tests --- diff --git a/Novacoin/AddressTools.cs b/Novacoin/AddressTools.cs index 9356305..a8d78bb 100644 --- a/Novacoin/AddressTools.cs +++ b/Novacoin/AddressTools.cs @@ -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); /// /// Encode a byte sequence as a base58-encoded string /// /// Byte sequence /// Encoding result - 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(); } + /// /// Encode a byte sequence to a base58-encoded string, including checksum /// @@ -86,32 +81,41 @@ namespace Novacoin /// /// Base58 data string /// Byte array - public static IEnumerable 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 leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount); - IEnumerable bytesWithoutLeadingZeros = intData.ToByteArray().Reverse().SkipWhile(b => b == 0); - - return leadingZeros.Concat(bytesWithoutLeadingZeros); + return bi; } + public static IEnumerable Base58DecodeCheck(string strBase58Check) { byte[] rawData = Base58Decode(strBase58Check).ToArray(); diff --git a/Novacoin/CKey.cs b/Novacoin/CKey.cs index 6f9ce35..3fe695e 100644 --- a/Novacoin/CKey.cs +++ b/Novacoin/CKey.cs @@ -93,5 +93,15 @@ namespace Novacoin { get { return _Public.Q.GetEncoded(); } } + + /// + /// Is this a compressed public key? + /// + /// + public bool IsCompressed + { + get { return _Public.Q.IsCompressed; } + } + } } diff --git a/Novacoin/CKeyPair.cs b/Novacoin/CKeyPair.cs index 3230344..885529b 100644 --- a/Novacoin/CKeyPair.cs +++ b/Novacoin/CKeyPair.cs @@ -1,6 +1,6 @@ using 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 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); + } + } + /// /// Create signature for supplied data /// @@ -86,13 +114,32 @@ namespace Novacoin get { return _Private.D.ToByteArray(); } } + public string ToHex() + { + List r = new List(Secret); + + if (IsCompressed) + { + r.Add(0x01); + } + + return Interop.ToHex(r); + } + public override string ToString() { - StringBuilder sb = new StringBuilder(); + List r = new List(); + + 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); } } } diff --git a/Novacoin/CPubKey.cs b/Novacoin/CPubKey.cs index 8ad4c3e..fa4c052 100644 --- a/Novacoin/CPubKey.cs +++ b/Novacoin/CPubKey.cs @@ -30,6 +30,16 @@ namespace Novacoin } /// + /// Init with base58 encoded sequence of bytes + /// + /// + public CPubKey(string strBase58) + { + ECPoint pQ = curve.Curve.DecodePoint(AddressTools.Base58DecodeCheck(strBase58).ToArray()); + _Public = new ECPublicKeyParameters(pQ, domain); + } + + /// /// Quick validity test /// /// Validation result @@ -38,18 +48,20 @@ namespace Novacoin get { return !_Public.Q.IsInfinity; } } - /// - /// Is this a compressed public key? - /// - /// - public bool IsCompressed + public string ToHex() { - get { return _Public.Q.IsCompressed; } + return Interop.ToHex(Public); } public override string ToString() { - return Interop.ToHex(Public); + List r = new List(); + + r.Add((byte)(AddrType.PUBKEY_ADDRESS)); + + r.AddRange(Public); + + return AddressTools.Base58EncodeCheck(r); } } } diff --git a/NovacoinTest/Program.cs b/NovacoinTest/Program.cs index 058e4ef..a6686e2 100644 --- a/NovacoinTest/Program.cs +++ b/NovacoinTest/Program.cs @@ -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