using System.Buffers.Binary; using System.Numerics; using System.Security.Cryptography; using System.Text; using Org.BouncyCastle.Asn1.Sec; namespace FrostFS.SDK.Cryptography; public static class KeyExtension { public const byte NeoAddressVersion = 0x35; private const int CompressedPublicKeyLength = 33; private const int UncompressedPublicKeyLength = 65; private static readonly uint CheckSigDescriptor = BinaryPrimitives.ReadUInt32LittleEndian(Encoding.ASCII.GetBytes("System.Crypto.CheckSig").Sha256()); public static byte[] Compress(this byte[] publicKey) { if (publicKey.Length != UncompressedPublicKeyLength) throw new FormatException( $"{nameof(Compress)} argument isn't uncompressed public key. " + $"expected length={UncompressedPublicKeyLength}, actual={publicKey.Length}" ); var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var point = secp256R1.Curve.DecodePoint(publicKey); return point.GetEncoded(true); } public static byte[] Decompress(this byte[] publicKey) { if (publicKey.Length != CompressedPublicKeyLength) throw new FormatException( $"{nameof(Decompress)} argument isn't compressed public key. " + $"expected length={CompressedPublicKeyLength}, actual={publicKey.Length}" ); var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var point = secp256R1.Curve.DecodePoint(publicKey); return point.GetEncoded(false); } private static byte[] CreateSignatureRedeemScript(this byte[] publicKey) { if (publicKey.Length != CompressedPublicKeyLength) throw new FormatException( $"{nameof(CreateSignatureRedeemScript)} argument isn't compressed public key. " + $"expected length={CompressedPublicKeyLength}, actual={publicKey.Length}" ); var script = new byte[] { 0x0c, CompressedPublicKeyLength }; //PUSHDATA1 33 script = ArrayHelper.Concat(script, publicKey); script = ArrayHelper.Concat(script, new byte[] { 0x41 }); //SYSCALL script = ArrayHelper.Concat(script, BitConverter.GetBytes(CheckSigDescriptor)); //Neo_Crypto_CheckSig return script; } public static byte[] GetScriptHash(this byte[] publicKey) { var script = publicKey.CreateSignatureRedeemScript(); return script.Sha256().RIPEMD160(); } private static string ToAddress(this byte[] scriptHash, byte version) { Span data = stackalloc byte[21]; data[0] = version; scriptHash.CopyTo(data[1..]); return Base58.Base58CheckEncode(data); } private static byte[] GetPrivateKeyFromWIF(string wif) { if (wif == null) throw new ArgumentNullException(); var data = wif.Base58CheckDecode(); if (data.Length != 34 || data[0] != 0x80 || data[33] != 0x01) throw new FormatException(); var privateKey = new byte[32]; Buffer.BlockCopy(data, 1, privateKey, 0, privateKey.Length); Array.Clear(data, 0, data.Length); return privateKey; } public static string Address(this ECDsa key) { return key.PublicKey().PublicKeyToAddress(); } public static string PublicKeyToAddress(this byte[] publicKey) { if (publicKey.Length != CompressedPublicKeyLength) throw new FormatException( nameof(publicKey) + $" isn't encoded compressed public key. " + $"expected length={CompressedPublicKeyLength}, actual={publicKey.Length}" ); return publicKey.GetScriptHash().ToAddress(NeoAddressVersion); } public static byte[] PublicKey(this ECDsa key) { var param = key.ExportParameters(false); var pubkey = new byte[33]; var pos = 33 - param.Q.X.Length; param.Q.X.CopyTo(pubkey, pos); if (new BigInteger(param.Q.Y.Reverse().Concat(new byte[] { 0x00 }).ToArray()).IsEven) pubkey[0] = 0x2; else pubkey[0] = 0x3; return pubkey; } public static byte[] PrivateKey(this ECDsa key) { return key.ExportParameters(true).D; } public static ECDsa LoadPrivateKey(this byte[] privateKey) { var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var publicKey = secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)).GetEncoded(false)[1..]; var key = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, D = privateKey, Q = new ECPoint { X = publicKey[..32], Y = publicKey[32..] } }); return key; } public static ECDsa LoadWif(this string wif) { var privateKey = GetPrivateKeyFromWIF(wif); return LoadPrivateKey(privateKey); } public static ECDsa LoadPublicKey(this byte[] publicKey) { var publicKeyFull = publicKey.Decompress()[1..]; var key = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, Q = new ECPoint { X = publicKeyFull[..32], Y = publicKeyFull[32..] } }); return key; } }