Skip to content

Instantly share code, notes, and snippets.

@cwschroeder
Created December 23, 2019 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cwschroeder/fa79ea20e2bbf13ee35f7d8c8b6f2a0e to your computer and use it in GitHub Desktop.
Save cwschroeder/fa79ea20e2bbf13ee35f7d8c8b6f2a0e to your computer and use it in GitHub Desktop.
ECC Zeug
void Main()
{
/*
. Seeding the random number generator... ok
. Generating key pair... ok (key size: 256 bits)
+ Public key: 0478758D742D29533E8ADFC95B7AA6071397473B0D567FEFE8D36C21958DAA22291E2CF4860C3754B39532E27C718FE68CE60E05CCA39F5F09DE29C9857ABC6B56
. Computing message hash... ok
+ Hash: 02F4CCF09D1F5740E28864C430F866B46B697646D376E7813D12B873DBE16CC6
. Signing message hash... ok (signature length = 71)
+ Signature: 304502204E7D544731E4B9A1343E532012E71B7812B069CB6907A246E759DE6D0AF3055B022100CA8C294F80BC5A84373B09733F164654AE0F559F365FA54C93BE4855BE8B54D8
. Preparing verification context... ok
. Verifying signature... ok
+ Press Enter to exit this program.
*/
for(int i =0;i<10;i++)
{
// create ECC key pair for curve secp256r1
(var priv, var pub) = Crypto.CreateEccKeyPair();
// create dummy message that will be signed
var plainData = Enumerable.Range(1,100).Select(b=>(byte)b).ToArray();
// create signature for message
var signature = Crypto.CreateSignature(plainData, priv);
// verify signature
var res = Crypto.VerifySignature(pub.Q.GetEncoded(), plainData, signature, false);
if (res != true){
Console.WriteLine("ERR");
}
if(i%100 == 0){
Thread.Sleep(100);
}
}
var testpkBytes = "0478758D742D29533E8ADFC95B7AA6071397473B0D567FEFE8D36C21958DAA22291E2CF4860C3754B39532E27C718FE68CE60E05CCA39F5F09DE29C9857ABC6B56".FromHexStr();
var hash ="02F4CCF09D1F5740E28864C430F866B46B697646D376E7813D12B873DBE16CC6".FromHexStr();
var sig ="304502204E7D544731E4B9A1343E532012E71B7812B069CB6907A246E759DE6D0AF3055B022100CA8C294F80BC5A84373B09733F164654AE0F559F365FA54C93BE4855BE8B54D8".FromHexStr();
var testEcParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1");
var testpkPoint = testEcParams.Curve.DecodePoint(testpkBytes.ToArray());
Asn1InputStream bIn = new Asn1InputStream(new MemoryStream(sig));
DerSequence seq = bIn.ReadObject() as DerSequence;
var resutl = Crypto.VerifySignature(testpkBytes,hash,sig);
var rnd = new Random();
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp192r1");
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,
ecParams.G, ecParams.N, ecParams.H,
ecParams.GetSeed());
ECKeyGenerationParameters keyGenParams =
new ECKeyGenerationParameters(domainParameters, new SecureRandom());
AsymmetricCipherKeyPair keyPair;
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.Init(keyGenParams);
keyPair = generator.GenerateKeyPair();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)keyPair.Private;
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.Public;
CreateAsn1Key(keyPair);
var bytes = publicKey.Q.GetEncoded(true).ToList();
var privateKeyBytes = privateKey.D.ToByteArray();
var recoveredPrivateKey = new ECPrivateKeyParameters(new BigInteger(privateKeyBytes), domainParameters);
// import public key from compressed byte array
var point = ecParams.Curve.DecodePoint(bytes.ToArray());
ECPublicKeyParameters decompressedKey = new ECPublicKeyParameters("EC", point, SecObjectIdentifiers.SecP192r1);
Console.WriteLine("Generated private key: " + ToHex(privateKey.D.ToByteArrayUnsigned()));
Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded()) + " (uncompressed)");
Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded(true)) + "(compressed)");
Console.WriteLine();
var pkBytes = string.Concat("04", "7CF014EDB89D61DA08B75EDD939BB3F6F5C9462F1F29A698BF5E047B20F5C8751E59382D1075B6FC138FA5339CD3E360").FromHexStr();
var pkPoint = ecParams.Curve.DecodePoint(pkBytes.ToArray());
Console.WriteLine("Loaded public key: " + ToHex(pkPoint.GetEncoded()) + " (uncompressed)");
// shared secret test
var keyPairA = Crypto.CreateEccKeyPair();
var keyPairB = Crypto.CreateEccKeyPair();
var secretA = Crypto.CreateSharedSecret(keyPairB.publicKey.Q.GetEncoded(), keyPairA.privateKey.D.ToByteArray());
var secretB = Crypto.CreateSharedSecret(keyPairA.publicKey.Q.GetEncoded(), keyPairB.privateKey.D.ToByteArray());
var existingString = publicKey.Q.GetEncoded().ToList().Skip(1).ToList().ToHexStr().ToUpperInvariant().Slice(4);
existingString.Dump();
Console.WriteLine($"Base 16 string length: {existingString.Length}\n");
var hexString = bytes.ToHexStr().ToUpperInvariant().Slice(5);
hexString.Dump();
Console.WriteLine($"Base 16 string length: {hexString.Length}\n");
var base32 = Base32.Encode(bytes.ToArray()).Slice(4);
base32.Dump();
Console.WriteLine($"Base 32 string length: {base32.Length}\n");
var base58 = Base58.Encode(bytes.ToArray()).Slice(8);
base58.Dump();
Console.WriteLine($"Base 58 string length: {base58.Length}\n");
var base64 = Convert.ToBase64String(bytes.ToArray()).Slice(0);
base64.Dump();
Console.WriteLine($"Base 64 string length: {base64.Length}\n");
var base85=Ascii85.Encode(bytes.ToArray()).Slice(0);
base85.Dump();
Console.WriteLine($"Base 85 string length: {base85.Length}\n");
}
public static void CreateAsn1Key(AsymmetricCipherKeyPair keyPair)
{
//ECPrivateKeyParameters privKey, ECPublicKeyParameters pubKey
var stefan = @"-----BEGIN EC PRIVATE KEY-----
MF8CAQEEGAS9qVyFO1coVxxzYWwBHZMfRUg31UJ5UqAKBggqhkjOPQMBAaE0AzIA
BNJ7ClSvMxCQm03TVWrZn/gVrvnbXU/+zEmK9kPpPpeUiZxnx/TK3btqFo9iI3D9
sg==
-----END EC PRIVATE KEY-----";
var pemReader = new PemReader(new StringReader(stefan));
var pemObj = pemReader.ReadPemObject();
var seq = (Asn1Sequence)Asn1Sequence.FromByteArray(pemObj.Content);
var kpriv = keyPair.Private as ECPrivateKeyParameters;
var kpub = keyPair.Public as ECPublicKeyParameters;
var vec = new Asn1EncodableVector();
vec.Add(seq[0]);
var derOs = new DerOctetString(kpriv.D.ToByteArray());
vec.Add(derOs);
vec.Add(seq[2]);
var dto = new DerTaggedObject(1, new DerBitString(kpub.Q.GetEncoded()));
vec.Add(dto);
var derSeq = new DerSequence(vec);
var sw = new StringWriter();
var pemWriter = new PemWriter(sw);
pemWriter.WriteObject(
new Org.BouncyCastle.Utilities.IO.Pem.PemObject("EC PRIVATE KEY",
derSeq.GetDerEncoded())
);
var result = sw.ToString();
}
// Write custom extension methods here. They will be available to all queries.
public static class MyExtensions
{
// Write custom extension methods here. They will be available to all queries.
public static string AsHexStr(this byte[] dataBytes, string separator = "")
{
if (dataBytes == null)
{
return string.Empty;
}
return string.Join(separator, dataBytes.Select(s => $"{s:X2}"));
}
public static byte[] FromHexStr(this string hexString)
{
hexString = hexString.Replace(" ", string.Empty);
hexString = hexString.Replace("-", string.Empty);
hexString = hexString.Replace(":", string.Empty);
hexString = hexString.Replace(",", string.Empty);
hexString = hexString.Replace("0x", string.Empty);
hexString = hexString.Replace("\n", string.Empty);
hexString = hexString.Replace("\r", string.Empty);
hexString = hexString.Replace("\t", string.Empty);
return Enumerable.Range(0, hexString.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hexString.Substring(x, 2), 16))
.ToArray();
}
}
// Define other methods and classes here
public static string ToHex(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", "");
}
public class Crypto
{
public static (ECPrivateKeyParameters privateKey, ECPublicKeyParameters publicKey) CreateEccKeyPair(string curve = "secp256r1")
{
var rnd = new Random();
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName(curve);
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,
ecParams.G, ecParams.N, ecParams.H,
ecParams.GetSeed());
ECKeyGenerationParameters keyGenParams =
new ECKeyGenerationParameters(domainParameters, new SecureRandom());
AsymmetricCipherKeyPair keyPair;
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.Init(keyGenParams);
keyPair = generator.GenerateKeyPair();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)keyPair.Private;
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.Public;
return (privateKey, publicKey);
}
public static BigInteger CreateSharedSecret(byte[] publicKeyBytes, byte[] privateKeyBytes, string curveName="secp256r1")
{
X9ECParameters ecP = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1");
ECDomainParameters eCDomainParameters = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N);
ECCurve curve = eCDomainParameters.Curve;
ECPoint q = curve.DecodePoint(publicKeyBytes);
ECPublicKeyParameters oEcPublicKeyParameters = new ECPublicKeyParameters(q, eCDomainParameters);
ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(privateKeyBytes), eCDomainParameters);
IBasicAgreement aKeyAgree = AgreementUtilities.GetBasicAgreement("ECDH");
aKeyAgree.Init(privateKey);
BigInteger sharedSecret = aKeyAgree.CalculateAgreement(oEcPublicKeyParameters);
Console.WriteLine($"Shared secred: {sharedSecret} (Length: {sharedSecret.ToByteArrayUnsigned().Length})\n");
return sharedSecret;
}
public static ECPoint Recover(byte[] hash, byte[] sigBytes, int rec)
{
BigInteger r = new BigInteger(1, sigBytes, 0, 32);
BigInteger s = new BigInteger(1, sigBytes, 32, 32);
BigInteger[] sig = new BigInteger[] { r, s };
ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true);
return Q;
}
public static ECPoint ECDSA_SIG_recover_key_GFp(BigInteger[] sig, byte[] hash, int recid, bool check)
{
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
int i = recid / 2;
Console.WriteLine("r: " + ToHex(sig[0].ToByteArrayUnsigned()));
Console.WriteLine("s: " + ToHex(sig[1].ToByteArrayUnsigned()));
BigInteger order = ecParams.N;
BigInteger field = (ecParams.Curve as FpCurve).Q;
BigInteger x = order.Multiply(new BigInteger(i.ToString())).Add(sig[0]);
if (x.CompareTo(field) >= 0) throw new Exception("X too large");
Console.WriteLine("Order: " + ToHex(order.ToByteArrayUnsigned()));
Console.WriteLine("Field: " + ToHex(field.ToByteArrayUnsigned()));
byte[] compressedPoint = new Byte[x.ToByteArrayUnsigned().Length + 1];
compressedPoint[0] = (byte)(0x02 + (recid % 2));
Buffer.BlockCopy(x.ToByteArrayUnsigned(), 0, compressedPoint, 1, compressedPoint.Length - 1);
ECPoint R = ecParams.Curve.DecodePoint(compressedPoint);
Console.WriteLine("R: " + ToHex(R.GetEncoded()));
if (check)
{
ECPoint O = R.Multiply(order);
if (!O.IsInfinity) throw new Exception("Check failed");
}
int n = (ecParams.Curve as FpCurve).Q.ToByteArrayUnsigned().Length * 8;
BigInteger e = new BigInteger(1, hash);
if (8 * hash.Length > n)
{
e = e.ShiftRight(8 - (n & 7));
}
e = BigInteger.Zero.Subtract(e).Mod(order);
BigInteger rr = sig[0].ModInverse(order);
BigInteger sor = sig[1].Multiply(rr).Mod(order);
BigInteger eor = e.Multiply(rr).Mod(order);
ECPoint Q = ecParams.G.Multiply(eor).Add(R.Multiply(sor));
Console.WriteLine("n: " + n);
Console.WriteLine("e: " + ToHex(e.ToByteArrayUnsigned()));
Console.WriteLine("rr: " + ToHex(rr.ToByteArrayUnsigned()));
Console.WriteLine("sor: " + ToHex(sor.ToByteArrayUnsigned()));
Console.WriteLine("eor: " + ToHex(eor.ToByteArrayUnsigned()));
Console.WriteLine("Q: " + ToHex(Q.GetEncoded()));
return Q;
}
public static byte[] CreateSignature(byte[] message, ECPrivateKeyParameters privateKey)
{
ECDsaSigner signer = new ECDsaSigner();
signer.Init(true, privateKey);
var sha = System.Security.Cryptography.SHA256Managed.Create();
var hash = sha.ComputeHash(message);
BigInteger[] sig = signer.GenerateSignature(hash);
byte[] sigBytes = new byte[64];
var r = sig[0].ToByteArrayUnsigned();
var s = sig[1].ToByteArrayUnsigned();
Buffer.BlockCopy(r, 0, sigBytes, 32-r.Length, r.Length);
Buffer.BlockCopy(s, 0, sigBytes, 32+32-s.Length, s.Length);
return sigBytes;
}
public static bool VerifySignature(byte[] pubkey, byte[] message, byte[] sigBytes, bool parseDerSignature=true)
{
var sha = System.Security.Cryptography.SHA256Managed.Create();
var hash = sha.ComputeHash(message);
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1");
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,
ecParams.G, ecParams.N, ecParams.H,
ecParams.GetSeed());
BigInteger r,s;
if (parseDerSignature)
{
Asn1InputStream bIn = new Asn1InputStream(new MemoryStream(sigBytes));
DerSequence seq = bIn.ReadObject() as DerSequence;
var r11 = seq[0].GetEncoded().ToList().Skip(2).ToArray();
var r21 = seq[1].GetEncoded().ToList().Skip(3).ToArray();
r = new BigInteger(1, r11, 0, 32);
s = new BigInteger(1, r21, 0, 32);
}
else
{
r = new BigInteger(1, sigBytes.Take(32).ToArray());
s = new BigInteger(1, sigBytes.Skip(32).Take(32).ToArray());
}
ECPublicKeyParameters publicKey = new ECPublicKeyParameters(ecParams.Curve.DecodePoint(pubkey), domainParameters);
ECDsaSigner signer = new ECDsaSigner();
signer.Init(false, publicKey);
return signer.VerifySignature(hash, r, s);
}
public static void FullSignatureTest(byte[] hash)
{
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,
ecParams.G, ecParams.N, ecParams.H,
ecParams.GetSeed());
ECKeyGenerationParameters keyGenParams =
new ECKeyGenerationParameters(domainParameters, new SecureRandom());
AsymmetricCipherKeyPair keyPair;
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.Init(keyGenParams);
keyPair = generator.GenerateKeyPair();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)keyPair.Private;
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.Public;
Console.WriteLine("Generated private key: " + ToHex(privateKey.D.ToByteArrayUnsigned()));
Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded()));
ECDsaSigner signer = new ECDsaSigner();
signer.Init(true, privateKey);
BigInteger[] sig = signer.GenerateSignature(hash);
int recid = -1;
for (int rec = 0; rec < 4; rec++)
{
try
{
ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true);
if (ToHex(publicKey.Q.GetEncoded()).Equals(ToHex(Q.GetEncoded())))
{
recid = rec;
break;
}
}
catch (Exception)
{
continue;
}
}
if (recid < 0) throw new Exception("Did not find proper recid");
byte[] fullSigBytes = new byte[65];
fullSigBytes[0] = (byte)(27 + recid);
Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, fullSigBytes, 1, 32);
Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, fullSigBytes, 33, 32);
Console.WriteLine("Generated full signature: " + Convert.ToBase64String(fullSigBytes));
byte[] sigBytes = new byte[64];
Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, sigBytes, 0, 32);
Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, sigBytes, 32, 32);
ECPoint genQ = ECDSA_SIG_recover_key_GFp(sig, hash, recid, false);
Console.WriteLine("Generated signature verifies: " + VerifySignature(genQ.GetEncoded(), hash, sigBytes));
}
}
public static class Ext
{
public static string ToHexStr(this List<byte> bytes)
{
StringBuilder sb = new StringBuilder(bytes.Count * 2);
for (int i = 0; i < bytes.Count; i++)
{
sb.AppendFormat("{0:x2}", bytes[i]);
//sb.Dump();
}
return sb.ToString();
}
public static string Slice(this string s, int size)
{
if (size==0 || size >= s.Length)
return s;
var sb = new StringBuilder();
for (int i = 0; i < s.Length; i++)
{
if (i > 0 && i % size == 0)
{
sb.Append(" ");
}
// if (i > 0 && i % 24 == 0)
// {
// sb.Append("\n");
// }
sb.Append(s[i]);
}
return sb.ToString();
}
}
public static class Base58
{
private const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public static string Encode(byte[] data)
{
// Decode byte[] to BigInteger
System.Numerics.BigInteger intData = 0;
for (int i = 0; i < data.Length; i++)
{
intData = intData * 256 + data[i];
}
// Encode BigInteger to Base58 string
string result = "";
while (intData > 0)
{
int remainder = (int)(intData % 58);
intData /= 58;
result = Digits[remainder] + result;
}
// Append `1` for each leading 0 byte
for (int i = 0; i < data.Length && data[i] == 0; i++)
{
result = '1' + result;
}
return result;
}
public static byte[] Decode(string s)
{
// Decode Base58 string to BigInteger
System.Numerics.BigInteger intData = 0;
for (int i = 0; i < s.Length; i++)
{
int digit = Digits.IndexOf(s[i]); //Slow
if (digit < 0)
throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", s[i], i));
intData = intData * 58 + digit;
}
// Encode BigInteger to byte[]
// Leading zero bytes get encoded as leading `1` characters
int leadingZeroCount = s.TakeWhile(c => c == '1').Count();
var leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount);
var bytesWithoutLeadingZeros =
intData.ToByteArray()
.Reverse()// to big endian
.SkipWhile(b => b == 0);//strip sign byte
var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray();
return result;
}
}
public static class Base32
{
private static readonly char[] DIGITS;
private static readonly int MASK;
private static readonly int SHIFT;
private static Dictionary<char, int> CHAR_MAP = new Dictionary<char, int>();
private const string SEPARATOR = "-";
static Base32()
{
DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".ToCharArray();
MASK = DIGITS.Length - 1;
SHIFT = numberOfTrailingZeros(DIGITS.Length);
for (int i = 0; i < DIGITS.Length; i++) CHAR_MAP[DIGITS[i]] = i;
}
private static int numberOfTrailingZeros(int i)
{
// HD, Figure 5-14
int y;
if (i == 0) return 32;
int n = 31;
y = i << 16; if (y != 0) { n = n - 16; i = y; }
y = i << 8; if (y != 0) { n = n - 8; i = y; }
y = i << 4; if (y != 0) { n = n - 4; i = y; }
y = i << 2; if (y != 0) { n = n - 2; i = y; }
return n - (int)((uint)(i << 1) >> 31);
}
public static byte[] Decode(string encoded)
{
// Remove whitespace and separators
encoded = encoded.Trim().Replace(SEPARATOR, "");
// Remove padding. Note: the padding is used as hint to determine how many
// bits to decode from the last incomplete chunk (which is commented out
// below, so this may have been wrong to start with).
encoded = Regex.Replace(encoded, "[=]*$", "");
// Canonicalize to all upper case
encoded = encoded.ToUpper();
if (encoded.Length == 0)
{
return new byte[0];
}
int encodedLength = encoded.Length;
int outLength = encodedLength * SHIFT / 8;
byte[] result = new byte[outLength];
int buffer = 0;
int next = 0;
int bitsLeft = 0;
foreach (char c in encoded.ToCharArray())
{
if (!CHAR_MAP.ContainsKey(c))
{
throw new DecodingException("Illegal character: " + c);
}
buffer <<= SHIFT;
buffer |= CHAR_MAP[c] & MASK;
bitsLeft += SHIFT;
if (bitsLeft >= 8)
{
result[next++] = (byte)(buffer >> (bitsLeft - 8));
bitsLeft -= 8;
}
}
// We'll ignore leftover bits for now.
//
// if (next != outLength || bitsLeft >= SHIFT) {
// throw new DecodingException("Bits left: " + bitsLeft);
// }
return result;
}
public static string Encode(byte[] data, bool padOutput = false)
{
if (data.Length == 0)
{
return "";
}
// SHIFT is the number of bits per output character, so the length of the
// output is the length of the input multiplied by 8/SHIFT, rounded up.
if (data.Length >= (1 << 28))
{
// The computation below will fail, so don't do it.
throw new ArgumentOutOfRangeException("data");
}
int outputLength = (data.Length * 8 + SHIFT - 1) / SHIFT;
StringBuilder result = new StringBuilder(outputLength);
int buffer = data[0];
int next = 1;
int bitsLeft = 8;
while (bitsLeft > 0 || next < data.Length)
{
if (bitsLeft < SHIFT)
{
if (next < data.Length)
{
buffer <<= 8;
buffer |= (data[next++] & 0xff);
bitsLeft += 8;
}
else
{
int pad = SHIFT - bitsLeft;
buffer <<= pad;
bitsLeft += pad;
}
}
int index = MASK & (buffer >> (bitsLeft - SHIFT));
bitsLeft -= SHIFT;
result.Append(DIGITS[index]);
}
if (padOutput)
{
int padding = 8 - (result.Length % 8);
if (padding > 0) result.Append(new string('=', padding == 8 ? 0 : padding));
}
return result.ToString();
}
private class DecodingException : Exception
{
public DecodingException(string message) : base(message)
{
}
}
}
public static class Ascii85
{
/// <summary>
/// Encodes the specified byte array in Ascii85.
/// </summary>
/// <param name="bytes">The bytes to encode.</param>
/// <returns>An Ascii85-encoded string representing the input byte array.</returns>
public static string Encode(byte[] bytes)
{
if (bytes == null)
throw new ArgumentNullException("bytes");
// preallocate a StringBuilder with enough room to store the encoded bytes
StringBuilder sb = new StringBuilder(bytes.Length * 5 / 4);
// walk the bytes
int count = 0;
uint value = 0;
foreach (byte b in bytes)
{
// build a 32-bit value from the bytes
value |= ((uint)b) << (24 - (count * 8));
count++;
// every 32 bits, convert the previous 4 bytes into 5 Ascii85 characters
if (count == 4)
{
if (value == 0)
sb.Append('z');
else
EncodeValue(sb, value, 0);
count = 0;
value = 0;
}
}
// encode any remaining bytes (that weren't a multiple of 4)
if (count > 0)
EncodeValue(sb, value, 4 - count);
return sb.ToString();
}
/// <summary>
/// Decodes the specified Ascii85 string into the corresponding byte array.
/// </summary>
/// <param name="encoded">The Ascii85 string.</param>
/// <returns>The decoded byte array.</returns>
public static byte[] Decode(string encoded)
{
if (encoded == null)
throw new ArgumentNullException("encoded");
// preallocate a memory stream with enough capacity to hold the decoded data
using (MemoryStream stream = new MemoryStream(encoded.Length * 4 / 5))
{
// walk the input string
int count = 0;
uint value = 0;
foreach (char ch in encoded)
{
if (ch == 'z' && count == 0)
{
// handle "z" block specially
DecodeValue(stream, value, 0);
}
else if (ch < c_firstCharacter || ch > c_lastCharacter)
{
throw new FormatException("Invalid character '{0}' in Ascii85 block.");
}
else
{
// build a 32-bit value from the input characters
try
{
checked { value += (uint)(s_powersOf85[count] * (ch - c_firstCharacter)); }
}
catch (OverflowException ex)
{
throw new FormatException("The current group of characters decodes to a value greater than UInt32.MaxValue.", ex);
}
count++;
// every five characters, convert the characters into the equivalent byte array
if (count == 5)
{
DecodeValue(stream, value, 0);
count = 0;
value = 0;
}
}
}
if (count == 1)
{
throw new FormatException("The final Ascii85 block must contain more than one character.");
}
else if (count > 1)
{
// decode any remaining characters
for (int padding = count; padding < 5; padding++)
{
try
{
checked { value += 84 * s_powersOf85[padding]; }
}
catch (OverflowException ex)
{
throw new FormatException("The current group of characters decodes to a value greater than UInt32.MaxValue.", ex);
}
}
DecodeValue(stream, value, 5 - count);
}
return stream.ToArray();
}
}
// Writes the Ascii85 characters for a 32-bit value to a StringBuilder.
private static void EncodeValue(StringBuilder sb, uint value, int paddingBytes)
{
char[] encoded = new char[5];
for (int index = 4; index >= 0; index--)
{
encoded[index] = (char)((value % 85) + c_firstCharacter);
value /= 85;
}
if (paddingBytes != 0)
Array.Resize(ref encoded, 5 - paddingBytes);
sb.Append(encoded);
}
// Writes the bytes of a 32-bit value to a stream.
private static void DecodeValue(Stream stream, uint value, int paddingChars)
{
stream.WriteByte((byte)(value >> 24));
if (paddingChars == 3)
return;
stream.WriteByte((byte)((value >> 16) & 0xFF));
if (paddingChars == 2)
return;
stream.WriteByte(((byte)((value >> 8) & 0xFF)));
if (paddingChars == 1)
return;
stream.WriteByte((byte)(value & 0xFF));
}
// the first and last characters used in the Ascii85 encoding character set
const char c_firstCharacter = '!';
const char c_lastCharacter = 'u';
static readonly uint[] s_powersOf85 = new uint[] { 85u * 85u * 85u * 85u, 85u * 85u * 85u, 85u * 85u, 85u, 1 };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment