Skip to content

Commit b47a971

Browse files
Merge pull request #625 from dfinity/unity-ii-improvement
Improve Unity ii integration samples.
2 parents abf2568 + 5ce9af2 commit b47a971

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+446
-385
lines changed

native-apps/unity_ii_applink/README.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,22 @@ This is a Unity project with [ICP.NET](https://github.com/BoomDAO/ICP.NET) embed
1111
## Workflow
1212
Before continuing, please read through the [Android App Links](https://developer.android.com/studio/write/app-link-indexing) to understand how Android App Links works.
1313

14-
Here is the basic workflow that how to integrate with Internet Identity from a Unity Android game. The basic idea is to open the Web Browser from the game, login in with II in the browser, and pass the DelegationIdentity back to the game.
14+
Here is the basic workflow that how to integrate with Internet Identity from a Unity Android game. The basic idea is to open the Web Browser from the game, login in with II in the browser, and pass the `DelegationChain` back to the game.
1515

1616
The steps in detail are described below:
1717

1818
1. Set up an [Internet Identity integration dapp](#ii_integration_dapp) which supports logging in with II, with an `assetlinks.json` file associated.
1919
Please refer to [ii_integration_dapp](./ii_integration_dapp/README.md) to set up the dapp.
20+
2021
2. Run a Unity game on Android, which is built from [android_integration sample](#unity_project).
2122
Please refer to [unity_project](./unity_project/README.md) to build the Unity Android game.
23+
2224
3. Launch the Web Browser from the game to open the dapp frontend deployed in #1, with the public key of `Ed25519Identity` as a parameter.
25+
2326
4. Login with your Internet Identity in the Web Browser.
24-
5. Launch the application via App Links, and pass the `DelegationIdentity` back to the game as the URL parameter.
25-
6. Call the backend canister with the `DelegationIdentity` to greet.
27+
28+
5. Launch the application via App Links, and pass the `DelegationChain` back to the game as the URL parameter.
29+
30+
6. Composite the `DelegationIdentity` with `DelegationChain` and the `Ed25519Identity`.
31+
32+
7. Call the backend canister with the `DelegationIdentity` to greet.

native-apps/unity_ii_applink/ii_integration_dapp/README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ This example derives from the [internet_identity_integration](https://github.com
44

55
## Overview
66

7-
This example shows a special case to support login with the `IncompleteEd25519KeyIdentity` which only contains the public key. The reason why we support this is all for security.
7+
This example shows a use case to support login with the two delegations on the `DelegationChain`.
88

99
As we described in [Internet Identity Integration](../README.md#workflow), users can log in with II from the game. Usually what they do is
1010

1111
1. Generate the `Ed25519KeyIdentity` supported by [ICP.NET](https://github.com/BoomDAO/ICP.NET) in the Unity game.
12-
2. For security purposes, only pass the public key of the `Ed25519KeyIdentity` to the Web browser for login, this is where `IncompleteEd25519KeyIdentity` can be used for.
13-
3. In [index.js](./src/greet_frontend/src/index.js), we describe how to retrieve the public key of the `Ed25519Identity` from the URL parameter, use it to instantiate an `IncompleteEd25519KeyIdentity`, and log in with Internet Identity.
12+
2. For security purposes, only pass the public key of the `Ed25519KeyIdentity` to the Web browser for login. And only the public key is necessary when creating a `DelegationChain`.
13+
3. In [index.js](./src/greet_frontend/src/index.js), we describe how to
14+
- log in with Internet Identity with the frontend generated session key
15+
- retrieve the public key of the `Ed25519Identity` from the URL parameter and create another delegation with it.
1416

1517
With this, users don't need to pass the private key around, also they don't need to store the private key outside of the game as they can regenerate the key pairs for every session.
1618

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"greet_backend": {
3+
"ic": "72rj2-biaaa-aaaan-qdatq-cai"
4+
},
5+
"greet_frontend": {
6+
"ic": "6x7nu-oaaaa-aaaan-qdaua-cai"
7+
}
8+
}

native-apps/unity_ii_applink/ii_integration_dapp/src/greet_frontend/assets/.well-known/assetlinks.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"relation": ["delegate_permission/common.handle_all_urls"],
33
"target": {
44
"namespace": "android_app",
5-
"package_name": "com.DefaultCompany.II_AppLink_Integration",
5+
"package_name": "com.dfinity.ii_applink_integration",
66
"sha256_cert_fingerprints":
7-
["86:C9:CA:6F:5A:53:7E:75:9C:D7:29:1E:8A:94:90:BE:90:0B:02:12:40:45:19:B6:65:84:3C:02:AB:B5:97:14"]
7+
["A3:E2:36:BC:E9:04:3F:8F:A9:C5:9B:B5:FE:89:95:C8:08:BA:35:2D:07:D8:76:13:65:A9:27:D6:33:6B:44:6E"]
88
}
99
}]

native-apps/unity_ii_applink/ii_integration_dapp/src/greet_frontend/src/index.html

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
<button id="open">Launch Application by AppLink</button>
2222
</form>
2323
<br />
24+
<form>
25+
<button id="greet">Greet</button>
26+
</form>
27+
<section id="greeting"></section>
2428
</main>
2529
</body>
2630
</html>
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,30 @@
1-
import {AuthClient} from "@dfinity/auth-client"
2-
import {SignIdentity} from "@dfinity/agent";
3-
import {DelegationIdentity, Ed25519PublicKey } from "@dfinity/identity";
4-
5-
// An incomplete Ed25519KeyIdentity with only the public key provided.
6-
class IncompleteEd25519KeyIdentity extends SignIdentity {
7-
constructor(publicKey) {
8-
super();
9-
this._publicKey = publicKey;
10-
}
11-
12-
getPublicKey () {
13-
return this._publicKey;
14-
}
15-
}
16-
17-
function fromHexString(hexString) {
18-
return new Uint8Array((hexString.match(/.{1,2}/g) ?? []).map(byte => parseInt(byte, 16))).buffer;
19-
}
1+
import {createActor, greet_backend} from "../../declarations/greet_backend";
2+
import {AuthClient} from "@dfinity/auth-client";
3+
import {HttpAgent} from "@dfinity/agent";
4+
import {DelegationIdentity, Ed25519PublicKey, ECDSAKeyIdentity, DelegationChain} from "@dfinity/identity";
5+
import {fromHexString} from "@dfinity/identity/lib/cjs/buffer";
206

21-
let myKeyIdentity;
22-
let sessionKeyIndex = -1;
7+
let appPublicKey;
238

249
var url = window.location.href;
25-
sessionKeyIndex = url.indexOf("sessionkey=");
26-
if (sessionKeyIndex !== -1) {
27-
// Parse the public session key and instantiate an IncompleteEd25519KeyIdentity.
28-
var sessionkey = url.substring(sessionKeyIndex + "sessionkey=".length);
29-
30-
var publicKey = Ed25519PublicKey.fromDer(fromHexString(sessionkey));
31-
myKeyIdentity = new IncompleteEd25519KeyIdentity(publicKey);
32-
} else {
33-
// TODO: initialize an Ed25519KeyIdentity();
10+
var publicKeyIndex = url.indexOf("sessionkey=");
11+
if (publicKeyIndex !== -1) {
12+
// Parse the public key.
13+
var publicKeyString = url.substring(publicKeyIndex + "sessionkey=".length);
14+
appPublicKey = Ed25519PublicKey.fromDer(fromHexString(publicKeyString));
3415
}
3516

36-
let delegationIdentity;
17+
let actor = greet_backend;
18+
let delegationChain;
3719

3820
const loginButton = document.getElementById("login");
3921
loginButton.onclick = async (e) => {
4022
e.preventDefault();
4123

4224
// Create an auth client.
25+
var middleKeyIdentity = await ECDSAKeyIdentity.generate();
4326
let authClient = await AuthClient.create({
44-
identity: myKeyIdentity,
27+
identity: middleKeyIdentity,
4528
});
4629

4730
// Start the login process and wait for it to finish.
@@ -53,9 +36,24 @@ loginButton.onclick = async (e) => {
5336
});
5437

5538
// At this point we're authenticated, and we can get the identity from the auth client.
56-
const identity = authClient.getIdentity();
57-
if (identity instanceof DelegationIdentity) {
58-
delegationIdentity = identity;
39+
const middleIdentity = authClient.getIdentity();
40+
41+
// Using the identity obtained from the auth client to create an agent to interact with the IC.
42+
const agent = new HttpAgent({identity: middleIdentity});
43+
actor = createActor(process.env.GREET_BACKEND_CANISTER_ID, {
44+
agent,
45+
});
46+
47+
// Create another delegation with the app public key, then we have two delegations on the chain.
48+
if (appPublicKey != null && middleIdentity instanceof DelegationIdentity ) {
49+
let middleToApp = await DelegationChain.create(
50+
middleKeyIdentity,
51+
appPublicKey,
52+
new Date(Date.now() + 15 * 60 * 1000),
53+
{ previous: middleIdentity.getDelegation() },
54+
);
55+
56+
delegationChain = middleToApp;
5957
}
6058

6159
return false;
@@ -65,19 +63,33 @@ const openButton = document.getElementById("open");
6563
openButton.onclick = async (e) => {
6664
e.preventDefault();
6765

68-
// if (sessionKeyIndex === -1) {
69-
// // TODO: warning for not login from a game.
70-
// return false;
71-
// }
66+
if (delegationChain == null){
67+
console.log("Invalid delegation chain.");
68+
return false;
69+
}
7270

7371
var url = "https://6x7nu-oaaaa-aaaan-qdaua-cai.icp0.io/authorize?";
74-
if (delegationIdentity != null) {
75-
var delegationString = JSON.stringify(delegationIdentity.getDelegation().toJSON());
76-
console.log(delegationString);
77-
url = url + "delegation=" + encodeURIComponent(delegationString);
78-
}
72+
var delegationString = JSON.stringify(delegationChain.toJSON());
73+
url = url + "delegation=" + encodeURIComponent(delegationString);
74+
//console.log(url);
7975

8076
window.open(url, "_self");
8177

8278
return false;
8379
};
80+
81+
const greetButton = document.getElementById("greet");
82+
greetButton.onclick = async (e) => {
83+
e.preventDefault();
84+
85+
greetButton.setAttribute("disabled", true);
86+
87+
// Interact with backend actor, calling the greet method
88+
const greeting = await actor.greet();
89+
90+
greetButton.removeAttribute("disabled");
91+
92+
document.getElementById("greeting").innerText = greeting;
93+
94+
return false;
95+
};

native-apps/unity_ii_deeplink/unity_project/Assets/ICP.NET/Chaos.NaCl.dll.meta native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/BouncyCastle.Cryptography.dll.meta

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.
Binary file not shown.

native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/EdjCase.Cryptography.BLS.dll.meta

-33
This file was deleted.
Binary file not shown.

native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/Chaos.NaCl.dll.meta native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/EdjCase.ICP.BLS.dll.meta

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# ICP.NET
2+
3+
The libraries in ICP.NET are built from https://github.com/BoomDAO/ICP.NET, with
4+
5+
1. Version `4.0.0`, hashtag `7b51d78b4f4356d767bb86074918a41973c41214`.
6+
2. `ByteUtil` class is changed from `internal` to `public` in `\ICP.NET\src\Candid\Utilities\ByteUtil.cs` file.

native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/README.md.meta

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/System.Collections.Immutable.dll.meta

-27
This file was deleted.
Binary file not shown.

native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/System.IO.Pipelines.dll.meta native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/System.Formats.Cbor.dll.meta

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.
Binary file not shown.

native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/Dahomey.Cbor.dll.meta native-apps/unity_ii_applink/unity_project/Assets/ICP.NET/Wasmtime.Dotnet.dll.meta

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native-apps/unity_ii_applink/unity_project/Assets/Scenes/SampleScene.unity

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ MonoBehaviour:
168168
m_Script: {fileID: 11500000, guid: 5c68e31d1a87467468d62c1cd739b0a0, type: 3}
169169
m_Name:
170170
m_EditorClassIdentifier:
171+
greetFrontend: https://6x7nu-oaaaa-aaaan-qdaua-cai.icp0.io/
171172
greetBackendCanister: 72rj2-biaaa-aaaan-qdatq-cai
172173
--- !u!114 &272906662
173174
MonoBehaviour:
@@ -181,7 +182,6 @@ MonoBehaviour:
181182
m_Script: {fileID: 11500000, guid: 17feafab2ee1b6b4684003f4b0edc397, type: 3}
182183
m_Name:
183184
m_EditorClassIdentifier:
184-
greetFrontend: https://6x7nu-oaaaa-aaaan-qdaua-cai.icp0.io
185185
--- !u!1 &671559154
186186
GameObject:
187187
m_ObjectHideFlags: 0

native-apps/unity_ii_applink/unity_project/Assets/Scripts/DeepLinkPlugin.cs

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
using UnityEngine;
2+
using EdjCase.ICP.Agent;
3+
using EdjCase.ICP.Agent.Identities;
4+
using EdjCase.ICP.Agent.Models;
5+
using EdjCase.ICP.Candid.Models;
26
using EdjCase.ICP.Candid.Utilities;
37
using Newtonsoft.Json;
8+
using System;
49
using System.Web;
10+
using System.Collections.Generic;
511

612
namespace IC.GameKit
713
{
@@ -22,7 +28,7 @@ public void Start()
2228

2329
public void OpenBrowser()
2430
{
25-
var target = mTestICPAgent.greetFrontend + "?sessionkey=" + ByteUtil.ToHexString(mTestICPAgent.TestIdentity.PublicKey.Value);
31+
var target = mTestICPAgent.greetFrontend + "?sessionkey=" + ByteUtil.ToHexString(mTestICPAgent.TestIdentity.PublicKey.ToDerEncoding());
2632
Application.OpenURL(target);
2733
}
2834

@@ -40,8 +46,36 @@ public void OnDeepLinkActivated(string url)
4046
}
4147

4248
var delegationString = HttpUtility.UrlDecode(url.Substring(indexOfDelegation + kDelegationParam.Length));
43-
var delegation = JsonConvert.DeserializeObject<DelegationChainModel>(delegationString);
44-
mTestICPAgent.Delegation = delegation;
49+
mTestICPAgent.DelegationIdentity = ConvertJsonToDelegationIdentity(delegationString);
50+
}
51+
52+
internal DelegationIdentity ConvertJsonToDelegationIdentity(string jsonDelegation)
53+
{
54+
var delegationChainModel = JsonConvert.DeserializeObject<DelegationChainModel>(jsonDelegation);
55+
if (delegationChainModel == null && delegationChainModel.delegations.Length == 0)
56+
{
57+
Debug.LogError("Invalid delegation chain.");
58+
return null;
59+
}
60+
61+
// Initialize DelegationIdentity.
62+
var delegations = new List<SignedDelegation>();
63+
foreach (var signedDelegationModel in delegationChainModel.delegations)
64+
{
65+
var pubKey = SubjectPublicKeyInfo.FromDerEncoding(ByteUtil.FromHexString(signedDelegationModel.delegation.pubkey));
66+
var expiration = ICTimestamp.FromNanoSeconds(Convert.ToUInt64(signedDelegationModel.delegation.expiration, 16));
67+
var delegation = new Delegation(pubKey, expiration);
68+
69+
var signature = ByteUtil.FromHexString(signedDelegationModel.signature);
70+
var signedDelegation = new SignedDelegation(delegation, signature);
71+
delegations.Add(signedDelegation);
72+
}
73+
74+
var chainPublicKey = SubjectPublicKeyInfo.FromDerEncoding(ByteUtil.FromHexString(delegationChainModel.publicKey));
75+
var delegationChain = new DelegationChain(chainPublicKey, delegations);
76+
var delegationIdentity = new DelegationIdentity(mTestICPAgent.TestIdentity, delegationChain);
77+
78+
return delegationIdentity;
4579
}
4680
}
4781
}

0 commit comments

Comments
 (0)