Skip to content

Commit

Permalink
feat(atomic-txn): add APIs for building, signing, and serializing Ato…
Browse files Browse the repository at this point in the history
…mic Txns

Start the process for creating an Atomic Txn with `Transaction.Atomic` API. TransactionGroup APIs
are now obsolete.

fix #131
  • Loading branch information
jasonboukheir committed Apr 12, 2022
1 parent c1a2cfb commit 858ff9e
Show file tree
Hide file tree
Showing 33 changed files with 1,431 additions and 28 deletions.
15 changes: 13 additions & 2 deletions Runtime/CareBoo.AlgoSdk.Crypto/Sha512.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,21 @@ public static class Sha512
};
#endif

public unsafe static Sha512_256_Hash Hash256Truncated<TByteArray>(TByteArray bytes)
public static Sha512_256_Hash Hash256Truncated<TByteArray>(TByteArray bytes)
where TByteArray : struct, IByteArray
{
return Hash256Truncated(bytes.GetUnsafePtr(), bytes.Length);
unsafe
{
return Hash256Truncated(bytes.GetUnsafePtr(), bytes.Length);
}
}

public static Sha512_256_Hash Hash256Truncated(NativeArray<byte>.ReadOnly bytes)
{
unsafe
{
return Hash256Truncated(bytes.GetUnsafeReadOnlyPtr(), bytes.Length);
}
}

public unsafe static Sha512_256_Hash Hash256Truncated(void* ptr, int length)
Expand Down
55 changes: 47 additions & 8 deletions Runtime/CareBoo.AlgoSdk/Account/Account.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Unity.Collections;

Expand All @@ -17,13 +18,17 @@ public interface IAccount
/// </summary>
public readonly struct Account
: IAccount
, ISigner
, IAsyncSigner
, IAsyncSignerWithProgress
{
readonly PrivateKey privateKey;

readonly Address address;

readonly bool isRekeyed;

/// <inheritdoc />
public Address Address => address;

public bool IsRekeyed => isRekeyed;
Expand Down Expand Up @@ -66,24 +71,58 @@ public void Deconstruct(out PrivateKey privateKey, out Address address)
address = this.address;
}

public SignedTxn<T> SignTxn<T>(T txn) where T : ITransaction, IEquatable<T>
{
using var kp = privateKey.ToKeyPair();
using var msg = txn.ToSignatureMessage(Allocator.Temp);
var sig = kp.SecretKey.Sign(msg);
return new SignedTxn<T> { Txn = txn, Sig = sig };
}

/// <inheritdoc />
public SignedTxn<T>[] SignTxns<T>(T[] txns, TxnIndices txnsToSign)
where T : ITransaction, IEquatable<T>
{
var signed = new SignedTxn<T>[txns.Length];
var txnIndices = txnsToSign.GetEnumerator();
while (txnIndices.MoveNext())
{
var i = txnIndices.Current;
signed[i] = SignTxn(txns[i]);
}

return signed;
}

/// <inheritdoc />
public UniTask<SignedTxn<T>[]> SignTxnsAsync<T>(T[] txns, TxnIndices txnsToSign, CancellationToken cancellationToken = default)
where T : ITransaction, IEquatable<T>
{
return UniTask.FromResult(SignTxns(txns, txnsToSign));
}

/// <inheritdoc />
public UniTask<SignedTxn<T>[]> SignTxnsAsync<T, TProgress>(T[] txns, TxnIndices txnsToSign, TProgress progress, CancellationToken cancellationToken = default)
where T : ITransaction, IEquatable<T>
where TProgress : IProgress<float>
{
progress.Report(1f);
return SignTxnsAsync(txns, txnsToSign);
}

[Obsolete]
public UniTask<SignedTxn<T>> SignTxnAsync<T>(T txn) where T : ITransaction, IEquatable<T>
{
return UniTask.FromResult(SignTxn(txn));
}

[Obsolete("Use AtomicTxn.Signing.SignWithAsync instead")]
public UniTask<SignedTxn<T>[]> SignTxnsAsync<T>(T[] txns) where T : ITransaction, IEquatable<T>
{
return UniTask.FromResult(SignTxns(txns));
}

public SignedTxn<T> SignTxn<T>(T txn) where T : ITransaction, IEquatable<T>
{
using var kp = privateKey.ToKeyPair();
using var msg = txn.ToSignatureMessage(Allocator.Temp);
var sig = kp.SecretKey.Sign(msg);
return new SignedTxn<T> { Txn = txn, Sig = sig };
}

[Obsolete("Use AtomicTxn.Signing.SignWith instead.")]
public SignedTxn<T>[] SignTxns<T>(T[] txns) where T : ITransaction, IEquatable<T>
{
var groupId = TransactionGroup.Of(txns).GetId();
Expand Down
8 changes: 4 additions & 4 deletions Runtime/CareBoo.AlgoSdk/Account/Signer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface ISigner
/// <param name="txnsToSign">Indexes of the transactions this signer should sign.</param>
/// <typeparam name="T">The type of the transactions.</typeparam>
/// <returns>An array of transactions with signatures. If the transaction at a given index was not signed, that signed transaction will have no signature.</returns>
SignedTxn<T>[] SignTxns<T>(T[] txns, int[] txnsToSign) where T : ITransaction, IEquatable<T>;
SignedTxn<T>[] SignTxns<T>(T[] txns, TxnIndices txnsToSign) where T : ITransaction, IEquatable<T>;
}

public interface IAsyncSigner
Expand All @@ -34,7 +34,7 @@ public interface IAsyncSigner
/// <returns>An array of transactions with signatures. If the transaction at a given index was not signed, that signed transaction will have no signature.</returns>
UniTask<SignedTxn<T>[]> SignTxnsAsync<T>(
T[] txns,
int[] txnsToSign,
TxnIndices txnsToSign,
CancellationToken cancellationToken = default
)
where T : ITransaction, IEquatable<T>;
Expand All @@ -56,11 +56,11 @@ public interface IAsyncSignerWithProgress : IAsyncSigner
/// <returns>An array of transactions with signatures. If the transaction at a given index was not signed, that signed transaction will have no signature.</returns>
UniTask<SignedTxn<T>[]> SignTxnsAsync<T, TProgress>(
T[] txns,
int[] txnsToSign,
TxnIndices txnsToSign,
TProgress progress,
CancellationToken cancellationToken = default
)
where T : ITransaction, IEquatable<T>
where TProgress : IProgress<T>;
where TProgress : IProgress<float>;
}
}
12 changes: 12 additions & 0 deletions Runtime/CareBoo.AlgoSdk/Api/AlgoApiSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ public static T DeserializeMessagePack<T>(NativeArray<byte> bytes)
return AlgoApiFormatterCache<T>.Formatter.Deserialize(ref reader);
}

/// <summary>
/// Deserialize messagepack bytes into an object
/// </summary>
/// <typeparam name="T">The type of the object that will be deserialized into</typeparam>
/// <param name="bytes">The messagepack bytes</param>
/// <returns>The data deserialized as T</returns>
public static T DeserializeMessagePack<T>(byte[] bytes)
{
using var nativeBytes = new NativeArray<byte>(bytes, Allocator.Temp);
return DeserializeMessagePack<T>(nativeBytes);
}

/// <summary>
/// Serialize an object into messagepack bytes
/// </summary>
Expand Down
84 changes: 84 additions & 0 deletions Runtime/CareBoo.AlgoSdk/Api/Kmd/KmdAccount.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Threading;
using AlgoSdk.Kmd;
using Cysharp.Threading.Tasks;
using Unity.Collections;
using UnityEngine;

namespace AlgoSdk
{
public struct KmdAccount
: IAccount
, IAsyncSigner
{
readonly KmdClient client;
readonly FixedString128Bytes walletId;
readonly FixedString128Bytes walletPassword;
readonly Address address;

FixedString128Bytes walletHandleToken;

public KmdAccount(
KmdClient client,
FixedString128Bytes walletId,
FixedString128Bytes walletPassword,
Address address,
FixedString128Bytes walletHandleToken = default
)
{
this.client = client;
this.walletId = walletId;
this.walletPassword = walletPassword;
this.address = address;

this.walletHandleToken = walletHandleToken;
}

public Address Address => address;

public async UniTask<FixedString128Bytes> RefreshWalletHandleToken(CancellationToken cancellationToken = default)
{
if (walletHandleToken.Length > 0)
{
var (renewErr, renewResponse) = await client.RenewWalletHandleToken(walletHandleToken).WithCancellation(cancellationToken);
if (renewErr)
{
Debug.LogError(renewErr);
}
}
else
{
var (initErr, initResponse) = await client.InitWalletHandleToken(walletId, walletPassword).WithCancellation(cancellationToken);
if (initErr)
{
Debug.LogError(initErr);
}
walletHandleToken = initResponse.WalletHandleToken;
}
return walletHandleToken;
}

public async UniTask<SignedTxn<T>[]> SignTxnsAsync<T>(T[] txns, TxnIndices txnsToSign, CancellationToken cancellationToken = default)
where T : ITransaction, IEquatable<T>
{
var result = new SignedTxn<T>[txns.Length];
var indexEnum = txnsToSign.GetEnumerator();
ErrorResponse signErr;
SignTransactionResponse signResp;
while (indexEnum.MoveNext())
{
var i = indexEnum.Current;
(signErr, signResp) = await client.SignTransaction(Address, AlgoApiSerializer.SerializeMessagePack(txns[i]), walletHandleToken, walletPassword);
if (signErr)
{
Debug.LogError(signErr);
}
else
{
result[i] = AlgoApiSerializer.DeserializeMessagePack<SignedTxn<T>>(signResp.SignedTransaction);
}
}
return result;
}
}
}
11 changes: 11 additions & 0 deletions Runtime/CareBoo.AlgoSdk/Api/Kmd/KmdAccount.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/CareBoo.AlgoSdk/Transaction/Atomic.meta

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

123 changes: 123 additions & 0 deletions Runtime/CareBoo.AlgoSdk/Transaction/Atomic/AtomicTxn.AsyncSigning.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Threading;
using AlgoSdk.MessagePack;
using Cysharp.Threading.Tasks;
using Unity.Collections;

namespace AlgoSdk
{
public static partial class AtomicTxn
{
/// <summary>
/// Represents an Atomic Txn that is being signed with asynchronous signers.
/// </summary>
/// <remarks>
/// Once all signatures have been added, serialize this transaction
/// group to msgpack with <see cref="FinishSigningAsync"/>.
/// </remarks>
public partial struct AsyncSigning : ISigning<AsyncSigning>
{
readonly Transaction[] txns;
readonly TransactionSignature[] sigs;
readonly List<UniTask> asyncSignings;

/// <summary>
/// Create a new Atomic Txn group that contains some asynchronous signers.
/// </summary>
/// <param name="txns">Transactions that are part of this group.</param>
/// <param name="sigs">Existing signatures.</param>
/// <param name="signedIndices">Indices of transactions with existing signatures.</param>
public AsyncSigning(Transaction[] txns, TransactionSignature[] sigs, TxnIndices signedIndices)
{
this.txns = txns;
this.sigs = sigs;
this.SignedTxnIndices = signedIndices;
this.asyncSignings = new List<UniTask>();
}

/// <inheritdoc />
public Transaction[] Txns => txns;

/// <inheritdoc />
public TransactionSignature[] Sigs => sigs;

/// <inheritdoc />
public TxnIndices SignedTxnIndices { get; set; }

/// <inheritdoc />
public AsyncSigning AsAsync() => this;

/// <inheritdoc cref="AtomicTxn.SignWithAsync{AsyncSigning,TSigner}(AsyncSigning,TSigner,TxnIndices,CancellationToken)" />
public AsyncSigning SignWithAsync<T>(T signer, TxnIndices txnsToSign, CancellationToken cancellationToken = default)
where T : IAsyncSigner
{
this.CheckTxnIndicesToSign(txnsToSign);

SignedTxnIndices |= txnsToSign;
var signing = signer.SignTxnsAsync(txns, txnsToSign, cancellationToken);
var settingSignatures = SetSignaturesAsync(signing, txnsToSign);
asyncSignings.Add(settingSignatures);

return this;
}

/// <inheritdoc cref="AtomicTxn.SignWithAsync{AsyncSigning,TSigner,TProgress}(AsyncSigning,TSigner,TxnIndices,TProgress,CancellationToken)" />
public AsyncSigning SignWithAsync<T, TProgress>(
T signer,
TxnIndices txnsToSign,
TProgress progress,
CancellationToken cancellationToken = default
)
where T : IAsyncSignerWithProgress
where TProgress : IProgress<float>
{
this.CheckTxnIndicesToSign(txnsToSign);

SignedTxnIndices |= txnsToSign;
var signing = signer.SignTxnsAsync(txns, txnsToSign, progress, cancellationToken);
var settingSignatures = SetSignaturesAsync(signing, txnsToSign);
asyncSignings.Add(settingSignatures);

return this;
}

/// <summary>
/// Finish signing this group and return the raw msgpack, ready to submit.
/// </summary>
/// <returns>Msgpack encoding of the transaction group.</returns>
public async UniTask<byte[]> FinishSigningAsync()
{
using var nativeBytes = await FinishSigningAsync(Allocator.Persistent);
return nativeBytes.ToArray();
}

/// <summary>
/// Finish signing this group and return the msgpack of the group, ready to send to the network.
/// </summary>
/// <param name="allocator">Allocator to use in the returned NativeList (which must be disposed).</param>
/// <returns>A natively-allocated, msgpack-encoded transaction group, ready to send to the network.</returns>
public async UniTask<NativeList<byte>> FinishSigningAsync(Allocator allocator)
{
var sigCount = SignedTxnIndices.Count();
if (sigCount != txns.Length)
throw new InvalidOperationException($"There are still {txns.Length - sigCount} transactions that need to be signed.");

await UniTask.WhenAll(asyncSignings);
var writer = new MessagePackWriter(allocator);
for (var i = 0; i < txns.Length; i++)
{
var signedTxn = new SignedTxn { Txn = txns[i], Signature = sigs[i] };
AlgoApiFormatterCache<SignedTxn>.Formatter.Serialize(ref writer, signedTxn);
}
return writer.Data;
}

async UniTask SetSignaturesAsync(UniTask<SignedTxn<Transaction>[]> signing, TxnIndices indices)
{
var signedTxns = await signing;
this.SetSignatures(signedTxns, indices);
}
}
}
}
Loading

0 comments on commit 858ff9e

Please sign in to comment.