Skip to content

Instantly share code, notes, and snippets.

@BastianBlokland
Last active February 16, 2024 22:19
Show Gist options
  • Save BastianBlokland/bbc02a407b05beaf3f55ead3dd10f808 to your computer and use it in GitHub Desktop.
Save BastianBlokland/bbc02a407b05beaf3f55ead3dd10f808 to your computer and use it in GitHub Desktop.
Way to get the game-center verification signature with Unity.

Even tho Unity has implemented a game-center api (Social api) it doesn't have a way to generate a verification signature, which you need to use game-center as a authentication method.

Luckily the objective-c code required is pretty straight forward. (Apple documentation: generateIdentityVerificationSignature)

#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>

typedef void (*IdentityVerificationSignatureCallback)(const char * publicKeyUrl, const char * signature, int signatureLength, const char * salt, int saltLength, const uint64_t timestamp, const char * error);

extern void generateIdentityVerificationSignature(IdentityVerificationSignatureCallback callback) {
    
    GKLocalPlayer * localPlayer = [GKLocalPlayer localPlayer];

    NSLog(@"LocalPlayer: %@", localPlayer.playerID);
    [localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL * publicKeyUrl, NSData * signature, NSData * salt, uint64_t timestamp, NSError * error) {

        NSLog(@"Received 'generateIdentityVerificationSignature' callback, error: %@", error.description);

        // Create a pool for releasing the resources we create
        @autoreleasepool {
            
            // PublicKeyUrl
            const char * publicKeyUrlCharPointer = NULL;
            if (publicKeyUrl != NULL)
            {
                const NSString * publicKeyUrlString = [[NSString alloc] initWithString:[publicKeyUrl absoluteString]];
                publicKeyUrlCharPointer = [publicKeyUrlString UTF8String];
            }

            // Signature
            const char * signatureBytes = [signature bytes];
            int signatureLength = (int)[signature length];

            // Salt
            const char * saltBytes = [salt bytes];
            int saltLength = (int)[salt length];

            // Error
            const NSString * errorString = error.description;
            const char * errorStringPointer = [errorString UTF8String];

            // Callback
            callback(publicKeyUrlCharPointer, signatureBytes, signatureLength, saltBytes, saltLength, timestamp, errorStringPointer);
        }
    }];
}

Strings are marshalled automatically for us and for the byte[]'s we pass a pointer to the obj-c memory, that way we can copy the data into managed arrays.

// Add a using for: AOT
// Add a using for: System.Runtime.InteropServices

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void IdentityVerificationSignatureCallback(
    string publicKeyUrl, 
    IntPtr signaturePointer, int signatureLength,
    IntPtr saltPointer, int saltLength,
    ulong timestamp,
    string error);

[DllImport("__Internal")]
private static extern void generateIdentityVerificationSignature(
    [MarshalAs(UnmanagedType.FunctionPtr)]IdentityVerificationSignatureCallback callback);

// Note: This callback has to be static because Unity's il2Cpp doesn't support marshalling instance methods.
[MonoPInvokeCallback(typeof(IdentityVerificationSignatureCallback))]
private static void OnIdentityVerificationSignatureGenerated(
    string publicKeyUrl, 
    IntPtr signaturePointer, int signatureLength,
    IntPtr saltPointer, int saltLength,
    ulong timestamp,
    string error)
{
    // Create a managed array for the signature
    var signature = new byte[signatureLength];
    Marshal.Copy(signaturePointer, signature, 0, signatureLength);

    // Create a managed array for the salt
    var salt = new byte[saltLength];
    Marshal.Copy(saltPointer, salt, 0, saltLength);

    UnityEngine.Debug.Log($"publicKeyUrl: {publicKeyUrl}");
    UnityEngine.Debug.Log($"signature: {signature.Length}");
    UnityEngine.Debug.Log($"salt: {salt.Length}");
    UnityEngine.Debug.Log($"timestamp: {timestamp}");
    UnityEngine.Debug.Log($"error: {error}");
}

You can then call generateIdentityVerificationSignature and give it OnIdentityVerificationSignatureGenerated as a callback, you can then in the callback save the results to a static variable for example.

Note: this assumes that the user is already logged-in to game-center, luckily Unity has already implemented the api to login:

Social.localUser.Authenticate(success =>
{

});

For info on where to place the objective-c code Unity has a manual page: PluginsForIOS

@natewilliford
Copy link

Hello there, I need to convert byte arrays of salt and signature to strings(in C#). Do I just use Convert.ToBase64String or there is some other method for it. Thanks in advance!

FWIW, PlayFab expects base64 encoded salt and signature.

@okankayhan
Copy link

okankayhan commented Nov 23, 2022

Updated the code to work with new auth handle fetchItemsForIdentityVerificationSignature + some casts to make it work.

.m File

#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>

typedef void (*IdentityVerificationSignatureCallback)(const char * publicKeyUrl, const char * signature, int signatureLength, const char * salt, int saltLength, const uint64_t timestamp, const char * error);

extern void generateIdentityVerificationSignature(IdentityVerificationSignatureCallback callback) {
    
    GKLocalPlayer * localPlayer = [GKLocalPlayer localPlayer];

    NSLog(@"LocalPlayer: %@", localPlayer.playerID);
    
    [localPlayer fetchItemsForIdentityVerificationSignature:^(NSURL * _Nullable publicKeyURL, NSData * _Nullable signature, NSData * _Nullable salt, uint64_t timestamp, NSError * _Nullable error) {

        NSLog(@"Received 'generateIdentityVerificationSignature' callback, error: %@", error.description);

        // Create a pool for releasing the resources we create
        @autoreleasepool {
            
            // PublicKeyUrl
            const char * publicKeyUrlCharPointer = NULL;
            if (publicKeyURL != NULL)
            {
                const NSString * publicKeyUrlString = [[NSString alloc] initWithString:[publicKeyURL absoluteString]];
                publicKeyUrlCharPointer = [publicKeyUrlString UTF8String];
            }


            // Signature
            const char * signatureBytes = (char*)[signature bytes];
            int signatureLength = (int)[signature length];

            // Salt
            const char * saltBytes = (char*)[salt bytes];
            int saltLength = (int)[salt length];

            // Error
            const NSString * errorString = error.description;
            const char * errorStringPointer = [errorString UTF8String];

            // Callback
            callback(publicKeyUrlCharPointer, signatureBytes, signatureLength, saltBytes, saltLength, timestamp, errorStringPointer);
        }
    }];
}

.cs file

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void IdentityVerificationSignatureCallback(
        string publicKeyUrl, 
        IntPtr signaturePointer, int signatureLength,
        IntPtr saltPointer, int saltLength,
        ulong timestamp,
        string error);

    [DllImport("__Internal")]
    public static extern void generateIdentityVerificationSignature(
        [MarshalAs(UnmanagedType.FunctionPtr)]IdentityVerificationSignatureCallback callback);

// Note: This callback has to be static because Unity's il2Cpp doesn't support marshalling instance methods.
    [MonoPInvokeCallback(typeof(IdentityVerificationSignatureCallback))]
    public static void OnIdentityVerificationSignatureGenerated(
        string publicKeyUrl, 
        IntPtr signaturePointer, int signatureLength,
        IntPtr saltPointer, int saltLength,
        ulong timestamp,
        string error)
    {
        // Create a managed array for the signature
        var signature = new byte[signatureLength];
        Marshal.Copy(signaturePointer, signature, 0, signatureLength);

        // Create a managed array for the salt
        var salt = new byte[saltLength];
        Marshal.Copy(saltPointer, salt, 0, saltLength);

        UnityEngine.Debug.Log($"publicKeyUrl: {publicKeyUrl}");
        UnityEngine.Debug.Log($"signature: {signature.Length}");
        UnityEngine.Debug.Log($"salt: {salt.Length}");
        UnityEngine.Debug.Log($"timestamp: {timestamp}");
        UnityEngine.Debug.Log($"error: {error}");
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment