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

@BastianBlokland
Copy link
Author

I must admit we didn't get around to it yet 😊

@mirkosrsen1
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!

@BastianBlokland
Copy link
Author

That would depend on what format the service you are using expects it in.

On the backend you end up needing both as raw bytes. You use salt, playerId, bundleId and timestamp to generate a new hash and then compare that to the signature using Apple's public key.
Apple has some more info on the (now deprecated 😢) docs for 'generateIdentityVerificationSignature'.

But having said that, if you are using an external service with a http api, its highly likely they expect them to be base64 encoded indeed. But their documentation should really say the format.

@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