Skip to content

Commit 19f139d

Browse files
committed
feat(account): add IAccount, ISigner, and IAsyncSigner interfaces
These are used to start implementing accounts for all types of signing. fix #82
1 parent cb39fb7 commit 19f139d

File tree

6 files changed

+184
-22
lines changed

6 files changed

+184
-22
lines changed
Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,105 @@
1+
using System;
2+
using Cysharp.Threading.Tasks;
3+
using Unity.Collections;
4+
15
namespace AlgoSdk
26
{
7+
public interface IAccount
8+
{
9+
/// <summary>
10+
/// Address of this account
11+
/// </summary>
12+
Address Address { get; }
13+
}
14+
315
/// <summary>
4-
/// Contains utility functions for generating new accounts (private keys).
16+
/// A local, in-memory account.
517
/// </summary>
6-
public static class Account
18+
public readonly struct Account
19+
: IAccount
20+
, ISigner
21+
, IAsyncSigner
722
{
23+
readonly PrivateKey privateKey;
24+
25+
readonly Address address;
26+
27+
readonly bool isRekeyed;
28+
29+
public Address Address => address;
30+
31+
public bool IsRekeyed => isRekeyed;
32+
33+
/// <summary>
34+
/// Instantiate an in-memory account.
35+
/// </summary>
36+
/// <param name="privateKey">The private key of the account.</param>
37+
public Account(PrivateKey privateKey)
38+
{
39+
this.privateKey = privateKey;
40+
this.address = privateKey.ToAddress();
41+
this.isRekeyed = false;
42+
}
43+
44+
/// <summary>
45+
/// Instantiate an in-memory account that is rekeyed.
46+
/// </summary>
47+
/// <param name="privateKey">The private key of the account.</param>
48+
/// <param name="address">The address of this account.</param>
49+
public Account(PrivateKey privateKey, Address address)
50+
{
51+
this.privateKey = privateKey;
52+
this.address = address;
53+
this.isRekeyed = !address.Equals(privateKey.ToAddress());
54+
}
55+
856
/// <summary>
9-
/// Generate a fresh account
57+
/// Generate a random, fresh account
1058
/// </summary>
11-
/// <returns>A private key, address tuple.</returns>
12-
public static (PrivateKey, Address) GenerateAccount()
59+
public static Account GenerateAccount()
1360
{
1461
var privateKey = AlgoSdk.Crypto.Random.Bytes<PrivateKey>();
15-
return (privateKey, privateKey.ToPublicKey());
62+
return new Account(privateKey);
63+
}
64+
65+
public void Deconstruct(out PrivateKey privateKey, out Address address)
66+
{
67+
privateKey = this.privateKey;
68+
address = this.address;
69+
}
70+
71+
public UniTask<SignedTxn<T>> SignTxnAsync<T>(T txn) where T : ITransaction, IEquatable<T>
72+
{
73+
return UniTask.FromResult(SignTxn(txn));
74+
}
75+
76+
public UniTask<SignedTxn<T>[]> SignTxnsAsync<T>(T[] txns) where T : ITransaction, IEquatable<T>
77+
{
78+
return UniTask.FromResult(SignTxns(txns));
79+
}
80+
81+
public SignedTxn<T> SignTxn<T>(T txn) where T : ITransaction, IEquatable<T>
82+
{
83+
using var kp = privateKey.ToKeyPair();
84+
using var msg = txn.ToSignatureMessage(Allocator.Temp);
85+
var sig = kp.SecretKey.Sign(msg);
86+
return new SignedTxn<T> { Txn = txn, Sig = sig };
87+
}
88+
89+
public SignedTxn<T>[] SignTxns<T>(T[] txns) where T : ITransaction, IEquatable<T>
90+
{
91+
var groupId = TransactionGroup.Of(txns).GetId();
92+
var signedTxns = new SignedTxn<T>[txns.Length];
93+
for (var i = 0; i < txns.Length; i++)
94+
{
95+
var txn = txns[i];
96+
txn.Group = groupId;
97+
signedTxns[i] = txn.Sender.Equals(Address)
98+
? SignTxn(txn)
99+
: new SignedTxn<T> { Txn = txn }
100+
;
101+
}
102+
return signedTxns;
16103
}
17104
}
18105
}

Runtime/CareBoo.AlgoSdk/Account/Logic.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static class Logic
1616
/// </summary>
1717
/// <param name="program">The stateful or stateless program to get the address of.</param>
1818
/// <returns>An <see cref="Address"/> for a program.</returns>
19-
public static Address GetAddress(byte[] program)
19+
public static Address GetAddress(CompiledTeal program)
2020
{
2121
using var bytes = GetSignBytes(program, Allocator.Temp);
2222
return Sha512.Hash256Truncated(bytes);
@@ -28,15 +28,15 @@ public static Address GetAddress(byte[] program)
2828
/// <param name="program">The program that will be signed.</param>
2929
/// <param name="allocator">Defines the memory lifetime used for <see cref="NativeByteArray"/>, which must be disposed.</param>
3030
/// <returns>A <see cref="NativeByteArray"/>. The caller must manage its lifetime.</returns>
31-
public static NativeByteArray GetSignBytes(byte[] program, Allocator allocator)
31+
public static NativeByteArray GetSignBytes(CompiledTeal program, Allocator allocator)
3232
{
33-
var bytes = new NativeByteArray(SigningPrefix.Length + program.Length, allocator);
33+
var bytes = new NativeByteArray(SigningPrefix.Length + program.Bytes.Length, allocator);
3434
try
3535
{
3636
for (var i = 0; i < SigningPrefix.Length; i++)
3737
bytes[i] = SigningPrefix[i];
3838
for (var i = SigningPrefix.Length; i < bytes.Length; i++)
39-
bytes[i] = program[i - SigningPrefix.Length];
39+
bytes[i] = program.Bytes[i - SigningPrefix.Length];
4040
}
4141
finally
4242
{
@@ -51,7 +51,7 @@ public static NativeByteArray GetSignBytes(byte[] program, Allocator allocator)
5151
/// <param name="program">Program to sign</param>
5252
/// <param name="secretKey">Key to sign this program with.</param>
5353
/// <returns><see cref="Sig"/></returns>
54-
public static Sig Sign(byte[] program, SecretKeyHandle secretKey)
54+
public static Sig Sign(CompiledTeal program, SecretKeyHandle secretKey)
5555
{
5656
using var programSignBytes = Logic.GetSignBytes(program, Allocator.Temp);
5757
return secretKey.Sign(programSignBytes);
@@ -64,7 +64,7 @@ public static Sig Sign(byte[] program, SecretKeyHandle secretKey)
6464
/// <param name="msig">A <see cref="Multisig"/> that contains the <see cref="PublicKey"/> matching <paramref name="privateKey"/>.</param>
6565
/// <param name="privateKey">The private key to sign with. Its corresponding <see cref="PublicKey"/> must be inside of <paramref name="msig"/>.</param>
6666
/// <returns>A tuple of the <see cref="Sig"/> from signing the program and its index in the <paramref name="msig"/></returns>
67-
public static (Sig, int) Sign(byte[] program, Multisig msig, PrivateKey privateKey)
67+
public static (Sig, int) Sign(CompiledTeal program, Multisig msig, PrivateKey privateKey)
6868
{
6969
if (msig.Subsigs == null)
7070
throw new ArgumentException("msig has null Sub signatures", nameof(msig));
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using Cysharp.Threading.Tasks;
3+
4+
namespace AlgoSdk
5+
{
6+
public interface ISigner
7+
{
8+
/// <summary>
9+
/// Sign a single transaction.
10+
/// </summary>
11+
/// <param name="txn">The transaction to sign.</param>
12+
/// <typeparam name="T">The type of the transaction.</typeparam>
13+
/// <returns>Transaction with signature if it was signed.</returns>
14+
SignedTxn<T> SignTxn<T>(T txn) where T : ITransaction, IEquatable<T>;
15+
16+
/// <summary>
17+
/// Sign a group of transactions.
18+
/// </summary>
19+
/// <param name="txns">The transactions to sign.</param>
20+
/// <typeparam name="T">The type of the transactions.</typeparam>
21+
/// <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>
22+
SignedTxn<T>[] SignTxns<T>(T[] txns) where T : ITransaction, IEquatable<T>;
23+
}
24+
25+
public interface IAsyncSigner
26+
{
27+
/// <summary>
28+
/// Sign a single transaction.
29+
/// </summary>
30+
/// <param name="txn">The transaction to sign.</param>
31+
/// <typeparam name="T">The type of the transaction.</typeparam>
32+
/// <returns>Transaction with signature if it was signed.</returns>
33+
UniTask<SignedTxn<T>> SignTxnAsync<T>(T txn) where T : ITransaction, IEquatable<T>;
34+
35+
/// <summary>
36+
/// Sign a group of transactions.
37+
/// </summary>
38+
/// <param name="txns">The transactions to sign.</param>
39+
/// <typeparam name="T">The type of the transactions.</typeparam>
40+
/// <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>
41+
UniTask<SignedTxn<T>[]> SignTxnsAsync<T>(T[] txns) where T : ITransaction, IEquatable<T>;
42+
}
43+
}

Runtime/CareBoo.AlgoSdk/Account/Signer.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/CareBoo.AlgoSdk/Transaction/ITransaction.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@ public interface ITransaction
1111
/// <summary>
1212
/// Paid by the sender to the FeeSink to prevent denial-of-service. The minimum fee on Algorand is currently 1000 microAlgos.
1313
/// </summary>
14-
ulong Fee { get; }
14+
ulong Fee { get; set; }
1515

1616
/// <summary>
1717
/// The first round for when the transaction is valid. If the transaction is sent prior to this round it will be rejected by the network.
1818
/// </summary>
19-
ulong FirstValidRound { get; }
19+
ulong FirstValidRound { get; set; }
2020

2121
/// <summary>
2222
/// The hash of the genesis block of the network for which the transaction is valid.
2323
/// </summary>
24-
GenesisHash GenesisHash { get; }
24+
GenesisHash GenesisHash { get; set; }
2525

2626
/// <summary>
2727
/// The ending round for which the transaction is valid. After this round, the transaction will be rejected by the network.
2828
/// </summary>
29-
ulong LastValidRound { get; }
29+
ulong LastValidRound { get; set; }
3030

3131
/// <summary>
3232
/// The address of the account that pays the fee and amount.
3333
/// </summary>
34-
Address Sender { get; }
34+
Address Sender { get; set; }
3535

3636
/// <summary>
3737
/// Specifies the type of transaction. This value is automatically generated using any of the developer tools.
@@ -41,27 +41,27 @@ public interface ITransaction
4141
/// <summary>
4242
/// The human-readable string that identifies the network for the transaction. The genesis ID is found in the genesis block.
4343
/// </summary>
44-
FixedString32Bytes GenesisId { get; }
44+
FixedString32Bytes GenesisId { get; set; }
4545

4646
/// <summary>
4747
/// The group specifies that the transaction is part of a group and, if so, specifies the hash of the transaction group. See <see cref="Transaction.GetGroupId"/>.
4848
/// </summary>
49-
Sha512_256_Hash Group { get; }
49+
Sha512_256_Hash Group { get; set; }
5050

5151
/// <summary>
5252
/// A lease enforces mutual exclusion of transactions. If this field is nonzero, then once the transaction is confirmed, it acquires the lease identified by the (Sender, Lease) pair of the transaction until the LastValid round passes. While this transaction possesses the lease, no other transaction specifying this lease can be confirmed. A lease is often used in the context of Algorand Smart Contracts to prevent replay attacks.
5353
/// </summary>
54-
Sha512_256_Hash Lease { get; }
54+
Sha512_256_Hash Lease { get; set; }
5555

5656
/// <summary>
5757
/// Any data up to 1000 bytes.
5858
/// </summary>
59-
byte[] Note { get; }
59+
byte[] Note { get; set; }
6060

6161
/// <summary>
6262
/// Specifies the authorized address. This address will be used to authorize all future transactions.
6363
/// </summary>
64-
Address RekeyTo { get; }
64+
Address RekeyTo { get; set; }
6565

6666
/// <summary>
6767
/// Copy this transactions fields to a <see cref="Transaction"/> which contains all possible transaction fields.

Runtime/CareBoo.AlgoSdk/Transaction/TransactionGroup.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,27 @@ public partial struct TransactionGroup
2525
/// </summary>
2626
public static readonly byte[] IdPrefix = Encoding.UTF8.GetBytes("TG");
2727

28+
/// <summary>
29+
/// Generate a TransactionGroup with the given transactions that can be used to generate a GroupId.
30+
/// </summary>
31+
/// <param name="txns">The transactions to use.</param>
32+
/// <typeparam name="T">The type of the transactions.</typeparam>
33+
public static TransactionGroup Of<T>(params T[] txns) where T : ITransaction, IEquatable<T>
34+
{
35+
if (txns == null || txns.Length == 0)
36+
throw new ArgumentException("Cannot get the group id of 0 transactions", nameof(txns));
37+
if (txns.Length > TransactionGroup.MaxSize)
38+
throw new ArgumentException($"Cannot get the group id of a group of more than {TransactionGroup.MaxSize} transactions", nameof(txns));
39+
40+
var txnIds = new TransactionId[txns.Length];
41+
for (var i = 0; i < txns.Length; i++)
42+
{
43+
txns[i].Group = default;
44+
txnIds[i] = txns[i].GetId();
45+
}
46+
return new TransactionGroup { Txns = txnIds };
47+
}
48+
2849
/// <summary>
2950
/// The list of transaction ids belonging to this group.
3051
/// </summary>

0 commit comments

Comments
 (0)