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
{
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>
/// </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();
\feffusing System.Collections.Generic;
using System.Linq;
-using System.Text;
+using System;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
}
}
+ 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>
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);
}
}
}
}
/// <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>
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);
}
}
}
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