Should anyone care, I use the following. Saves you from needing a managed C++ project:
public static class ECDSA
{
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_start", CallingConvention = CallingConvention.Cdecl)]
private static extern void StartCrypto();
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_stop", CallingConvention = CallingConvention.Cdecl)]
private static extern void StopCrypto();
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_verify", CallingConvention = CallingConvention.Cdecl)]
public static extern int VerifySignature(byte[] msg, int msglen, byte[] sig, int siglen, byte[] pubkey, int pubkeylen);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_sign", CallingConvention = CallingConvention.Cdecl)]
public static extern int SignMessage(byte[] msg, int msglen, byte[] sig, ref int siglen, byte[] seckey, ref int nonce);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_sign_compact", CallingConvention = CallingConvention.Cdecl)]
public static extern int SignCompact(byte[] msg, int msglen, byte[] sig64, byte[] seckey, ref int nonce, ref int recid);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_recover_compact", CallingConvention = CallingConvention.Cdecl)]
public static extern int RecoverCompact(byte[] msg, int msglen, byte[] sig64, byte[] pubkey, ref int pubkeylen, int compressed, int recid);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_seckey_verify", CallingConvention = CallingConvention.Cdecl)]
public static extern int VerifySecretKey(byte[] seckey);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_pubkey_verify", CallingConvention = CallingConvention.Cdecl)]
public static extern int VerifyPublicKey(byte[] pubkey, int pubkeylen);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_pubkey_create", CallingConvention = CallingConvention.Cdecl)]
public static extern int PublicKeyFromSecretKey(byte[] pubkey, ref int pubkeylen, byte[] seckey, int compressed);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_pubkey_decompress", CallingConvention = CallingConvention.Cdecl)]
public static extern int DecompressPublicKey(byte[] pubkey, ref int pubkeylen);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_pubkey_compress", CallingConvention = CallingConvention.Cdecl)]
public static extern int CompressPublicKey(byte[] pubkey, ref int pubkeylen);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_privkey_export", CallingConvention = CallingConvention.Cdecl)]
public static extern int ExportPrivateKey(byte[] seckey, byte[] privkey, ref int privkeylen, int compressed);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_privkey_import", CallingConvention = CallingConvention.Cdecl)]
public static extern int ImportPrivateKey(byte[] seckey, byte[] privkey, int privkeylen);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_privkey_tweak_add", CallingConvention = CallingConvention.Cdecl)]
public static extern int PrivateKeyTweakAdd(byte[] seckey, byte[] tweak);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_pubkey_tweak_add", CallingConvention = CallingConvention.Cdecl)]
public static extern int PublicKeyTweakAdd(byte[] pubkey, int pubkeylen, byte[] tweak);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_privkey_tweak_mul", CallingConvention = CallingConvention.Cdecl)]
public static extern int PrivateKeyTweakMul(byte[] seckey, byte[] tweak);
[DllImport("secp256k1.dll", EntryPoint = "secp256k1_ecdsa_pubkey_tweak_mul", CallingConvention = CallingConvention.Cdecl)]
public static extern int PublicKeyTweakMul(byte[] pubkey, int pubkeylen, byte[] tweak);
}
It's probably a little bit slower when signing a transaction because of the pinvokes when looping to find a correct nonce, but other than that, it's pure C# instead of managed C++.
Edit: actually, now that I looked at the code a bit closer, this is just as fast, if not faster than the managed C++ version, since it's calling managed and unmanaged code in the loop, so it needs to do a pinvoke in the loop too.
Managed C++ is a fickle beast, you never know where it goes managed or native (those transitions are murder). At least with the code above, you know for certain when it happens.