Skip to content

Commit 532b786

Browse files
Merge pull request #79 from algorandfoundation/chore/extract-signature-bytes-length
feat: better constants for the rust crate and exposed through an enum + impl in the ffi crate
2 parents 039a859 + 56e3099 commit 532b786

File tree

10 files changed

+124
-30
lines changed

10 files changed

+124
-30
lines changed

crates/algokit_transact/src/address.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ impl FromStr for Address {
7070
let decoded = base32::decode(base32::Alphabet::Rfc4648 { padding: false }, s)
7171
.expect("decoded value should exist");
7272

73-
let pub_key: [u8; 32] = decoded[..ALGORAND_PUBLIC_KEY_BYTE_LENGTH]
73+
let pub_key: [u8; ALGORAND_PUBLIC_KEY_BYTE_LENGTH] = decoded
74+
[..ALGORAND_PUBLIC_KEY_BYTE_LENGTH]
7475
.try_into()
7576
.map_err(|_| {
7677
AlgoKitTransactError::InvalidAddress(
@@ -107,12 +108,13 @@ impl Display for Address {
107108
/// # Returns
108109
/// A formatting result with a string containing the base32-encoded address or error
109110
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
110-
let mut address_bytes = [0u8; 36]; // 32 bytes pub_key + 4 bytes checksum
111+
let mut address_bytes =
112+
[0u8; ALGORAND_PUBLIC_KEY_BYTE_LENGTH + ALGORAND_CHECKSUM_BYTE_LENGTH];
111113

112-
address_bytes[..32].copy_from_slice(&self.pub_key);
114+
address_bytes[..ALGORAND_PUBLIC_KEY_BYTE_LENGTH].copy_from_slice(&self.pub_key);
113115

114116
let checksum = self.checksum();
115-
address_bytes[32..].copy_from_slice(&checksum);
117+
address_bytes[ALGORAND_PUBLIC_KEY_BYTE_LENGTH..].copy_from_slice(&checksum);
116118

117119
let result = base32::encode(base32::Alphabet::Rfc4648 { padding: false }, &address_bytes);
118120
write!(f, "{}", result)

crates/algokit_transact/src/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ pub const HASH_BYTES_LENGTH: usize = 32;
22
pub const ALGORAND_CHECKSUM_BYTE_LENGTH: usize = 4;
33
pub const ALGORAND_ADDRESS_LENGTH: usize = 58;
44
pub const ALGORAND_PUBLIC_KEY_BYTE_LENGTH: usize = 32;
5+
pub const ALGORAND_SECRET_KEY_BYTE_LENGTH: usize = 32;
6+
pub const ALGORAND_SIGNATURE_BYTE_LENGTH: usize = 64;
57
pub const ALGORAND_SIGNATURE_ENCODING_INCR: usize = 75;
68
pub type Byte32 = [u8; 32];

crates/algokit_transact/src/lib.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
mod address;
2-
mod constants;
2+
pub mod constants;
33
mod error;
44
mod traits;
55
mod transactions;
66
mod utils;
77

88
// Re-export all the public items
99
pub use address::Address;
10-
pub use constants::{
11-
Byte32, ALGORAND_ADDRESS_LENGTH, ALGORAND_CHECKSUM_BYTE_LENGTH,
12-
ALGORAND_PUBLIC_KEY_BYTE_LENGTH, HASH_BYTES_LENGTH,
13-
};
10+
pub use constants::*;
1411
pub use error::AlgoKitTransactError;
1512
pub use traits::{AlgorandMsgpack, EstimateTransactionSize, TransactionId};
1613
pub use transactions::{

crates/algokit_transact/src/test_utils/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
transactions::{AssetTransferTransactionBuilder, PaymentTransactionBuilder},
33
Address, AlgorandMsgpack, Byte32, SignedTransaction, Transaction, TransactionHeaderBuilder,
4-
TransactionId,
4+
TransactionId, ALGORAND_PUBLIC_KEY_BYTE_LENGTH, HASH_BYTES_LENGTH,
55
};
66
use base64::{prelude::BASE64_STANDARD, Engine};
77
use convert_case::{Case, Casing};
@@ -107,7 +107,7 @@ impl TransactionMother {
107107
pub struct AddressMother {}
108108
impl AddressMother {
109109
pub fn zero_address() -> Address {
110-
Address::from_pubkey(&[0; 32])
110+
Address::from_pubkey(&[0; ALGORAND_PUBLIC_KEY_BYTE_LENGTH])
111111
}
112112

113113
pub fn address() -> Address {
@@ -131,7 +131,7 @@ impl TransactionTestData {
131131
pub fn new(transaction: Transaction, signing_private_key: Byte32) -> Self {
132132
let signing_key: SigningKey = SigningKey::from_bytes(&signing_private_key);
133133
let id = transaction.id().unwrap();
134-
let raw_id: [u8; 32] = transaction.raw_id().unwrap();
134+
let raw_id: [u8; HASH_BYTES_LENGTH] = transaction.raw_id().unwrap();
135135
let unsigned_bytes = transaction.encode().unwrap();
136136
let signature = signing_key.sign(&unsigned_bytes);
137137
let signed_txn = SignedTransaction {

crates/algokit_transact/src/tests.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::constants::ALGORAND_SIGNATURE_ENCODING_INCR;
1+
use crate::constants::{ALGORAND_SIGNATURE_BYTE_LENGTH, ALGORAND_SIGNATURE_ENCODING_INCR};
22
use crate::{
33
test_utils::{AddressMother, TransactionMother},
44
Address, AlgorandMsgpack, EstimateTransactionSize, SignedTransaction, Transaction,
@@ -19,7 +19,7 @@ fn test_payment_transaction_encoding() {
1919

2020
let signed_tx = SignedTransaction {
2121
transaction: payment_tx.clone(),
22-
signature: [0; 64],
22+
signature: [0; ALGORAND_SIGNATURE_BYTE_LENGTH],
2323
};
2424
let encoded_stx = signed_tx.encode().unwrap();
2525
let decoded_stx = SignedTransaction::decode(&encoded_stx).unwrap();
@@ -50,7 +50,7 @@ fn test_asset_transfer_transaction_encoding() {
5050

5151
let signed_tx = SignedTransaction {
5252
transaction: asset_transfer_tx.clone(),
53-
signature: [0; 64],
53+
signature: [0; ALGORAND_SIGNATURE_BYTE_LENGTH],
5454
};
5555
let encoded_stx = signed_tx.encode().unwrap();
5656
let decoded_stx = SignedTransaction::decode(&encoded_stx).unwrap();
@@ -100,7 +100,7 @@ fn test_pay_transaction_raw_id() {
100100
let payment_tx = tx_builder.build().unwrap();
101101
let signed_tx = SignedTransaction {
102102
transaction: payment_tx.clone(),
103-
signature: [0; 64],
103+
signature: [0; ALGORAND_SIGNATURE_BYTE_LENGTH],
104104
};
105105

106106
assert_eq!(payment_tx.raw_id().unwrap(), expected_tx_id);
@@ -115,7 +115,7 @@ fn test_pay_transaction_id() {
115115
let payment_tx = tx_builder.build().unwrap();
116116
let signed_tx = SignedTransaction {
117117
transaction: payment_tx.clone(),
118-
signature: [0; 64],
118+
signature: [0; ALGORAND_SIGNATURE_BYTE_LENGTH],
119119
};
120120

121121
assert_eq!(payment_tx.id().unwrap(), expected_tx_id);
@@ -131,7 +131,7 @@ fn test_estimate_transaction_size() {
131131

132132
let signed_tx = SignedTransaction {
133133
transaction: payment_tx.clone(),
134-
signature: [0; 64],
134+
signature: [0; ALGORAND_SIGNATURE_BYTE_LENGTH],
135135
};
136136
let actual_size = signed_tx.encode().unwrap().len();
137137

crates/algokit_transact/src/transactions/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ pub use common::{TransactionHeader, TransactionHeaderBuilder};
1414
use payment::PaymentTransactionBuilderError;
1515
pub use payment::{PaymentTransactionBuilder, PaymentTransactionFields};
1616

17-
use crate::constants::{ALGORAND_SIGNATURE_ENCODING_INCR, HASH_BYTES_LENGTH};
17+
use crate::constants::{
18+
ALGORAND_SIGNATURE_BYTE_LENGTH, ALGORAND_SIGNATURE_ENCODING_INCR, HASH_BYTES_LENGTH,
19+
};
1820
use crate::error::AlgoKitTransactError;
1921
use crate::traits::{AlgorandMsgpack, EstimateTransactionSize, TransactionId};
2022
use serde::{Deserialize, Serialize};
@@ -76,7 +78,7 @@ pub struct SignedTransaction {
7678
/// The Ed25519 signature authorizing the transaction.
7779
#[serde(rename = "sig")]
7880
#[serde_as(as = "Bytes")]
79-
pub signature: [u8; 64],
81+
pub signature: [u8; ALGORAND_SIGNATURE_BYTE_LENGTH],
8082
}
8183

8284
impl AlgorandMsgpack for SignedTransaction {

crates/algokit_transact/src/utils.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::address::Address;
22
use crate::constants::{Byte32, ALGORAND_CHECKSUM_BYTE_LENGTH, HASH_BYTES_LENGTH};
3+
use crate::ALGORAND_PUBLIC_KEY_BYTE_LENGTH;
34
use sha2::{Digest, Sha512_256};
45
use std::collections::BTreeMap;
56

@@ -37,7 +38,7 @@ pub fn is_zero(n: &u64) -> bool {
3738
}
3839

3940
pub fn is_zero_addr(addr: &Address) -> bool {
40-
addr.pub_key == [0u8; 32]
41+
addr.pub_key == [0u8; ALGORAND_PUBLIC_KEY_BYTE_LENGTH]
4142
}
4243

4344
pub fn is_zero_addr_opt(addr: &Option<Address>) -> bool {

crates/algokit_transact_ffi/build.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use algokit_transact::{ALGORAND_SECRET_KEY_BYTE_LENGTH, HASH_BYTES_LENGTH};
2+
13
include!("src/lib.rs");
24

35
fn main() {
@@ -11,12 +13,12 @@ fn generate_test_data() {
1113

1214
#[derive(Serialize)]
1315
struct TransactionTestData {
14-
signing_private_key: [u8; 32],
16+
signing_private_key: [u8; ALGORAND_SECRET_KEY_BYTE_LENGTH],
1517
transaction: Transaction,
1618
unsigned_bytes: Vec<u8>,
1719
signed_bytes: Vec<u8>,
1820
id: String,
19-
raw_id: [u8; 32],
21+
raw_id: [u8; HASH_BYTES_LENGTH],
2022
}
2123

2224
test_utils::TestDataMother::export(

crates/algokit_transact_ffi/src/lib.rs

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use algokit_transact::constants::*;
12
use algokit_transact::{AlgorandMsgpack, Byte32, EstimateTransactionSize, TransactionId};
2-
use ffi_macros::{ffi_func, ffi_record};
3+
use ffi_macros::{ffi_enum, ffi_func, ffi_record};
34
use serde::{Deserialize, Serialize};
45
use serde_bytes::ByteBuf;
56

@@ -116,9 +117,16 @@ impl TryFrom<Address> for algokit_transact::Address {
116117
type Error = AlgoKitTransactError;
117118

118119
fn try_from(value: Address) -> Result<Self, Self::Error> {
119-
let pub_key: [u8; 32] = value.pub_key.to_vec().try_into().map_err(|_| {
120-
AlgoKitTransactError::EncodingError("public key should be 32 bytes".to_string())
121-
})?;
120+
let pub_key: [u8; ALGORAND_PUBLIC_KEY_BYTE_LENGTH] =
121+
value.pub_key.to_vec().try_into().map_err(|_| {
122+
AlgoKitTransactError::EncodingError(
123+
format!(
124+
"public key should be {} bytes",
125+
ALGORAND_PUBLIC_KEY_BYTE_LENGTH
126+
)
127+
.to_string(),
128+
)
129+
})?;
122130

123131
Ok(algokit_transact::Address::from_pubkey(&pub_key))
124132
}
@@ -401,7 +409,10 @@ pub fn attach_signature(
401409
let encoded_tx = algokit_transact::Transaction::decode(encoded_tx)?;
402410
let signed_tx = algokit_transact::SignedTransaction {
403411
transaction: encoded_tx,
404-
signature: signature.try_into().expect("signature should be 64 bytes"),
412+
signature: signature.try_into().expect(&format!(
413+
"signature should be {} bytes",
414+
ALGORAND_SIGNATURE_BYTE_LENGTH
415+
)),
405416
};
406417
Ok(signed_tx.encode()?)
407418
}
@@ -429,7 +440,13 @@ pub fn estimate_transaction_size(transaction: &Transaction) -> Result<u64, AlgoK
429440
pub fn address_from_pub_key(pub_key: &[u8]) -> Result<Address, AlgoKitTransactError> {
430441
Ok(
431442
algokit_transact::Address::from_pubkey(pub_key.try_into().map_err(|_| {
432-
AlgoKitTransactError::EncodingError("public key should be 32 bytes".to_string())
443+
AlgoKitTransactError::EncodingError(
444+
format!(
445+
"public key should be {} bytes",
446+
ALGORAND_PUBLIC_KEY_BYTE_LENGTH
447+
)
448+
.to_string(),
449+
)
433450
})?)
434451
.into(),
435452
)
@@ -458,6 +475,53 @@ pub fn get_transaction_id(tx: &Transaction) -> Result<String, AlgoKitTransactErr
458475
Ok(tx_internal.id()?)
459476
}
460477

478+
/// Enum containing all constants used in this crate.
479+
#[ffi_enum]
480+
pub enum AlgorandConstant {
481+
/// Length of hash digests (32)
482+
HashLength,
483+
484+
/// Length of the checksum used in Algorand addresses (4)
485+
ChecksumLength,
486+
487+
/// Length of a base32-encoded Algorand address (58)
488+
AddressLength,
489+
490+
/// Length of an Algorand public key in bytes (32)
491+
PublicKeyLength,
492+
493+
/// Length of an Algorand secret key in bytes (32)
494+
SecretKeyLength,
495+
496+
/// Length of an Algorand signature in bytes (64)
497+
SignatureLength,
498+
499+
/// Increment in the encoded byte size when a signature is attached to a transaction (75)
500+
SignatureEncodingIncrLength,
501+
}
502+
503+
impl AlgorandConstant {
504+
/// Get the numeric value of the constant
505+
pub fn value(&self) -> u64 {
506+
match self {
507+
AlgorandConstant::HashLength => HASH_BYTES_LENGTH as u64,
508+
AlgorandConstant::ChecksumLength => ALGORAND_CHECKSUM_BYTE_LENGTH as u64,
509+
AlgorandConstant::AddressLength => ALGORAND_ADDRESS_LENGTH as u64,
510+
AlgorandConstant::PublicKeyLength => ALGORAND_PUBLIC_KEY_BYTE_LENGTH as u64,
511+
AlgorandConstant::SecretKeyLength => ALGORAND_SECRET_KEY_BYTE_LENGTH as u64,
512+
AlgorandConstant::SignatureLength => ALGORAND_SIGNATURE_BYTE_LENGTH as u64,
513+
AlgorandConstant::SignatureEncodingIncrLength => {
514+
ALGORAND_SIGNATURE_ENCODING_INCR as u64
515+
}
516+
}
517+
}
518+
}
519+
520+
#[ffi_func]
521+
pub fn get_algorand_constant(constant: AlgorandConstant) -> u64 {
522+
constant.value()
523+
}
524+
461525
#[cfg(test)]
462526
mod tests {
463527
use super::*;

crates/ffi_macros/src/lib.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use convert_case::{Case, Casing};
44
use proc_macro::TokenStream;
55
use quote::{quote, ToTokens};
6-
use syn::{parse_macro_input, Field, Fields, ItemFn, ItemStruct, Type, TypePath};
6+
use syn::{parse_macro_input, Field, Fields, ItemEnum, ItemFn, ItemStruct, Type, TypePath};
77

88
#[proc_macro_attribute]
99
pub fn ffi_func(_attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -56,6 +56,30 @@ pub fn ffi_record(_attr: TokenStream, item: TokenStream) -> TokenStream {
5656
combined.into()
5757
}
5858

59+
#[proc_macro_attribute]
60+
pub fn ffi_enum(_attr: TokenStream, item: TokenStream) -> TokenStream {
61+
let input = parse_macro_input!(item as ItemEnum);
62+
63+
let enum_attrs = quote! {
64+
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
65+
#[cfg_attr(feature = "ffi_wasm", derive(Tsify))]
66+
#[cfg_attr(
67+
feature = "ffi_wasm",
68+
tsify(into_wasm_abi, from_wasm_abi, large_number_types_as_bigints)
69+
)]
70+
#[cfg_attr(feature = "ffi_wasm", serde(rename_all = "camelCase"))]
71+
#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Enum))]
72+
};
73+
74+
// Combine original attributes with new ones
75+
let combined = quote! {
76+
#enum_attrs
77+
#input
78+
};
79+
80+
combined.into()
81+
}
82+
5983
fn is_option_type(field: &Field) -> bool {
6084
if let Type::Path(TypePath { path, .. }) = &field.ty {
6185
if let Some(segment) = path.segments.last() {

0 commit comments

Comments
 (0)