Skip to content

Commit

Permalink
✨ Add Solana Mobile Stack implementation (#61)
Browse files Browse the repository at this point in the history
* ✨ Add Solana Mobile Stack implementation

* 📝 Add link to the Unity asset store
  • Loading branch information
GabrielePicco authored Mar 18, 2023
1 parent 34bdac1 commit 4289680
Show file tree
Hide file tree
Showing 53 changed files with 17,117 additions and 15,358 deletions.
Binary file modified Packages/NativeWebSocket.dll
Binary file not shown.
4 changes: 2 additions & 2 deletions Packages/NativeWebSocket.dll.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Solana.Unity-SDK started as a fork of [unity-solana-wallet](https://github.com/a

## 📝 [Documentation](http://developers.garbles.fun/)

## 🎮 [Unity Asset Store](https://assetstore.unity.com/packages/decentralization/infrastructure/solana-sdk-for-unity-246931)

The SDK is also available on the [Unity Asset Store](https://assetstore.unity.com/packages/decentralization/infrastructure/solana-sdk-for-unity-246931)

## 🚀 [Live Demo](https://magicblock-labs.github.io/Solana.Unity-SDK/)

[![](https://solana.unity-sdk.gg/demo.gif)](https://garbles-labs.github.io/Solana.Unity-SDK/)
Expand Down
2 changes: 0 additions & 2 deletions Runtime/Plugins/Phantom.jslib
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ mergeInto(LibraryManager.library, {
stringToUTF8(sign, strPtr, lenSign);
Module.dynCall_vi(callback, strPtr);
} catch (err) {
window.alert('Phantom error: ' + err.message);
console.error(err.message);
}
} else {
Expand All @@ -62,7 +61,6 @@ mergeInto(LibraryManager.library, {
stringToUTF8(sign, strPtr, lenSign);
Module.dynCall_vi(callback, strPtr);
} catch (err) {
window.alert('Phantom error: ' + err.message);
console.error(err.message);
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion Runtime/codebase/PhantomWallet/PhantomDeepLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private RpcCluster GetCluster()
{
RpcCluster.DevNet => RpcCluster.DevNet,
RpcCluster.TestNet => RpcCluster.TestNet,
RpcCluster.Custom => CustomRpcUri.Contains("devnet") ? RpcCluster.DevNet : RpcCluster.MainNet,
RpcCluster.MainNet => RpcCluster.MainNet,
_ => RpcCluster.MainNet
};
}
Expand Down
8 changes: 8 additions & 0 deletions Runtime/codebase/SolanaMobileStack.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Runtime/codebase/SolanaMobileStack/AssociationContract.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace SolanaMobileStack
{
public static class AssociationContract
{
public const string SchemeMobileWalletAdapter = "solana-wallet";
public const string LocalPathSuffix = "v1/associate/local";
}
}
11 changes: 11 additions & 0 deletions Runtime/codebase/SolanaMobileStack/AssociationContract.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Runtime/codebase/SolanaMobileStack/Crypto.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

219 changes: 219 additions & 0 deletions Runtime/codebase/SolanaMobileStack/Crypto/ECDSASignatures.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
using System;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using UnityEngine.Assertions;

namespace SolanaMobileStack.Crypto
{
public static class EcdsaSignatures
{

// CONSTANTS
private const int EncodedPublicKeyLengthBytes = 65;
private const int P256DerSignaturePrefixLen = 2; // 0x30 || 1-byte length
private const byte P256DerSignaturePrefixType = (byte)0x30;
private const int P256DerSignatureComponentPrefixLen = 2; // 0x02 || 1-byte length
private const byte P256DerSignatureComponentPrefixType = (byte)0x02;
private const int P256DerSignatureComponentMinLen = 1;
private const int P256DerSignatureComponentMaxLen = 33;
private const int P256P1363ComponentLen = 32;
private const int P256P1363SignatureLen = 64;


/// <summary>
/// Utils to encode a P256 public key into a byte array
/// </summary>
/// <param name="ecPublicKey"></param>
/// <returns></returns>
public static byte[] EncodeP256PublicKey(ECPublicKeyParameters ecPublicKey) {
var w = ecPublicKey.Q;
var x = w.AffineXCoord.GetEncoded();
var y = w.AffineYCoord.GetEncoded();
var encodedPublicKey = new byte[EncodedPublicKeyLengthBytes];
encodedPublicKey[0] = 0x04;
int xLen = Math.Min(x.Length, 32);
int yLen = Math.Min(y.Length, 32);
Array.Copy(x, x.Length - xLen, encodedPublicKey, 33 - xLen, xLen);
Array.Copy(y, y.Length - yLen, encodedPublicKey, 65 - yLen, yLen);
return encodedPublicKey;
}

/// <summary>
/// Convert a DER-encoded ECDSA signature to the P1363 format
/// </summary>
/// <param name="derSignature"></param>
/// <param name="offset"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static byte[] ConvertEcp256SignatureDeRtoP1363(byte[] derSignature, int offset) {
if ((offset + P256DerSignaturePrefixLen) > derSignature.Length) {
throw new ArgumentException("DER signature buffer too short to define sequence");
}

byte derType = derSignature[offset];
if (derType != P256DerSignaturePrefixType) {
throw new ArgumentException("DER signature has invalid type");
}

int derSeqLen = derSignature[offset + 1];

byte[] p1363Signature = new byte[P256P1363SignatureLen];
int sOff = UnpackDerIntegerToP1363Component(derSignature,
offset + P256DerSignaturePrefixLen, p1363Signature, 0);
int totalOff = UnpackDerIntegerToP1363Component(derSignature, sOff, p1363Signature,
P256P1363ComponentLen);

if ((offset + P256DerSignaturePrefixLen + derSeqLen) != totalOff) {
throw new ArgumentException("Invalid DER signature length");
}

return p1363Signature;
}

/// <summary>
/// Convert a P1363-encoded ECDSA signature to the DER format
/// </summary>
/// <param name="p1363Signature"></param>
/// <param name="p1363Offset"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static byte[] ConvertEcp256SignatureP1363ToDer(byte[] p1363Signature, int p1363Offset)
{
if ((p1363Offset + P256P1363SignatureLen) > p1363Signature.Length)
{
throw new Exception("Invalid P1363 signature length");
}

int rDerIntLen = CalculateDerIntLengthOfP1363Component(p1363Signature, p1363Offset);
int sDerIntLen = CalculateDerIntLengthOfP1363Component(p1363Signature, p1363Offset + P256P1363ComponentLen);

byte[] derSignature = new byte[P256DerSignaturePrefixLen +
2 * P256DerSignatureComponentPrefixLen + rDerIntLen + sDerIntLen];
derSignature[0] = P256DerSignaturePrefixType;
derSignature[1] = (byte)(2 * P256DerSignatureComponentPrefixLen + rDerIntLen + sDerIntLen);
int sOff = PackP1363ComponentToDerInteger(p1363Signature, p1363Offset, rDerIntLen,
derSignature, P256DerSignaturePrefixLen);
int totalLen = PackP1363ComponentToDerInteger(p1363Signature,
p1363Offset + P256P1363ComponentLen, sDerIntLen, derSignature, sOff);
Assert.IsTrue(totalLen == derSignature.Length);
return derSignature;
}

/// <summary>
/// Pack a P1363-encoded ECDSA signature component into a DER-encoded integer
/// </summary>
/// <param name="p1363Signature"></param>
/// <param name="p1363Offset"></param>
/// <param name="p1363ComponentDerIntLength"></param>
/// <param name="derSignature"></param>
/// <param name="derOffset"></param>
/// <returns></returns>
private static int PackP1363ComponentToDerInteger(
byte[] p1363Signature,
int p1363Offset,
int p1363ComponentDerIntLength,
byte[] derSignature,
int derOffset) {

Assert.IsTrue(p1363Offset > 0);
Assert.IsTrue(p1363ComponentDerIntLength is > 1 and <= P256P1363ComponentLen + 1);

derSignature[derOffset] = P256DerSignatureComponentPrefixType;
derSignature[derOffset + 1] = (byte)p1363ComponentDerIntLength;

int leadingBytes = Math.Max(0, p1363ComponentDerIntLength - P256P1363ComponentLen);
int copyLen = Math.Min(p1363ComponentDerIntLength, P256P1363ComponentLen);
Array.Fill(derSignature, (byte) 0, derOffset + P256DerSignatureComponentPrefixLen,
leadingBytes) ;
Array.Copy(p1363Signature, p1363Offset + P256P1363ComponentLen - copyLen,
derSignature, derOffset + P256DerSignatureComponentPrefixLen + leadingBytes,
copyLen);

return derOffset + P256DerSignatureComponentPrefixLen + p1363ComponentDerIntLength;
}

/// <summary>
/// Calculate the length of a DER-encoded integer from a P1363-encoded ECDSA signature component
/// </summary>
/// <param name="p1363Signature"></param>
/// <param name="p1363Offset"></param>
/// <returns></returns>
private static int CalculateDerIntLengthOfP1363Component(byte[] p1363Signature, int p1363Offset) {
byte val = p1363Signature[p1363Offset];
if (val > 127) {
return P256P1363ComponentLen + 1;
}
return P256P1363ComponentLen;
}

/// <summary>
/// Unpack a DER-encoded integer into a P1363-encoded ECDSA signature component
/// </summary>
/// <param name="derSignature"></param>
/// <param name="derOffset"></param>
/// <param name="p1363Signature"></param>
/// <param name="p1363Offset"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="ArgumentException"></exception>
private static int UnpackDerIntegerToP1363Component(byte[] derSignature, int derOffset, byte[] p1363Signature, int p1363Offset) {
if ((derOffset + P256DerSignatureComponentPrefixLen) > derSignature.Length) {
throw new ArgumentOutOfRangeException("DER signature buffer too short to define component");
}

var componentDerType = derSignature[derOffset];
int componentLen = derSignature[derOffset + 1];

if (componentDerType != P256DerSignatureComponentPrefixType ||
componentLen < P256DerSignatureComponentMinLen ||
componentLen > P256DerSignatureComponentMaxLen) {
throw new ArgumentException("DER signature component not well formed");
}

if ((derOffset + P256DerSignatureComponentPrefixLen + componentLen) >
derSignature.Length) {
throw new ArgumentException("DER signature component exceeds buffer length");
}

var copyLen = Math.Min(componentLen, P256P1363ComponentLen);

var srcOffset = derOffset + P256DerSignatureComponentPrefixLen +
componentLen - copyLen;
var dstOffset = p1363Offset + P256P1363ComponentLen - copyLen;
Array.Fill(p1363Signature, (byte)0, p1363Offset, P256P1363ComponentLen - copyLen);
Array.Copy(derSignature, srcOffset, p1363Signature, dstOffset, copyLen);
return derOffset + P256DerSignatureComponentPrefixLen + componentLen;
}


/// <summary>
/// Decode a DER-encoded ECDSA signature into a P1363-encoded ECDSA signature
/// </summary>
/// <param name="encodedPublicKey"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static ECPublicKeyParameters DecodeP256PublicKey(byte[] encodedPublicKey)
{
if (encodedPublicKey.Length < EncodedPublicKeyLengthBytes || encodedPublicKey[0] != 0x04)
{
throw new ArgumentException("input is not an EC P-256 public key");
}

byte[] x = new byte[32];
byte[] y = new byte[32];
Array.Copy(encodedPublicKey, 1, x, 0, 32);
Array.Copy(encodedPublicKey, 33, y, 0, 32);

X9ECParameters ecP = SecNamedCurves.GetByName("secp256r1");
ECDomainParameters ecSpec = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H);
BigInteger xBig = new BigInteger(1, x);
BigInteger yBig = new BigInteger(1, y);

ECPoint w = ecP.Curve.CreatePoint(xBig, yBig);
return new ECPublicKeyParameters(w, ecSpec);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Runtime/codebase/SolanaMobileStack/Interfaces.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SolanaMobileStack.JsonRpcClient.Responses;

namespace SolanaMobileStack.Interfaces
{
public interface IAdapterOperations
{
public Task<AuthorizationResult> Authorize(Uri identityUri, Uri iconUri, string identityName, string rpcCluster);
public Task<AuthorizationResult> Reauthorize(Uri identityUri, Uri iconUri, string identityName, string authToken);
public Task<SignedResult> SignTransactions(IEnumerable<byte[]> transactions);
public Task<SignedResult> SignMessages(IEnumerable<byte[]> messages, IEnumerable<byte[]> addresses);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SolanaMobileStack.Interfaces
{
public interface IMessageReceiver
{
void Receive(string message);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SolanaMobileStack.Interfaces
{
public interface IMessageSender
{
void Send(byte[] message);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 4289680

Please sign in to comment.