-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial ZK login implementation. #119
- Loading branch information
Showing
7 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
using System; | ||
using System.Numerics; | ||
using System.Security.Cryptography; | ||
using System.Text; | ||
using Jose; | ||
|
||
|
||
namespace Sui.ZKLogin.SDK | ||
{ | ||
public static class Address | ||
{ | ||
public const int MAX_HEADER_LEN_B64 = 248; | ||
public const int MAX_PADDED_UNSIGNED_JWT_LEN = 64 * 25; | ||
|
||
private static readonly char[] HexChars = "0123456789abcdef".ToCharArray(); | ||
|
||
private static string BytesToHex(byte[] bytes) | ||
{ | ||
char[] hex = new char[bytes.Length * 2 + 2]; | ||
hex[0] = '0'; | ||
hex[1] = 'x'; | ||
|
||
for (int i = 0; i < bytes.Length; i++) | ||
{ | ||
hex[i * 2 + 2] = HexChars[bytes[i] >> 4]; | ||
hex[i * 2 + 3] = HexChars[bytes[i] & 0xF]; | ||
} | ||
return new string(hex); | ||
} | ||
|
||
public static void LengthChecks(string jwt) | ||
{ | ||
var parts = jwt.Split("."); | ||
string header = parts[0]; | ||
|
||
if (header.Length > MAX_HEADER_LEN_B64) | ||
throw new ArgumentException("Header is too long"); | ||
|
||
int L = (header.Length + 1 + parts[1].Length) * 8; | ||
int K = (512 + 448 - ((L % 512) + 1)) % 512; | ||
int paddedUnsignedJwtLen = (L + 1 + K + 64) / 8; | ||
|
||
if (paddedUnsignedJwtLen > MAX_PADDED_UNSIGNED_JWT_LEN) | ||
throw new ArgumentException("JWT is too long"); | ||
} | ||
|
||
public static string JwtToAddress(string jwt, string userSalt) | ||
{ | ||
LengthChecks(jwt); | ||
|
||
var payload = JWT.Decode<JwtPayload>(jwt); | ||
if (string.IsNullOrEmpty(payload.Sub) || string.IsNullOrEmpty(payload.Iss) || string.IsNullOrEmpty(payload.Aud)) | ||
throw new ArgumentException("Missing jwt data"); | ||
|
||
// Check if Aud is an array by checking if it contains a comma | ||
// This is a simple way to detect multiple audience values | ||
if (payload.Aud.Contains(",")) | ||
throw new ArgumentException("Not supported aud. Aud is an array, string was expected."); | ||
|
||
return ComputeZkLoginAddress(new ZkLoginAddressOptions | ||
{ | ||
UserSalt = userSalt, | ||
ClaimName = "sub", | ||
ClaimValue = payload.Sub, | ||
Aud = payload.Aud, | ||
Iss = payload.Iss | ||
}); | ||
} | ||
|
||
|
||
public static string ComputeZkLoginAddress(ZkLoginAddressOptions options) | ||
{ | ||
var seed = GenAddressSeed(options.UserSalt, options.ClaimName, options.ClaimValue, options.Aud); | ||
return ComputeZkLoginAddressFromSeed(seed, options.Iss); | ||
} | ||
|
||
private static BigInteger GenAddressSeed(string userSalt, string claimName, string claimValue, string aud) | ||
{ | ||
using var sha256 = SHA256.Create(); | ||
var saltBytes = Encoding.UTF8.GetBytes(userSalt); | ||
var claimBytes = Encoding.UTF8.GetBytes($"{claimName}:{claimValue}:{aud}"); | ||
|
||
var combined = new byte[saltBytes.Length + claimBytes.Length]; | ||
Buffer.BlockCopy(saltBytes, 0, combined, 0, saltBytes.Length); | ||
Buffer.BlockCopy(claimBytes, 0, combined, saltBytes.Length, claimBytes.Length); | ||
|
||
var hash = sha256.ComputeHash(combined); | ||
return new BigInteger(hash); | ||
} | ||
|
||
private static string ComputeZkLoginAddressFromSeed(BigInteger seed, string iss) | ||
{ | ||
using var sha256 = SHA256.Create(); | ||
var issBytes = Encoding.UTF8.GetBytes(iss); | ||
var seedBytes = seed.ToByteArray(); | ||
|
||
var combined = new byte[seedBytes.Length + issBytes.Length]; | ||
Buffer.BlockCopy(seedBytes, 0, combined, 0, seedBytes.Length); | ||
Buffer.BlockCopy(issBytes, 0, combined, seedBytes.Length, issBytes.Length); | ||
|
||
var hash = sha256.ComputeHash(combined); | ||
return BytesToHex(hash); | ||
} | ||
} | ||
|
||
public class ZkLoginAddressOptions | ||
{ | ||
public string ClaimName { get; set; } | ||
public string ClaimValue { get; set; } | ||
public string UserSalt { get; set; } | ||
public string Iss { get; set; } | ||
public string Aud { get; set; } | ||
} | ||
|
||
public class JwtPayload | ||
{ | ||
public string Sub { get; set; } | ||
public string Iss { get; set; } | ||
public string Aud { get; set; } | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
using System; | ||
using System.Numerics; | ||
using System.Linq; | ||
|
||
namespace Sui.ZKLogin.SDK | ||
{ | ||
public static class PoseidonHasher | ||
{ | ||
public static readonly BigInteger BN254_FIELD_SIZE = BigInteger.Parse("21888242871839275222246405745257275088548364400416034343698204186575808495617"); | ||
|
||
private static readonly Func<BigInteger[], BigInteger>[] PoseidonNumToHashFN = { | ||
Poseidon.Hash1, | ||
Poseidon.Hash2, | ||
Poseidon.Hash3, | ||
Poseidon.Hash4, | ||
Poseidon.Hash5, | ||
Poseidon.Hash6, | ||
Poseidon.Hash7, | ||
Poseidon.Hash8, | ||
Poseidon.Hash9, | ||
Poseidon.Hash10, | ||
Poseidon.Hash11, | ||
Poseidon.Hash12, | ||
Poseidon.Hash13, | ||
Poseidon.Hash14, | ||
Poseidon.Hash15, | ||
Poseidon.Hash16 | ||
}; | ||
|
||
public static BigInteger PoseidonHash(object[] inputs) | ||
{ | ||
var bigIntInputs = inputs.Select(x => { | ||
var b = ToBigInteger(x); | ||
if (b < 0 || b >= BN254_FIELD_SIZE) | ||
throw new ArgumentException($"Element {b} not in the BN254 field"); | ||
return b; | ||
}).ToArray(); | ||
|
||
if (bigIntInputs.Length <= 16 && PoseidonNumToHashFN.Length >= bigIntInputs.Length) | ||
{ | ||
return PoseidonNumToHashFN[bigIntInputs.Length - 1](bigIntInputs); | ||
} | ||
else if (bigIntInputs.Length <= 32) | ||
{ | ||
var hash1 = PoseidonHash(bigIntInputs.Take(16).Cast<object>().ToArray()); | ||
var hash2 = PoseidonHash(bigIntInputs.Skip(16).Cast<object>().ToArray()); | ||
return PoseidonHash(new object[] { hash1, hash2 }); | ||
} | ||
|
||
throw new ArgumentException($"Unable to hash a vector of length {bigIntInputs.Length}"); | ||
} | ||
|
||
private static BigInteger ToBigInteger(object input) | ||
{ | ||
return input switch | ||
{ | ||
BigInteger bi => bi, | ||
int i => new BigInteger(i), | ||
long l => new BigInteger(l), | ||
string s => BigInteger.Parse(s), | ||
_ => throw new ArgumentException($"Unsupported input type: {input.GetType()}") | ||
}; | ||
} | ||
} | ||
|
||
// This interface needs to be implemented based on the poseidon-lite functionality | ||
public static class Poseidon | ||
{ | ||
public static BigInteger Hash1(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash2(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash3(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash4(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash5(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash6(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash7(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash8(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash9(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash10(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash11(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash12(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash13(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash14(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash15(BigInteger[] inputs) => throw new NotImplementedException(); | ||
public static BigInteger Hash16(BigInteger[] inputs) => throw new NotImplementedException(); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Assets/Sui-Unity-SDK/Code/Sui.ZKLogin/SDK/Poseidon.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using UnityEngine; | ||
|
||
|
||
namespace Sui.ZKLogin.SDK | ||
{ | ||
public class Utils : MonoBehaviour | ||
{ | ||
// Start is called before the first frame update | ||
void Start() | ||
{ | ||
|
||
} | ||
|
||
// Update is called once per frame | ||
void Update() | ||
{ | ||
|
||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.