Skip to content
SDKs · UnityComing soon

RootHerald Unity Plugin

Static-link the RootHerald native library into your Unity build (IL2CPP) and gate any UI element on a hardware-rooted device verdict. Your Unity build's signature is the only one your players see — no Root-Herald-signed DLL ships next to your game.

Coming soon — in development

This SDK is planned and not yet available. Until it ships, mint attestation tokens on the device and verify them server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.

Use the IL2CPP scripting backend

This guide assumes you have selected IL2CPP in Player Settings → Other Settings → Configuration → Scripting Backend. IL2CPP is the only Unity scripting backend that supports static-linking native code into the player binary via [DllImport("__Internal")]. Most shipping commercial Unity titles already use IL2CPP because it's required for iOS and produces faster, AOT-compiled binaries on every other platform too.

Mono-backend projects cannot static-link and would force Root Herald back to dynamic (DLL) linkage, which defeats the EV-signature property described on the native C++ SDK page. If you must use Mono, treat the Root Herald artefact as an opaque managed-side asset shipped via our NuGet — and accept that our org name will appear in your binary signature footprint.

Install

1. Copy the native static archive for each platform you ship into Assets/RootHerald/Plugins/. These are static libraries; Unity's IL2CPP backend links them into the player executable at build time, so nothing Root-Herald-branded ships as a separate file beside the game.

  • RootHerald.lib (Win64 MSVC) → Plugins/Windows/x86_64/
  • librootherald.a (macOS universal) → Plugins/macOS/
  • librootherald.a (Linux x86_64) → Plugins/Linux/
  • rootherald-release.aarPlugins/Android/ (already effectively static)
  • RootHeraldKit.xcframeworkPlugins/iOS/ (already effectively static)

2. In each platform's plugin import settings, set Plugin type → Native and select Load library at build time. This is what tells IL2CPP to resolve the archive at the player's final link step rather than at runtime.

3. Install the managed wrapper via NuGetForUnity to get the C# convenience wrapper around the C ABI:

Packages.configxml
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="RootHerald.Native" version="0.2.0" />
</packages>

4. In the wrapper's P/Invoke declarations, use __Internal as the library name so IL2CPP resolves against the statically-linked archive:

RootHeraldNative.cscsharp
#if ENABLE_IL2CPP
using System;
using System.Runtime.InteropServices;

internal static class RootHeraldNative
{
    // Keyless ABI 3.0: Create takes no api_key and no endpoint.
    [DllImport("__Internal")]
    public static extern IntPtr RootHeraldClient_Create();

    [DllImport("__Internal")]
    public static extern void RootHeraldClient_Destroy(IntPtr client);

    // Collect a quote-over-nonce evidence blob. Returns a caller-owned char*
    // (marshal to string, then free). No verdict, no network to RootHerald.
    [DllImport("__Internal")]
    public static extern int RootHeraldClient_CollectEvidence(
        string nonceB64, out IntPtr outEvidenceJson);

    [DllImport("__Internal")]
    public static extern void RootHeraldClient_FreeEvidence(IntPtr evidenceJson);

    // ...EnrollBegin / EnrollComplete / GetDeviceInfo / CollectPosture likewise.
}
#endif

Usage

The client is keyless — it collects an evidence blob over a nonce your backend issued, then hands the blob back to your backend, which relays it to POST /api/v1/attestations/verify and enforces the verdict. The verdict never travels through the game client.

RootHeraldGate.cscsharp
using System.Threading.Tasks;
using RootHerald;
using UnityEngine;
using UnityEngine.UI;

public class RootHeraldGate : MonoBehaviour
{
    public Text   StatusText;
    public Button VerifyButton;

    private void Awake() => VerifyButton.onClick.AddListener(OnVerify);

    private async void OnVerify()
    {
        StatusText.text = "Attesting…";
        bool trusted = await AttestAsync("signup");
        StatusText.text = trusted ? "TRUSTED" : "BLOCKED";
    }

    public async Task<bool> AttestAsync(string action)
    {
        // 1. Ask YOUR backend for a fresh challenge nonce (it called
        //    /api/v1/attestations/challenge with its rh_sk_ secret).
        string nonce = await MyBackend.GetChallengeNonceAsync(action);

        // 2. Collect a keyless quote-over-nonce evidence blob on-device.
        using var client = new RootHeraldClient();   // no key, no endpoint
        string evidenceJson = await client.CollectEvidenceAsync(nonce);

        // 3. Relay to your backend, which POSTs it to /attestations/verify
        //    and returns whether the device was trusted.
        return await MyBackend.VerifyAndAuthorizeAsync(action, evidenceJson);
    }
}

Sample project

A complete template ships with the plugin when it's released. Drop it onto Unity 2022 LTS+ and press Play.

WebGL builds

WebGL cannot call the native SDK. For browser-shipped Unity titles, the server leg is the same as everywhere else: your backend issues the challenge nonce, relays the collected evidence to POST /api/v1/attestations/verify, and enforces the verdict using the server SDK @rootherald/node. The browser evidence collector itself is the (coming-soon) @rootherald/browser package.